Skip to content

app start control #196

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ private static Span CreateAppStartSpan(string name, string category)
return _spanFactory.CreateAutoAppStartSpan(name, category, category.Equals(APP_START_CATEGORY));
}

internal Span GetAppStartSpan() => _rootSpan;

internal void SubsystemRegistration()
{
_rootSpan = CreateAppStartSpan(UNITY_RUNTIME_SPAN_NAME, APP_START_CATEGORY);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace BugsnagUnityPerformance
{
internal class AppStartSpanControl : IAppStartSpanControl
{
private const string Prefix = "[AppStart/UnityRuntime]";
private readonly Span _span;

public AppStartSpanControl(Span span) => _span = span;

public void SetType(string type)
{
_span?.TryUpdateAppStartSpan(type, Prefix);
}

public void ClearType()
{
_span?.TryUpdateAppStartSpan(null, Prefix);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace BugsnagUnityPerformance
{
internal class SpanControlRegistry
{
private readonly AppStartHandler _appStartHandler;

public SpanControlRegistry(AppStartHandler handler)
{
_appStartHandler = handler;
}

public T GetSpanControl<T>(ISpanQuery<T> query) where T : class
{
if (query is SpanType.AppStartQuery)
{
var span = _appStartHandler.GetAppStartSpan();
return span != null ? new AppStartSpanControl(span) as T : null;
}

return null;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class BugsnagPerformance
private SystemMetricsCollector _systemMetricsCollector;
private static List<WeakReference<Span>> _potentiallyOpenSpans = new List<WeakReference<Span>>();
private Func<BugsnagNetworkRequestInfo, BugsnagNetworkRequestInfo> _networkRequestCallback;
private static SpanControlRegistry _spanControlRegistry;

private static Dictionary<WeakReference<BugsnagUnityWebRequest>, Span> _networkSpans = new Dictionary<WeakReference<BugsnagUnityWebRequest>, Span>();

Expand All @@ -44,7 +45,6 @@ internal class SceneLoadSpanContainer
{
public List<Span> Spans = new List<Span>();
}

public static void Start(PerformanceConfiguration configuration)
{
#if BUGSNAG_DEBUG
Expand Down Expand Up @@ -122,6 +122,11 @@ public static Span StartNetworkSpan(string url, HttpVerb httpVerb, SpanOptions s
return _sharedInstance._spanFactory.CreateManualNetworkSpan(url, httpVerb, spanOptions);
}

public static T GetSpanControl<T>(ISpanQuery<T> query) where T : class
{
return _spanControlRegistry?.GetSpanControl(query);
}

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void SubsystemRegistration()
{
Expand All @@ -141,6 +146,7 @@ private BugsnagPerformance()
_tracer = new Tracer(_sampler, _delivery, _frameMetricsCollector, _systemMetricsCollector);
_spanFactory = new SpanFactory(_tracer.OnSpanEnd, _frameMetricsCollector);
_appStartHandler = new AppStartHandler(_spanFactory);
_spanControlRegistry = new SpanControlRegistry(_appStartHandler);
_pValueUpdater = new PValueUpdater(_delivery, _sampler);
}

Expand Down
70 changes: 51 additions & 19 deletions BugsnagPerformance/Assets/BugsnagPerformance/Scripts/Public/Span.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class Span : ISpanContext
private const string MEMORY_SPACES_ART_SIZE_KEY = "bugsnag.system.memory.spaces.art.size";
private const string MEMORY_SPACES_ART_USED_KEY = "bugsnag.system.memory.spaces.art.used";
private const string MEMORY_SPACES_ART_MEAN_KEY = "bugsnag.system.memory.spaces.art.mean";
private const string APP_START_NAME_KEY = "bugsnag.app_start.name";
private const double CPU_METRICS_MIN_THRESHOLD = 0.0001;

public string Name { get; internal set; }
Expand All @@ -42,7 +43,7 @@ public class Span : ISpanContext
public DateTimeOffset EndTime { get; internal set; }
internal double samplingProbability { get; private set; }
internal bool Ended;
private object _endLock = new object();
private readonly object _endLock = new object();
private OnSpanEnd _onSpanEnd;
internal bool IsAppStartSpan;
internal bool WasDiscarded;
Expand All @@ -52,6 +53,8 @@ public class Span : ISpanContext
private int _customAttributeCount;
private int _maxCustomAttributes;
internal bool IsFrozenFrameSpan;
private readonly object _attributesLock = new object();


public Span(string name, SpanKind kind, string id,
string traceId, string parentSpanId, DateTimeOffset startTime,
Expand Down Expand Up @@ -190,12 +193,15 @@ public void UpdateSamplingProbability(double value)
internal void SetAttributeInternal(string key, bool value) => SetAttributeWithoutChecks(key, value);
private void SetAttributeWithoutChecks(string key, object value)
{
if (value == null)
lock (_attributesLock)
{
_attributes.Remove(key);
return;
if (value == null)
{
_attributes.Remove(key);
return;
}
_attributes[key] = value;
}
_attributes[key] = value;
}


Expand All @@ -215,31 +221,57 @@ private void SetAttributeWithChecks(string key, object value)
MainThreadDispatchBehaviour.LogWarning($"Attempting to set attribute: {key} on span: {Name} after the span has ended.");
return;
}

if (_attributes.ContainsKey(key))
lock (_attributesLock)
{
if (value == null)
if (_attributes.ContainsKey(key))
{
_attributes.Remove(key);
_customAttributeCount--;
if (value == null)
{
_attributes.Remove(key);
_customAttributeCount--;
}
else
{
_attributes[key] = value;
}
return;
}
else

if (_customAttributeCount >= _maxCustomAttributes)
{
_attributes[key] = value;
DroppedAttributesCount++;
return;
}
return;
_attributes[key] = value;
_customAttributeCount++;
}
}

if (_customAttributeCount >= _maxCustomAttributes)
internal bool TryUpdateAppStartSpan(string? type, string namePrefix = "[AppStart/UnityRuntime]")
{
lock (_endLock)
{
DroppedAttributesCount++;
return;
if (Ended)
{
return false;
}
Name = type == null ? namePrefix : namePrefix + type;
lock (_attributesLock)
{
if (type == null) _attributes.Remove(APP_START_NAME_KEY);
else _attributes[APP_START_NAME_KEY] = type;
}
return true;
}
_attributes[key] = value;
_customAttributeCount++;
}

internal Dictionary<string, object> GetAttributes() => new Dictionary<string, object>(_attributes);
internal Dictionary<string, object> GetAttributes()
{
lock (_attributesLock)
{
return new Dictionary<string, object>(_attributes);
}
}

internal void SetCallbackComplete()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace BugsnagUnityPerformance
{
public interface IAppStartSpanControl
{
void SetType(string type);
void ClearType();
}

public interface ISpanQuery<T> { }

public static class SpanType
{
public static readonly ISpanQuery<IAppStartSpanControl> AppStart = new AppStartQuery();
internal sealed class AppStartQuery : ISpanQuery<IAppStartSpanControl> { }
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

## TBD

### Additions

- Allow app start span customisation. [#196](https://github.com/bugsnag/bugsnag-unity-performance/pull/196)

### Bug Fixes

- Fix issue where CPU metrics where reported when no valid CPU data was avaliable. [#193](https://github.com/bugsnag/bugsnag-unity-performance/pull/193)
- Fix issue where CPU metrics were reported when no valid CPU data was available. [#193](https://github.com/bugsnag/bugsnag-unity-performance/pull/193)

## v1.10.0 (2025-06-03)

Expand Down
16 changes: 16 additions & 0 deletions features/app_start_spans.feature
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,19 @@ Scenario: App Start Off
* the trace "Bugsnag-Span-Sampling" header equals "1:1"
* the trace payload field "resourceSpans.0.scopeSpans.0.spans.0.name" equals "AppStartOff"

Scenario: App Start Customisation
When I run the game in the "AppStartCustomisation" state
And I wait for 4 spans
Then the trace Bugsnag-Integrity header is valid
And the trace "Bugsnag-Api-Key" header equals "a35a2a72bd230ac0aa0f52715bbdc6aa"
* the trace "Bugsnag-Span-Sampling" header equals "1:4"
* the trace payload field "resourceSpans.0.scopeSpans.0.spans.3.name" equals "[AppStart/UnityRuntime]ColdStart"

Scenario: App Start Clear Customisation
When I run the game in the "AppStartClearCustomisation" state
And I wait for 4 spans
Then the trace Bugsnag-Integrity header is valid
And the trace "Bugsnag-Api-Key" header equals "a35a2a72bd230ac0aa0f52715bbdc6aa"
* the trace "Bugsnag-Span-Sampling" header equals "1:4"
* the trace payload field "resourceSpans.0.scopeSpans.0.spans.3.name" equals "[AppStart/UnityRuntime]"

29 changes: 29 additions & 0 deletions features/fixtures/mazerunner/Assets/Scenes/MainScene.unity
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: ef6b6638c32824cd09e5bc90ba6c791c, type: 3}
m_Name:
m_EditorClassIdentifier:
ShouldStartNotifier: 0
--- !u!114 &1124192959
MonoBehaviour:
m_ObjectHideFlags: 0
Expand All @@ -1120,6 +1121,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 31195cb4a65c4458eb5d46cb119e51bf, type: 3}
m_Name:
m_EditorClassIdentifier:
ShouldStartNotifier: 0
--- !u!1 &1294582013
GameObject:
m_ObjectHideFlags: 0
Expand Down Expand Up @@ -1550,6 +1552,8 @@ GameObject:
- component: {fileID: 2053587487}
- component: {fileID: 2053587489}
- component: {fileID: 2053587488}
- component: {fileID: 2053587490}
- component: {fileID: 2053587491}
m_Layer: 0
m_Name: AppStart
m_TagString: Untagged
Expand Down Expand Up @@ -1611,6 +1615,31 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
ShouldStartNotifier: 0
--- !u!114 &2053587490
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2053587485}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe103c8e2fc0e4d429f29e72dddcbaae, type: 3}
m_Name:
m_EditorClassIdentifier:
ShouldStartNotifier: 0
--- !u!114 &2053587491
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2053587485}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3c87ca81bf59a4b64969be3ff3117a53, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &2126837005
GameObject:
m_ObjectHideFlags: 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using BugsnagUnityPerformance;

public class AppStartClearCustomisation : Scenario
{
public override void PreparePerformanceConfig(string apiKey, string host)
{
base.PreparePerformanceConfig(apiKey, host);
SetMaxBatchSize(4);
Configuration.AutoInstrumentAppStart = AutoInstrumentAppStartSetting.START_ONLY;
}

public override void Run()
{
BugsnagPerformance.GetSpanControl(SpanType.AppStart)?.SetType("ColdStart");
BugsnagPerformance.GetSpanControl(SpanType.AppStart)?.ClearType();
BugsnagPerformance.ReportAppStarted();
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading