mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
163 lines
3.9 KiB
TypeScript
163 lines
3.9 KiB
TypeScript
import {CallTreeNode} from '../callTreeNode';
|
|
import {Frame} from '../frame';
|
|
|
|
interface ProfileStats {
|
|
discardedSamplesCount: number;
|
|
negativeSamplesCount: number;
|
|
}
|
|
|
|
export class Profile {
|
|
// The epoch time at which this profile was started. All relative timestamp should be
|
|
// relative to this.
|
|
// Some older formats may not have a timestamp defined.
|
|
timestamp: number | null;
|
|
// Duration of the profile
|
|
duration: number;
|
|
// Releative timestamp of the first sample in the timestamp.
|
|
startedAt: number;
|
|
// Releative timestamp of the last sample in the timestamp.
|
|
endedAt: number;
|
|
threadId: number;
|
|
type: string;
|
|
|
|
// Unit in which the timings are reported in
|
|
unit = 'microseconds';
|
|
// Name of the profile
|
|
name = 'Unknown';
|
|
|
|
callTree: CallTreeNode = new CallTreeNode(Frame.Root, null);
|
|
framesInStack: Set<Profiling.Event['frame']> = new Set();
|
|
|
|
// Min duration of a single frame in our profile
|
|
minFrameDuration = Number.POSITIVE_INFINITY;
|
|
|
|
samples: CallTreeNode[] = [];
|
|
sample_durations_ns: number[] = [];
|
|
weights: number[] = [];
|
|
rawWeights: number[] = [];
|
|
|
|
stats: ProfileStats = {
|
|
discardedSamplesCount: 0,
|
|
negativeSamplesCount: 0,
|
|
};
|
|
|
|
callTreeNodeProfileIdMap: Map<CallTreeNode, string[]> = new Map();
|
|
|
|
constructor({
|
|
duration,
|
|
startedAt,
|
|
endedAt,
|
|
name,
|
|
unit,
|
|
threadId,
|
|
timestamp,
|
|
type,
|
|
}: {
|
|
duration: number;
|
|
endedAt: number;
|
|
name: string;
|
|
startedAt: number;
|
|
threadId: number;
|
|
type: string;
|
|
unit: string;
|
|
timestamp?: number;
|
|
}) {
|
|
this.threadId = threadId;
|
|
this.duration = duration;
|
|
this.startedAt = startedAt;
|
|
this.endedAt = endedAt;
|
|
this.name = name;
|
|
this.unit = unit;
|
|
this.type = type ?? '';
|
|
this.timestamp = timestamp ?? null;
|
|
}
|
|
|
|
static Empty = new Profile({
|
|
duration: 1000,
|
|
startedAt: 0,
|
|
endedAt: 1000,
|
|
name: 'Empty Profile',
|
|
unit: 'milliseconds',
|
|
threadId: 0,
|
|
type: '',
|
|
}).build();
|
|
|
|
isEmpty(): boolean {
|
|
return this === Profile.Empty;
|
|
}
|
|
|
|
trackSampleStats(duration: number) {
|
|
// Keep track of discarded samples and ones that may have negative weights
|
|
if (duration === 0) {
|
|
this.stats.discardedSamplesCount++;
|
|
}
|
|
if (duration < 0) {
|
|
this.stats.negativeSamplesCount++;
|
|
}
|
|
if (duration > 0) {
|
|
this.rawWeights.push(duration);
|
|
}
|
|
}
|
|
|
|
forEach(
|
|
openFrame: (node: CallTreeNode, value: number) => void,
|
|
closeFrame: (node: CallTreeNode, value: number) => void
|
|
): void {
|
|
const prevStack: CallTreeNode[] = [];
|
|
let value = 0;
|
|
|
|
let sampleIndex = 0;
|
|
|
|
for (const stackTop of this.samples) {
|
|
let top: CallTreeNode | null = stackTop;
|
|
|
|
while (top && !top.isRoot && !prevStack.includes(top)) {
|
|
top = top.parent;
|
|
}
|
|
|
|
while (prevStack.length > 0 && prevStack[prevStack.length - 1] !== top) {
|
|
const node = prevStack.pop()!;
|
|
closeFrame(node, value);
|
|
}
|
|
|
|
const toOpen: CallTreeNode[] = [];
|
|
let node: CallTreeNode | null = stackTop;
|
|
|
|
while (node && !node.isRoot && node !== top) {
|
|
toOpen.push(node);
|
|
node = node.parent;
|
|
}
|
|
|
|
for (let i = toOpen.length - 1; i >= 0; i--) {
|
|
openFrame(toOpen[i], value);
|
|
prevStack.push(toOpen[i]);
|
|
}
|
|
|
|
value += this.weights[sampleIndex++];
|
|
}
|
|
|
|
// Close any remaining frames
|
|
for (let i = prevStack.length - 1; i >= 0; i--) {
|
|
closeFrame(prevStack[i], value);
|
|
}
|
|
}
|
|
|
|
build(): Profile {
|
|
this.duration = Math.max(
|
|
this.duration,
|
|
this.weights.reduce((a, b) => a + b, 0)
|
|
);
|
|
|
|
// We had no frames with duration > 0, so set min duration to timeline duration
|
|
// which effectively disables any zooming on the flamegraphs
|
|
if (
|
|
this.minFrameDuration === Number.POSITIVE_INFINITY ||
|
|
this.minFrameDuration === 0
|
|
) {
|
|
this.minFrameDuration = this.duration;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
}
|