From 8990d6eda2dea16faa8270f0dad4c7b54e92b78b Mon Sep 17 00:00:00 2001 From: Nitin Misra Date: Fri, 18 Sep 2026 14:03:01 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20emit=20app=5Fstartup.durati?= =?UTF-8?q?on=5Fms=20alongside=20the=20seconds=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit app_startup.duration carried seconds under an unsuffixed key, the same ambiguity that produced the 1000x ANR display bug fixed in 0.1.17. Every app_startup span (native cold and warm, web cold and bfcache warm) now also carries app_startup.duration_ms. The seconds key is kept because RUM plugins before 0.1.34 read only that one; backends coalesce the ms key over seconds * 1000. Also corrects docs/configuration.md (no hot start exists) and the 0.1.8 changelog entry, which named attribute keys the SDK never emitted. Version 0.1.18. --- CHANGELOG.md | 23 +++++++++++++++++++++-- docs/configuration.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- src/core/attributes.test.ts | 1 + src/core/attributes.ts | 4 ++++ src/core/scope.ts | 2 +- src/native/index.ts | 3 ++- src/native/instrumentations/lifecycle.ts | 1 + src/web/instrumentations/startup.ts | 2 ++ 10 files changed, 36 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f20e3b..039e10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.18] - 2026-09-18 + +### Added + +- **`app_startup.duration_ms`** on every `app_startup` span (native cold and + warm, web cold and bfcache warm), next to the seconds-valued + `app_startup.duration`. The unsuffixed key gave no unit and produced the same + 1000x display bug `anr.duration` did (fixed in 0.1.17 by renaming). Unlike + ANR, the seconds key is *kept*: RUM plugins before 0.1.34 read only + `app_startup.duration`, so dropping it would blank their startup panels. + Backends coalesce `app_startup.duration_ms` over `app_startup.duration * 1000`. + +### Fixed + +- `docs/configuration.md` claimed `enableStartupTracking` measured a "hot" start + and the 0.1.8 changelog entry named attribute keys that were never emitted. + ## [0.1.17] - 2026-09-07 Eight data-quality defects found validating browser RUM against live data. Four @@ -634,8 +651,10 @@ slows trace/log export from 5s to 30s. All of it is opt-in-able — see below. the cold-start duration is measured from the OS process start, not from `Scout.initialize`. On background-to-active transitions, a `warm` `app_startup` span is emitted with duration measured to the next animation - frame. Both carry `app.startup.type` (`cold` | `warm`) and - `app.startup.duration_seconds`. + frame. Both carry `app_startup.type` (`cold` | `warm`) and + `app_startup.duration` (seconds). *(Corrected 2026-09-18: this entry + originally named the keys `app.startup.type` / `app.startup.duration_seconds`, + which were never what the SDK emitted.)* ## [0.1.6] - 2026-05-22 diff --git a/docs/configuration.md b/docs/configuration.md index b0342ee..0cf7334 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -171,7 +171,7 @@ Every auto-instrumentation can be turned off independently. All default to `true | `interactionEvents` | `['click','change','submit','input']` | Web only. Which DOM events auto-tap tracking listens to; the value lands on the span as `user_interaction.type`. See below. | | `enableErrorTracking` | `true` | `window.onerror`, `unhandledrejection`, native crashes via KSCrash + NDK signal handler + MetricKit + ApplicationExitInfo. Emits `error`, `app_crash`, `native_crash` spans. | | `enableLifecycleTracking` | `true` | App `foreground`/`background`/`paused`/`resumed`. Emits `app_paused` / `app_resumed` spans + `view.in_foreground_periods_json` on screen_view. | -| `enableStartupTracking` | `true` | Cold/warm/hot start timing. Emits `app_startup` span. | +| `enableStartupTracking` | `true` | Cold/warm start timing. Emits `app_startup` spans with `app_startup.type` (`cold` \| `warm`), `app_startup.duration` (seconds) and `app_startup.duration_ms` (milliseconds). Native cold start is measured from the OS process start; web cold start from navigation start to `loadEventEnd`. | | `enableConnectivityTracking` | `true` | Network type changes (`wifi` → `cellular`), connection quality. | | `enablePerformanceMetrics` | `true` | Memory and CPU samples. | | `enableLongTaskDetection` | `true` | JS long tasks > `longTaskThresholdMs`. Emits `long_task` span. | diff --git a/package-lock.json b/package-lock.json index e3be9d5..af4c23e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@base-14/scout-react", - "version": "0.1.17", + "version": "0.1.18", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@base-14/scout-react", - "version": "0.1.17", + "version": "0.1.18", "license": "MIT", "dependencies": { "@opentelemetry/api": "^1.9.1", diff --git a/package.json b/package.json index ae3b04f..14984fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@base-14/scout-react", - "version": "0.1.17", + "version": "0.1.18", "description": "Zero-config OpenTelemetry RUM for React and React Native. Auto-captures clicks, navigation, errors, lifecycle, network, performance, and web vitals.", "license": "MIT", "author": "base-14", diff --git a/src/core/attributes.test.ts b/src/core/attributes.test.ts index d836254..cc27172 100644 --- a/src/core/attributes.test.ts +++ b/src/core/attributes.test.ts @@ -21,6 +21,7 @@ describe('attribute / span / metric name contract', () => { expect(ATTR.USER_INTERACTION_TARGET).toBe('user_interaction.target'); expect(ATTR.APP_STARTUP_TYPE).toBe('app_startup.type'); expect(ATTR.APP_STARTUP_DURATION).toBe('app_startup.duration'); + expect(ATTR.APP_STARTUP_DURATION_MS).toBe('app_startup.duration_ms'); expect(ATTR.LONG_TASK_DURATION).toBe('long_task.duration'); expect(ATTR.ANR_DURATION_MS).toBe('anr.duration_ms'); expect(ATTR.ANR_THRESHOLD_MS).toBe('anr.threshold_ms'); diff --git a/src/core/attributes.ts b/src/core/attributes.ts index 7ca3b35..3345220 100644 --- a/src/core/attributes.ts +++ b/src/core/attributes.ts @@ -82,6 +82,10 @@ export const ATTR = { DISPLAY_SCROLL_MAX_SCROLL_HEIGHT_TIME_MS: 'display.scroll.max_scroll_height_time_ms', APP_STARTUP_TYPE: 'app_startup.type', APP_STARTUP_DURATION: 'app_startup.duration', + /** Milliseconds. Pairs with the seconds-valued `app_startup.duration` + * the way `anr.duration_ms` replaced `anr.duration`; readers coalesce + * this over the legacy key while both exist. */ + APP_STARTUP_DURATION_MS: 'app_startup.duration_ms', APP_STARTUP_METRIC: 'app_startup.metric', APP_STARTUP_IS_PREWARMED: 'app_startup.is_prewarmed', APP_STARTUP_HAS_SAVED_INSTANCE_STATE_BUNDLE: diff --git a/src/core/scope.ts b/src/core/scope.ts index fbf40d3..9a942b9 100644 --- a/src/core/scope.ts +++ b/src/core/scope.ts @@ -1,2 +1,2 @@ export const SCOPE_NAME = 'base14.scout.react'; -export const SCOPE_VERSION = '0.1.17'; +export const SCOPE_VERSION = '0.1.18'; diff --git a/src/native/index.ts b/src/native/index.ts index 7594f2f..9cb7e2c 100644 --- a/src/native/index.ts +++ b/src/native/index.ts @@ -314,12 +314,13 @@ export const Scout = { nativeStartMs !== null ? (Date.now() - nativeStartMs) / 1000 : core.timeSinceAppStartMs() / 1000; + const fbcMs = Math.round(coldDurationSec * 1000); core.emitSpan(SPAN.APP_STARTUP, { [ATTR.APP_STARTUP_TYPE]: 'cold', [ATTR.APP_STARTUP_DURATION]: coldDurationSec, + [ATTR.APP_STARTUP_DURATION_MS]: fbcMs, ...core.commonAttributes(), }); - const fbcMs = Math.round(coldDurationSec * 1000); core.emitSpan(SPAN.APP_VITAL, { [ATTR.VITAL_NAME]: 'fbc', [ATTR.VITAL_TYPE]: 'startup', diff --git a/src/native/instrumentations/lifecycle.ts b/src/native/instrumentations/lifecycle.ts index fe73ffd..204c18d 100644 --- a/src/native/instrumentations/lifecycle.ts +++ b/src/native/instrumentations/lifecycle.ts @@ -55,6 +55,7 @@ export function installNativeLifecycleTracker( scout.emitSpan(SPAN.APP_STARTUP, { [ATTR.APP_STARTUP_TYPE]: 'warm', [ATTR.APP_STARTUP_DURATION]: durationMs / 1000, + [ATTR.APP_STARTUP_DURATION_MS]: Math.round(durationMs), ...scout.commonAttributes(), }); } catch {} diff --git a/src/web/instrumentations/startup.ts b/src/web/instrumentations/startup.ts index 9d82f83..f395305 100644 --- a/src/web/instrumentations/startup.ts +++ b/src/web/instrumentations/startup.ts @@ -29,6 +29,7 @@ export function installStartupTracker(scout: Scout): () => void { scout.emitSpan(SPAN.APP_STARTUP, { [ATTR.APP_STARTUP_TYPE]: 'cold', [ATTR.APP_STARTUP_DURATION]: duration, + [ATTR.APP_STARTUP_DURATION_MS]: Math.round(duration * 1000), [ATTR.BROWSER_NAV_DOM_COMPLETE_MS]: nav.domComplete, [ATTR.BROWSER_NAV_DOM_CONTENT_LOADED_MS]: nav.domContentLoadedEventEnd, [ATTR.BROWSER_NAV_DOM_INTERACTIVE_MS]: nav.domInteractive, @@ -52,6 +53,7 @@ export function installStartupTracker(scout: Scout): () => void { scout.emitSpan(SPAN.APP_STARTUP, { [ATTR.APP_STARTUP_TYPE]: 'warm', [ATTR.APP_STARTUP_DURATION]: 0, + [ATTR.APP_STARTUP_DURATION_MS]: 0, ...scout.commonAttributes(), }); scout.addBreadcrumb(BREADCRUMB_TYPE.STARTUP, 'warm start (bfcache)');