Skip to content

Commit 67aff3c

Browse files
committed
Add an importer for the text format taken as input by flamegraph.pl.
1 parent 7f30744 commit 67aff3c

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
// @flow
6+
7+
import type { MixedObject } from 'firefox-profiler/types';
8+
9+
/**
10+
* The "perf script" format is the plain text format that is output by an
11+
* invocation of `perf script`, where `perf` is the Linux perf command line tool.
12+
*/
13+
export function isFlameGraphFormat(profile: string): boolean {
14+
if (profile.startsWith('{')) {
15+
// Make sure we don't accidentally match JSON
16+
return false;
17+
}
18+
19+
const firstLine = profile.substring(0, profile.indexOf('\n'));
20+
return !!firstLine.match(/[^;]*(;[^;]*)* [0-9]+/);
21+
}
22+
23+
// Don't try and type this more specifically.
24+
type GeckoProfileVersion24 = MixedObject;
25+
26+
const CATEGORIES = [
27+
{ name: 'Other', color: 'grey', subcategories: ['Other'] },
28+
{ name: 'Java', color: 'yellow', subcategories: ['Other'] },
29+
{ name: 'Native', color: 'blue', subcategories: ['Other'] },
30+
];
31+
const OTHER_CATEGORY_INDEX = 0;
32+
const JAVA_CATEGORY_INDEX = 1;
33+
const NATIVE_CATEGORY_INDEX = 2;
34+
35+
/**
36+
* Convert the flamegraph.pl input text format into the gecko profile format (version 24).
37+
*/
38+
export function convertFlameGraphProfile(
39+
profile: string
40+
): GeckoProfileVersion24 {
41+
function _createThread(name, pid, tid) {
42+
const markers = {
43+
schema: {
44+
name: 0,
45+
startTime: 1,
46+
endTime: 2,
47+
phase: 3,
48+
category: 4,
49+
data: 5,
50+
},
51+
data: [],
52+
};
53+
const samples = {
54+
schema: {
55+
stack: 0,
56+
time: 1,
57+
responsiveness: 2,
58+
},
59+
data: [],
60+
};
61+
const frameTable = {
62+
schema: {
63+
location: 0,
64+
relevantForJS: 1,
65+
innerWindowID: 2,
66+
implementation: 3,
67+
optimizations: 4,
68+
line: 5,
69+
column: 6,
70+
category: 7,
71+
subcategory: 8,
72+
},
73+
data: [],
74+
};
75+
const stackTable = {
76+
schema: {
77+
prefix: 0,
78+
frame: 1,
79+
},
80+
data: [],
81+
};
82+
const stringTable = [];
83+
84+
const stackMap = new Map();
85+
function getOrCreateStack(frame, prefix) {
86+
const key = prefix === null ? `${frame}` : `${frame},${prefix}`;
87+
let stack = stackMap.get(key);
88+
if (stack === undefined) {
89+
stack = stackTable.data.length;
90+
stackTable.data.push([prefix, frame]);
91+
stackMap.set(key, stack);
92+
}
93+
return stack;
94+
}
95+
96+
const frameMap = new Map();
97+
function getOrCreateFrame(frameString: string) {
98+
let frame = frameMap.get(frameString);
99+
if (frame === undefined) {
100+
frame = frameTable.data.length;
101+
const location = stringTable.length;
102+
stringTable.push(frameString.replace(/_\[j\]$/, ''));
103+
104+
let category = OTHER_CATEGORY_INDEX;
105+
if (frameString.endsWith('_[j]')) {
106+
category = JAVA_CATEGORY_INDEX;
107+
} else if (!frameString.includes('::')) {
108+
category = NATIVE_CATEGORY_INDEX;
109+
}
110+
const implementation = null;
111+
const optimizations = null;
112+
const line = null;
113+
const relevantForJS = false;
114+
const subcategory = null;
115+
const innerWindowID = 0;
116+
const column = null;
117+
frameTable.data.push([
118+
location,
119+
relevantForJS,
120+
innerWindowID,
121+
implementation,
122+
optimizations,
123+
line,
124+
column,
125+
category,
126+
subcategory,
127+
]);
128+
frameMap.set(frameString, frame);
129+
}
130+
return frame;
131+
}
132+
133+
function addSample(time, stackArray) {
134+
const stack = stackArray.reduce((prefix, stackFrame) => {
135+
const frame = getOrCreateFrame(stackFrame);
136+
return getOrCreateStack(frame, prefix);
137+
}, null);
138+
// We don't have this information, so simulate that there's no latency at
139+
// all in processing events.
140+
const responsiveness = 0;
141+
samples.data.push([stack, time, responsiveness]);
142+
}
143+
144+
return {
145+
addSample,
146+
finish: () => {
147+
return {
148+
tid,
149+
pid,
150+
name,
151+
markers,
152+
samples,
153+
frameTable,
154+
stackTable,
155+
stringTable,
156+
registerTime: 0,
157+
unregisterTime: null,
158+
processType: 'default',
159+
};
160+
},
161+
};
162+
}
163+
164+
const thread = _createThread('MainThread', 0, 0);
165+
const lines = profile.split('\n');
166+
167+
let timeStamp = 0;
168+
for (const line of lines) {
169+
if (line === '') {
170+
continue;
171+
}
172+
173+
const matched = line.match(/([^;]*(;[^;]*)*) ([0-9]+)/);
174+
if (!matched) {
175+
console.log('unexpected line format', line);
176+
continue;
177+
}
178+
179+
const [, frames, , duration] = matched;
180+
let count = parseInt(duration);
181+
while (count-- > 0) {
182+
const stack = frames.split(';');
183+
if (stack.length !== 0) {
184+
thread.addSample(timeStamp++, stack);
185+
}
186+
}
187+
}
188+
189+
return {
190+
meta: {
191+
interval: 1,
192+
processType: 0,
193+
product: 'Firefox',
194+
stackwalk: 1,
195+
debug: 0,
196+
gcpoison: 0,
197+
asyncstack: 1,
198+
startTime: 0,
199+
shutdownTime: null,
200+
version: 24,
201+
presymbolicated: true,
202+
categories: CATEGORIES,
203+
markerSchema: [],
204+
},
205+
libs: [],
206+
threads: [thread.finish()],
207+
processes: [],
208+
pausedRanges: [],
209+
};
210+
}

src/profile-logic/process-profile.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import {
2525
isPerfScriptFormat,
2626
convertPerfScriptProfile,
2727
} from './import/linux-perf';
28+
import {
29+
isFlameGraphFormat,
30+
convertFlameGraphProfile,
31+
} from './import/flame-graph';
2832
import { isArtTraceFormat, convertArtTraceProfile } from './import/art-trace';
2933
import {
3034
PROCESSED_PROFILE_VERSION,
@@ -1852,6 +1856,8 @@ export async function unserializeProfileOfArbitraryFormat(
18521856
// The profile could be JSON or the output from `perf script`. Try `perf script` first.
18531857
if (isPerfScriptFormat(arbitraryFormat)) {
18541858
arbitraryFormat = convertPerfScriptProfile(arbitraryFormat);
1859+
} else if (isFlameGraphFormat(arbitraryFormat)) {
1860+
arbitraryFormat = convertFlameGraphProfile(arbitraryFormat);
18551861
} else {
18561862
// Try parsing as JSON.
18571863
arbitraryFormat = JSON.parse(arbitraryFormat);

0 commit comments

Comments
 (0)