Skip to content

Commit c711cfc

Browse files
committed
♻️ manage metric sampling inside telemetry
- allow to apply a telemetry sample rate to telemetry events emitted before the configuration is applied - centralize telemetry sampling draws in telemetry.ts which now exposes `enabledMetrics` - expose replay metrics from recorderApi
1 parent 6b502ea commit c711cfc

21 files changed

+121
-97
lines changed

packages/core/src/domain/telemetry/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type { Telemetry } from './telemetry'
1+
export type { Telemetry, SampleRateByMetric } from './telemetry'
22
export {
33
TelemetryService,
44
addTelemetryDebug,

packages/core/src/domain/telemetry/telemetry.spec.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ import {
2020
startTelemetryCollection,
2121
addTelemetryMetrics,
2222
addTelemetryDebug,
23+
type SampleRateByMetric,
2324
} from './telemetry'
2425
import type { TelemetryEvent } from './telemetryEvent.types'
2526
import { StatusType, TelemetryType } from './rawTelemetryEvent.types'
2627

2728
const NETWORK_METRICS_KIND = 'Network metrics'
2829
const PERFORMANCE_METRICS_KIND = 'Performance metrics'
2930

30-
function startAndSpyTelemetry(configuration?: Partial<Configuration>) {
31+
function startAndSpyTelemetry(configuration?: Partial<Configuration>, sampleRateByMetric: SampleRateByMetric = {}) {
3132
const observable = new Observable<TelemetryEvent & Context>()
3233

3334
const events: TelemetryEvent[] = []
@@ -42,7 +43,8 @@ function startAndSpyTelemetry(configuration?: Partial<Configuration>) {
4243
...configuration,
4344
} as Configuration,
4445
hooks,
45-
observable
46+
observable,
47+
sampleRateByMetric
4648
)
4749

4850
return {
@@ -157,7 +159,10 @@ describe('telemetry', () => {
157159

158160
describe('addTelemetryMetrics', () => {
159161
it('should collect metrics when sampled', async () => {
160-
const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 100 })
162+
const { getTelemetryEvents } = startAndSpyTelemetry(
163+
{ telemetrySampleRate: 100 },
164+
{ [PERFORMANCE_METRICS_KIND]: 100 }
165+
)
161166

162167
addTelemetryMetrics(PERFORMANCE_METRICS_KIND, { speed: 1000 })
163168

@@ -172,8 +177,22 @@ describe('telemetry', () => {
172177
])
173178
})
174179

175-
it('should not notify metrics when not sampled', async () => {
176-
const { getTelemetryEvents } = startAndSpyTelemetry({ telemetrySampleRate: 0 })
180+
it('should not notify metrics when telemetry not sampled', async () => {
181+
const { getTelemetryEvents } = startAndSpyTelemetry(
182+
{ telemetrySampleRate: 0 },
183+
{ [PERFORMANCE_METRICS_KIND]: 100 }
184+
)
185+
186+
addTelemetryMetrics(PERFORMANCE_METRICS_KIND, { speed: 1000 })
187+
188+
expect(await getTelemetryEvents()).toEqual([])
189+
})
190+
191+
it('should not notify metrics when metric not sampled', async () => {
192+
const { getTelemetryEvents } = startAndSpyTelemetry(
193+
{ telemetrySampleRate: 100 },
194+
{ [PERFORMANCE_METRICS_KIND]: 0 }
195+
)
177196

178197
addTelemetryMetrics(PERFORMANCE_METRICS_KIND, { speed: 1000 })
179198

packages/core/src/domain/telemetry/telemetry.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ export const enum TelemetryService {
5959
export interface Telemetry {
6060
stop: () => void
6161
enabled: boolean
62+
enabledMetrics: { [metricName: string]: boolean }
63+
}
64+
65+
export interface SampleRateByMetric {
66+
[metricName: string]: number
6267
}
6368

6469
const TELEMETRY_EXCLUDED_SITES: string[] = [INTAKE_SITE_US1_FED]
@@ -78,25 +83,34 @@ export function startTelemetry(
7883
hooks: AbstractHooks,
7984
reportError: (error: RawError) => void,
8085
pageMayExitObservable: Observable<PageMayExitEvent>,
81-
createEncoder: (streamId: DeflateEncoderStreamId) => Encoder
86+
createEncoder: (streamId: DeflateEncoderStreamId) => Encoder,
87+
sampleRateByMetric: SampleRateByMetric = {}
8288
): Telemetry {
8389
const observable = new Observable<TelemetryEvent & Context>()
8490

8591
const { stop } = startTelemetryTransport(configuration, reportError, pageMayExitObservable, createEncoder, observable)
8692

87-
const { enabled } = startTelemetryCollection(telemetryService, configuration, hooks, observable)
93+
const { enabled, enabledMetrics } = startTelemetryCollection(
94+
telemetryService,
95+
configuration,
96+
hooks,
97+
observable,
98+
sampleRateByMetric
99+
)
88100

89101
return {
90102
stop,
91103
enabled,
104+
enabledMetrics,
92105
}
93106
}
94107

95108
export function startTelemetryCollection(
96109
telemetryService: TelemetryService,
97110
configuration: Configuration,
98111
hooks: AbstractHooks,
99-
observable: Observable<TelemetryEvent & Context>
112+
observable: Observable<TelemetryEvent & Context>,
113+
sampleRateByMetric: SampleRateByMetric
100114
) {
101115
const alreadySentEventsByKind: Record<string, Set<string>> = {}
102116

@@ -109,10 +123,18 @@ export function startTelemetryCollection(
109123
[TelemetryType.USAGE]: telemetryEnabled && performDraw(configuration.telemetryUsageSampleRate),
110124
}
111125

126+
const telemetryEnabledPerMetrics: { [metricName: string]: boolean } = {}
127+
Object.keys(sampleRateByMetric).forEach((metricName) => {
128+
telemetryEnabledPerMetrics[metricName] = telemetryEnabled && performDraw(sampleRateByMetric[metricName])
129+
})
130+
112131
const runtimeEnvInfo = getRuntimeEnvInfo()
113132
const telemetryObservable = getTelemetryObservable()
114133
telemetryObservable.subscribe(({ rawEvent, kind }) => {
115-
if (!telemetryEnabledPerType[rawEvent.type!]) {
134+
if (
135+
!telemetryEnabledPerType[rawEvent.type!] ||
136+
(kind in telemetryEnabledPerMetrics && !telemetryEnabledPerMetrics[kind])
137+
) {
116138
return
117139
}
118140

@@ -154,6 +176,7 @@ export function startTelemetryCollection(
154176

155177
return {
156178
enabled: telemetryEnabled,
179+
enabledMetrics: telemetryEnabledPerMetrics,
157180
}
158181

159182
function toTelemetryEvent(

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export type {
4040
TelemetryUsageEvent,
4141
RawTelemetryUsage,
4242
RawTelemetryUsageFeature,
43+
SampleRateByMetric,
4344
} from './domain/telemetry'
4445
export {
4546
startTelemetry,

packages/rum-core/src/boot/rumPublicApi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
Account,
1414
RumInternalContext,
1515
Telemetry,
16+
SampleRateByMetric,
1617
} from '@datadog/browser-core'
1718
import {
1819
ContextManagerMethod,
@@ -443,6 +444,7 @@ export interface RecorderApi {
443444
isRecording: () => boolean
444445
getReplayStats: (viewId: string) => ReplayStats | undefined
445446
getSessionReplayLink: () => string | undefined
447+
getTelemetrySampleRateByMetric: (configuration: RumConfiguration) => SampleRateByMetric | undefined
446448
}
447449

448450
export interface ProfilerApi {

packages/rum-core/src/boot/startRum.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { createLocationChangeObservable } from '../browser/locationChangeObserva
3737
import type { RumConfiguration } from '../domain/configuration'
3838
import type { ViewOptions } from '../domain/view/trackViews'
3939
import { startFeatureFlagContexts } from '../domain/contexts/featureFlagContext'
40-
import { startCustomerDataTelemetry } from '../domain/startCustomerDataTelemetry'
40+
import { startCustomerDataTelemetry, CUSTOMER_DATA_METRIC_NAME } from '../domain/startCustomerDataTelemetry'
4141
import type { PageStateHistory } from '../domain/contexts/pageStateHistory'
4242
import { startPageStateHistory } from '../domain/contexts/pageStateHistory'
4343
import { startDisplayContext } from '../domain/contexts/displayContext'
@@ -94,13 +94,19 @@ export function startRum(
9494
})
9595
cleanupTasks.push(() => pageMayExitSubscription.unsubscribe())
9696

97+
const sampleRateByMetric = {
98+
[CUSTOMER_DATA_METRIC_NAME]: configuration.customerDataTelemetrySampleRate,
99+
...recorderApi.getTelemetrySampleRateByMetric(configuration),
100+
}
101+
97102
const telemetry = startTelemetry(
98103
TelemetryService.RUM,
99104
configuration,
100105
hooks,
101106
reportError,
102107
pageMayExitObservable,
103-
createEncoder
108+
createEncoder,
109+
sampleRateByMetric
104110
)
105111
cleanupTasks.push(telemetry.stop)
106112

@@ -118,7 +124,7 @@ export function startRum(
118124
createEncoder
119125
)
120126
cleanupTasks.push(() => batch.stop())
121-
startCustomerDataTelemetry(configuration, telemetry, lifeCycle, batch.flushController.flushObservable)
127+
startCustomerDataTelemetry(telemetry, lifeCycle, batch.flushController.flushObservable)
122128
} else {
123129
startRumEventBridge(lifeCycle)
124130
}

packages/rum-core/src/domain/startCustomerDataTelemetry.spec.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import type { FlushEvent, Context, Telemetry } from '@datadog/browser-core'
22
import { Observable, resetExperimentalFeatures } from '@datadog/browser-core'
33
import type { Clock, MockTelemetry } from '@datadog/browser-core/test'
44
import { mockClock, startMockTelemetry } from '@datadog/browser-core/test'
5-
import { mockRumConfiguration } from '../../test'
65
import { RumEventType } from '../rawRumEvent.types'
76
import type { RumEvent } from '../rumEvent.types'
87
import { LifeCycle, LifeCycleEventType } from './lifeCycle'
9-
import { MEASURES_PERIOD_DURATION, startCustomerDataTelemetry } from './startCustomerDataTelemetry'
10-
import type { RumConfiguration } from './configuration'
8+
import {
9+
MEASURES_PERIOD_DURATION,
10+
startCustomerDataTelemetry,
11+
CUSTOMER_DATA_METRIC_NAME,
12+
} from './startCustomerDataTelemetry'
1113

1214
describe('customerDataTelemetry', () => {
1315
let clock: Clock
@@ -17,12 +19,6 @@ describe('customerDataTelemetry', () => {
1719
let lifeCycle: LifeCycle
1820
const viewEvent = { type: RumEventType.VIEW } as RumEvent & Context
1921

20-
const config: Partial<RumConfiguration> = {
21-
telemetrySampleRate: 100,
22-
customerDataTelemetrySampleRate: 100,
23-
maxTelemetryEventsPerPage: 2,
24-
}
25-
2622
function generateBatch({
2723
eventNumber,
2824
batchBytesCount = 1,
@@ -46,15 +42,18 @@ describe('customerDataTelemetry', () => {
4642
})
4743
}
4844

49-
function setupCustomerTelemetryCollection(partialConfig: Partial<RumConfiguration> = config) {
50-
const configuration = mockRumConfiguration(partialConfig)
45+
function setupCustomerTelemetryCollection(telemetryEnabled: boolean = true) {
5146
batchFlushObservable = new Observable()
5247
lifeCycle = new LifeCycle()
5348
fakeContextBytesCount = 1
5449

5550
telemetry = startMockTelemetry()
5651

57-
startCustomerDataTelemetry(configuration, { enabled: true } as Telemetry, lifeCycle, batchFlushObservable)
52+
startCustomerDataTelemetry(
53+
{ enabledMetrics: { [CUSTOMER_DATA_METRIC_NAME]: telemetryEnabled } } as unknown as Telemetry,
54+
lifeCycle,
55+
batchFlushObservable
56+
)
5857
}
5958

6059
beforeEach(() => {
@@ -132,10 +131,7 @@ describe('customerDataTelemetry', () => {
132131
})
133132

134133
it('should not collect customer data telemetry when telemetry disabled', async () => {
135-
setupCustomerTelemetryCollection({
136-
telemetrySampleRate: 100,
137-
customerDataTelemetrySampleRate: 0,
138-
})
134+
setupCustomerTelemetryCollection(false)
139135

140136
generateBatch({ eventNumber: 1 })
141137
clock.tick(MEASURES_PERIOD_DURATION)

packages/rum-core/src/domain/startCustomerDataTelemetry.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { Context, FlushEvent, Observable, Telemetry } from '@datadog/browser-core'
2-
import { performDraw, ONE_SECOND, addTelemetryMetrics, setInterval } from '@datadog/browser-core'
3-
import type { RumConfiguration } from './configuration'
2+
import { ONE_SECOND, addTelemetryMetrics, setInterval } from '@datadog/browser-core'
43
import type { LifeCycle } from './lifeCycle'
54
import { LifeCycleEventType } from './lifeCycle'
65

76
export const MEASURES_PERIOD_DURATION = 10 * ONE_SECOND
7+
export const CUSTOMER_DATA_METRIC_NAME = 'Customer data measures'
88

99
interface Measure extends Context {
1010
min: number
@@ -22,12 +22,11 @@ let currentPeriodMeasures: CurrentPeriodMeasures
2222
let batchHasRumEvent: boolean
2323

2424
export function startCustomerDataTelemetry(
25-
configuration: RumConfiguration,
2625
telemetry: Telemetry,
2726
lifeCycle: LifeCycle,
2827
batchFlushObservable: Observable<FlushEvent>
2928
) {
30-
const customerDataTelemetryEnabled = telemetry.enabled && performDraw(configuration.customerDataTelemetrySampleRate)
29+
const customerDataTelemetryEnabled = telemetry.enabledMetrics[CUSTOMER_DATA_METRIC_NAME]
3130
if (!customerDataTelemetryEnabled) {
3231
return
3332
}
@@ -61,7 +60,7 @@ function sendCurrentPeriodMeasures() {
6160
return
6261
}
6362

64-
addTelemetryMetrics('Customer data measures', currentPeriodMeasures)
63+
addTelemetryMetrics(CUSTOMER_DATA_METRIC_NAME, currentPeriodMeasures)
6564
initCurrentPeriodMeasures()
6665
}
6766

packages/rum-core/test/noopRecorderApi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export const noopRecorderApi: RecorderApi = {
88
onRumStart: noop,
99
getReplayStats: () => undefined,
1010
getSessionReplayLink: () => undefined,
11+
getTelemetrySampleRateByMetric: () => undefined,
1112
}

packages/rum-slim/src/boot/stubRecorderApi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ export function makeRecorderApiStub() {
1313
isRecording: () => false,
1414
getReplayStats: () => undefined,
1515
getSessionReplayLink: () => getSessionReplayLinkStrategy(),
16+
getTelemetrySampleRateByMetric: () => undefined,
1617
}
1718
}

0 commit comments

Comments
 (0)