Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔊 [RUMF-1380] Collect configuration telemetry event #1760

Merged
merged 8 commits into from
Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/core/src/domain/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getCurrentSite } from '../../browser/cookie'
import { catchUserErrors } from '../../tools/catchUserErrors'
import { display } from '../../tools/display'
import { assign, isPercentage, ONE_KIBI_BYTE, ONE_SECOND } from '../../tools/utils'
import type { RawTelemetryConfiguration } from '../telemetry'
import { updateExperimentalFeatures } from './experimentalFeatures'
import { initSimulation } from './simulation'
import type { TransportConfiguration } from './transportConfiguration'
Expand Down Expand Up @@ -41,6 +42,7 @@ export interface InitConfiguration {
enableExperimentalFeatures?: string[] | undefined
replica?: ReplicaUserConfiguration | undefined
datacenter?: string
telemetryConfigurationSampleRate?: number

// simulation options
simulationStart?: string | undefined
Expand All @@ -63,6 +65,7 @@ export interface Configuration extends TransportConfiguration {
cookieOptions: CookieOptions
sampleRate: number
telemetrySampleRate: number
telemetryConfigurationSampleRate: number
service: string | undefined
silentMultipleInit: boolean

Expand Down Expand Up @@ -93,6 +96,14 @@ export function validateAndBuildConfiguration(initConfiguration: InitConfigurati
return
}

if (
initConfiguration.telemetryConfigurationSampleRate !== undefined &&
!isPercentage(initConfiguration.telemetryConfigurationSampleRate)
) {
display.error('Telemetry Configuration Sample Rate should be a number between 0 and 100')
return
}

// Set the experimental feature flags as early as possible, so we can use them in most places
updateExperimentalFeatures(initConfiguration.enableExperimentalFeatures)

Expand All @@ -105,6 +116,7 @@ export function validateAndBuildConfiguration(initConfiguration: InitConfigurati
cookieOptions: buildCookieOptions(initConfiguration),
sampleRate: initConfiguration.sampleRate ?? 100,
telemetrySampleRate: initConfiguration.telemetrySampleRate ?? 20,
telemetryConfigurationSampleRate: initConfiguration.telemetryConfigurationSampleRate ?? 5,
service: initConfiguration.service,
silentMultipleInit: !!initConfiguration.silentMultipleInit,

Expand Down Expand Up @@ -149,3 +161,17 @@ export function buildCookieOptions(initConfiguration: InitConfiguration) {
function mustUseSecureCookie(initConfiguration: InitConfiguration) {
return !!initConfiguration.useSecureSessionCookie || !!initConfiguration.useCrossSiteSessionCookie
}

export function serializeConfiguration(configuration: InitConfiguration): Partial<RawTelemetryConfiguration> {
return {
session_sample_rate: configuration.sampleRate,
telemetry_sample_rate: configuration.telemetrySampleRate,
telemetry_configuration_sample_rate: configuration.telemetryConfigurationSampleRate,
amortemousque marked this conversation as resolved.
Show resolved Hide resolved
use_before_send: !!configuration.beforeSend,
use_cross_site_session_cookie: configuration.useCrossSiteSessionCookie,
use_secure_session_cookie: configuration.useSecureSessionCookie,
use_proxy: configuration.proxyUrl !== undefined ? !!configuration.proxyUrl : undefined,
silent_multiple_init: configuration.silentMultipleInit,
track_session_across_subdomains: configuration.trackSessionAcrossSubdomains,
}
}
1 change: 1 addition & 0 deletions packages/core/src/domain/configuration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export {
buildCookieOptions,
DefaultPrivacyLevel,
validateAndBuildConfiguration,
serializeConfiguration,
} from './configuration'
export { createEndpointBuilder, EndpointBuilder, EndpointType } from './endpointBuilder'
export {
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/domain/telemetry/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export {
Telemetry,
RawTelemetryEvent,
addTelemetryDebug,
addTelemetryError,
startFakeTelemetry,
resetTelemetry,
startTelemetry,
isTelemetryReplicationAllowed,
addTelemetryConfiguration,
} from './telemetry'

export * from './rawTelemetryEvent.types'
export * from './telemetryEvent.types'
14 changes: 14 additions & 0 deletions packages/core/src/domain/telemetry/rawTelemetryEvent.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { TelemetryEvent, TelemetryConfigurationEvent } from './telemetryEvent.types'

export const TelemetryType = {
log: 'log',
configuration: 'configuration',
} as const

export const enum StatusType {
debug = 'debug',
error = 'error',
}

export type RawTelemetryEvent = TelemetryEvent['telemetry']
export type RawTelemetryConfiguration = TelemetryConfigurationEvent['telemetry']['configuration']
57 changes: 55 additions & 2 deletions packages/core/src/domain/telemetry/telemetry.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import type { StackTrace } from '@datadog/browser-core'
import { callMonitored } from '../../tools/monitor'
import type { Configuration } from '../configuration'
import { updateExperimentalFeatures, INTAKE_SITE_US1, INTAKE_SITE_US1_FED } from '../configuration'
import { resetTelemetry, startTelemetry, scrubCustomerFrames, formatError } from './telemetry'
import {
resetExperimentalFeatures,
updateExperimentalFeatures,
INTAKE_SITE_US1,
INTAKE_SITE_US1_FED,
} from '../configuration'
import {
resetTelemetry,
startTelemetry,
scrubCustomerFrames,
formatError,
addTelemetryConfiguration,
} from './telemetry'

function startAndSpyTelemetry(configuration?: Partial<Configuration>) {
const telemetry = startTelemetry({
Expand Down Expand Up @@ -31,6 +42,48 @@ describe('telemetry', () => {
expect(notifySpy).toHaveBeenCalledTimes(1)
})

describe('addTelemetryConfiguration', () => {
amortemousque marked this conversation as resolved.
Show resolved Hide resolved
afterEach(() => {
resetExperimentalFeatures()
})

it('should collects configuration when sampled and ff is enabled', () => {
updateExperimentalFeatures(['telemetry_configuration'])

const { notifySpy } = startAndSpyTelemetry({ telemetrySampleRate: 100, telemetryConfigurationSampleRate: 100 })

addTelemetryConfiguration({})

expect(notifySpy).toHaveBeenCalled()
})

it('should not notify configuration when sampled and ff is disabled', () => {
const { notifySpy } = startAndSpyTelemetry({ telemetrySampleRate: 100, telemetryConfigurationSampleRate: 100 })

addTelemetryConfiguration({})

expect(notifySpy).not.toHaveBeenCalled()
})

it('should not notify configuration when not sampled and ff is enabled', () => {
updateExperimentalFeatures(['telemetry_configuration'])
const { notifySpy } = startAndSpyTelemetry({ telemetrySampleRate: 100, telemetryConfigurationSampleRate: 0 })

addTelemetryConfiguration({})

expect(notifySpy).not.toHaveBeenCalled()
})

it('should not notify configuration when telemetrySampleRate is 0 and ff is enabled', () => {
updateExperimentalFeatures(['telemetry_configuration'])
const { notifySpy } = startAndSpyTelemetry({ telemetrySampleRate: 0, telemetryConfigurationSampleRate: 100 })

addTelemetryConfiguration({})

expect(notifySpy).not.toHaveBeenCalled()
})
})

it('should contains feature flags', () => {
updateExperimentalFeatures(['foo'])
const { notifySpy } = startAndSpyTelemetry()
Expand Down
33 changes: 18 additions & 15 deletions packages/core/src/domain/telemetry/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { toStackTraceString } from '../../tools/error'
import { assign, combine, jsonStringify, performDraw, includes, startsWith, arrayFrom } from '../../tools/utils'
import type { Configuration } from '../configuration'
import {
isExperimentalFeatureEnabled,
getExperimentalFeatures,
getSimulationLabel,
INTAKE_SITE_STAGING,
Expand All @@ -16,15 +17,12 @@ import { Observable } from '../../tools/observable'
import { timeStampNow } from '../../tools/timeUtils'
import { displayIfDebugEnabled, startMonitorErrorCollection } from '../../tools/monitor'
import type { TelemetryEvent } from './telemetryEvent.types'
import type { RawTelemetryConfiguration, RawTelemetryEvent } from './rawTelemetryEvent.types'
import { StatusType, TelemetryType } from './rawTelemetryEvent.types'

// replaced at build time
declare const __BUILD_ENV__SDK_VERSION__: string

const enum StatusType {
debug = 'debug',
error = 'error',
}

const ALLOWED_FRAME_URLS = [
'https://www.datadoghq-browser-agent.com',
'https://www.datad0g-browser-agent.com',
Expand All @@ -37,22 +35,14 @@ export interface Telemetry {
observable: Observable<TelemetryEvent & Context>
}

export interface RawTelemetryEvent extends Context {
message: string
status: StatusType
error?: {
kind?: string
stack: string
}
}

const TELEMETRY_EXCLUDED_SITES: string[] = [INTAKE_SITE_US1_FED]

const telemetryConfiguration: {
maxEventsPerPage: number
sentEventCount: number
telemetryEnabled: boolean
} = { maxEventsPerPage: 0, sentEventCount: 0, telemetryEnabled: false }
telemetryConfigurationEnabled: boolean
} = { maxEventsPerPage: 0, sentEventCount: 0, telemetryEnabled: false, telemetryConfigurationEnabled: false }

let onRawTelemetryEventCollected: ((event: RawTelemetryEvent) => void) | undefined

Expand All @@ -61,6 +51,8 @@ export function startTelemetry(configuration: Configuration): Telemetry {
const observable = new Observable<TelemetryEvent & Context>()

telemetryConfiguration.telemetryEnabled = performDraw(configuration.telemetrySampleRate)
telemetryConfiguration.telemetryConfigurationEnabled =
telemetryConfiguration.telemetryEnabled && performDraw(configuration.telemetryConfigurationSampleRate)

onRawTelemetryEventCollected = (event: RawTelemetryEvent) => {
if (!includes(TELEMETRY_EXCLUDED_SITES, configuration.site) && telemetryConfiguration.telemetryEnabled) {
Expand Down Expand Up @@ -132,6 +124,7 @@ export function addTelemetryDebug(message: string, context?: Context) {
addTelemetry(
assign(
{
type: TelemetryType.log,
message,
status: StatusType.debug,
},
Expand All @@ -144,13 +137,23 @@ export function addTelemetryError(e: unknown) {
addTelemetry(
assign(
{
type: TelemetryType.log,
status: StatusType.error,
},
formatError(e)
)
)
}

export function addTelemetryConfiguration(configuration: RawTelemetryConfiguration) {
if (isExperimentalFeatureEnabled('telemetry_configuration') && telemetryConfiguration.telemetryConfigurationEnabled) {
addTelemetry({
type: TelemetryType.configuration,
configuration,
})
}
}

function addTelemetry(event: RawTelemetryEvent) {
if (onRawTelemetryEventCollected && telemetryConfiguration.sentEventCount < telemetryConfiguration.maxEventsPerPage) {
telemetryConfiguration.sentEventCount += 1
Expand Down
Loading