rsnext/packages/next/trace/trace.ts
Shu Ding 37b859f36d
Small code improvements (#37227)
Two small things noticed today. When calling `getDependencies` concurrently (which is happening right now) the cache doesn't work as it's after the `await`, so here we move it to cache the promise instead. Also `NUM_OF_MICROSEC_IN_SEC` isn't accurate and it should be `NANOSEC` actually.

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `yarn lint`
- [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)
2022-05-26 23:19:47 +00:00

121 lines
2.9 KiB
TypeScript

import { SpanId } from './shared'
import { reporter } from './report'
const NUM_OF_MICROSEC_IN_NANOSEC = BigInt('1000')
let count = 0
const getId = () => {
count++
return count
}
// eslint typescript has a bug with TS enums
/* eslint-disable no-shadow */
export enum SpanStatus {
Started,
Stopped,
}
export class Span {
name: string
id: SpanId
parentId?: SpanId
duration: number | null
attrs: { [key: string]: any }
status: SpanStatus
now: number
_start: bigint
constructor({
name,
parentId,
attrs,
startTime,
}: {
name: string
parentId?: SpanId
startTime?: bigint
attrs?: Object
}) {
this.name = name
this.parentId = parentId
this.duration = null
this.attrs = attrs ? { ...attrs } : {}
this.status = SpanStatus.Started
this.id = getId()
this._start = startTime || process.hrtime.bigint()
// hrtime cannot be used to reconstruct tracing span's actual start time
// since it does not have relation to clock time:
// `These times are relative to an arbitrary time in the past, and not related to the time of day and therefore not subject to clock drift`
// https://nodejs.org/api/process.html#processhrtimetime
// Capturing current datetime as additional metadata for external reconstruction.
this.now = Date.now()
}
// Durations are reported as microseconds. This gives 1000x the precision
// of something like Date.now(), which reports in milliseconds.
// Additionally, ~285 years can be safely represented as microseconds as
// a float64 in both JSON and JavaScript.
stop(stopTime?: bigint) {
const end: bigint = stopTime || process.hrtime.bigint()
const duration = (end - this._start) / NUM_OF_MICROSEC_IN_NANOSEC
this.status = SpanStatus.Stopped
if (duration > Number.MAX_SAFE_INTEGER) {
throw new Error(`Duration is too long to express as float64: ${duration}`)
}
const timestamp = this._start / NUM_OF_MICROSEC_IN_NANOSEC
reporter.report(
this.name,
Number(duration),
Number(timestamp),
this.id,
this.parentId,
this.attrs,
this.now
)
}
traceChild(name: string, attrs?: Object) {
return new Span({ name, parentId: this.id, attrs })
}
manualTraceChild(
name: string,
startTime: bigint,
stopTime: bigint,
attrs?: Object
) {
const span = new Span({ name, parentId: this.id, attrs, startTime })
span.stop(stopTime)
}
setAttribute(key: string, value: any) {
this.attrs[key] = String(value)
}
traceFn<T>(fn: () => T): T {
try {
return fn()
} finally {
this.stop()
}
}
async traceAsyncFn<T>(fn: () => T | Promise<T>): Promise<T> {
try {
return await fn()
} finally {
this.stop()
}
}
}
export const trace = (
name: string,
parentId?: SpanId,
attrs?: { [key: string]: string }
) => {
return new Span({ name, parentId, attrs })
}
export const flushAllTraces = () => reporter.flushAll()