From 146237434209b45d8b96e7a6953f37488e89b2ab Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 25 Aug 2026 16:14:39 +0200 Subject: [PATCH 1/3] feat(attributes): Add app.vitals.start.value and deprecate cold/warm split Mobile SDKs are moving to a single duration plus app.vitals.start.type. Do not alias the split names onto the unified key: cold and warm are mutually exclusive type-specific attributes, not 1:1 copies. Fixes #318 Co-authored-by: Cursor --- .../sentry-conventions/src/attributes.ts | 172 +++++++++++++++--- .../app/app__vitals__start__cold__value.json | 9 + .../app/app__vitals__start__value.json | 21 +++ .../app/app__vitals__start__warm__value.json | 9 + model/attributes/app_start_cold.json | 8 +- model/attributes/app_start_warm.json | 8 +- model/measurements/app_start_cold.json | 2 +- model/measurements/app_start_warm.json | 2 +- python/src/sentry_conventions/attributes.py | 91 ++++++++- 9 files changed, 288 insertions(+), 34 deletions(-) create mode 100644 model/attributes/app/app__vitals__start__value.json diff --git a/javascript/sentry-conventions/src/attributes.ts b/javascript/sentry-conventions/src/attributes.ts index 6a5bf0d0b..f89c501c5 100644 --- a/javascript/sentry-conventions/src/attributes.ts +++ b/javascript/sentry-conventions/src/attributes.ts @@ -1155,7 +1155,7 @@ export type APP_NAME_TYPE = string; * * Aliases: {@link APP_VITALS_START_COLD_VALUE} `app.vitals.start.cold.value` * - * @deprecated Use {@link APP_VITALS_START_COLD_VALUE} (app.vitals.start.cold.value) instead - Replaced by app.vitals.start.cold.value to align with the app.vitals.* namespace for mobile performance attributes + * @deprecated Use {@link APP_VITALS_START_VALUE} (app.vitals.start.value) instead - Replaced by app.vitals.start.value plus app.vitals.start.type * @example 1234.56 */ export const APP_START_COLD = 'app_start_cold'; @@ -1226,7 +1226,7 @@ export type APP_START_TYPE_TYPE = string; * * Aliases: {@link APP_VITALS_START_WARM_VALUE} `app.vitals.start.warm.value` * - * @deprecated Use {@link APP_VITALS_START_WARM_VALUE} (app.vitals.start.warm.value) instead - Replaced by app.vitals.start.warm.value to align with the app.vitals.* namespace for mobile performance attributes + * @deprecated Use {@link APP_VITALS_START_VALUE} (app.vitals.start.value) instead - Replaced by app.vitals.start.value plus app.vitals.start.type * @example 1234.56 */ export const APP_START_WARM = 'app_start_warm'; @@ -1457,6 +1457,7 @@ export type APP_VITALS_STALL_PERCENTAGE_TYPE = number; * * Aliases: {@link APP_START_COLD} `app_start_cold` * + * @deprecated Use {@link APP_VITALS_START_VALUE} (app.vitals.start.value) instead - Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names. * @example 1234.56 */ export const APP_VITALS_START_COLD_VALUE = 'app.vitals.start.cold.value'; @@ -1552,6 +1553,27 @@ export const APP_VITALS_START_TYPE = 'app.vitals.start.type'; */ export type APP_VITALS_START_TYPE_TYPE = string; +// Path: model/attributes/app/app__vitals__start__value.json + +/** + * The duration of an app start in milliseconds. Use with app.vitals.start.type to distinguish cold vs warm starts. `app.vitals.start.value` + * + * Attribute Value Type: `number` {@link APP_VITALS_START_VALUE_TYPE} + * + * Apply Scrubbing: manual + * + * Attribute defined in OTEL: No + * Visibility: public + * + * @example 1234.56 + */ +export const APP_VITALS_START_VALUE = 'app.vitals.start.value'; + +/** + * Type for {@link APP_VITALS_START_VALUE} app.vitals.start.value + */ +export type APP_VITALS_START_VALUE_TYPE = number; + // Path: model/attributes/app/app__vitals__start__warm__value.json /** @@ -1566,6 +1588,7 @@ export type APP_VITALS_START_TYPE_TYPE = string; * * Aliases: {@link APP_START_WARM} `app_start_warm` * + * @deprecated Use {@link APP_VITALS_START_VALUE} (app.vitals.start.value) instead - Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names. * @example 1234.56 */ export const APP_VITALS_START_WARM_VALUE = 'app.vitals.start.warm.value'; @@ -17769,6 +17792,7 @@ export const ATTRIBUTE_TYPE: Record = { 'app.vitals.start.reason': 'string', 'app.vitals.start.screen': 'string', 'app.vitals.start.type': 'string', + 'app.vitals.start.value': 'double', 'app.vitals.start.warm.value': 'double', 'app.vitals.ttfd.value': 'double', 'app.vitals.ttid.value': 'double', @@ -18564,6 +18588,7 @@ export type AttributeName = | typeof APP_VITALS_START_REASON | typeof APP_VITALS_START_SCREEN | typeof APP_VITALS_START_TYPE + | typeof APP_VITALS_START_VALUE | typeof APP_VITALS_START_WARM_VALUE | typeof APP_VITALS_TTFD_VALUE | typeof APP_VITALS_TTID_VALUE @@ -20162,7 +20187,13 @@ export const ATTRIBUTE_METADATA: Record = { app_start_cold: { brief: 'The duration of a cold app start in milliseconds', type: 'double', - keys: ['app.vitals.start.cold.value', 'app_start_cold'], + keys: [ + 'app.vitals.start.value', + 'app.vitals.start.cold.value', + 'app.vitals.start.warm.value', + 'app_start_cold', + 'app_start_warm', + ], applyScrubbing: { key: 'manual', }, @@ -20170,13 +20201,13 @@ export const ATTRIBUTE_METADATA: Record = { visibility: 'public', example: 1234.56, deprecation: { - replacement: 'app.vitals.start.cold.value', - reason: - 'Replaced by app.vitals.start.cold.value to align with the app.vitals.* namespace for mobile performance attributes', + replacement: 'app.vitals.start.value', + reason: 'Replaced by app.vitals.start.value plus app.vitals.start.type', status: 'backfill', }, aliases: ['app.vitals.start.cold.value'], changelog: [ + { version: 'next', description: 'Retargeted deprecation replacement to app.vitals.start.value' }, { version: '0.5.0', prs: [323], description: 'Added and deprecated in favor of app.vitals.start.cold.value' }, ], }, @@ -20219,7 +20250,13 @@ export const ATTRIBUTE_METADATA: Record = { app_start_warm: { brief: 'The duration of a warm app start in milliseconds', type: 'double', - keys: ['app.vitals.start.warm.value', 'app_start_warm'], + keys: [ + 'app.vitals.start.value', + 'app.vitals.start.cold.value', + 'app.vitals.start.warm.value', + 'app_start_cold', + 'app_start_warm', + ], applyScrubbing: { key: 'manual', }, @@ -20227,13 +20264,13 @@ export const ATTRIBUTE_METADATA: Record = { visibility: 'public', example: 1234.56, deprecation: { - replacement: 'app.vitals.start.warm.value', - reason: - 'Replaced by app.vitals.start.warm.value to align with the app.vitals.* namespace for mobile performance attributes', + replacement: 'app.vitals.start.value', + reason: 'Replaced by app.vitals.start.value plus app.vitals.start.type', status: 'backfill', }, aliases: ['app.vitals.start.warm.value'], changelog: [ + { version: 'next', description: 'Retargeted deprecation replacement to app.vitals.start.value' }, { version: '0.5.0', prs: [323], description: 'Added and deprecated in favor of app.vitals.start.warm.value' }, ], }, @@ -20373,15 +20410,30 @@ export const ATTRIBUTE_METADATA: Record = { 'app.vitals.start.cold.value': { brief: 'The duration of a cold app start in milliseconds', type: 'double', - keys: ['app.vitals.start.cold.value', 'app_start_cold'], + keys: [ + 'app.vitals.start.value', + 'app.vitals.start.cold.value', + 'app.vitals.start.warm.value', + 'app_start_cold', + 'app_start_warm', + ], applyScrubbing: { key: 'manual', }, isInOtel: false, visibility: 'public', example: 1234.56, + deprecation: { + replacement: 'app.vitals.start.value', + reason: + 'Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.', + status: 'backfill', + }, aliases: ['app_start_cold'], - changelog: [{ version: '0.5.0', prs: [313], description: 'Added app.vitals.start.cold.value attribute' }], + changelog: [ + { version: 'next', description: 'Deprecated in favor of app.vitals.start.value plus app.vitals.start.type' }, + { version: '0.5.0', prs: [313], description: 'Added app.vitals.start.cold.value attribute' }, + ], }, 'app.vitals.start.prewarmed': { brief: 'Whether the app start was prewarmed.', @@ -20433,18 +20485,56 @@ export const ATTRIBUTE_METADATA: Record = { aliases: ['app_start_type'], changelog: [{ version: '0.5.0', prs: [313], description: 'Added app.vitals.start.type attribute' }], }, + 'app.vitals.start.value': { + brief: + 'The duration of an app start in milliseconds. Use with app.vitals.start.type to distinguish cold vs warm starts.', + type: 'double', + keys: [ + 'app.vitals.start.value', + 'app.vitals.start.cold.value', + 'app.vitals.start.warm.value', + 'app_start_cold', + 'app_start_warm', + ], + applyScrubbing: { + key: 'manual', + }, + isInOtel: false, + visibility: 'public', + example: 1234.56, + changelog: [{ version: 'next', description: 'Added app.vitals.start.value as the unified app start duration' }], + additionalContext: [ + 'Send this together with app.vitals.start.type (`cold` or `warm`). This replaces app.vitals.start.cold.value and app.vitals.start.warm.value.', + 'Not aliased to the cold/warm attributes: those names are type-specific, not 1:1 copies of this key. Relay still derives this attribute (and type) from the older names when they are present.', + ], + }, 'app.vitals.start.warm.value': { brief: 'The duration of a warm app start in milliseconds', type: 'double', - keys: ['app.vitals.start.warm.value', 'app_start_warm'], + keys: [ + 'app.vitals.start.value', + 'app.vitals.start.cold.value', + 'app.vitals.start.warm.value', + 'app_start_cold', + 'app_start_warm', + ], applyScrubbing: { key: 'manual', }, isInOtel: false, visibility: 'public', example: 1234.56, + deprecation: { + replacement: 'app.vitals.start.value', + reason: + 'Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.', + status: 'backfill', + }, aliases: ['app_start_warm'], - changelog: [{ version: '0.5.0', prs: [313], description: 'Added app.vitals.start.warm.value attribute' }], + changelog: [ + { version: 'next', description: 'Deprecated in favor of app.vitals.start.value plus app.vitals.start.type' }, + { version: '0.5.0', prs: [313], description: 'Added app.vitals.start.warm.value attribute' }, + ], }, 'app.vitals.ttfd.value': { brief: 'The duration of time to full display in milliseconds', @@ -31788,10 +31878,16 @@ export const ATTRIBUTE_SEARCH_METADATA: Record deprecationChain: ['app.vitals.stall.percentage', 'stall_percentage'], }, 'app.vitals.start.cold.value': { - canonicalName: 'app.vitals.start.cold.value', + canonicalName: 'app.vitals.start.value', type: 'double', brief: 'The duration of a cold app start in milliseconds', - deprecationChain: ['app.vitals.start.cold.value', 'app_start_cold'], + deprecationChain: [ + 'app.vitals.start.value', + 'app.vitals.start.cold.value', + 'app.vitals.start.warm.value', + 'app_start_cold', + 'app_start_warm', + ], }, 'app.vitals.start.prewarmed': { canonicalName: 'app.vitals.start.prewarmed', @@ -31818,11 +31914,30 @@ export const ATTRIBUTE_SEARCH_METADATA: Record brief: 'The type of app start, for example `cold` or `warm`', deprecationChain: ['app.vitals.start.type', 'app_start_type'], }, + 'app.vitals.start.value': { + canonicalName: 'app.vitals.start.value', + type: 'double', + brief: + 'The duration of an app start in milliseconds. Use with app.vitals.start.type to distinguish cold vs warm starts.', + deprecationChain: [ + 'app.vitals.start.value', + 'app.vitals.start.cold.value', + 'app.vitals.start.warm.value', + 'app_start_cold', + 'app_start_warm', + ], + }, 'app.vitals.start.warm.value': { - canonicalName: 'app.vitals.start.warm.value', + canonicalName: 'app.vitals.start.value', type: 'double', brief: 'The duration of a warm app start in milliseconds', - deprecationChain: ['app.vitals.start.warm.value', 'app_start_warm'], + deprecationChain: [ + 'app.vitals.start.value', + 'app.vitals.start.cold.value', + 'app.vitals.start.warm.value', + 'app_start_cold', + 'app_start_warm', + ], }, 'app.vitals.ttfd.value': { canonicalName: 'app.vitals.ttfd.value', @@ -31837,10 +31952,16 @@ export const ATTRIBUTE_SEARCH_METADATA: Record deprecationChain: ['app.vitals.ttid.value', 'time_to_initial_display'], }, app_start_cold: { - canonicalName: 'app.vitals.start.cold.value', + canonicalName: 'app.vitals.start.value', type: 'double', brief: 'The duration of a cold app start in milliseconds', - deprecationChain: ['app.vitals.start.cold.value', 'app_start_cold'], + deprecationChain: [ + 'app.vitals.start.value', + 'app.vitals.start.cold.value', + 'app.vitals.start.warm.value', + 'app_start_cold', + 'app_start_warm', + ], }, app_start_type: { canonicalName: 'app.vitals.start.type', @@ -31849,10 +31970,16 @@ export const ATTRIBUTE_SEARCH_METADATA: Record deprecationChain: ['app.vitals.start.type', 'app_start_type'], }, app_start_warm: { - canonicalName: 'app.vitals.start.warm.value', + canonicalName: 'app.vitals.start.value', type: 'double', brief: 'The duration of a warm app start in milliseconds', - deprecationChain: ['app.vitals.start.warm.value', 'app_start_warm'], + deprecationChain: [ + 'app.vitals.start.value', + 'app.vitals.start.cold.value', + 'app.vitals.start.warm.value', + 'app_start_cold', + 'app_start_warm', + ], }, 'art.gc.blocking_count': { canonicalName: 'art.gc.blocking_count', @@ -36414,6 +36541,7 @@ export type Attributes = { [APP_VITALS_START_REASON]?: APP_VITALS_START_REASON_TYPE; [APP_VITALS_START_SCREEN]?: APP_VITALS_START_SCREEN_TYPE; [APP_VITALS_START_TYPE]?: APP_VITALS_START_TYPE_TYPE; + [APP_VITALS_START_VALUE]?: APP_VITALS_START_VALUE_TYPE; [APP_VITALS_START_WARM_VALUE]?: APP_VITALS_START_WARM_VALUE_TYPE; [APP_VITALS_TTFD_VALUE]?: APP_VITALS_TTFD_VALUE_TYPE; [APP_VITALS_TTID_VALUE]?: APP_VITALS_TTID_VALUE_TYPE; diff --git a/model/attributes/app/app__vitals__start__cold__value.json b/model/attributes/app/app__vitals__start__cold__value.json index 4614c8ec1..4683b51c0 100644 --- a/model/attributes/app/app__vitals__start__cold__value.json +++ b/model/attributes/app/app__vitals__start__cold__value.json @@ -8,8 +8,17 @@ "is_in_otel": false, "example": 1234.56, "alias": ["app_start_cold"], + "deprecation": { + "replacement": "app.vitals.start.value", + "reason": "Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.", + "_status": "backfill" + }, "visibility": "public", "changelog": [ + { + "version": "next", + "description": "Deprecated in favor of app.vitals.start.value plus app.vitals.start.type" + }, { "version": "0.5.0", "prs": [313], diff --git a/model/attributes/app/app__vitals__start__value.json b/model/attributes/app/app__vitals__start__value.json new file mode 100644 index 000000000..a7354ec52 --- /dev/null +++ b/model/attributes/app/app__vitals__start__value.json @@ -0,0 +1,21 @@ +{ + "key": "app.vitals.start.value", + "brief": "The duration of an app start in milliseconds. Use with app.vitals.start.type to distinguish cold vs warm starts.", + "type": "double", + "apply_scrubbing": { + "key": "manual" + }, + "is_in_otel": false, + "example": 1234.56, + "visibility": "public", + "additional_context": [ + "Send this together with app.vitals.start.type (`cold` or `warm`). This replaces app.vitals.start.cold.value and app.vitals.start.warm.value.", + "Not aliased to the cold/warm attributes: those names are type-specific, not 1:1 copies of this key. Relay still derives this attribute (and type) from the older names when they are present." + ], + "changelog": [ + { + "version": "next", + "description": "Added app.vitals.start.value as the unified app start duration" + } + ] +} diff --git a/model/attributes/app/app__vitals__start__warm__value.json b/model/attributes/app/app__vitals__start__warm__value.json index 824f023a2..79f8c4414 100644 --- a/model/attributes/app/app__vitals__start__warm__value.json +++ b/model/attributes/app/app__vitals__start__warm__value.json @@ -8,8 +8,17 @@ "is_in_otel": false, "example": 1234.56, "alias": ["app_start_warm"], + "deprecation": { + "replacement": "app.vitals.start.value", + "reason": "Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.", + "_status": "backfill" + }, "visibility": "public", "changelog": [ + { + "version": "next", + "description": "Deprecated in favor of app.vitals.start.value plus app.vitals.start.type" + }, { "version": "0.5.0", "prs": [313], diff --git a/model/attributes/app_start_cold.json b/model/attributes/app_start_cold.json index 0381aebe2..d1dd8868b 100644 --- a/model/attributes/app_start_cold.json +++ b/model/attributes/app_start_cold.json @@ -9,12 +9,16 @@ "example": 1234.56, "alias": ["app.vitals.start.cold.value"], "deprecation": { - "replacement": "app.vitals.start.cold.value", - "reason": "Replaced by app.vitals.start.cold.value to align with the app.vitals.* namespace for mobile performance attributes", + "replacement": "app.vitals.start.value", + "reason": "Replaced by app.vitals.start.value plus app.vitals.start.type", "_status": "backfill" }, "visibility": "public", "changelog": [ + { + "version": "next", + "description": "Retargeted deprecation replacement to app.vitals.start.value" + }, { "version": "0.5.0", "prs": [323], diff --git a/model/attributes/app_start_warm.json b/model/attributes/app_start_warm.json index a73587cac..b012a2d98 100644 --- a/model/attributes/app_start_warm.json +++ b/model/attributes/app_start_warm.json @@ -9,12 +9,16 @@ "example": 1234.56, "alias": ["app.vitals.start.warm.value"], "deprecation": { - "replacement": "app.vitals.start.warm.value", - "reason": "Replaced by app.vitals.start.warm.value to align with the app.vitals.* namespace for mobile performance attributes", + "replacement": "app.vitals.start.value", + "reason": "Replaced by app.vitals.start.value plus app.vitals.start.type", "_status": "backfill" }, "visibility": "public", "changelog": [ + { + "version": "next", + "description": "Retargeted deprecation replacement to app.vitals.start.value" + }, { "version": "0.5.0", "prs": [323], diff --git a/model/measurements/app_start_cold.json b/model/measurements/app_start_cold.json index 9b73fba0f..0590b990e 100644 --- a/model/measurements/app_start_cold.json +++ b/model/measurements/app_start_cold.json @@ -4,5 +4,5 @@ "brief": "The time it takes for the app to start from a cold state", "unit": "millisecond", "platform": "mobile", - "attribute": "app.vitals.start.cold.value" + "attribute": "app.vitals.start.value" } diff --git a/model/measurements/app_start_warm.json b/model/measurements/app_start_warm.json index b62779c24..4d9caea1e 100644 --- a/model/measurements/app_start_warm.json +++ b/model/measurements/app_start_warm.json @@ -4,5 +4,5 @@ "brief": "The time it takes for the app to start from a warm state", "unit": "millisecond", "platform": "mobile", - "attribute": "app.vitals.start.warm.value" + "attribute": "app.vitals.start.value" } diff --git a/python/src/sentry_conventions/attributes.py b/python/src/sentry_conventions/attributes.py index e78a4b14d..334ecb8e0 100644 --- a/python/src/sentry_conventions/attributes.py +++ b/python/src/sentry_conventions/attributes.py @@ -197,6 +197,8 @@ class _AttributeNamesMeta(type): "APP_APP_NAME", "APP_APP_START_TIME", "APP_APP_VERSION", + "APP_VITALS_START_COLD_VALUE", + "APP_VITALS_START_WARM_VALUE", "APP_START_COLD", "APP_START_TYPE", "APP_START_WARM", @@ -1151,6 +1153,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): Defined in OTEL: No Visibility: public Aliases: app_start_cold + DEPRECATED: Use app.vitals.start.value instead - Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names. Example: 1234.56 """ @@ -1205,6 +1208,17 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): Example: "cold" """ + # Path: model/attributes/app/app__vitals__start__value.json + APP_VITALS_START_VALUE: Literal["app.vitals.start.value"] = "app.vitals.start.value" + """The duration of an app start in milliseconds. Use with app.vitals.start.type to distinguish cold vs warm starts. + + Type: float + Apply Scrubbing: manual + Defined in OTEL: No + Visibility: public + Example: 1234.56 + """ + # Path: model/attributes/app/app__vitals__start__warm__value.json APP_VITALS_START_WARM_VALUE: Literal["app.vitals.start.warm.value"] = ( "app.vitals.start.warm.value" @@ -1216,6 +1230,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): Defined in OTEL: No Visibility: public Aliases: app_start_warm + DEPRECATED: Use app.vitals.start.value instead - Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names. Example: 1234.56 """ @@ -1252,7 +1267,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): Defined in OTEL: No Visibility: public Aliases: app.vitals.start.cold.value - DEPRECATED: Use app.vitals.start.cold.value instead - Replaced by app.vitals.start.cold.value to align with the app.vitals.* namespace for mobile performance attributes + DEPRECATED: Use app.vitals.start.value instead - Replaced by app.vitals.start.value plus app.vitals.start.type Example: 1234.56 """ @@ -1278,7 +1293,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): Defined in OTEL: No Visibility: public Aliases: app.vitals.start.warm.value - DEPRECATED: Use app.vitals.start.warm.value instead - Replaced by app.vitals.start.warm.value to align with the app.vitals.* namespace for mobile performance attributes + DEPRECATED: Use app.vitals.start.value instead - Replaced by app.vitals.start.value plus app.vitals.start.type Example: 1234.56 """ @@ -11412,15 +11427,27 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): brief="The duration of a cold app start in milliseconds", type=AttributeType.DOUBLE, keys=( + "app.vitals.start.value", "app.vitals.start.cold.value", + "app.vitals.start.warm.value", "app_start_cold", + "app_start_warm", ), apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), is_in_otel=False, visibility=Visibility.PUBLIC, example=1234.56, + deprecation=DeprecationInfo( + replacement="app.vitals.start.value", + reason="Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.", + status=DeprecationStatus.BACKFILL, + ), aliases=["app_start_cold"], changelog=[ + ChangelogEntry( + version="next", + description="Deprecated in favor of app.vitals.start.value plus app.vitals.start.type", + ), ChangelogEntry( version="0.5.0", prs=[313], @@ -11496,19 +11523,56 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): ), ], ), + "app.vitals.start.value": AttributeMetadata( + brief="The duration of an app start in milliseconds. Use with app.vitals.start.type to distinguish cold vs warm starts.", + type=AttributeType.DOUBLE, + keys=( + "app.vitals.start.value", + "app.vitals.start.cold.value", + "app.vitals.start.warm.value", + "app_start_cold", + "app_start_warm", + ), + apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), + is_in_otel=False, + visibility=Visibility.PUBLIC, + example=1234.56, + changelog=[ + ChangelogEntry( + version="next", + description="Added app.vitals.start.value as the unified app start duration", + ), + ], + additional_context=[ + "Send this together with app.vitals.start.type (`cold` or `warm`). This replaces app.vitals.start.cold.value and app.vitals.start.warm.value.", + "Not aliased to the cold/warm attributes: those names are type-specific, not 1:1 copies of this key. Relay still derives this attribute (and type) from the older names when they are present.", + ], + ), "app.vitals.start.warm.value": AttributeMetadata( brief="The duration of a warm app start in milliseconds", type=AttributeType.DOUBLE, keys=( + "app.vitals.start.value", + "app.vitals.start.cold.value", "app.vitals.start.warm.value", + "app_start_cold", "app_start_warm", ), apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), is_in_otel=False, visibility=Visibility.PUBLIC, example=1234.56, + deprecation=DeprecationInfo( + replacement="app.vitals.start.value", + reason="Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.", + status=DeprecationStatus.BACKFILL, + ), aliases=["app_start_warm"], changelog=[ + ChangelogEntry( + version="next", + description="Deprecated in favor of app.vitals.start.value plus app.vitals.start.type", + ), ChangelogEntry( version="0.5.0", prs=[313], @@ -11560,20 +11624,27 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): brief="The duration of a cold app start in milliseconds", type=AttributeType.DOUBLE, keys=( + "app.vitals.start.value", "app.vitals.start.cold.value", + "app.vitals.start.warm.value", "app_start_cold", + "app_start_warm", ), apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), is_in_otel=False, visibility=Visibility.PUBLIC, example=1234.56, deprecation=DeprecationInfo( - replacement="app.vitals.start.cold.value", - reason="Replaced by app.vitals.start.cold.value to align with the app.vitals.* namespace for mobile performance attributes", + replacement="app.vitals.start.value", + reason="Replaced by app.vitals.start.value plus app.vitals.start.type", status=DeprecationStatus.BACKFILL, ), aliases=["app.vitals.start.cold.value"], changelog=[ + ChangelogEntry( + version="next", + description="Retargeted deprecation replacement to app.vitals.start.value", + ), ChangelogEntry( version="0.5.0", prs=[323], @@ -11612,7 +11683,10 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): brief="The duration of a warm app start in milliseconds", type=AttributeType.DOUBLE, keys=( + "app.vitals.start.value", + "app.vitals.start.cold.value", "app.vitals.start.warm.value", + "app_start_cold", "app_start_warm", ), apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), @@ -11620,12 +11694,16 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): visibility=Visibility.PUBLIC, example=1234.56, deprecation=DeprecationInfo( - replacement="app.vitals.start.warm.value", - reason="Replaced by app.vitals.start.warm.value to align with the app.vitals.* namespace for mobile performance attributes", + replacement="app.vitals.start.value", + reason="Replaced by app.vitals.start.value plus app.vitals.start.type", status=DeprecationStatus.BACKFILL, ), aliases=["app.vitals.start.warm.value"], changelog=[ + ChangelogEntry( + version="next", + description="Retargeted deprecation replacement to app.vitals.start.value", + ), ChangelogEntry( version="0.5.0", prs=[323], @@ -24267,6 +24345,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): "app.vitals.start.reason": str, "app.vitals.start.screen": str, "app.vitals.start.type": str, + "app.vitals.start.value": float, "app.vitals.start.warm.value": float, "app.vitals.ttfd.value": float, "app.vitals.ttid.value": float, From c4e3bcf6f9467397f27ce51a4de3d637b8f32afb Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 25 Aug 2026 16:15:45 +0200 Subject: [PATCH 2/3] meta(attributes): Record PR 576 on app start value changelog entries Co-authored-by: Cursor --- .../sentry-conventions/src/attributes.ts | 20 ++++++++++++++----- .../app/app__vitals__start__cold__value.json | 1 + .../app/app__vitals__start__value.json | 1 + .../app/app__vitals__start__warm__value.json | 1 + model/attributes/app_start_cold.json | 1 + model/attributes/app_start_warm.json | 1 + python/src/sentry_conventions/attributes.py | 5 +++++ 7 files changed, 25 insertions(+), 5 deletions(-) diff --git a/javascript/sentry-conventions/src/attributes.ts b/javascript/sentry-conventions/src/attributes.ts index f89c501c5..ce7c434e6 100644 --- a/javascript/sentry-conventions/src/attributes.ts +++ b/javascript/sentry-conventions/src/attributes.ts @@ -20207,7 +20207,7 @@ export const ATTRIBUTE_METADATA: Record = { }, aliases: ['app.vitals.start.cold.value'], changelog: [ - { version: 'next', description: 'Retargeted deprecation replacement to app.vitals.start.value' }, + { version: 'next', prs: [576], description: 'Retargeted deprecation replacement to app.vitals.start.value' }, { version: '0.5.0', prs: [323], description: 'Added and deprecated in favor of app.vitals.start.cold.value' }, ], }, @@ -20270,7 +20270,7 @@ export const ATTRIBUTE_METADATA: Record = { }, aliases: ['app.vitals.start.warm.value'], changelog: [ - { version: 'next', description: 'Retargeted deprecation replacement to app.vitals.start.value' }, + { version: 'next', prs: [576], description: 'Retargeted deprecation replacement to app.vitals.start.value' }, { version: '0.5.0', prs: [323], description: 'Added and deprecated in favor of app.vitals.start.warm.value' }, ], }, @@ -20431,7 +20431,11 @@ export const ATTRIBUTE_METADATA: Record = { }, aliases: ['app_start_cold'], changelog: [ - { version: 'next', description: 'Deprecated in favor of app.vitals.start.value plus app.vitals.start.type' }, + { + version: 'next', + prs: [576], + description: 'Deprecated in favor of app.vitals.start.value plus app.vitals.start.type', + }, { version: '0.5.0', prs: [313], description: 'Added app.vitals.start.cold.value attribute' }, ], }, @@ -20502,7 +20506,9 @@ export const ATTRIBUTE_METADATA: Record = { isInOtel: false, visibility: 'public', example: 1234.56, - changelog: [{ version: 'next', description: 'Added app.vitals.start.value as the unified app start duration' }], + changelog: [ + { version: 'next', prs: [576], description: 'Added app.vitals.start.value as the unified app start duration' }, + ], additionalContext: [ 'Send this together with app.vitals.start.type (`cold` or `warm`). This replaces app.vitals.start.cold.value and app.vitals.start.warm.value.', 'Not aliased to the cold/warm attributes: those names are type-specific, not 1:1 copies of this key. Relay still derives this attribute (and type) from the older names when they are present.', @@ -20532,7 +20538,11 @@ export const ATTRIBUTE_METADATA: Record = { }, aliases: ['app_start_warm'], changelog: [ - { version: 'next', description: 'Deprecated in favor of app.vitals.start.value plus app.vitals.start.type' }, + { + version: 'next', + prs: [576], + description: 'Deprecated in favor of app.vitals.start.value plus app.vitals.start.type', + }, { version: '0.5.0', prs: [313], description: 'Added app.vitals.start.warm.value attribute' }, ], }, diff --git a/model/attributes/app/app__vitals__start__cold__value.json b/model/attributes/app/app__vitals__start__cold__value.json index 4683b51c0..484eb5607 100644 --- a/model/attributes/app/app__vitals__start__cold__value.json +++ b/model/attributes/app/app__vitals__start__cold__value.json @@ -17,6 +17,7 @@ "changelog": [ { "version": "next", + "prs": [576], "description": "Deprecated in favor of app.vitals.start.value plus app.vitals.start.type" }, { diff --git a/model/attributes/app/app__vitals__start__value.json b/model/attributes/app/app__vitals__start__value.json index a7354ec52..056251f42 100644 --- a/model/attributes/app/app__vitals__start__value.json +++ b/model/attributes/app/app__vitals__start__value.json @@ -15,6 +15,7 @@ "changelog": [ { "version": "next", + "prs": [576], "description": "Added app.vitals.start.value as the unified app start duration" } ] diff --git a/model/attributes/app/app__vitals__start__warm__value.json b/model/attributes/app/app__vitals__start__warm__value.json index 79f8c4414..8d84863c1 100644 --- a/model/attributes/app/app__vitals__start__warm__value.json +++ b/model/attributes/app/app__vitals__start__warm__value.json @@ -17,6 +17,7 @@ "changelog": [ { "version": "next", + "prs": [576], "description": "Deprecated in favor of app.vitals.start.value plus app.vitals.start.type" }, { diff --git a/model/attributes/app_start_cold.json b/model/attributes/app_start_cold.json index d1dd8868b..ea3b3e94b 100644 --- a/model/attributes/app_start_cold.json +++ b/model/attributes/app_start_cold.json @@ -17,6 +17,7 @@ "changelog": [ { "version": "next", + "prs": [576], "description": "Retargeted deprecation replacement to app.vitals.start.value" }, { diff --git a/model/attributes/app_start_warm.json b/model/attributes/app_start_warm.json index b012a2d98..34e805c13 100644 --- a/model/attributes/app_start_warm.json +++ b/model/attributes/app_start_warm.json @@ -17,6 +17,7 @@ "changelog": [ { "version": "next", + "prs": [576], "description": "Retargeted deprecation replacement to app.vitals.start.value" }, { diff --git a/python/src/sentry_conventions/attributes.py b/python/src/sentry_conventions/attributes.py index 334ecb8e0..162c5dc28 100644 --- a/python/src/sentry_conventions/attributes.py +++ b/python/src/sentry_conventions/attributes.py @@ -11446,6 +11446,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): changelog=[ ChangelogEntry( version="next", + prs=[576], description="Deprecated in favor of app.vitals.start.value plus app.vitals.start.type", ), ChangelogEntry( @@ -11540,6 +11541,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): changelog=[ ChangelogEntry( version="next", + prs=[576], description="Added app.vitals.start.value as the unified app start duration", ), ], @@ -11571,6 +11573,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): changelog=[ ChangelogEntry( version="next", + prs=[576], description="Deprecated in favor of app.vitals.start.value plus app.vitals.start.type", ), ChangelogEntry( @@ -11643,6 +11646,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): changelog=[ ChangelogEntry( version="next", + prs=[576], description="Retargeted deprecation replacement to app.vitals.start.value", ), ChangelogEntry( @@ -11702,6 +11706,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): changelog=[ ChangelogEntry( version="next", + prs=[576], description="Retargeted deprecation replacement to app.vitals.start.value", ), ChangelogEntry( From b2e9942bfba47cc1d2bd25b50fe4a2d79b3da22d Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Tue, 25 Aug 2026 16:30:44 +0200 Subject: [PATCH 3/3] fix(attributes): Use transform deprecation for app start cold/warm split backfill joined cold and warm into one keys family. A transformation keeps the type-specific names distinct while still documenting Relay's value+type derivation. Co-authored-by: Cursor --- .../sentry-conventions/src/attributes.ts | 110 +++++------------- .../app_vitals_start_cold_warm_to_value.json | 82 +++++++++++++ .../app/app__vitals__start__cold__value.json | 5 +- .../app/app__vitals__start__value.json | 2 +- .../app/app__vitals__start__warm__value.json | 5 +- model/attributes/app_start_cold.json | 5 +- model/attributes/app_start_warm.json | 5 +- model/measurements/app_start_cold.json | 2 +- model/measurements/app_start_warm.json | 2 +- python/src/sentry_conventions/attributes.py | 62 +++------- 10 files changed, 146 insertions(+), 134 deletions(-) create mode 100644 model/attribute_transformations/app_vitals_start_cold_warm_to_value.json diff --git a/javascript/sentry-conventions/src/attributes.ts b/javascript/sentry-conventions/src/attributes.ts index ce7c434e6..08a9a1a30 100644 --- a/javascript/sentry-conventions/src/attributes.ts +++ b/javascript/sentry-conventions/src/attributes.ts @@ -20187,13 +20187,7 @@ export const ATTRIBUTE_METADATA: Record = { app_start_cold: { brief: 'The duration of a cold app start in milliseconds', type: 'double', - keys: [ - 'app.vitals.start.value', - 'app.vitals.start.cold.value', - 'app.vitals.start.warm.value', - 'app_start_cold', - 'app_start_warm', - ], + keys: ['app_start_cold'], applyScrubbing: { key: 'manual', }, @@ -20203,11 +20197,16 @@ export const ATTRIBUTE_METADATA: Record = { deprecation: { replacement: 'app.vitals.start.value', reason: 'Replaced by app.vitals.start.value plus app.vitals.start.type', - status: 'backfill', + status: 'transform', + transformation: 'app_vitals_start_cold_warm_to_value', }, aliases: ['app.vitals.start.cold.value'], changelog: [ - { version: 'next', prs: [576], description: 'Retargeted deprecation replacement to app.vitals.start.value' }, + { + version: 'next', + prs: [576], + description: 'Retargeted deprecation replacement to app.vitals.start.value via transform', + }, { version: '0.5.0', prs: [323], description: 'Added and deprecated in favor of app.vitals.start.cold.value' }, ], }, @@ -20250,13 +20249,7 @@ export const ATTRIBUTE_METADATA: Record = { app_start_warm: { brief: 'The duration of a warm app start in milliseconds', type: 'double', - keys: [ - 'app.vitals.start.value', - 'app.vitals.start.cold.value', - 'app.vitals.start.warm.value', - 'app_start_cold', - 'app_start_warm', - ], + keys: ['app_start_warm'], applyScrubbing: { key: 'manual', }, @@ -20266,11 +20259,16 @@ export const ATTRIBUTE_METADATA: Record = { deprecation: { replacement: 'app.vitals.start.value', reason: 'Replaced by app.vitals.start.value plus app.vitals.start.type', - status: 'backfill', + status: 'transform', + transformation: 'app_vitals_start_cold_warm_to_value', }, aliases: ['app.vitals.start.warm.value'], changelog: [ - { version: 'next', prs: [576], description: 'Retargeted deprecation replacement to app.vitals.start.value' }, + { + version: 'next', + prs: [576], + description: 'Retargeted deprecation replacement to app.vitals.start.value via transform', + }, { version: '0.5.0', prs: [323], description: 'Added and deprecated in favor of app.vitals.start.warm.value' }, ], }, @@ -20410,13 +20408,7 @@ export const ATTRIBUTE_METADATA: Record = { 'app.vitals.start.cold.value': { brief: 'The duration of a cold app start in milliseconds', type: 'double', - keys: [ - 'app.vitals.start.value', - 'app.vitals.start.cold.value', - 'app.vitals.start.warm.value', - 'app_start_cold', - 'app_start_warm', - ], + keys: ['app.vitals.start.cold.value'], applyScrubbing: { key: 'manual', }, @@ -20427,14 +20419,15 @@ export const ATTRIBUTE_METADATA: Record = { replacement: 'app.vitals.start.value', reason: 'Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.', - status: 'backfill', + status: 'transform', + transformation: 'app_vitals_start_cold_warm_to_value', }, aliases: ['app_start_cold'], changelog: [ { version: 'next', prs: [576], - description: 'Deprecated in favor of app.vitals.start.value plus app.vitals.start.type', + description: 'Deprecated in favor of app.vitals.start.value plus app.vitals.start.type via transform', }, { version: '0.5.0', prs: [313], description: 'Added app.vitals.start.cold.value attribute' }, ], @@ -20493,13 +20486,7 @@ export const ATTRIBUTE_METADATA: Record = { brief: 'The duration of an app start in milliseconds. Use with app.vitals.start.type to distinguish cold vs warm starts.', type: 'double', - keys: [ - 'app.vitals.start.value', - 'app.vitals.start.cold.value', - 'app.vitals.start.warm.value', - 'app_start_cold', - 'app_start_warm', - ], + keys: ['app.vitals.start.value'], applyScrubbing: { key: 'manual', }, @@ -20511,19 +20498,13 @@ export const ATTRIBUTE_METADATA: Record = { ], additionalContext: [ 'Send this together with app.vitals.start.type (`cold` or `warm`). This replaces app.vitals.start.cold.value and app.vitals.start.warm.value.', - 'Not aliased to the cold/warm attributes: those names are type-specific, not 1:1 copies of this key. Relay still derives this attribute (and type) from the older names when they are present.', + 'Not aliased to the cold/warm attributes: those names are type-specific, not 1:1 copies of this key. Ingest derives this attribute (and type) from the older names via the app_vitals_start_cold_warm_to_value transformation.', ], }, 'app.vitals.start.warm.value': { brief: 'The duration of a warm app start in milliseconds', type: 'double', - keys: [ - 'app.vitals.start.value', - 'app.vitals.start.cold.value', - 'app.vitals.start.warm.value', - 'app_start_cold', - 'app_start_warm', - ], + keys: ['app.vitals.start.warm.value'], applyScrubbing: { key: 'manual', }, @@ -20534,14 +20515,15 @@ export const ATTRIBUTE_METADATA: Record = { replacement: 'app.vitals.start.value', reason: 'Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.', - status: 'backfill', + status: 'transform', + transformation: 'app_vitals_start_cold_warm_to_value', }, aliases: ['app_start_warm'], changelog: [ { version: 'next', prs: [576], - description: 'Deprecated in favor of app.vitals.start.value plus app.vitals.start.type', + description: 'Deprecated in favor of app.vitals.start.value plus app.vitals.start.type via transform', }, { version: '0.5.0', prs: [313], description: 'Added app.vitals.start.warm.value attribute' }, ], @@ -31891,13 +31873,7 @@ export const ATTRIBUTE_SEARCH_METADATA: Record canonicalName: 'app.vitals.start.value', type: 'double', brief: 'The duration of a cold app start in milliseconds', - deprecationChain: [ - 'app.vitals.start.value', - 'app.vitals.start.cold.value', - 'app.vitals.start.warm.value', - 'app_start_cold', - 'app_start_warm', - ], + deprecationChain: ['app.vitals.start.cold.value'], }, 'app.vitals.start.prewarmed': { canonicalName: 'app.vitals.start.prewarmed', @@ -31929,25 +31905,13 @@ export const ATTRIBUTE_SEARCH_METADATA: Record type: 'double', brief: 'The duration of an app start in milliseconds. Use with app.vitals.start.type to distinguish cold vs warm starts.', - deprecationChain: [ - 'app.vitals.start.value', - 'app.vitals.start.cold.value', - 'app.vitals.start.warm.value', - 'app_start_cold', - 'app_start_warm', - ], + deprecationChain: ['app.vitals.start.value'], }, 'app.vitals.start.warm.value': { canonicalName: 'app.vitals.start.value', type: 'double', brief: 'The duration of a warm app start in milliseconds', - deprecationChain: [ - 'app.vitals.start.value', - 'app.vitals.start.cold.value', - 'app.vitals.start.warm.value', - 'app_start_cold', - 'app_start_warm', - ], + deprecationChain: ['app.vitals.start.warm.value'], }, 'app.vitals.ttfd.value': { canonicalName: 'app.vitals.ttfd.value', @@ -31965,13 +31929,7 @@ export const ATTRIBUTE_SEARCH_METADATA: Record canonicalName: 'app.vitals.start.value', type: 'double', brief: 'The duration of a cold app start in milliseconds', - deprecationChain: [ - 'app.vitals.start.value', - 'app.vitals.start.cold.value', - 'app.vitals.start.warm.value', - 'app_start_cold', - 'app_start_warm', - ], + deprecationChain: ['app_start_cold'], }, app_start_type: { canonicalName: 'app.vitals.start.type', @@ -31983,13 +31941,7 @@ export const ATTRIBUTE_SEARCH_METADATA: Record canonicalName: 'app.vitals.start.value', type: 'double', brief: 'The duration of a warm app start in milliseconds', - deprecationChain: [ - 'app.vitals.start.value', - 'app.vitals.start.cold.value', - 'app.vitals.start.warm.value', - 'app_start_cold', - 'app_start_warm', - ], + deprecationChain: ['app_start_warm'], }, 'art.gc.blocking_count': { canonicalName: 'art.gc.blocking_count', diff --git a/model/attribute_transformations/app_vitals_start_cold_warm_to_value.json b/model/attribute_transformations/app_vitals_start_cold_warm_to_value.json new file mode 100644 index 000000000..a1f5b2772 --- /dev/null +++ b/model/attribute_transformations/app_vitals_start_cold_warm_to_value.json @@ -0,0 +1,82 @@ +{ + "id": "app_vitals_start_cold_warm_to_value", + "brief": "Derive unified app.vitals.start.value and app.vitals.start.type from the type-specific cold and warm duration attributes. Cold and warm are mutually exclusive, so they are not 1:1 aliases of the unified key.", + "inputs": [ + { + "attribute": "app.vitals.start.cold.value", + "brief": "Deprecated cold app start duration." + }, + { + "attribute": "app.vitals.start.warm.value", + "brief": "Deprecated warm app start duration." + }, + { + "attribute": "app_start_cold", + "brief": "Legacy cold app start duration." + }, + { + "attribute": "app_start_warm", + "brief": "Legacy warm app start duration." + } + ], + "outputs": [ + { + "attribute": "app.vitals.start.value", + "brief": "Unified app start duration in milliseconds." + }, + { + "attribute": "app.vitals.start.type", + "brief": "App start type, cold or warm." + } + ], + "actions": [ + "If app.vitals.start.value is already present, keep it and do not overwrite it.", + "Prefer app.vitals.start.cold.value over app_start_cold, and app.vitals.start.warm.value over app_start_warm, when both a namespaced and a legacy name are present.", + "If a cold duration is present and app.vitals.start.value is missing, copy that duration to app.vitals.start.value and set app.vitals.start.type to \"cold\" when type is missing.", + "Otherwise, if a warm duration is present and app.vitals.start.value is missing, copy that duration to app.vitals.start.value and set app.vitals.start.type to \"warm\" when type is missing.", + "Do not copy a source duration that exceeds 180000 milliseconds.", + "Leave the source cold and warm attributes in place. Do not treat cold and warm as interchangeable copies of the unified key." + ], + "examples": [ + { + "name": "cold namespaced duration", + "input": { + "app.vitals.start.cold.value": 1234.0 + }, + "output": { + "app.vitals.start.value": 1234.0, + "app.vitals.start.type": "cold" + } + }, + { + "name": "warm namespaced duration", + "input": { + "app.vitals.start.warm.value": 567.0 + }, + "output": { + "app.vitals.start.value": 567.0, + "app.vitals.start.type": "warm" + } + }, + { + "name": "legacy cold duration", + "input": { + "app_start_cold": 1234.0 + }, + "output": { + "app.vitals.start.value": 1234.0, + "app.vitals.start.type": "cold" + } + }, + { + "name": "legacy warm duration", + "input": { + "app_start_warm": 567.0 + }, + "output": { + "app.vitals.start.value": 567.0, + "app.vitals.start.type": "warm" + } + } + ] +} diff --git a/model/attributes/app/app__vitals__start__cold__value.json b/model/attributes/app/app__vitals__start__cold__value.json index 484eb5607..bb13b2626 100644 --- a/model/attributes/app/app__vitals__start__cold__value.json +++ b/model/attributes/app/app__vitals__start__cold__value.json @@ -11,14 +11,15 @@ "deprecation": { "replacement": "app.vitals.start.value", "reason": "Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.", - "_status": "backfill" + "_status": "transform", + "transformation": "app_vitals_start_cold_warm_to_value" }, "visibility": "public", "changelog": [ { "version": "next", "prs": [576], - "description": "Deprecated in favor of app.vitals.start.value plus app.vitals.start.type" + "description": "Deprecated in favor of app.vitals.start.value plus app.vitals.start.type via transform" }, { "version": "0.5.0", diff --git a/model/attributes/app/app__vitals__start__value.json b/model/attributes/app/app__vitals__start__value.json index 056251f42..94710f00d 100644 --- a/model/attributes/app/app__vitals__start__value.json +++ b/model/attributes/app/app__vitals__start__value.json @@ -10,7 +10,7 @@ "visibility": "public", "additional_context": [ "Send this together with app.vitals.start.type (`cold` or `warm`). This replaces app.vitals.start.cold.value and app.vitals.start.warm.value.", - "Not aliased to the cold/warm attributes: those names are type-specific, not 1:1 copies of this key. Relay still derives this attribute (and type) from the older names when they are present." + "Not aliased to the cold/warm attributes: those names are type-specific, not 1:1 copies of this key. Ingest derives this attribute (and type) from the older names via the app_vitals_start_cold_warm_to_value transformation." ], "changelog": [ { diff --git a/model/attributes/app/app__vitals__start__warm__value.json b/model/attributes/app/app__vitals__start__warm__value.json index 8d84863c1..5f662a28e 100644 --- a/model/attributes/app/app__vitals__start__warm__value.json +++ b/model/attributes/app/app__vitals__start__warm__value.json @@ -11,14 +11,15 @@ "deprecation": { "replacement": "app.vitals.start.value", "reason": "Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.", - "_status": "backfill" + "_status": "transform", + "transformation": "app_vitals_start_cold_warm_to_value" }, "visibility": "public", "changelog": [ { "version": "next", "prs": [576], - "description": "Deprecated in favor of app.vitals.start.value plus app.vitals.start.type" + "description": "Deprecated in favor of app.vitals.start.value plus app.vitals.start.type via transform" }, { "version": "0.5.0", diff --git a/model/attributes/app_start_cold.json b/model/attributes/app_start_cold.json index ea3b3e94b..e1f2d010f 100644 --- a/model/attributes/app_start_cold.json +++ b/model/attributes/app_start_cold.json @@ -11,14 +11,15 @@ "deprecation": { "replacement": "app.vitals.start.value", "reason": "Replaced by app.vitals.start.value plus app.vitals.start.type", - "_status": "backfill" + "_status": "transform", + "transformation": "app_vitals_start_cold_warm_to_value" }, "visibility": "public", "changelog": [ { "version": "next", "prs": [576], - "description": "Retargeted deprecation replacement to app.vitals.start.value" + "description": "Retargeted deprecation replacement to app.vitals.start.value via transform" }, { "version": "0.5.0", diff --git a/model/attributes/app_start_warm.json b/model/attributes/app_start_warm.json index 34e805c13..070da7626 100644 --- a/model/attributes/app_start_warm.json +++ b/model/attributes/app_start_warm.json @@ -11,14 +11,15 @@ "deprecation": { "replacement": "app.vitals.start.value", "reason": "Replaced by app.vitals.start.value plus app.vitals.start.type", - "_status": "backfill" + "_status": "transform", + "transformation": "app_vitals_start_cold_warm_to_value" }, "visibility": "public", "changelog": [ { "version": "next", "prs": [576], - "description": "Retargeted deprecation replacement to app.vitals.start.value" + "description": "Retargeted deprecation replacement to app.vitals.start.value via transform" }, { "version": "0.5.0", diff --git a/model/measurements/app_start_cold.json b/model/measurements/app_start_cold.json index 0590b990e..07fa90051 100644 --- a/model/measurements/app_start_cold.json +++ b/model/measurements/app_start_cold.json @@ -1,7 +1,7 @@ { "key": "app_start_cold", "full_name": "App Start Cold", - "brief": "The time it takes for the app to start from a cold state", + "brief": "The time it takes for the app to start from a cold state. Corresponds to app.vitals.start.value when app.vitals.start.type is cold.", "unit": "millisecond", "platform": "mobile", "attribute": "app.vitals.start.value" diff --git a/model/measurements/app_start_warm.json b/model/measurements/app_start_warm.json index 4d9caea1e..d38e6a613 100644 --- a/model/measurements/app_start_warm.json +++ b/model/measurements/app_start_warm.json @@ -1,7 +1,7 @@ { "key": "app_start_warm", "full_name": "App Start Warm", - "brief": "The time it takes for the app to start from a warm state", + "brief": "The time it takes for the app to start from a warm state. Corresponds to app.vitals.start.value when app.vitals.start.type is warm.", "unit": "millisecond", "platform": "mobile", "attribute": "app.vitals.start.value" diff --git a/python/src/sentry_conventions/attributes.py b/python/src/sentry_conventions/attributes.py index 162c5dc28..795a5236a 100644 --- a/python/src/sentry_conventions/attributes.py +++ b/python/src/sentry_conventions/attributes.py @@ -11426,13 +11426,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): "app.vitals.start.cold.value": AttributeMetadata( brief="The duration of a cold app start in milliseconds", type=AttributeType.DOUBLE, - keys=( - "app.vitals.start.value", - "app.vitals.start.cold.value", - "app.vitals.start.warm.value", - "app_start_cold", - "app_start_warm", - ), + keys=("app.vitals.start.cold.value",), apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), is_in_otel=False, visibility=Visibility.PUBLIC, @@ -11440,14 +11434,15 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): deprecation=DeprecationInfo( replacement="app.vitals.start.value", reason="Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.", - status=DeprecationStatus.BACKFILL, + status=DeprecationStatus.TRANSFORM, + transformation="app_vitals_start_cold_warm_to_value", ), aliases=["app_start_cold"], changelog=[ ChangelogEntry( version="next", prs=[576], - description="Deprecated in favor of app.vitals.start.value plus app.vitals.start.type", + description="Deprecated in favor of app.vitals.start.value plus app.vitals.start.type via transform", ), ChangelogEntry( version="0.5.0", @@ -11527,13 +11522,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): "app.vitals.start.value": AttributeMetadata( brief="The duration of an app start in milliseconds. Use with app.vitals.start.type to distinguish cold vs warm starts.", type=AttributeType.DOUBLE, - keys=( - "app.vitals.start.value", - "app.vitals.start.cold.value", - "app.vitals.start.warm.value", - "app_start_cold", - "app_start_warm", - ), + keys=("app.vitals.start.value",), apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), is_in_otel=False, visibility=Visibility.PUBLIC, @@ -11547,19 +11536,13 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): ], additional_context=[ "Send this together with app.vitals.start.type (`cold` or `warm`). This replaces app.vitals.start.cold.value and app.vitals.start.warm.value.", - "Not aliased to the cold/warm attributes: those names are type-specific, not 1:1 copies of this key. Relay still derives this attribute (and type) from the older names when they are present.", + "Not aliased to the cold/warm attributes: those names are type-specific, not 1:1 copies of this key. Ingest derives this attribute (and type) from the older names via the app_vitals_start_cold_warm_to_value transformation.", ], ), "app.vitals.start.warm.value": AttributeMetadata( brief="The duration of a warm app start in milliseconds", type=AttributeType.DOUBLE, - keys=( - "app.vitals.start.value", - "app.vitals.start.cold.value", - "app.vitals.start.warm.value", - "app_start_cold", - "app_start_warm", - ), + keys=("app.vitals.start.warm.value",), apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), is_in_otel=False, visibility=Visibility.PUBLIC, @@ -11567,14 +11550,15 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): deprecation=DeprecationInfo( replacement="app.vitals.start.value", reason="Replaced by app.vitals.start.value plus app.vitals.start.type. Cold and warm are not aliases of the unified value because they are mutually exclusive type-specific names.", - status=DeprecationStatus.BACKFILL, + status=DeprecationStatus.TRANSFORM, + transformation="app_vitals_start_cold_warm_to_value", ), aliases=["app_start_warm"], changelog=[ ChangelogEntry( version="next", prs=[576], - description="Deprecated in favor of app.vitals.start.value plus app.vitals.start.type", + description="Deprecated in favor of app.vitals.start.value plus app.vitals.start.type via transform", ), ChangelogEntry( version="0.5.0", @@ -11626,13 +11610,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): "app_start_cold": AttributeMetadata( brief="The duration of a cold app start in milliseconds", type=AttributeType.DOUBLE, - keys=( - "app.vitals.start.value", - "app.vitals.start.cold.value", - "app.vitals.start.warm.value", - "app_start_cold", - "app_start_warm", - ), + keys=("app_start_cold",), apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), is_in_otel=False, visibility=Visibility.PUBLIC, @@ -11640,14 +11618,15 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): deprecation=DeprecationInfo( replacement="app.vitals.start.value", reason="Replaced by app.vitals.start.value plus app.vitals.start.type", - status=DeprecationStatus.BACKFILL, + status=DeprecationStatus.TRANSFORM, + transformation="app_vitals_start_cold_warm_to_value", ), aliases=["app.vitals.start.cold.value"], changelog=[ ChangelogEntry( version="next", prs=[576], - description="Retargeted deprecation replacement to app.vitals.start.value", + description="Retargeted deprecation replacement to app.vitals.start.value via transform", ), ChangelogEntry( version="0.5.0", @@ -11686,13 +11665,7 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): "app_start_warm": AttributeMetadata( brief="The duration of a warm app start in milliseconds", type=AttributeType.DOUBLE, - keys=( - "app.vitals.start.value", - "app.vitals.start.cold.value", - "app.vitals.start.warm.value", - "app_start_cold", - "app_start_warm", - ), + keys=("app_start_warm",), apply_scrubbing=ApplyScrubbingInfo(key=ApplyScrubbing.MANUAL), is_in_otel=False, visibility=Visibility.PUBLIC, @@ -11700,14 +11673,15 @@ class ATTRIBUTE_NAMES(metaclass=_AttributeNamesMeta): deprecation=DeprecationInfo( replacement="app.vitals.start.value", reason="Replaced by app.vitals.start.value plus app.vitals.start.type", - status=DeprecationStatus.BACKFILL, + status=DeprecationStatus.TRANSFORM, + transformation="app_vitals_start_cold_warm_to_value", ), aliases=["app.vitals.start.warm.value"], changelog=[ ChangelogEntry( version="next", prs=[576], - description="Retargeted deprecation replacement to app.vitals.start.value", + description="Retargeted deprecation replacement to app.vitals.start.value via transform", ), ChangelogEntry( version="0.5.0",