Skip to content

Commit 48dad0c

Browse files
ivonastojanovicgodlygeek
authored andcommitted
Support inverted temporal inverted flame graphs
Allow `memray flamegraph` to support `--temporal` and `--inverted` at the same time. Signed-off-by: Ivona Stojanovic <[email protected]>
1 parent bb9c0db commit 48dad0c

File tree

6 files changed

+151
-49
lines changed

6 files changed

+151
-49
lines changed

src/memray/commands/common.py

-3
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,6 @@ def run(self, args: argparse.Namespace, parser: argparse.ArgumentParser) -> None
271271

272272
if hasattr(args, "inverted"):
273273
kwargs["inverted"] = args.inverted
274-
if temporal and args.inverted:
275-
parser.error("Can't create an inverted temporal flame graph.")
276-
277274
self.write_report(
278275
result_path,
279276
output_file,

src/memray/reporters/assets/flamegraph_common.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,17 @@ export function onFilterImportSystem() {
146146
});
147147
} else {
148148
data = invertedNoImportsData;
149+
if (temporal) {
150+
hideImports = true;
151+
intervals = invertedNoImportsIntervals;
152+
}
149153
}
150154
} else {
151155
filteredChart.unRegisterFilter(FILTER_IMPORT_SYSTEM);
152-
if (inverted) {
153-
//We can remove this check when we add ``flamegraphData`` to the temporal flamegraph
154-
data = flamegraphData;
156+
data = flamegraphData;
157+
if (temporal) {
158+
hideImports = false;
159+
intervals = flamegraphIntervals;
155160
}
156161
}
157162
this.hideImportSystemFrames = !this.hideImportSystemFrames;

src/memray/reporters/assets/temporal_flamegraph.js

+134-42
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,22 @@ import {
1717
var active_plot = null;
1818
var current_dimensions = null;
1919

20-
var parent_index_by_child_index = (function () {
21-
let ret = new Array(packed_data.nodes.children.length);
20+
var parent_index_by_child_index = generateParentIndexes(packed_data.nodes);
21+
var inverted_no_imports_parent_index_by_child_index = inverted
22+
? generateParentIndexes(packed_data.inverted_no_imports_nodes)
23+
: null;
24+
25+
function generateParentIndexes(nodes) {
26+
let ret = new Array(nodes.children.length);
2227
console.log("finding parent index for each node");
23-
for (const [parentIndex, children] of packed_data.nodes.children.entries()) {
28+
for (const [parentIndex, children] of nodes.children.entries()) {
2429
children.forEach((idx) => (ret[idx] = parentIndex));
2530
}
2631
console.assert(ret[0] === undefined, "root node has a parent");
2732
return ret;
28-
})();
29-
30-
function packedDataToTree(packedData, rangeStart, rangeEnd) {
31-
const { strings, nodes, unique_threads } = packedData;
33+
}
3234

35+
function generateNodeObjects(strings, nodes) {
3336
console.log("constructing nodes");
3437
const node_objects = nodes.name.map((_, i) => ({
3538
name: strings[nodes["name"][i]],
@@ -51,6 +54,83 @@ function packedDataToTree(packedData, rangeStart, rangeEnd) {
5154
node["children"] = node["children"].map((idx) => node_objects[idx]);
5255
}
5356

57+
return node_objects;
58+
}
59+
60+
function initTrees(packedData) {
61+
const {
62+
strings,
63+
nodes,
64+
inverted_no_imports_nodes,
65+
unique_threads,
66+
intervals,
67+
no_imports_interval_list,
68+
} = packedData;
69+
70+
const flamegraphNodeObjects = generateNodeObjects(strings, nodes);
71+
const invertedNoImportsNodeObjects = inverted
72+
? generateNodeObjects(strings, inverted_no_imports_nodes)
73+
: null;
74+
75+
flamegraphIntervals = intervals;
76+
invertedNoImportsIntervals = no_imports_interval_list;
77+
78+
return {
79+
flamegraphNodeObjects: flamegraphNodeObjects,
80+
invertedNoImportsNodeObjects: invertedNoImportsNodeObjects,
81+
};
82+
}
83+
84+
function findHWMAllocations(
85+
intervals,
86+
node_objects,
87+
hwmSnapshot,
88+
parent_index_by_child_index
89+
) {
90+
intervals.forEach((interval) => {
91+
let [allocBefore, deallocBefore, nodeIndex, count, bytes] = interval;
92+
93+
if (
94+
allocBefore <= hwmSnapshot &&
95+
(deallocBefore === null || deallocBefore > hwmSnapshot)
96+
) {
97+
while (nodeIndex !== undefined) {
98+
node_objects[nodeIndex].n_allocations += count;
99+
node_objects[nodeIndex].value += bytes;
100+
nodeIndex = parent_index_by_child_index[nodeIndex];
101+
}
102+
}
103+
});
104+
}
105+
106+
function findLeakedAllocations(
107+
intervals,
108+
node_objects,
109+
rangeStart,
110+
rangeEnd,
111+
parent_index_by_child_index
112+
) {
113+
intervals.forEach((interval) => {
114+
let [allocBefore, deallocBefore, nodeIndex, count, bytes] = interval;
115+
116+
if (
117+
allocBefore >= rangeStart &&
118+
allocBefore <= rangeEnd &&
119+
(deallocBefore === null || deallocBefore > rangeEnd)
120+
) {
121+
while (nodeIndex !== undefined) {
122+
node_objects[nodeIndex].n_allocations += count;
123+
node_objects[nodeIndex].value += bytes;
124+
nodeIndex = parent_index_by_child_index[nodeIndex];
125+
}
126+
}
127+
});
128+
}
129+
130+
function packedDataToTree(packedData, rangeStart, rangeEnd) {
131+
const { flamegraphNodeObjects, invertedNoImportsNodeObjects } =
132+
initTrees(packedData);
133+
54134
const hwms = packedData.high_water_mark_by_snapshot;
55135
if (hwms) {
56136
console.log("finding highest high water mark in range");
@@ -113,48 +193,53 @@ function packedDataToTree(packedData, rangeStart, rangeEnd) {
113193

114194
// We could binary search rather than using a linear scan...
115195
console.log("finding hwm allocations");
116-
packedData.intervals.forEach((interval) => {
117-
let [allocBefore, deallocBefore, nodeIndex, count, bytes] = interval;
118-
119-
if (
120-
allocBefore <= hwmSnapshot &&
121-
(deallocBefore === null || deallocBefore > hwmSnapshot)
122-
) {
123-
while (nodeIndex !== undefined) {
124-
node_objects[nodeIndex].n_allocations += count;
125-
node_objects[nodeIndex].value += bytes;
126-
nodeIndex = parent_index_by_child_index[nodeIndex];
127-
}
128-
}
129-
});
196+
findHWMAllocations(
197+
flamegraphIntervals,
198+
flamegraphNodeObjects,
199+
hwmSnapshot,
200+
parent_index_by_child_index
201+
);
202+
if (inverted) {
203+
findHWMAllocations(
204+
invertedNoImportsIntervals,
205+
invertedNoImportsNodeObjects,
206+
hwmSnapshot,
207+
inverted_no_imports_parent_index_by_child_index
208+
);
209+
}
130210
} else {
131211
// We could binary search rather than using a linear scan...
132212
console.log("finding leaked allocations");
133-
packedData.intervals.forEach((interval) => {
134-
let [allocBefore, deallocBefore, nodeIndex, count, bytes] = interval;
135-
136-
if (
137-
allocBefore >= rangeStart &&
138-
allocBefore <= rangeEnd &&
139-
(deallocBefore === null || deallocBefore > rangeEnd)
140-
) {
141-
while (nodeIndex !== undefined) {
142-
node_objects[nodeIndex].n_allocations += count;
143-
node_objects[nodeIndex].value += bytes;
144-
nodeIndex = parent_index_by_child_index[nodeIndex];
145-
}
146-
}
147-
});
213+
findLeakedAllocations(
214+
flamegraphIntervals,
215+
flamegraphNodeObjects,
216+
rangeStart,
217+
rangeEnd,
218+
parent_index_by_child_index
219+
);
220+
if (inverted) {
221+
findLeakedAllocations(
222+
invertedNoImportsIntervals,
223+
invertedNoImportsNodeObjects,
224+
rangeStart,
225+
rangeEnd,
226+
inverted_no_imports_parent_index_by_child_index
227+
);
228+
}
148229
}
149230

150-
console.log("total allocations in range: " + node_objects[0].n_allocations);
151-
console.log("total bytes in range: " + node_objects[0].value);
152-
153-
node_objects.forEach((node) => {
231+
flamegraphNodeObjects.forEach((node) => {
154232
node.children = node.children.filter((node) => node.n_allocations > 0);
155233
});
156234

157-
return node_objects[0];
235+
if (inverted) {
236+
invertedNoImportsNodeObjects.forEach((node) => {
237+
node.children = node.children.filter((node) => node.n_allocations > 0);
238+
});
239+
}
240+
241+
flamegraphData = flamegraphNodeObjects[0];
242+
invertedNoImportsData = inverted ? invertedNoImportsNodeObjects[0] : null;
158243
}
159244

160245
function initMemoryGraph(memory_records) {
@@ -259,7 +344,14 @@ function refreshFlamegraph(event) {
259344
console.log("last possible index is " + memory_records.length);
260345

261346
console.log("constructing tree");
262-
data = packedDataToTree(packed_data, idx0, idx1);
347+
packedDataToTree(packed_data, idx0, idx1);
348+
349+
data = inverted && hideImports ? invertedNoImportsData : flamegraphData;
350+
intervals =
351+
inverted && hideImports ? invertedNoImportsIntervals : flamegraphIntervals;
352+
353+
console.log("total allocations in range: " + data.n_allocations);
354+
console.log("total bytes in range: " + data.value);
263355

264356
console.log("drawing chart");
265357
getFilteredChart().drawChart(data);

src/memray/reporters/flamegraph.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ def _from_any_snapshot(
255255
inverted_no_imports_frames = [cls._create_root_node()]
256256

257257
interval_list: List[Tuple[int, Optional[int], int, int, int]] = []
258+
no_imports_interval_list: List[Tuple[int, Optional[int], int, int, int]] = []
258259

259260
NodeKey = Tuple[int, StackFrame, str]
260261
node_index_by_key: Dict[NodeKey, int] = {}
@@ -316,7 +317,7 @@ def _from_any_snapshot(
316317
node_index_by_key=inverted_no_imports_node_index_by_key,
317318
record=record_data,
318319
inverted=inverted,
319-
interval_list=interval_list,
320+
interval_list=no_imports_interval_list,
320321
)
321322

322323
all_strings = StringRegistry()
@@ -340,6 +341,8 @@ def _from_any_snapshot(
340341

341342
if interval_list:
342343
data["intervals"] = interval_list
344+
if no_imports_interval_list:
345+
data["no_imports_interval_list"] = no_imports_interval_list
343346

344347
return cls(data, memory_records=memory_records)
345348

src/memray/reporters/templates/base.html

+1
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ <h5 class="modal-title" id="helpModalLabel">How to interpret {{ kind }} reports<
144144
const merge_threads = {{ merge_threads|tojson }};
145145
const memory_records = {{ memory_records|tojson }};
146146
const inverted = {{ inverted|tojson }};
147+
const temporal = packed_data.intervals != null;
147148
</script>
148149
{% endblock scripts %}
149150
</body>

src/memray/reporters/templates/temporal_flamegraph.html

+4
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,9 @@
3939
{% block flamegraph_script %}
4040
<script type="text/javascript">
4141
{{ include_file("assets/temporal_flamegraph.js") }}
42+
var hideImports = false;
43+
var intervals = null;
44+
var flamegraphIntervals = null;
45+
var invertedNoImportsIntervals = null;
4246
</script>
4347
{% endblock %}

0 commit comments

Comments
 (0)