Skip to content

Commit 586c2e8

Browse files
committed
EventSerializer
1 parent db77f35 commit 586c2e8

File tree

1 file changed

+79
-80
lines changed
  • packages/dd-trace/src/profiling/profilers

1 file changed

+79
-80
lines changed

packages/dd-trace/src/profiling/profilers/events.js

+79-80
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const MS_TO_NS = 1000000
1515
const pprofValueType = 'timeline'
1616
const pprofValueUnit = 'nanoseconds'
1717

18+
const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
19+
1820
function labelFromStr (stringTable, key, valStr) {
1921
return new Label({ key, str: stringTable.dedup(valStr) })
2022
}
@@ -146,6 +148,76 @@ if (node16) {
146148
decoratorTypes.net = NetDecorator
147149
}
148150

151+
// Translates performance entries into pprof samples.
152+
class EventSerializer {
153+
constructor () {
154+
this.stringTable = new StringTable()
155+
this.samples = []
156+
this.locations = []
157+
this.functions = []
158+
this.decorators = {}
159+
160+
// A synthetic single-frame location to serve as the location for timeline
161+
// samples. We need these as the profiling backend (mimicking official pprof
162+
// tool's behavior) ignores these.
163+
const fn = new Function({ id: this.functions.length + 1, name: this.stringTable.dedup('') })
164+
this.functions.push(fn)
165+
const line = new Line({ functionId: fn.id })
166+
const location = new Location({ id: this.locations.length + 1, line: [line] })
167+
this.locations.push(location)
168+
this.locationId = [location.id]
169+
170+
this.timestampLabelKey = this.stringTable.dedup(END_TIMESTAMP_LABEL)
171+
}
172+
173+
addEvent (item) {
174+
const { entryType, startTime, duration } = item
175+
let decorator = this.decorators[entryType]
176+
if (!decorator) {
177+
const DecoratorCtor = decoratorTypes[entryType]
178+
if (DecoratorCtor) {
179+
decorator = new DecoratorCtor(this.stringTable)
180+
decorator.eventTypeLabel = labelFromStrStr(this.stringTable, 'event', entryType)
181+
this.decorators[entryType] = decorator
182+
} else {
183+
// Shouldn't happen but it's better to not rely on observer only getting
184+
// requested event types.
185+
return
186+
}
187+
}
188+
const endTime = startTime + duration
189+
const sampleInput = {
190+
value: [Math.round(duration * MS_TO_NS)],
191+
locationId: this.locationId,
192+
label: [
193+
decorator.eventTypeLabel,
194+
new Label({ key: this.timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
195+
]
196+
}
197+
decorator.decorateSample(sampleInput, item)
198+
this.samples.push(new Sample(sampleInput))
199+
}
200+
201+
createProfile (startDate, endDate) {
202+
const timeValueType = new ValueType({
203+
type: this.stringTable.dedup(pprofValueType),
204+
unit: this.stringTable.dedup(pprofValueUnit)
205+
})
206+
207+
return new Profile({
208+
sampleType: [timeValueType],
209+
timeNanos: endDate.getTime() * MS_TO_NS,
210+
periodType: timeValueType,
211+
period: 1,
212+
durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
213+
sample: this.samples,
214+
location: this.locations,
215+
function: this.functions,
216+
stringTable: this.stringTable
217+
})
218+
}
219+
}
220+
149221
/**
150222
* This class generates pprof files with timeline events sourced from Node.js
151223
* performance measurement APIs.
@@ -155,15 +227,17 @@ class EventsProfiler {
155227
this.type = 'events'
156228
this._flushIntervalNanos = (options.flushInterval || 60000) * 1e6 // 60 sec
157229
this._observer = undefined
158-
this.entries = []
230+
this.eventSerializer = new EventSerializer()
159231
}
160232

161233
start () {
162234
// if already started, do nothing
163235
if (this._observer) return
164236

165237
function add (items) {
166-
this.entries.push(...items.getEntries())
238+
for (const item of items.getEntries()) {
239+
this.eventSerializer.addEvent(item)
240+
}
167241
}
168242
this._observer = new PerformanceObserver(add.bind(this))
169243
this._observer.observe({ entryTypes: Object.keys(decoratorTypes) })
@@ -177,87 +251,12 @@ class EventsProfiler {
177251
}
178252

179253
profile (restart, startDate, endDate) {
180-
const stringTable = new StringTable()
181-
const locations = []
182-
const functions = []
183-
184-
// A synthetic single-frame location to serve as the location for timeline
185-
// samples. We need these as the profiling backend (mimicking official pprof
186-
// tool's behavior) ignores these.
187-
const locationId = (() => {
188-
const fn = new Function({ id: functions.length + 1, name: stringTable.dedup('') })
189-
functions.push(fn)
190-
const line = new Line({ functionId: fn.id })
191-
const location = new Location({ id: locations.length + 1, line: [line] })
192-
locations.push(location)
193-
return [location.id]
194-
})()
195-
196-
const decorators = {}
197-
const timestampLabelKey = stringTable.dedup(END_TIMESTAMP_LABEL)
198-
199-
const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
200-
const lateEntries = []
201-
const perfEndDate = endDate.getTime() - performance.timeOrigin
202-
const samples = this.entries.map((item) => {
203-
const eventType = item.entryType
204-
let decorator = decorators[eventType]
205-
if (!decorator) {
206-
const DecoratorCtor = decoratorTypes[eventType]
207-
if (DecoratorCtor) {
208-
decorator = new DecoratorCtor(stringTable)
209-
decorator.eventTypeLabel = labelFromStrStr(stringTable, 'event', eventType)
210-
decorators[eventType] = decorator
211-
} else {
212-
// Shouldn't happen but it's better to not rely on observer only getting
213-
// requested event types.
214-
return null
215-
}
216-
}
217-
const { startTime, duration } = item
218-
if (startTime >= perfEndDate) {
219-
// An event past the current recording end date; save it for the next
220-
// profile. Not supposed to happen as long as there's no async activity
221-
// between capture of the endDate value in profiler.js _collect() and
222-
// here, but better be safe than sorry.
223-
lateEntries.push(item)
224-
return null
225-
}
226-
const endTime = startTime + duration
227-
const sampleInput = {
228-
value: [Math.round(duration * MS_TO_NS)],
229-
locationId,
230-
label: [
231-
decorator.eventTypeLabel,
232-
new Label({ key: timestampLabelKey, num: dateOffset + BigInt(Math.round(endTime * MS_TO_NS)) })
233-
]
234-
}
235-
decorator.decorateSample(sampleInput, item)
236-
return new Sample(sampleInput)
237-
}).filter(v => v)
238-
239-
this.entries = lateEntries
240-
241-
const timeValueType = new ValueType({
242-
type: stringTable.dedup(pprofValueType),
243-
unit: stringTable.dedup(pprofValueUnit)
244-
})
245-
246254
if (!restart) {
247255
this.stop()
248256
}
249-
250-
return new Profile({
251-
sampleType: [timeValueType],
252-
timeNanos: endDate.getTime() * MS_TO_NS,
253-
periodType: timeValueType,
254-
period: 1,
255-
durationNanos: (endDate.getTime() - startDate.getTime()) * MS_TO_NS,
256-
sample: samples,
257-
location: locations,
258-
function: functions,
259-
stringTable
260-
})
257+
const profile = this.eventSerializer.createProfile(startDate, endDate)
258+
this.eventSerializer = new EventSerializer()
259+
return profile
261260
}
262261

263262
encode (profile) {

0 commit comments

Comments
 (0)