Skip to content

[RUM-19398] Benchmarks: add cold start scenario - #1520

Draft
marco-saia-datadog wants to merge 3 commits into
developfrom
marco.saia/RUM-19398/benchmarks-ttfd
Draft

marco-saia-datadog wants to merge 3 commits into
developfrom
marco.saia/RUM-19398/benchmarks-ttfd

Conversation

@marco-saia-datadog

Copy link
Copy Markdown
Member

What does this PR do?

Adds a coldStart scenario to the benchmark app. It measures the time to fully displayed (TTFD) on cold start, with baseline and instrumented run types, on iOS and Android.

  • baseline: a NavigationContainer with one stack screen, showing static content without network calls, and no Datadog SDK.
  • instrumented: the same app, wrapped in DatadogProvider (with the default verbosity, as in customer release builds) and with React Navigation view tracking. Once the first screen is laid out and onInitialization has fired, the app calls DdRum.reportAppFullyDisplayed().

One measurement for both run types. At the same point in the app, both run types call a new native method, BenchmarkVitals.reportFullyDisplayed(). It measures now − process start without going through the SDK:

Process start Notes
iOS sysctl kp_proc.p_starttime Launches pre-warmed by iOS (ActivePrewarm) are not reported
Android Process.getStartElapsedRealtime()

It submits the value once, directly, as a distribution metric (rn.ios.benchmark.ttfd / rn.android.benchmark.ttfd, in ms) to /api/v1/distribution_points. The tags match the existing benchmark metrics. As a distribution, the median and p90 are computed across runs. Because the measurement doesn't depend on the SDK, it can also be checked against the TTFD the SDK reports in RUM.

Arm, then restart. In the Synthetics mobile tests, the app is already running when the first Open deep link step runs. A deep link therefore can't start a measurement from process start. So a coldStart deep link arms the scenario instead: it saves the run type in UserDefaults / SharedPreferences. On the next launch, the app reads the saved config, clears it, and runs the scenario once. In Synthetics, each sample is:

Open deep link   benchmark://start?scenario=coldStart&runType=<baseline|instrumented>
Restart application
Wait ~10s        (lets the metric upload)

A single test can repeat these steps to collect several samples per run.

Motivation

The benchmark app doesn't measure startup today. This PR lets us track the SDK's cold start overhead (instrumented − baseline) over time, next to the existing CPU, memory and FPS metrics.

Additional Notes

  • Native changes (BenchmarkVitals): reportFullyDisplayed, armColdStart and consumeColdStartConfig, on both platforms. A new FullyDisplayed.swift is registered in project.pbxproj, and there's a new FullyDisplayed.kt.
  • JS changes: the new Scenario.ColdStart, src/scenario/ColdStart/, the helpers in testUtils.ts, and the arming logic in App.tsx. Other scenarios are unaffected.
  • .benchmarks-ci.yml is unchanged. CI already builds and uploads the app as latest, which the Synthetics tests pick up.
  • To do in Datadog after merge:
    • Create 4 Synthetics mobile tests (iOS/Android × baseline/instrumented)
    • Enable percentiles on new TTFD metrics
    • Update internal dashboards: p50 and p90 per run type, and p50 instrumented − p50 baseline, filtered on scenario:coldstart.
  • Testing locally: see the "Cold start scenario (TTFD)" section in benchmarks/README.md.
    • iOS simulator: arming, then a restart, runs the scenario.
    • Android: arming, then a restart, runs the scenario.
    • Metric received in Datadog

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests
  • Make sure you discussed the feature or bugfix with the maintaining team in an Issue
  • Make sure each commit and the PR mention the Issue number (cf the CONTRIBUTING doc)
  • If this PR is auto-generated, please make sure also to manually update the code related to the change

@datadog-official

datadog-official Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Pipelines  Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 1d0266b | Docs | View more details | Give us feedback!

@marco-saia-datadog

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T15:10:36.970336Z 4fd5de2 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fd5de2f45

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread benchmarks/src/App.tsx
setupFromTestConfig(DEFAULT_ENV_TEST_CONFIG);

consumeColdStartConfig().then(coldStartConfig => {
setupFromTestConfig(coldStartConfig);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Defer vitals collection until after measuring cold start

When an armed coldStart is consumed, this routes it through setupFromTestConfig, which awaits startCollectingVitals before mounting ColdStartScenario. Both native implementations initialize the benchmark meter/exporter and observers during that call, so the process-start-to-layout TTFD includes benchmark instrumentation setup rather than only the application startup being compared. Skip or defer the regular vitals collector for this scenario so it cannot distort both baseline and instrumented results.

Useful? React with 👍 / 👎.

clientToken: datadogConfig.clientToken,
applicationID: datadogConfig.applicationID,
apiKey: datadogConfig.apiKey,
env: datadogConfig.env,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route TTFD uploads to the configured Datadog site

When DD_SITE is anything other than US1, this native config drops datadogConfig.site, while both new native uploaders hardcode https://api.datadoghq.com. Consequently an EU, US3, US5, AP, or government-site API key is sent to the wrong intake and the cold-start metric is not recorded. Pass the configured site through and select the corresponding distribution intake.

Useful? React with 👍 / 👎.

apiKey = config.getString("apiKey") ?: "",
run = config.getString("runType") ?: "",
scenario = config.getString("scenario") ?: "",
env = BuildConfig.BUILD_TYPE,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Tag Android TTFD metrics with the configured environment

For Android benchmark builds, DD_ENV is passed into reportFullyDisplayed, but this code replaces it with BuildConfig.BUILD_TYPE. The CI configuration uses DD_ENV=dev while the uploaded application is typically a release build, so Android TTFD points are tagged env:release instead of env:dev and disappear from environment-scoped benchmark queries or dashboards. Use the env value from the native config, as the iOS implementation does.

Useful? React with 👍 / 👎.

Comment thread benchmarks/src/App.tsx
Comment on lines +33 to +34
consumeColdStartConfig().then(coldStartConfig => {
setupFromTestConfig(coldStartConfig);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve the armed scenario before starting the environment scenario

When BENCH_SCENARIO contains any valid scenario and a cold start is armed, the environment scenario is started immediately while this asynchronous consume starts the cold scenario independently. The first scenario can therefore mount and initialize the SDK before the cold scenario replaces it, and both calls also start separate native vitals meters; an instrumented cold-start sample then no longer measures a fresh SDK initialization. Consume the pending configuration first and choose it in preference to the environment default rather than starting both.

Useful? React with 👍 / 👎.

@marco-saia-datadog
marco-saia-datadog force-pushed the marco.saia/RUM-19398/benchmarks-ttfd branch from 4fd5de2 to 1d0266b Compare October 29, 2026 16:49

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant