Skip to content

Commit

Permalink
feat: round values from RUM report
Browse files Browse the repository at this point in the history
also tracer to main.ts
  • Loading branch information
xnanodax committed Feb 6, 2025
1 parent 6b14442 commit 4293f77
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 65 deletions.
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export * from './v3/getDynamicQuietWindowDuration'
export * from './v3/getSpanFromPerformanceEntry'
export * from './v3/hooks'
export type * from './v3/hooksTypes'
export * from './v3/tracer'
// eslint-disable-next-line import/first, import/newline-after-import
import * as match from './v3/matchSpan'
export { match }
Expand Down
92 changes: 92 additions & 0 deletions src/v3/convertToRum.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { describe, expect, it } from 'vitest'
import { convertTraceToRUM } from './convertToRum'
import { createTraceRecording } from './recordingComputeUtils'
import { ActiveTraceInput } from './spanTypes'
import {
AnyScope,
createMockSpanAndAnnotation,
createTimestamp,
} from './testUtility/createMockFactory'
import type { CompleteTraceDefinition } from './types'

describe('convertTraceToRUM', () => {
it('should round all numeric values in the trace recording', () => {
const definition: CompleteTraceDefinition<never, AnyScope, 'origin'> = {
name: 'test-trace',
scopes: [],
requiredSpans: [() => true],
computedSpanDefinitions: [],
computedValueDefinitions: [],
variantsByOriginatedFrom: {
origin: { timeoutDuration: 45_000 },
},
}

const input: ActiveTraceInput<{}, 'origin'> = {
id: 'test',
startTime: createTimestamp(0),
scope: {},
originatedFrom: 'origin',
}

const traceRecording = createTraceRecording(
{
definition,
recordedItems: [
createMockSpanAndAnnotation(100.501, {
name: 'test-component',
type: 'component-render',
scope: {},
duration: 50.499,
isIdle: false,
renderCount: 1,
renderedOutput: 'loading',
}),
createMockSpanAndAnnotation(
200.001,
{
name: 'test-component',
type: 'component-render',
scope: {},
duration: 50.999,
isIdle: true,
renderCount: 2,
renderedOutput: 'content',
},
{ occurrence: 2 },
),
],
input: {
id: 'test',
startTime: createTimestamp(0),
scope: { ticketId: '74' },
originatedFrom: 'origin',
},
},
{ transitionFromState: 'active' },
)

const context = {
definition,
input,
}

const result = convertTraceToRUM(traceRecording, context)

// Check rounded values in embeddedSpans
const embeddedSpan = result.embeddedSpans['component-render|test-component']
if (embeddedSpan) {
expect(Number.isInteger(embeddedSpan.totalDuration)).toBe(true)
expect(Number.isInteger(embeddedSpan.spans[0]!.startOffset)).toBe(true)
expect(Number.isInteger(embeddedSpan.spans[0]!.duration)).toBe(true)
expect(Number.isInteger(embeddedSpan.spans[1]!.startOffset)).toBe(true)
expect(Number.isInteger(embeddedSpan.spans[1]!.duration)).toBe(true)

// Check specific rounded values
expect(embeddedSpan.spans[0]!.startOffset).toBe(101) // 100.501 rounded
expect(embeddedSpan.spans[0]!.duration).toBe(50) // 50.499 rounded
expect(embeddedSpan.spans[1]!.startOffset).toBe(200) // 200.001 rounded
expect(embeddedSpan.spans[1]!.duration).toBe(51) // 50.999 rounded
}
})
})
33 changes: 32 additions & 1 deletion src/v3/convertToRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,33 @@ export function getSpanSummaryAttributes<AllPossibleScopesT>(
return spanAttributes
}

type RoundFunction = (x: number) => number

function recursivelyRoundValues<T extends object>(
obj: T,
roundFunc: RoundFunction = (x) => Math.round(x),
): T {
const result: Record<string, unknown> = {}

for (const [key, value] of Object.entries(obj as object)) {
if (typeof value === 'number') {
result[key] = roundFunc(value)
} else if (Array.isArray(value)) {
result[key] = value.map((item: number | T) =>
typeof item === 'number'
? roundFunc(item)
: recursivelyRoundValues(item, roundFunc),
)
} else if (value && typeof value === 'object') {
result[key] = recursivelyRoundValues(value, roundFunc)
} else {
result[key] = value
}
}

return result as T
}

export function convertTraceToRUM<
TracerScopeKeysT extends KeysOfUnion<AllPossibleScopesT>,
AllPossibleScopesT,
Expand Down Expand Up @@ -172,10 +199,14 @@ export function convertTraceToRUM<
}
}

return {
const result: RumTraceRecording<
SelectScopeByKey<TracerScopeKeysT, AllPossibleScopesT>
> = {
...otherTraceRecordingAttributes,
embeddedSpans: Object.fromEntries(embeddedSpans),
nonEmbeddedSpans: [...nonEmbeddedSpans],
spanAttributes,
}

return recursivelyRoundValues(result)
}
69 changes: 5 additions & 64 deletions src/v3/recordingComputeUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,15 @@ import {
getComputedSpans,
getComputedValues,
} from './recordingComputeUtils'
import type { SpanAndAnnotation, SpanAnnotation } from './spanAnnotationTypes'
import type { Span } from './spanTypes'
import type { CompleteTraceDefinition, Timestamp } from './types'
import {
createMockSpanAndAnnotation,
createTimestamp,
} from './testUtility/createMockFactory'
import type { CompleteTraceDefinition } from './types'

type AnyScope = Record<string, unknown>

describe('recordingComputeUtils', () => {
const EPOCH_START = 1_000
const createTimestamp = (now: number): Timestamp => ({
epoch: EPOCH_START + now,
now,
})

const createAnnotation = (
span: Span<AnyScope>,
traceStartTime: Timestamp,
partial: Partial<SpanAnnotation> = {},
): SpanAnnotation => ({
id: 'test-id',
operationRelativeStartTime: span.startTime.now - traceStartTime.now,
operationRelativeEndTime:
span.startTime.now + span.duration - traceStartTime.now,
occurrence: 1,
recordedInState: 'active',
labels: [],
...partial,
})

const createMockSpan = <TSpan extends Span<AnyScope>>(
startTimeNow: number,
partial: Partial<TSpan>,
): TSpan =>
(partial.type === 'component-render'
? {
name: 'test-component',
type: 'component-render',
scope: {},
startTime: createTimestamp(startTimeNow),
duration: 100,
attributes: {},
isIdle: true,
renderCount: 1,
renderedOutput: 'content',
...partial,
}
: {
name: 'test-span',
type: 'mark',
startTime: createTimestamp(startTimeNow),
duration: 100,
attributes: {},
...partial,
}) as TSpan

const createMockSpanAndAnnotation = <TSpan extends Span<AnyScope>>(
startTimeNow: number,
spanPartial: Partial<TSpan> = {},
annotationPartial: Partial<SpanAnnotation> = {},
): SpanAndAnnotation<AnyScope> => {
const span = createMockSpan<TSpan>(startTimeNow, spanPartial)
return {
span,
annotation: createAnnotation(span, createTimestamp(0), annotationPartial),
}
}

const onEnd = jest.fn()

describe('error status propagation', () => {
const baseDefinition: CompleteTraceDefinition<never, AnyScope, 'origin'> = {
name: 'test-trace',
Expand Down
65 changes: 65 additions & 0 deletions src/v3/testUtility/createMockFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { SpanAndAnnotation, SpanAnnotation } from '../spanAnnotationTypes'
import { Span } from '../spanTypes'
import type { Timestamp } from '../types'

const EPOCH_START = 1_000

export const createTimestamp = (now: number): Timestamp => ({
epoch: EPOCH_START + now,
now,
})

export type AnyScope = Record<string, unknown>

export const createAnnotation = (
span: Span<AnyScope>,
traceStartTime: Timestamp,
partial: Partial<SpanAnnotation> = {},
): SpanAnnotation => ({
id: 'test-id',
operationRelativeStartTime: span.startTime.now - traceStartTime.now,
operationRelativeEndTime:
span.startTime.now + span.duration - traceStartTime.now,
occurrence: 1,
recordedInState: 'active',
labels: [],
...partial,
})

export const createMockSpan = <TSpan extends Span<AnyScope>>(
startTimeNow: number,
partial: Partial<TSpan>,
): TSpan =>
(partial.type === 'component-render'
? {
name: 'test-component',
type: 'component-render',
scope: {},
startTime: createTimestamp(startTimeNow),
duration: 100,
attributes: {},
isIdle: true,
renderCount: 1,
renderedOutput: 'content',
...partial,
}
: {
name: 'test-span',
type: 'mark',
startTime: createTimestamp(startTimeNow),
duration: 100,
attributes: {},
...partial,
}) as TSpan

export const createMockSpanAndAnnotation = <TSpan extends Span<AnyScope>>(
startTimeNow: number,
spanPartial: Partial<TSpan> = {},
annotationPartial: Partial<SpanAnnotation> = {},
): SpanAndAnnotation<AnyScope> => {
const span = createMockSpan<TSpan>(startTimeNow, spanPartial)
return {
span,
annotation: createAnnotation(span, createTimestamp(0), annotationPartial),
}
}

0 comments on commit 4293f77

Please sign in to comment.