mirror of
https://github.com/jlfwong/speedscope.git
synced 2024-10-06 23:27:34 +03:00
Compare commits
5 Commits
a565f1d4eb
...
2b04b6f9f5
Author | SHA1 | Date | |
---|---|---|---|
|
2b04b6f9f5 | ||
|
d6639f42f3 | ||
|
0c900275f9 | ||
|
357f4747db | ||
|
c1e2271334 |
@ -1,229 +0,0 @@
|
||||
import {FrameInfo} from '../lib/profile'
|
||||
import {lastOf} from '../lib/utils'
|
||||
import {ProfileBuilderInfo, Sample, StackFrame, TraceEventJsonObject} from './trace-event'
|
||||
|
||||
/**
|
||||
* The chrome json trace event spec only specifies name and category
|
||||
* as required stack frame properties
|
||||
*
|
||||
* https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.b4y98p32171
|
||||
*/
|
||||
function frameInfoForEvent({name, category}: StackFrame): FrameInfo {
|
||||
return {
|
||||
key: `${name}:${category}`,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization function to enable O(1) access to the set of active nodes in the stack by node ID.
|
||||
*/
|
||||
function getActiveNodeArrays(profile: TraceEventJsonObject): Map<number, number[]> {
|
||||
const map: Map<number, number[]> = new Map<number, number[]>()
|
||||
|
||||
// Given a nodeId, `getActiveNodes` gets all the parent nodes in reversed call order
|
||||
const getActiveNodes = (id: number): number[] => {
|
||||
if (map.has(id)) return map.get(id) || []
|
||||
|
||||
const node = profile.stackFrames[id]
|
||||
if (!node) throw new Error(`No such node ${id}`)
|
||||
if (node.parent) {
|
||||
const array = getActiveNodes(node.parent).concat([id])
|
||||
map.set(id, array)
|
||||
return array
|
||||
} else {
|
||||
return [id]
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(profile.stackFrames).forEach(nodeId => {
|
||||
const id = Number(nodeId)
|
||||
map.set(id, getActiveNodes(id))
|
||||
})
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the time difference in microseconds between the previous
|
||||
* sample and the current sample
|
||||
*/
|
||||
function getTimeDeltas(samples: Sample[]) {
|
||||
const timeDeltas: number[] = []
|
||||
let lastTimeStamp = Number(samples[0].ts)
|
||||
|
||||
samples.forEach((sample: Sample, idx: number) => {
|
||||
if (idx === 0) {
|
||||
timeDeltas.push(0)
|
||||
} else {
|
||||
const timeDiff = Number(sample.ts) - lastTimeStamp
|
||||
lastTimeStamp = Number(sample.ts)
|
||||
timeDeltas.push(timeDiff)
|
||||
}
|
||||
})
|
||||
|
||||
return timeDeltas
|
||||
}
|
||||
|
||||
export function constructProfileFromJsonObject(
|
||||
contents: TraceEventJsonObject,
|
||||
samplesForPidTid: Sample[],
|
||||
{profileBuilder}: ProfileBuilderInfo,
|
||||
) {
|
||||
const activeNodeArraysById = getActiveNodeArrays(contents)
|
||||
|
||||
/**
|
||||
* The json object format maintains an object of stack frames where the
|
||||
* key is the frame id and the value is the stack frame object.
|
||||
*/
|
||||
function getFrameById(frameId: string | number): StackFrame {
|
||||
return contents.stackFrames[String(frameId)]
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper function to get the active nodes for a given node id. We should
|
||||
* always have active nodes for any given node.
|
||||
*/
|
||||
function getActiveNodeIds(nodeId: number): number[] {
|
||||
const activeNodeIds = activeNodeArraysById.get(nodeId)
|
||||
if (!activeNodeIds) throw new Error(`No such node ID ${nodeId}`)
|
||||
return activeNodeIds
|
||||
}
|
||||
|
||||
// We need to leave frames in the same order that we start them, so we keep a stack
|
||||
// of frames that are currently open
|
||||
const frameStack: StackFrame[] = []
|
||||
|
||||
/**
|
||||
* Enter a frame, pushing it to the top of the stack so that we keep track of what functions
|
||||
* are currently being executed
|
||||
*/
|
||||
function enterFrame(frame: StackFrame, timestamp: number) {
|
||||
frameStack.push(frame)
|
||||
profileBuilder.enterFrame(frameInfoForEvent(frame), timestamp)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to leave a frame. First we check if the frame matches what we expect to be the
|
||||
* next thing to leave (top of the stack). If this is not the case we warn, and then leave
|
||||
* the frame at the top of the stack
|
||||
*/
|
||||
function tryToLeaveFrame(frame: StackFrame, timestamp: number) {
|
||||
const lastActiveFrame = lastOf(frameStack)
|
||||
|
||||
if (lastActiveFrame == null) {
|
||||
console.warn(
|
||||
`Tried to end frame "${
|
||||
frameInfoForEvent(frame).key
|
||||
}", but the stack was empty. Doing nothing instead.`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const frameInfo = frameInfoForEvent(frame)
|
||||
const lastActiveFrameInfo = frameInfoForEvent(lastActiveFrame)
|
||||
|
||||
if (frame.name !== lastActiveFrame.name) {
|
||||
console.warn(
|
||||
`ts=${timestamp}: Tried to end "${frameInfo.key}" when "${lastActiveFrameInfo.key}" was on the top of the stack. Doing nothing instead.`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (frameInfo.key !== lastActiveFrameInfo.key) {
|
||||
console.warn(
|
||||
`ts=${timestamp}: Tried to end "${frameInfo.key}" when "${lastActiveFrameInfo.key}" was on the top of the stack. Ending ${lastActiveFrameInfo.key} instead.`,
|
||||
)
|
||||
}
|
||||
|
||||
frameStack.pop()
|
||||
profileBuilder.leaveFrame(lastActiveFrameInfo, timestamp)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle opening and closing the appropriate frames at a given timestamp
|
||||
*
|
||||
* @param activeNodeIds - The ids of the functions that are active at this timestamp
|
||||
* @param lastActiveNodeIds - The ids of the functions active at the previous timestamp
|
||||
* @param timestamp - The current timestamp (microseconds)
|
||||
*/
|
||||
function handleSample(activeNodeIds: number[], lastActiveNodeIds: number[], timestamp: number) {
|
||||
// Frames which are present only in the currentNodeIds and not in lastActiveNodeIds
|
||||
const startFrameIds = activeNodeIds.filter(id => !lastActiveNodeIds.includes(id))
|
||||
|
||||
// Frames which are present only in the PreviousNodeIds and not in activeNodeIds
|
||||
const endFrameIds = lastActiveNodeIds.filter(id => !activeNodeIds.includes(id))
|
||||
|
||||
// Before we take the first event in the end ids, let's first see if there are any
|
||||
// end events that exactly match the top of the stack. We'll prioritize first by key,
|
||||
// then by name if we can't find a key match.
|
||||
while (endFrameIds.length > 0) {
|
||||
const stackTop = lastOf(frameStack)
|
||||
|
||||
if (stackTop != null) {
|
||||
const bFrameInfo = frameInfoForEvent(stackTop)
|
||||
|
||||
let swapped: boolean = false
|
||||
|
||||
for (let i = 1; i < endFrameIds.length; i++) {
|
||||
const eEvent = getFrameById(endFrameIds[i])
|
||||
const eFrameInfo = frameInfoForEvent(eEvent)
|
||||
|
||||
if (bFrameInfo.key === eFrameInfo.key) {
|
||||
// We have a match! Process this one first.
|
||||
const temp = endFrameIds[0]
|
||||
endFrameIds[0] = endFrameIds[i]
|
||||
endFrameIds[i] = temp
|
||||
swapped = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!swapped) {
|
||||
// There was no key match, let's see if we can find a name match
|
||||
for (let i = 1; i < endFrameIds.length; i++) {
|
||||
const eEvent = getFrameById(endFrameIds[i])
|
||||
|
||||
if (eEvent.name === stackTop.name) {
|
||||
// We have a match! Process this one first.
|
||||
const temp = endFrameIds[0]
|
||||
endFrameIds[0] = endFrameIds[i]
|
||||
endFrameIds[i] = temp
|
||||
swapped = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const endFrameId = endFrameIds.shift()!
|
||||
tryToLeaveFrame(getFrameById(endFrameId), timestamp)
|
||||
}
|
||||
|
||||
startFrameIds.forEach(frameId => {
|
||||
const frame = getFrameById(frameId)
|
||||
enterFrame(frame, timestamp)
|
||||
})
|
||||
}
|
||||
|
||||
let currentTimestamp = 0
|
||||
let lastActiveNodeIds: number[] = []
|
||||
|
||||
const timeDeltas = getTimeDeltas(samplesForPidTid)
|
||||
|
||||
for (let i = 0; i < samplesForPidTid.length; i++) {
|
||||
const nodeId = samplesForPidTid[i].sf
|
||||
const timeDelta = Math.max(timeDeltas[i], 0)
|
||||
const node = getFrameById(nodeId)
|
||||
|
||||
if (!node) throw new Error(`Missing node ${nodeId}`)
|
||||
|
||||
currentTimestamp += timeDelta
|
||||
const activeNodeIds = getActiveNodeIds(nodeId)
|
||||
|
||||
handleSample(activeNodeIds, lastActiveNodeIds, currentTimestamp)
|
||||
lastActiveNodeIds = activeNodeIds
|
||||
}
|
||||
|
||||
handleSample([], lastActiveNodeIds, currentTimestamp)
|
||||
}
|
@ -1,7 +1,12 @@
|
||||
import {sortBy, zeroPad, getOrInsert, lastOf} from '../lib/utils'
|
||||
import {ProfileGroup, CallTreeProfileBuilder, FrameInfo, Profile} from '../lib/profile'
|
||||
import {
|
||||
ProfileGroup,
|
||||
CallTreeProfileBuilder,
|
||||
FrameInfo,
|
||||
Profile,
|
||||
StackListProfileBuilder,
|
||||
} from '../lib/profile'
|
||||
import {TimeFormatter} from '../lib/value-formatters'
|
||||
import {constructProfileFromJsonObject} from './trace-event-json'
|
||||
|
||||
// This file concerns import from the "Trace Event Format", authored by Google
|
||||
// and used for Google's own chrome://trace.
|
||||
@ -66,7 +71,7 @@ interface XTraceEvent extends TraceEvent {
|
||||
// The trace format supports a number of event types that we ignore.
|
||||
type ImportableTraceEvent = BTraceEvent | ETraceEvent | XTraceEvent
|
||||
|
||||
export interface StackFrame {
|
||||
interface StackFrame {
|
||||
line: string
|
||||
column: string
|
||||
funcLine: string
|
||||
@ -77,7 +82,7 @@ export interface StackFrame {
|
||||
parent?: number
|
||||
}
|
||||
|
||||
export interface Sample {
|
||||
interface Sample {
|
||||
cpu: string
|
||||
name: string
|
||||
ts: string
|
||||
@ -89,12 +94,18 @@ export interface Sample {
|
||||
stackFrameData?: StackFrame
|
||||
}
|
||||
|
||||
export interface TraceEventJsonObject {
|
||||
interface TraceWithSamples {
|
||||
traceEvents: TraceEvent[]
|
||||
samples: Sample[]
|
||||
stackFrames: {[key in string]: StackFrame}
|
||||
}
|
||||
|
||||
interface TraceEventObject {
|
||||
traceEvents: TraceEvent[]
|
||||
}
|
||||
|
||||
type Trace = TraceEvent[] | TraceEventObject | TraceWithSamples
|
||||
|
||||
function pidTidKey(pid: number, tid: number): string {
|
||||
// We zero-pad the PID and TID to make sorting them by pid/tid pair later easier.
|
||||
return `${zeroPad('' + pid, 10)}:${zeroPad('' + tid, 10)}`
|
||||
@ -290,44 +301,45 @@ export type ProfileBuilderInfo = {
|
||||
*
|
||||
* See https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.xqopa5m0e28f
|
||||
*/
|
||||
function partitionToProfileBuilderPairs(events: TraceEvent[]): [string, ProfileBuilderInfo][] {
|
||||
const importableEvents = filterIgnoredEventTypes(events)
|
||||
const partitionedTraceEvents = partitionByPidTid(importableEvents)
|
||||
|
||||
function getProfileNameByPidTid(
|
||||
events: TraceEvent[],
|
||||
partitionedTraceEvents: Map<string, TraceEvent[]>,
|
||||
): Map<string, string> {
|
||||
const processNamesByPid = getProcessNamesByPid(events)
|
||||
const threadNamesByPidTid = getThreadNamesByPidTid(events)
|
||||
|
||||
const profilePairs: [string, ProfileBuilderInfo][] = []
|
||||
const profileNamesByPidTid = new Map<string, string>()
|
||||
|
||||
partitionedTraceEvents.forEach(importableEvents => {
|
||||
if (importableEvents.length === 0) return
|
||||
|
||||
const {pid, tid} = importableEvents[0]
|
||||
|
||||
const profileBuilder = new CallTreeProfileBuilder()
|
||||
profileBuilder.setValueFormatter(new TimeFormatter('microseconds'))
|
||||
|
||||
const profileKey = pidTidKey(pid, tid)
|
||||
const processName = processNamesByPid.get(pid)
|
||||
const threadName = threadNamesByPidTid.get(profileKey)
|
||||
|
||||
if (processName != null && threadName != null) {
|
||||
profileBuilder.setName(`${processName} (pid ${pid}), ${threadName} (tid ${tid})`)
|
||||
profileNamesByPidTid.set(
|
||||
profileKey,
|
||||
`${processName} (pid ${pid}), ${threadName} (tid ${tid})`,
|
||||
)
|
||||
} else if (processName != null) {
|
||||
profileBuilder.setName(`${processName} (pid ${pid}, tid ${tid})`)
|
||||
profileNamesByPidTid.set(profileKey, `${processName} (pid ${pid}, tid ${tid})`)
|
||||
} else if (threadName != null) {
|
||||
profileBuilder.setName(`${threadName} (pid ${pid}, tid ${tid})`)
|
||||
profileNamesByPidTid.set(profileKey, `${threadName} (pid ${pid}, tid ${tid})`)
|
||||
} else {
|
||||
profileBuilder.setName(`pid ${pid}, tid ${tid}`)
|
||||
profileNamesByPidTid.set(profileKey, `pid ${pid}, tid ${tid}`)
|
||||
}
|
||||
|
||||
profilePairs.push([profileKey, {pid, tid, profileBuilder, importableEvents}])
|
||||
})
|
||||
|
||||
return profilePairs
|
||||
return profileNamesByPidTid
|
||||
}
|
||||
|
||||
function constructProfileFromTraceEvents({profileBuilder, importableEvents}: ProfileBuilderInfo) {
|
||||
function constructProfileFromTraceEvents(
|
||||
importableEvents: ImportableTraceEvent[],
|
||||
name: string,
|
||||
): Profile {
|
||||
// The trace event format is hard to deal with because it specifically
|
||||
// allows events to be recorded out of order, *but* event ordering is still
|
||||
// important for events with the same timestamp. Because of this, rather
|
||||
@ -346,6 +358,10 @@ function constructProfileFromTraceEvents({profileBuilder, importableEvents}: Pro
|
||||
// events to match whatever is on the top of the stack.
|
||||
const [bEventQueue, eEventQueue] = convertToEventQueues(importableEvents)
|
||||
|
||||
const profileBuilder = new CallTreeProfileBuilder()
|
||||
profileBuilder.setValueFormatter(new TimeFormatter('microseconds'))
|
||||
profileBuilder.setName(name)
|
||||
|
||||
const frameStack: BTraceEvent[] = []
|
||||
const enterFrame = (b: BTraceEvent) => {
|
||||
frameStack.push(b)
|
||||
@ -461,21 +477,137 @@ function constructProfileFromTraceEvents({profileBuilder, importableEvents}: Pro
|
||||
console.warn(`Frame "${frame.key}" was still open at end of profile. Closing automatically.`)
|
||||
profileBuilder.leaveFrame(frame, profileBuilder.getTotalWeight())
|
||||
}
|
||||
|
||||
return profileBuilder.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Partition by thread and then build the profile appropriately based on the format
|
||||
* Returns an array containing the time difference in microseconds between the previous
|
||||
* sample and the current sample
|
||||
*/
|
||||
function constructProfileGroup(
|
||||
events: TraceEvent[],
|
||||
buildFunction: (info: ProfileBuilderInfo) => void,
|
||||
): ProfileGroup {
|
||||
const profileBuilderPairs = partitionToProfileBuilderPairs(events)
|
||||
function getTimeDeltasForSamples(samples: Sample[]) {
|
||||
const timeDeltas: number[] = []
|
||||
let lastTimeStamp = Number(samples[0].ts)
|
||||
|
||||
const profilePairs = profileBuilderPairs.map(([key, info]): [string, Profile] => {
|
||||
buildFunction(info)
|
||||
samples.forEach((sample: Sample, idx: number) => {
|
||||
if (idx === 0) {
|
||||
timeDeltas.push(0)
|
||||
} else {
|
||||
const timeDiff = Number(sample.ts) - lastTimeStamp
|
||||
lastTimeStamp = Number(sample.ts)
|
||||
timeDeltas.push(timeDiff)
|
||||
}
|
||||
})
|
||||
|
||||
return [key, info.profileBuilder.build()]
|
||||
return timeDeltas
|
||||
}
|
||||
|
||||
/**
|
||||
* The chrome json trace event spec only specifies name and category
|
||||
* as required stack frame properties
|
||||
*
|
||||
* https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.b4y98p32171
|
||||
*/
|
||||
function frameInfoForSampleFrame({name, category}: StackFrame): FrameInfo {
|
||||
return {
|
||||
key: `${name}:${category}`,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
function getActiveFrames(
|
||||
stackFrames: TraceWithSamples['stackFrames'],
|
||||
frameId: number,
|
||||
): FrameInfo[] {
|
||||
const frames = []
|
||||
let parent: number | undefined = frameId
|
||||
|
||||
while (parent) {
|
||||
const frame: StackFrame = stackFrames[parent]
|
||||
|
||||
if (!frame) {
|
||||
throw new Error(`Could not find frame for id ${parent}`)
|
||||
}
|
||||
|
||||
frames.push(frameInfoForSampleFrame(frame))
|
||||
parent = frame.parent
|
||||
}
|
||||
|
||||
return frames.reverse()
|
||||
}
|
||||
|
||||
function constructProfileFromSampleList(
|
||||
contents: TraceWithSamples,
|
||||
samples: Sample[],
|
||||
name: string,
|
||||
) {
|
||||
const profileBuilder = new StackListProfileBuilder()
|
||||
|
||||
profileBuilder.setValueFormatter(new TimeFormatter('microseconds'))
|
||||
profileBuilder.setName(name)
|
||||
|
||||
const timeDeltas = getTimeDeltasForSamples(samples)
|
||||
|
||||
samples.forEach((sample, index) => {
|
||||
const timeDelta = timeDeltas[index]
|
||||
const activeFrames = getActiveFrames(contents.stackFrames, sample.sf)
|
||||
|
||||
profileBuilder.appendSampleWithWeight(activeFrames, timeDelta)
|
||||
})
|
||||
|
||||
return profileBuilder.build()
|
||||
}
|
||||
|
||||
function eventListToProfileGroup(events: TraceEvent[]): ProfileGroup {
|
||||
const importableEvents = filterIgnoredEventTypes(events)
|
||||
const partitionedTraceEvents = partitionByPidTid(importableEvents)
|
||||
const profileNamesByPidTid = getProfileNameByPidTid(events, partitionedTraceEvents)
|
||||
|
||||
const profilePairs: [string, Profile][] = []
|
||||
|
||||
profileNamesByPidTid.forEach((name, pidTidKey) => {
|
||||
const importableEventsForPidTid = partitionedTraceEvents.get(pidTidKey)
|
||||
|
||||
if (!importableEventsForPidTid) {
|
||||
throw new Error(`Could not find events for key: ${importableEventsForPidTid}`)
|
||||
}
|
||||
|
||||
profilePairs.push([pidTidKey, constructProfileFromTraceEvents(importableEventsForPidTid, name)])
|
||||
})
|
||||
|
||||
// For now, we just sort processes by pid & tid.
|
||||
// TODO: The standard specifies that metadata events with the name
|
||||
// "process_sort_index" and "thread_sort_index" can be used to influence the
|
||||
// order, but for simplicity we'll ignore that until someone complains :)
|
||||
sortBy(profilePairs, p => p[0])
|
||||
|
||||
return {
|
||||
name: '',
|
||||
indexToView: 0,
|
||||
profiles: profilePairs.map(p => p[1]),
|
||||
}
|
||||
}
|
||||
|
||||
function sampleListToProfileGroup(contents: TraceWithSamples): ProfileGroup {
|
||||
const importableEvents = filterIgnoredEventTypes(contents.traceEvents)
|
||||
const partitionedTraceEvents = partitionByPidTid(importableEvents)
|
||||
const partitionedSamples = partitionByPidTid(contents.samples)
|
||||
const profileNamesByPidTid = getProfileNameByPidTid(contents.traceEvents, partitionedTraceEvents)
|
||||
|
||||
const profilePairs: [string, Profile][] = []
|
||||
|
||||
profileNamesByPidTid.forEach((name, pidTidKey) => {
|
||||
const samplesForPidTid = partitionedSamples.get(pidTidKey)
|
||||
|
||||
if (!samplesForPidTid) {
|
||||
throw new Error(`Could not find samples for key: ${samplesForPidTid}`)
|
||||
}
|
||||
|
||||
if (samplesForPidTid.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
profilePairs.push([pidTidKey, constructProfileFromSampleList(contents, samplesForPidTid, name)])
|
||||
})
|
||||
|
||||
// For now, we just sort processes by pid & tid.
|
||||
@ -525,54 +657,36 @@ function isTraceEventList(maybeEventList: any): maybeEventList is TraceEvent[] {
|
||||
|
||||
function isTraceEventListObject(
|
||||
maybeTraceEventObject: any,
|
||||
): maybeTraceEventObject is {traceEvents: TraceEvent[]} {
|
||||
): maybeTraceEventObject is TraceEventObject {
|
||||
if (!('traceEvents' in maybeTraceEventObject)) return false
|
||||
return isTraceEventList(maybeTraceEventObject['traceEvents'])
|
||||
}
|
||||
|
||||
function isTraceEventJsonObject(
|
||||
function isTraceEventWithSamples(
|
||||
maybeTraceEventObject: any,
|
||||
): maybeTraceEventObject is TraceEventJsonObject {
|
||||
): maybeTraceEventObject is TraceWithSamples {
|
||||
return (
|
||||
'traceEvents' in maybeTraceEventObject &&
|
||||
'stackFrames' in maybeTraceEventObject &&
|
||||
'samples' in maybeTraceEventObject &&
|
||||
isTraceEventFormatted(maybeTraceEventObject['traceEvents'])
|
||||
isTraceEventList(maybeTraceEventObject['traceEvents'])
|
||||
)
|
||||
}
|
||||
|
||||
export function isTraceEventFormatted(
|
||||
rawProfile: any,
|
||||
): rawProfile is {traceEvents: TraceEvent[]} | TraceEvent[] {
|
||||
export function isTraceEventFormatted(rawProfile: any): rawProfile is Trace {
|
||||
// We're only going to support the JSON formatted profiles for now.
|
||||
// The spec also discusses support for data embedded in ftrace supported data: https://lwn.net/Articles/365835/.
|
||||
|
||||
return isTraceEventListObject(rawProfile) || isTraceEventList(rawProfile)
|
||||
}
|
||||
|
||||
export function importTraceEvents(
|
||||
rawProfile: {traceEvents: TraceEvent[]} | TraceEvent[] | TraceEventJsonObject,
|
||||
): ProfileGroup {
|
||||
if (isTraceEventJsonObject(rawProfile)) {
|
||||
const samplesByPidTid = partitionByPidTid(rawProfile.samples)
|
||||
|
||||
function jsonObjectTraceBuilder(info: ProfileBuilderInfo) {
|
||||
const {pid, tid} = info
|
||||
const key = pidTidKey(pid, tid)
|
||||
const samples = samplesByPidTid.get(key)
|
||||
|
||||
if (!samples) {
|
||||
throw new Error(`Could not find samples for key: ${key}`)
|
||||
}
|
||||
|
||||
return constructProfileFromJsonObject(rawProfile as TraceEventJsonObject, samples, info)
|
||||
}
|
||||
|
||||
return constructProfileGroup(rawProfile.traceEvents, jsonObjectTraceBuilder)
|
||||
export function importTraceEvents(rawProfile: Trace): ProfileGroup {
|
||||
if (isTraceEventWithSamples(rawProfile)) {
|
||||
return sampleListToProfileGroup(rawProfile)
|
||||
} else if (isTraceEventListObject(rawProfile)) {
|
||||
return constructProfileGroup(rawProfile.traceEvents, constructProfileFromTraceEvents)
|
||||
return eventListToProfileGroup(rawProfile.traceEvents)
|
||||
} else if (isTraceEventList(rawProfile)) {
|
||||
return constructProfileGroup(rawProfile, constructProfileFromTraceEvents)
|
||||
return eventListToProfileGroup(rawProfile)
|
||||
} else {
|
||||
const _exhaustiveCheck: never = rawProfile
|
||||
return _exhaustiveCheck
|
||||
|
Loading…
Reference in New Issue
Block a user