Skip to content

Commit 060338b

Browse files
feat: cold, warm and hot launch times meter (#66)
<!-- CURSOR_SUMMARY --> > [!NOTE] > Adds automatic measurement of cold/warm app launch times and emits "AppStart" spans with precise start/end timestamps, plus supporting infra and example app tweaks. > > - **Observability (Auto-instrumentation)**: > - Introduces `LaunchMeter` to measure app launch durations (cold/warm) using app lifecycle + first-frame render. > - Adds early uptime capture via `AppStartTime` initialized from ObjC constructor in `ObjCBridge`. > - Extends `Options.AutoInstrumented` with `launchTimes` and wires meter via `options.launchMeter`. > - Emits `"AppStart"` spans with `start.type` attribute and accurate `startTime`/`endTime` when `launchTimes` is enabled. > - **Traces**: > - Adds internal `TracerDecorator.startSpan(name:attributes:startTime:)` to set explicit span start times. > - **Common**: > - Adds lightweight generic `Store` for reducer-based state updates. > - **Package**: > - Adds `ObjCBridge` target and dependencies; pins `ios-client-sdk` to `10.0.0`. > - **Example App**: > - Moves `LDClient.start` to `Client.init()`; removes AppDelegate SDK setup and a manual flush in `TraceView`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 4a51e42. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 99a9f3e commit 060338b

File tree

17 files changed

+555
-68
lines changed

17 files changed

+555
-68
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "2600"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES"
8+
buildArchitectures = "Automatic">
9+
<BuildActionEntries>
10+
<BuildActionEntry
11+
buildForTesting = "YES"
12+
buildForRunning = "YES"
13+
buildForProfiling = "YES"
14+
buildForArchiving = "YES"
15+
buildForAnalyzing = "YES">
16+
<BuildableReference
17+
BuildableIdentifier = "primary"
18+
BlueprintIdentifier = "LaunchDarklyObservability"
19+
BuildableName = "LaunchDarklyObservability"
20+
BlueprintName = "LaunchDarklyObservability"
21+
ReferencedContainer = "container:">
22+
</BuildableReference>
23+
</BuildActionEntry>
24+
</BuildActionEntries>
25+
</BuildAction>
26+
<TestAction
27+
buildConfiguration = "Debug"
28+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
29+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
30+
shouldUseLaunchSchemeArgsEnv = "YES"
31+
shouldAutocreateTestPlan = "YES">
32+
</TestAction>
33+
<LaunchAction
34+
buildConfiguration = "Debug"
35+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37+
launchStyle = "0"
38+
useCustomWorkingDirectory = "NO"
39+
ignoresPersistentStateOnLaunch = "NO"
40+
debugDocumentVersioning = "YES"
41+
debugServiceExtension = "internal"
42+
allowLocationSimulation = "YES">
43+
</LaunchAction>
44+
<ProfileAction
45+
buildConfiguration = "Release"
46+
shouldUseLaunchSchemeArgsEnv = "YES"
47+
savedToolIdentifier = ""
48+
useCustomWorkingDirectory = "NO"
49+
debugDocumentVersioning = "YES">
50+
<MacroExpansion>
51+
<BuildableReference
52+
BuildableIdentifier = "primary"
53+
BlueprintIdentifier = "LaunchDarklyObservability"
54+
BuildableName = "LaunchDarklyObservability"
55+
BlueprintName = "LaunchDarklyObservability"
56+
ReferencedContainer = "container:">
57+
</BuildableReference>
58+
</MacroExpansion>
59+
</ProfileAction>
60+
<AnalyzeAction
61+
buildConfiguration = "Debug">
62+
</AnalyzeAction>
63+
<ArchiveAction
64+
buildConfiguration = "Release"
65+
revealArchiveInOrganizer = "YES">
66+
</ArchiveAction>
67+
</Scheme>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "2600"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES"
8+
buildArchitectures = "Automatic">
9+
<BuildActionEntries>
10+
<BuildActionEntry
11+
buildForTesting = "YES"
12+
buildForRunning = "YES"
13+
buildForProfiling = "YES"
14+
buildForArchiving = "YES"
15+
buildForAnalyzing = "YES">
16+
<BuildableReference
17+
BuildableIdentifier = "primary"
18+
BlueprintIdentifier = "LaunchDarklySessionReplay"
19+
BuildableName = "LaunchDarklySessionReplay"
20+
BlueprintName = "LaunchDarklySessionReplay"
21+
ReferencedContainer = "container:">
22+
</BuildableReference>
23+
</BuildActionEntry>
24+
</BuildActionEntries>
25+
</BuildAction>
26+
<TestAction
27+
buildConfiguration = "Debug"
28+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
29+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
30+
shouldUseLaunchSchemeArgsEnv = "YES"
31+
shouldAutocreateTestPlan = "YES">
32+
</TestAction>
33+
<LaunchAction
34+
buildConfiguration = "Debug"
35+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37+
launchStyle = "0"
38+
useCustomWorkingDirectory = "NO"
39+
ignoresPersistentStateOnLaunch = "NO"
40+
debugDocumentVersioning = "YES"
41+
debugServiceExtension = "internal"
42+
allowLocationSimulation = "YES">
43+
</LaunchAction>
44+
<ProfileAction
45+
buildConfiguration = "Release"
46+
shouldUseLaunchSchemeArgsEnv = "YES"
47+
savedToolIdentifier = ""
48+
useCustomWorkingDirectory = "NO"
49+
debugDocumentVersioning = "YES">
50+
<MacroExpansion>
51+
<BuildableReference
52+
BuildableIdentifier = "primary"
53+
BlueprintIdentifier = "LaunchDarklySessionReplay"
54+
BuildableName = "LaunchDarklySessionReplay"
55+
BlueprintName = "LaunchDarklySessionReplay"
56+
ReferencedContainer = "container:">
57+
</BuildableReference>
58+
</MacroExpansion>
59+
</ProfileAction>
60+
<AnalyzeAction
61+
buildConfiguration = "Debug">
62+
</AnalyzeAction>
63+
<ArchiveAction
64+
buildConfiguration = "Release"
65+
revealArchiveInOrganizer = "YES">
66+
</ArchiveAction>
67+
</Scheme>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "2600"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES"
8+
buildArchitectures = "Automatic">
9+
<BuildActionEntries>
10+
<BuildActionEntry
11+
buildForTesting = "YES"
12+
buildForRunning = "YES"
13+
buildForProfiling = "YES"
14+
buildForArchiving = "YES"
15+
buildForAnalyzing = "YES">
16+
<BuildableReference
17+
BuildableIdentifier = "primary"
18+
BlueprintIdentifier = "LaunchDarklyObservability"
19+
BuildableName = "LaunchDarklyObservability"
20+
BlueprintName = "LaunchDarklyObservability"
21+
ReferencedContainer = "container:">
22+
</BuildableReference>
23+
</BuildActionEntry>
24+
<BuildActionEntry
25+
buildForTesting = "YES"
26+
buildForRunning = "YES"
27+
buildForProfiling = "YES"
28+
buildForArchiving = "YES"
29+
buildForAnalyzing = "YES">
30+
<BuildableReference
31+
BuildableIdentifier = "primary"
32+
BlueprintIdentifier = "LaunchDarklySessionReplay"
33+
BuildableName = "LaunchDarklySessionReplay"
34+
BlueprintName = "LaunchDarklySessionReplay"
35+
ReferencedContainer = "container:">
36+
</BuildableReference>
37+
</BuildActionEntry>
38+
<BuildActionEntry
39+
buildForTesting = "YES"
40+
buildForRunning = "YES"
41+
buildForProfiling = "YES"
42+
buildForArchiving = "YES"
43+
buildForAnalyzing = "YES">
44+
<BuildableReference
45+
BuildableIdentifier = "primary"
46+
BlueprintIdentifier = "StartupMetrics"
47+
BuildableName = "StartupMetrics"
48+
BlueprintName = "StartupMetrics"
49+
ReferencedContainer = "container:">
50+
</BuildableReference>
51+
</BuildActionEntry>
52+
</BuildActionEntries>
53+
</BuildAction>
54+
<TestAction
55+
buildConfiguration = "Debug"
56+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
57+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
58+
shouldUseLaunchSchemeArgsEnv = "YES"
59+
shouldAutocreateTestPlan = "YES">
60+
<Testables>
61+
<TestableReference
62+
skipped = "NO">
63+
<BuildableReference
64+
BuildableIdentifier = "primary"
65+
BlueprintIdentifier = "CommonTests"
66+
BuildableName = "CommonTests"
67+
BlueprintName = "CommonTests"
68+
ReferencedContainer = "container:">
69+
</BuildableReference>
70+
</TestableReference>
71+
<TestableReference
72+
skipped = "NO">
73+
<BuildableReference
74+
BuildableIdentifier = "primary"
75+
BlueprintIdentifier = "ObservabilityTests"
76+
BuildableName = "ObservabilityTests"
77+
BlueprintName = "ObservabilityTests"
78+
ReferencedContainer = "container:">
79+
</BuildableReference>
80+
</TestableReference>
81+
</Testables>
82+
</TestAction>
83+
<LaunchAction
84+
buildConfiguration = "Debug"
85+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
86+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
87+
launchStyle = "0"
88+
useCustomWorkingDirectory = "NO"
89+
ignoresPersistentStateOnLaunch = "NO"
90+
debugDocumentVersioning = "YES"
91+
debugServiceExtension = "internal"
92+
allowLocationSimulation = "YES">
93+
</LaunchAction>
94+
<ProfileAction
95+
buildConfiguration = "Release"
96+
shouldUseLaunchSchemeArgsEnv = "YES"
97+
savedToolIdentifier = ""
98+
useCustomWorkingDirectory = "NO"
99+
debugDocumentVersioning = "YES">
100+
<MacroExpansion>
101+
<BuildableReference
102+
BuildableIdentifier = "primary"
103+
BlueprintIdentifier = "LaunchDarklyObservability"
104+
BuildableName = "LaunchDarklyObservability"
105+
BlueprintName = "LaunchDarklyObservability"
106+
ReferencedContainer = "container:">
107+
</BuildableReference>
108+
</MacroExpansion>
109+
</ProfileAction>
110+
<AnalyzeAction
111+
buildConfiguration = "Debug">
112+
</AnalyzeAction>
113+
<ArchiveAction
114+
buildConfiguration = "Release"
115+
revealArchiveInOrganizer = "YES">
116+
</ArchiveAction>
117+
</Scheme>

ExampleApp/ExampleApp.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@
260260
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
261261
CODE_SIGN_STYLE = Automatic;
262262
CURRENT_PROJECT_VERSION = 1;
263+
DEVELOPMENT_TEAM = "";
263264
ENABLE_PREVIEWS = YES;
264265
GENERATE_INFOPLIST_FILE = YES;
265266
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
@@ -288,6 +289,7 @@
288289
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
289290
CODE_SIGN_STYLE = Automatic;
290291
CURRENT_PROJECT_VERSION = 1;
292+
DEVELOPMENT_TEAM = "";
291293
ENABLE_PREVIEWS = YES;
292294
GENERATE_INFOPLIST_FILE = YES;
293295
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;

ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 6 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,17 @@
11
import UIKit
2-
import LaunchDarkly
32
import LaunchDarklyObservability
43

5-
let mobileKey = "mob-48fd3788-eab7-4b72-b607-e41712049dbd"
6-
let config = { () -> LDConfig in
7-
var config = LDConfig(
8-
mobileKey: mobileKey,
9-
autoEnvAttributes: .enabled
10-
)
11-
config.plugins = [
12-
Observability(
13-
options: .init(
14-
otlpEndpoint: "http://localhost:4318",
15-
sessionBackgroundTimeout: 3,
16-
isDebug: true,
17-
logs: .enabled,
18-
traces: .enabled,
19-
metrics: .enabled
20-
)
21-
)
22-
]
23-
/*
24-
config.plugins = [
25-
Observability(
26-
options: .init(
27-
// otlpEndpoint: "http://localhost:4318",
28-
sessionBackgroundTimeout: 3,
29-
isDebug: true,
30-
disableLogs: false,
31-
disableTraces: false,
32-
disableMetrics: false
33-
)
34-
)
35-
]
36-
*/
37-
return config
38-
}()
39-
40-
let context = { () -> LDContext in
41-
var contextBuilder = LDContextBuilder(
42-
key: "12345"
43-
)
44-
contextBuilder.kind("user")
45-
do {
46-
return try contextBuilder.build().get()
47-
} catch {
48-
abort()
49-
}
50-
}()
51-
524
final class AppDelegate: NSObject, UIApplicationDelegate {
5+
func application(
6+
_ application: UIApplication,
7+
willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
8+
) -> Bool {
9+
return true
10+
}
5311
func application(
5412
_ application: UIApplication,
5513
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
5614
) -> Bool {
57-
LDClient.start(
58-
config: config,
59-
context: context,
60-
startWaitSeconds: 5.0,
61-
completion: { (timedOut: Bool) -> Void in
62-
if timedOut {
63-
// Client may not have the most recent flags for the configured context
64-
} else {
65-
// Client has received flags for the configured context
66-
}
67-
}
68-
)
6915
return true
7016
}
7117
}

ExampleApp/ExampleApp/Client.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ struct Client {
3636
}
3737
}()
3838

39-
func start() {
39+
init() {
4040
LDClient.start(
4141
config: config,
4242
context: context,

ExampleApp/ExampleApp/ExampleAppApp.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ struct ExampleAppApp: App {
2525
}
2626
}
2727
.environmentObject(browser)
28-
.onAppear {
29-
client.start()
30-
}
3128
}
3229
}
3330
}

ExampleApp/ExampleApp/Instrumentation/Manual/TraceView.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ struct TraceView: View {
4242
.task(id: started) {
4343
guard started else {
4444
span?.end()
45-
_ = LDObserve.shared.flush()
4645
return name = ""
4746
}
4847
span = LDObserve.shared.startSpan(name: name, attributes: [:])

0 commit comments

Comments
 (0)