3
import * as d3 from "https://cdn.skypack.dev/d3@5";
4
import {axisLeft} from "https://cdn.skypack.dev/d3-axis@1";
5
import {scaleLinear} from "https://cdn.skypack.dev/d3-scale@1";
6
import {zoom, zoomIdentity} from "https://cdn.skypack.dev/d3-zoom@1";
7
import {brushX} from "https://cdn.skypack.dev/d3-brush@1";
9
const schemeTableau10 = [
22
function version_space() {
24
return (addr, increment) => {
25
if (!(addr in version)) {
28
const r = version[addr];
36
function Segment(addr, size, stream, frames, version) {
37
return {addr, size, stream, version, frames};
40
function Block(addr, size, requested_size, frames, free_requested, version) {
41
return {addr, size, requested_size, frames, free_requested, version};
44
function EventSelector(outer, events, stack_info, memory_view) {
45
const events_div = outer
49
'grid-column: 1; grid-row: 1; overflow: auto; font-family: monospace',
52
const events_selection = events_div
57
.text(e => formatEvent(e))
60
let selected_event_idx = null;
64
if (selected_event_idx !== null) {
65
const selected_event = d3.select(
66
events_div.node().children[selected_event_idx],
68
selected_event.attr('style', '');
71
const div = d3.select(events_div.node().children[idx]);
72
div.attr('style', `background-color: ${schemeTableau10[5]}`);
73
const [reserved, allocated] = memory_view.draw(idx);
74
const enter = () => eventStack(div.datum(), allocated, reserved);
75
stack_info.highlight(enter);
76
div.node().scrollIntoViewIfNeeded(false);
80
selected_event_idx = idx;
83
d3.select('body').on('keydown', _e => {
84
const key = d3.event.key;
85
const actions = {ArrowDown: 1, ArrowUp: -1};
86
if (selected_event_idx !== null && key in actions) {
87
const new_idx = selected_event_idx + actions[key];
88
es.select(Math.max(0, Math.min(new_idx, events.length - 1)));
89
d3.event.preventDefault();
95
t => eventStack(t.datum()),
97
d => es.select(d.datum().idx),
103
function formatSize(num) {
106
const units = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi'];
107
for (const unit of units) {
108
if (Math.abs(num) < 1024.0) {
109
return `${num.toFixed(1)}${unit}B (${orig} bytes)`;
113
return `${num.toFixed(1)}YiB`;
115
function formatAddr(event) {
116
const prefix = event.action.startsWith('segment') ? 's' : 'b';
117
return `${prefix}${event.addr.toString(16)}_${event.version}`;
119
function formatEvent(event) {
121
event.stream === null ? '' : `\n (stream ${event.stream})`;
122
switch (event.action) {
124
return `OOM (requested ${formatSize(event.size)}, CUDA has ${formatSize(
126
)} memory free)${stream}`;
130
return `${event.action.padEnd(14)} ${formatAddr(event).padEnd(
132
)} ${formatSize(event.size)}${stream}`;
136
function eventStack(e, allocated, reserved) {
137
let event = formatEvent(e);
138
if (reserved !== undefined) {
139
event = `(${formatSize(allocated)} allocated / ${formatSize(
141
)} reserved)\n${event}`;
143
return event + '\n' + format_frames(e.frames);
146
function hashCode(num) {
147
const numStr = num.toString();
149
for (let i = 0; i < numStr.length; i++) {
150
const charCode = numStr.charCodeAt(i);
151
hash = (hash << 5) - hash + charCode;
157
function addStroke(d) {
158
d.attr('stroke', 'red')
159
.attr('stroke-width', '2')
160
.attr('vector-effect', 'non-scaling-stroke');
163
function removeStroke(d) {
164
d.attr('stroke', '');
167
function calculate_fragmentation(blocks, sorted_segments) {
168
const sorted_blocks = Object.values(blocks).sort((a, b) => a.addr - b.addr);
171
let sum_squared_free = 0;
172
for (const seg of sorted_segments) {
174
total_size += seg.size;
176
block_i < sorted_blocks.length &&
177
sorted_blocks[block_i].addr < seg.addr + seg.size
179
const block = sorted_blocks[block_i];
180
if (block.addr > addr) {
181
sum_squared_free += (block.addr - addr) ** 2;
183
addr = block.addr + block.size;
186
if (addr < seg.addr + seg.size) {
187
sum_squared_free += (seg.addr + seg.size - addr) ** 2;
190
console.log(sum_squared_free / total_size ** 2);
193
function MemoryView(outer, stack_info, snapshot, device) {
196
.attr('style', 'grid-column: 2; grid-row: 1; width: 100%; height: 100%;')
197
.attr('viewBox', '0 0 200 100')
198
.attr('preserveAspectRatio', 'xMinYMin meet');
199
const g = svg.append('g');
200
const seg_zoom = zoom();
201
seg_zoom.on('zoom', () => {
202
g.attr('transform', d3.event.transform);
206
const sorted_segments = [];
207
const block_map = {};
208
for (const seg of snapshot.segments) {
209
if (seg.device !== device) {
212
sorted_segments.push(
221
for (const b of seg.blocks) {
222
if (b.state !== 'active_pending_free' && b.state !== 'active_allocated') {
225
block_map[b.addr] = Block(
230
b.state === 'active_pending_free',
235
sorted_segments.sort((x, y) => x.addr - y.addr);
237
function simulate_memory(idx) {
239
const l_segments = sorted_segments.map(x => {
242
const l_block_map = {...block_map};
244
function map_segment(merge, seg) {
245
let idx = l_segments.findIndex(e => e.addr > seg.addr);
247
l_segments.splice(idx, 0, seg);
251
idx = l_segments.length;
253
l_segments.splice(idx, 0, seg);
254
if (idx + 1 < l_segments.length) {
255
const next = l_segments[idx + 1];
256
if (seg.addr + seg.size === next.addr && seg.stream === next.stream) {
257
seg.size += next.size;
258
l_segments.splice(idx + 1, 1);
262
const prev = l_segments[idx - 1];
263
if (prev.addr + prev.size === seg.addr && prev.stream === seg.stream) {
264
prev.size += seg.size;
265
l_segments.splice(idx, 1);
269
function unmap_segment(merge, seg) {
272
l_segments.findIndex(x => x.addr === seg.addr),
277
const seg_end = seg.addr + seg.size;
278
const idx = l_segments.findIndex(
279
e => e.addr <= seg.addr && seg_end <= e.addr + e.size,
281
const existing = l_segments[idx];
282
const existing_end = existing.addr + existing.size;
283
if (existing.addr === seg.addr) {
284
existing.addr += seg.size;
285
existing.size -= seg.size;
286
if (existing.size === 0) {
287
l_segments.splice(idx, 1);
289
} else if (existing_end === seg_end) {
290
existing.size -= seg.size;
292
existing.size = seg.addr - existing.addr;
294
seg.size = existing_end - seg_end;
295
l_segments.splice(idx + 1, 0, seg);
298
const events = snapshot.device_traces[device];
299
for (let i = events.length - 1; i > idx; i--) {
300
const event = events[i];
301
switch (event.action) {
303
l_block_map[event.addr] = Block(
312
case 'free_requested':
313
l_block_map[event.addr].free_requested = false;
315
case 'free_completed':
316
l_block_map[event.addr] = Block(
326
delete l_block_map[event.addr];
329
case 'segment_unmap':
331
event.action === 'segment_unmap',
341
case 'segment_alloc':
344
event.action === 'segment_map',
360
const new_blocks = Object.values(l_block_map);
361
return [l_segments, new_blocks];
366
const [segments_unsorted, blocks] = simulate_memory(idx);
367
g.selectAll('g').remove();
369
const segment_d = g.append('g');
370
const block_g = g.append('g');
371
const block_r = g.append('g');
373
segment_d.selectAll('rect').remove();
374
block_g.selectAll('rect').remove();
375
block_r.selectAll('rect').remove();
376
const segments = [...segments_unsorted].sort((x, y) =>
377
x.size === y.size ? x.addr - y.addr : x.size - y.size,
380
const segments_by_addr = [...segments].sort((x, y) => x.addr - y.addr);
382
const max_size = segments.length === 0 ? 0 : segments.at(-1).size;
384
const xScale = scaleLinear().domain([0, max_size]).range([0, 200]);
385
const padding = xScale.invert(1);
388
let cur_row_size = 0;
389
for (const seg of segments) {
391
seg.internal_free = 0;
392
if (cur_row_size + seg.size > max_size) {
396
seg.offset = cur_row_size;
398
cur_row_size += seg.size + padding;
401
const num_rows = cur_row + 1;
403
const yScale = scaleLinear().domain([0, num_rows]).range([0, 100]);
405
const segments_selection = segment_d
410
.attr('x', x => xScale(x.offset))
411
.attr('y', x => yScale(x.row))
412
.attr('width', x => xScale(x.size))
413
.attr('height', yScale(4 / 5))
414
.attr('stroke', 'black')
415
.attr('stroke-width', '1')
416
.attr('vector-effect', 'non-scaling-stroke')
417
.attr('fill', 'white');
424
const free = t.size - t.occupied;
426
if (t.internal_free > 0) {
427
internal = ` (${(t.internal_free / free) * 100}% internal)`;
430
`s${t.addr.toString(16)}_${t.version}: segment ${formatSize(
433
`${formatSize(free)} free${internal} (stream ${
435
})\n${format_frames(t.frames)}`
439
d.attr('stroke', 'black')
440
.attr('stroke-width', '1')
441
.attr('vector-effect', 'non-scaling-stroke');
445
function find_segment(addr) {
447
let right = segments_by_addr.length - 1;
448
while (left <= right) {
449
const mid = Math.floor((left + right) / 2);
450
if (addr < segments_by_addr[mid].addr) {
454
segments_by_addr[mid].addr + segments_by_addr[mid].size
458
return segments_by_addr[mid];
464
for (const b of blocks) {
465
b.segment = find_segment(b.addr);
466
b.segment.occupied += b.requested_size;
467
b.segment.internal_free += b.size - b.requested_size;
470
const block_selection = block_g
475
.attr('x', x => xScale(x.segment.offset + (x.addr - x.segment.addr)))
476
.attr('y', x => yScale(x.segment.row))
477
.attr('width', x => xScale(x.requested_size))
478
.attr('height', yScale(4 / 5))
479
.attr('fill', (x, _i) =>
483
Math.abs(hashCode(x.addr)) % schemeTableau10.length
493
if (t.free_requested) {
494
requested = ' (block freed but waiting due to record_stream)';
497
`b${t.addr.toString(16)}_${t.version} ` +
498
`${formatSize(t.requested_size)} allocation${requested} (stream ${
501
format_frames(t.frames)
507
const free_selection = block_r
514
x.segment.offset + (x.addr - x.segment.addr) + x.requested_size,
517
.attr('y', x => yScale(x.segment.row))
518
.attr('width', x => xScale(x.size - x.requested_size))
519
.attr('height', yScale(4 / 5))
520
.attr('fill', (_x, _i) => 'red');
528
`Free space lost due to rounding ${formatSize(
529
t.size - t.requested_size,
531
` (stream ${t.segment.stream})\n` +
532
format_frames(t.frames)
538
const reserved = segments.reduce((x, y) => x + y.size, 0);
539
const allocated = blocks.reduce((x, y) => x + y.requested_size, 0);
540
return [reserved, allocated];
545
function StackInfo(outer) {
546
const stack_trace = outer
548
.attr('style', 'grid-column: 1 / 3; grid-row: 2; overflow: auto');
551
stack_trace.text('');
556
register(dom, enter, leave = _e => {}, select = _e => {}) {
558
.on('mouseover', _e => {
560
stack_trace.text(enter(d3.select(d3.event.target)));
562
.on('mousedown', _e => {
563
const obj = d3.select(d3.event.target);
565
enter: () => stack_trace.text(enter(obj)),
566
leave: () => leave(obj),
570
.on('mouseleave', _e => {
571
leave(d3.select(d3.event.target));
575
highlight(enter, leave = () => {}) {
576
selected = {enter: () => stack_trace.text(enter()), leave};
582
function create_segment_view(dst, snapshot, device) {
587
'display: grid; grid-template-columns: 1fr 2fr; grid-template-rows: 2fr 1fr; height: 100%; gap: 10px',
590
const events = snapshot.device_traces[device];
591
const stack_info = StackInfo(outer);
592
const memory_view = MemoryView(outer, stack_info, snapshot, device);
593
const event_selector = EventSelector(outer, events, stack_info, memory_view);
595
window.requestAnimationFrame(function () {
596
event_selector.select(events.length > 0 ? events.length - 1 : null);
600
function annotate_snapshot(snapshot) {
601
snapshot.segment_version = version_space();
602
snapshot.block_version = version_space();
603
snapshot.categories = [];
604
const empty_list = [];
606
const stream_names = {0: 0};
607
function stream_name(s) {
608
if (!(s in stream_names)) {
609
stream_names[s] = next_stream++;
611
return stream_names[s];
613
const new_traces = [];
614
for (const device_trace of snapshot.device_traces) {
615
const new_trace = [];
616
new_traces.push(new_trace);
617
for (const t of device_trace) {
618
if (!('frames' in t)) {
619
t.frames = empty_list;
624
t.stream = stream_name(t.stream);
626
case 'free_completed':
627
t.version = snapshot.block_version(t.addr, true);
628
if (new_trace.length > 0) {
630
const prev = new_trace.at(-1);
631
if (prev.action === 'free_requested' && prev.addr === t.addr) {
632
prev.action = 'free';
637
case 'free_requested':
639
t.version = snapshot.block_version(t.addr, false);
642
case 'segment_unmap':
643
t.version = snapshot.segment_version(t.addr, true);
645
case 'segment_alloc':
647
t.version = snapshot.segment_version(t.addr, false);
652
if ('category' in t && !snapshot.categories.includes(t.category)) {
653
snapshot.categories.push(t.category);
655
t.idx = new_trace.length;
659
snapshot.device_traces = new_traces;
661
if (next_stream == 1) {
662
for (const device_trace of snapshot.device_traces) {
663
for (const t of device_trace) {
669
for (const seg of snapshot.segments) {
670
seg.stream = stream_name(seg.stream);
671
seg.version = snapshot.segment_version(seg.address, false);
672
let addr = seg.address;
673
for (const b of seg.blocks) {
675
if (!('frames' in b)) {
678
if ('history' in b) {
679
b.frames = b.history[0].frames || empty_list;
680
b.requested_size = b.requested_size || b.history[0].real_size;
682
b.frames = empty_list;
683
b.requested_size = b.requested_size || b.size;
686
b.version = snapshot.block_version(b.addr, false);
692
snapshot.categories.length > 0 &&
693
!snapshot.categories.includes('unknown')
695
snapshot.categores.push('unknown');
699
function elideRepeats(frames) {
701
const length = frames.length;
702
for (let i = 0; i < length; ) {
705
while (j < length && f === frames[j]) {
716
result.push(f, `<repeats ${j - i - 1} times>`);
723
function frameFilter({name, filename}) {
724
const omitFunctions = [
726
'CapturedTraceback::gather',
735
const omitFilenames = [
742
'Objects/methodobject.c',
745
'cpython/abstract.h',
748
for (const of of omitFunctions) {
749
if (name.includes(of)) {
754
for (const of of omitFilenames) {
755
if (filename.includes(of)) {
763
function format_frames(frames) {
764
if (frames.length === 0) {
765
return `<block was allocated before _record_history was enabled>`;
767
const frame_strings = frames
769
.map(f => `${f.filename}:${f.line}:${f.name}`);
770
return elideRepeats(frame_strings).join('\n');
773
function process_alloc_data(snapshot, device, plot_segments, max_entries) {
775
const initially_allocated = [];
777
const addr_to_alloc = {};
779
const alloc = plot_segments ? 'segment_alloc' : 'alloc';
780
const [free, free_completed] = plot_segments
781
? ['segment_free', 'segment_free']
782
: ['free', 'free_completed'];
783
for (const e of snapshot.device_traces[device]) {
787
addr_to_alloc[e.addr] = elements.length - 1;
788
actions.push(elements.length - 1);
792
if (e.addr in addr_to_alloc) {
793
actions.push(addr_to_alloc[e.addr]);
794
delete addr_to_alloc[e.addr];
797
initially_allocated.push(elements.length - 1);
798
actions.push(elements.length - 1);
805
for (const seg of snapshot.segments) {
806
if (seg.device !== device) {
810
if (!(seg.address in addr_to_alloc)) {
814
size: seg.total_size,
817
version: seg.version,
819
elements.push(element);
820
initially_allocated.push(elements.length - 1);
823
for (const b of seg.blocks) {
824
if (b.state === 'active_allocated' && !(b.addr in addr_to_alloc)) {
828
size: b.requested_size,
833
elements.push(element);
834
initially_allocated.push(elements.length - 1);
839
initially_allocated.reverse();
843
if (actions.length === 0 && initially_allocated.length > 0) {
844
actions.push(initially_allocated.pop());
848
const current_data = [];
853
let total_summarized_mem = 0;
856
const max_at_time = [];
858
const summarized_mem = {
861
offsets: [total_mem],
865
const summarized_elems = {};
867
function advance(n) {
868
summarized_mem.timesteps.push(timestep);
869
summarized_mem.offsets.push(total_mem);
870
summarized_mem.size.push(total_summarized_mem);
872
for (let i = 0; i < n; i++) {
873
max_at_time.push(total_mem + total_summarized_mem);
877
const sizes = elements
878
.map((x, i) => [x.size, i])
879
.sort(([x, _xi], [y, _yi]) => y - x);
881
const draw_elem = {};
882
for (const [_s, e] of sizes.slice(0, max_entries)) {
886
function add_allocation(elem) {
887
const element_obj = elements[elem];
888
const size = element_obj.size;
891
if (snapshot.categories.length > 0) {
892
color = snapshot.categories.indexOf(element_obj.category || 'unknown');
896
timesteps: [timestep],
897
offsets: [total_mem],
901
current_data.push(e);
904
element_obj.max_allocated_mem = total_mem + total_summarized_mem;
907
for (const elem of initially_allocated) {
908
if (elem in draw_elem) {
909
add_allocation(elem);
911
total_summarized_mem += elements[elem].size;
912
summarized_elems[elem] = true;
916
for (const elem of actions) {
917
const size = elements[elem].size;
918
if (!(elem in draw_elem)) {
919
if (elem in summarized_elems) {
921
total_summarized_mem -= size;
922
summarized_elems[elem] = null;
924
total_summarized_mem += size;
925
summarized_elems[elem] = true;
930
const idx = current.findLastIndex(x => x === elem);
934
add_allocation(elem);
938
const removed = current_data[idx];
939
removed.timesteps.push(timestep);
940
removed.offsets.push(removed.offsets.at(-1));
941
current.splice(idx, 1);
942
current_data.splice(idx, 1);
944
if (idx < current.length) {
945
for (let j = idx; j < current.length; j++) {
946
const e = current_data[j];
947
e.timesteps.push(timestep);
948
e.offsets.push(e.offsets.at(-1));
949
e.timesteps.push(timestep + 3);
950
e.offsets.push(e.offsets.at(-1) - size);
956
max_size = Math.max(total_mem + total_summarized_mem, max_size);
959
for (const elem of current_data) {
960
elem.timesteps.push(timestep);
961
elem.offsets.push(elem.offsets.at(-1));
963
data.push(summarized_mem);
967
allocations_over_time: data,
970
elements_length: elements.length,
971
context_for_id: id => {
972
const elem = elements[id];
973
let text = `Addr: ${formatAddr(elem)}`;
974
text = `${text}, Size: ${formatSize(elem.size)} allocation`;
975
text = `${text}, Total memory used after allocation: ${formatSize(
976
elem.max_allocated_mem,
978
if (elem.stream !== null) {
979
text = `${text}, stream ${elem.stream}`;
981
if (!elem.action.includes('alloc')) {
982
text = `${text}\nalloc not recorded, stack trace for free:`;
984
text = `${text}\n${format_frames(elem.frames)}`;
996
colors = schemeTableau10,
998
function format_points(d) {
1000
const xs = d.timesteps.map(t => xscale(t));
1001
const bottom = d.offsets.map(t => yscale(t));
1002
const m = Array.isArray(size)
1003
? (t, i) => yscale(t + size[i])
1004
: t => yscale(t + size);
1005
const top = d.offsets.map(m);
1006
const p0 = xs.map((x, i) => `${x},${bottom[i]}`);
1007
const p1 = xs.map((x, i) => `${x},${top[i]}`).reverse();
1008
return `${p0.join(' ')} ${p1.join(' ')}`;
1011
const max_timestep = data.max_at_time.length;
1012
const max_size = data.max_size;
1014
const plot_width = width - left_pad;
1015
const plot_height = height;
1017
const yscale = scaleLinear().domain([0, max_size]).range([plot_height, 0]);
1018
const yaxis = axisLeft(yscale).tickFormat(d3.format('.3s'));
1019
const xscale = scaleLinear().domain([0, max_timestep]).range([0, plot_width]);
1020
const plot_coordinate_space = svg
1022
.attr('transform', `translate(${left_pad}, ${0})`);
1023
const plot_outer = plot_coordinate_space.append('g');
1025
function view_rect(a) {
1030
.attr('width', plot_width)
1031
.attr('height', plot_height)
1032
.attr('fill', 'white');
1035
view_rect(plot_outer);
1037
const cp = svg.append('clipPath').attr('id', 'clip');
1039
plot_outer.attr('clip-path', 'url(#clip)');
1041
const zoom_group = plot_outer.append('g');
1042
const scrub_group = zoom_group.append('g');
1044
const plot = scrub_group
1045
.selectAll('polygon')
1046
.data(data.allocations_over_time)
1049
.attr('points', format_points)
1050
.attr('fill', d => colors[d.color % colors.length]);
1052
const axis = plot_coordinate_space.append('g').call(yaxis);
1054
function handleZoom() {
1055
const t = d3.event.transform;
1056
zoom_group.attr('transform', t);
1057
axis.call(yaxis.scale(d3.event.transform.rescaleY(yscale)));
1060
const thezoom = zoom().on('zoom', handleZoom);
1061
plot_outer.call(thezoom);
1064
select_window: (stepbegin, stepend, max) => {
1065
const begin = xscale(stepbegin);
1066
const size = xscale(stepend) - xscale(stepbegin);
1067
const scale = plot_width / size;
1068
const translate = -begin;
1069
const yscale = max_size / max;
1072
`scale(${scale / yscale}, 1) translate(${translate}, 0)`,
1078
.translate(0, -(plot_height - plot_height / yscale)),
1081
set_delegate: delegate => {
1083
.on('mouseover', function (_e, _d) {
1084
delegate.set_selected(d3.select(this));
1086
.on('mousedown', function (_e, _d) {
1087
delegate.default_selected = d3.select(this);
1089
.on('mouseleave', function (_e, _d) {
1090
delegate.set_selected(delegate.default_selected);
1096
function ContextViewer(text, data) {
1097
let current_selected = null;
1100
default_selected: null,
1101
set_selected: d => {
1102
if (current_selected !== null) {
1103
current_selected.attr('stroke', null).attr('stroke-width', null);
1108
const dd = d.datum();
1109
if (dd.elem === 'summarized') {
1111
'Small tensors that were not plotted to cutdown on render time.\n' +
1112
'Use detail slider to see smaller allocations.',
1115
text.text(`${dd.elem} ${data.context_for_id(dd.elem)}`);
1117
d.attr('stroke', 'black')
1118
.attr('stroke-width', 1)
1119
.attr('vector-effect', 'non-scaling-stroke');
1121
current_selected = d;
1126
function MiniMap(mini_svg, plot, data, left_pad, width, height = 70) {
1127
const max_at_time = data.max_at_time;
1128
const plot_width = width - left_pad;
1129
const yscale = scaleLinear().domain([0, data.max_size]).range([height, 0]);
1130
const minixscale = scaleLinear()
1131
.domain([0, max_at_time.length])
1132
.range([left_pad, width]);
1134
const mini_points = [
1135
[max_at_time.length, 0],
1139
for (const [i, m] of max_at_time.entries()) {
1140
const [_lastx, lasty] = mini_points[mini_points.length - 1];
1142
mini_points.push([i, lasty]);
1143
mini_points.push([i, m]);
1144
} else if (i === max_at_time.length - 1) {
1145
mini_points.push([i, m]);
1149
let points = mini_points.map(([t, o]) => `${minixscale(t)}, ${yscale(o)}`);
1150
points = points.join(' ');
1153
.attr('points', points)
1154
.attr('fill', schemeTableau10[0]);
1156
const xscale = scaleLinear()
1157
.domain([0, max_at_time.length])
1158
.range([0, plot_width]);
1160
const brush = brushX();
1165
brush.on('brush', function () {
1166
const [begin, end] = d3.event.selection.map(x => x - left_pad);
1168
const stepbegin = Math.floor(xscale.invert(begin));
1169
const stepend = Math.floor(xscale.invert(end));
1171
for (let i = stepbegin; i < stepend; i++) {
1172
max = Math.max(max, max_at_time[i]);
1174
plot.select_window(stepbegin, stepend, max);
1176
mini_svg.call(brush);
1180
function Legend(plot_svg, categories) {
1189
.attr('x', (c, i) => xstart)
1190
.attr('y', (c, i) => ystart + i * 15)
1193
.attr('fill', (c, i) => schemeTableau10[i % schemeTableau10.length]);
1200
.attr('x', (c, i) => xstart + 20)
1201
.attr('y', (c, i) => ystart + i * 15 + 8)
1202
.attr('font-family', 'helvetica')
1203
.attr('font-size', 10)
1208
function create_trace_view(
1212
plot_segments = false,
1213
max_entries = 15000,
1215
const left_pad = 70;
1216
const data = process_alloc_data(snapshot, device, plot_segments, max_entries);
1217
dst.selectAll('svg').remove();
1218
dst.selectAll('div').remove();
1220
const d = dst.append('div');
1222
.attr('type', 'range')
1224
.attr('max', data.elements_length)
1225
.attr('value', max_entries)
1226
.on('change', function () {
1227
create_trace_view(dst, snapshot, device, plot_segments, this.value);
1229
d.append('label').text('Detail');
1231
const grid_container = dst
1235
'display: grid; grid-template-columns: 1fr; grid-template-rows: 10fr 1fr 8fr; height: 100%; gap: 10px',
1238
const plot_svg = grid_container
1240
.attr('display', 'block')
1241
.attr('viewBox', '0 0 1024 576')
1242
.attr('preserveAspectRatio', 'none')
1243
.attr('style', 'grid-column: 1; grid-row: 1; width: 100%; height: 100%;');
1245
const plot = MemoryPlot(plot_svg, data, left_pad, 1024, 576);
1247
if (snapshot.categories.length !== 0) {
1248
Legend(plot_svg.append('g'), snapshot.categories);
1251
const mini_svg = grid_container
1253
.attr('display', 'block')
1254
.attr('viewBox', '0 0 1024 60')
1255
.attr('preserveAspectRatio', 'none')
1256
.attr('style', 'grid-column: 1; grid-row: 2; width: 100%; height: 100%;');
1258
MiniMap(mini_svg, plot, data, left_pad, 1024);
1259
const context_div = grid_container
1263
'grid-column: 1; grid-row: 3; width: 100%; height: 100%; overflow: auto;',
1265
const delegate = ContextViewer(context_div.append('pre').text('none'), data);
1266
plot.set_delegate(delegate);
1269
function unpickle(buffer) {
1270
const bytebuffer = new Uint8Array(buffer);
1271
const decoder = new TextDecoder();
1279
const APPENDS = 'e'.charCodeAt(0);
1280
const BINGET = 'h'.charCodeAt(0);
1281
const BININT = 'J'.charCodeAt(0);
1282
const BININT1 = 'K'.charCodeAt(0);
1283
const BININT2 = 'M'.charCodeAt(0);
1284
const EMPTY_DICT = '}'.charCodeAt(0);
1285
const EMPTY_LIST = ']'.charCodeAt(0);
1288
const LONG_BINGET = 'j'.charCodeAt(0);
1289
const MARK = '('.charCodeAt(0);
1290
const MEMOIZE = 0x94;
1292
const SETITEMS = 'u'.charCodeAt(0);
1293
const SHORT_BINUNICODE = 0x8c;
1294
const STOP = '.'.charCodeAt(0);
1295
const TUPLE2 = 0x86;
1296
const APPEND = 'a'.charCodeAt(0);
1297
const NEWFALSE = 0x89;
1298
const BINPUT = 'q'.charCodeAt(0);
1299
const BINUNICODE = 'X'.charCodeAt(0);
1300
const EMPTY_TUPLE = ')'.charCodeAt(0);
1301
const NEWTRUE = 0x88;
1302
const NONE = 'N'.charCodeAt(0);
1303
const BINFLOAT = 'G'.charCodeAt(0);
1304
const TUPLE = 't'.charCodeAt(0);
1305
const TUPLE1 = 0x85;
1306
const TUPLE3 = 0x87;
1308
const LONG_BINPUT = 'r'.charCodeAt(0);
1309
const LIST = 'l'.charCodeAt(0);
1310
const DICT = 'd'.charCodeAt(0);
1311
const SETITEM = 's'.charCodeAt(0);
1313
const scratch_buffer = new ArrayBuffer(8);
1314
const scratch_bytes = new Uint8Array(scratch_buffer);
1315
const big = new BigInt64Array(scratch_buffer);
1316
const float64 = new Float64Array(scratch_buffer);
1318
function read_uint4() {
1320
bytebuffer[offset] +
1321
bytebuffer[offset + 1] * 256 +
1322
bytebuffer[offset + 2] * 65536 +
1323
bytebuffer[offset + 3] * 16777216;
1327
function setitems(d, mark) {
1328
for (let i = mark; i < stack.length; i += 2) {
1329
d[stack[i]] = stack[i + 1];
1331
stack.splice(mark, Infinity);
1335
const opcode = bytebuffer[offset++];
1339
const version = bytebuffer[offset++];
1340
if (version < 2 || version > 4) {
1341
throw new Error(`Unhandled version ${version}`);
1347
const v = stack.pop();
1348
stack.at(-1).push(v);
1353
const mark = marks.pop();
1354
const arr = stack[mark - 1];
1355
arr.push(...stack.splice(mark, Infinity));
1361
const mark = marks.pop();
1362
stack.push([...stack.splice(mark, Infinity)]);
1375
stack.push(memo[bytebuffer[offset++]]);
1379
let i32 = read_uint4();
1380
if (i32 > 0x7fffffff) {
1387
stack.push(bytebuffer[offset++]);
1391
const v = bytebuffer[offset] + bytebuffer[offset + 1] * 256;
1407
const s = bytebuffer[offset++];
1409
for (let i = 0; i < s; i++) {
1410
scratch_bytes[i] = bytebuffer[offset++];
1412
const fill = scratch_bytes[s - 1] >= 128 ? 0xff : 0x0;
1413
for (let i = s; i < 8; i++) {
1414
scratch_bytes[i] = fill;
1416
stack.push(Number(big[0]));
1418
let scratch_bytes_unbounded = [];
1419
for (let i = 0; i < s; i++) {
1420
scratch_bytes_unbounded.push(bytebuffer[offset++]);
1425
const negative = scratch_bytes_unbounded[s - 1] >= 128;
1430
for (let i = 0; i < s; i++) {
1431
const twos_complement = (0xff ^ scratch_bytes_unbounded[i]) + carry;
1432
carry = twos_complement > 0xff ? 1 : 0;
1433
scratch_bytes_unbounded[i] = 0xff & twos_complement;
1437
const hex_str = Array.from(scratch_bytes_unbounded.reverse(), byte => {
1438
return byte.toString(16).padStart(2, '0');
1441
const big_int = negative ? -BigInt(`0x${hex_str}`) : BigInt(`0x${hex_str}`);
1442
stack.push(big_int);
1448
const idx = read_uint4();
1449
stack.push(memo[idx]);
1453
marks.push(stack.length);
1456
memo[memo_id++] = stack.at(-1);
1459
memo[bytebuffer[offset++]] = stack.at(-1);
1462
memo[read_uint4()] = stack.at(-1);
1466
const mark = marks.pop();
1467
const d = stack[mark - 1];
1472
const v = stack.pop();
1473
const k = stack.pop();
1474
stack.at(-1)[k] = v;
1479
const mark = marks.pop();
1485
case SHORT_BINUNICODE:
1487
const n = bytebuffer[offset++];
1488
stack.push(decoder.decode(new Uint8Array(buffer, offset, n)));
1494
const n = read_uint4();
1495
stack.push(decoder.decode(new Uint8Array(buffer, offset, n)));
1505
stack.push([stack.pop()]);
1508
stack.push(stack.splice(-2, Infinity));
1511
stack.push(stack.splice(-3, Infinity));
1514
for (let i = 7; i >= 0; i--) {
1516
scratch_bytes[i] = bytebuffer[offset++];
1518
stack.push(float64[0]);
1521
throw new Error(`UNKNOWN OPCODE: ${opcode}`);
1526
function decode_base64(input) {
1527
function decode_char(i, shift) {
1528
const nChr = input.charCodeAt(i);
1530
nChr > 64 && nChr < 91
1532
: nChr > 96 && nChr < 123
1534
: nChr > 47 && nChr < 58
1543
const output = new Uint8Array((input.length / 4) * 3);
1544
for (let i = 0, j = 0; i < input.length; i += 4, j += 3) {
1546
decode_char(i, 18) +
1547
decode_char(i + 1, 12) +
1548
decode_char(i + 2, 6) +
1550
output[j] = u24 >> 16;
1551
output[j + 1] = (u24 >> 8) & 0xff;
1552
output[j + 2] = u24 & 0xff;
1554
return output.buffer;
1558
'Active Memory Timeline': create_trace_view,
1559
'Allocator State History': create_segment_view,
1560
'Active Cached Segment Timeline': (dst, snapshot, device) =>
1561
create_trace_view(dst, snapshot, device, true),
1564
const snapshot_cache = {};
1565
const snapshot_to_loader = {};
1566
const snapshot_to_url = {};
1567
const selection_to_div = {};
1578
const head = d3.select('head');
1579
head.append('style').text(style);
1580
const body = d3.select('body');
1581
const snapshot_select = body.append('select');
1582
const view = body.append('select');
1583
for (const x in kinds) {
1584
view.append('option').text(x);
1586
const gpu = body.append('select');
1588
function unpickle_and_annotate(data) {
1589
data = unpickle(data);
1591
annotate_snapshot(data);
1595
function snapshot_change(f) {
1596
const view_value = view.node().value;
1597
let device = Number(gpu.node().value);
1598
const snapshot = snapshot_cache[f];
1599
gpu.selectAll('option').remove();
1600
const has_segments = {};
1601
for (const s of snapshot.segments) {
1602
has_segments[s.device] = true;
1604
let device_valid = false;
1605
for (const [i, trace] of snapshot.device_traces.entries()) {
1606
if (trace.length > 0 || i in has_segments) {
1607
gpu.append('option').text(i);
1609
device_valid = true;
1610
gpu.node().selectedIndex = gpu.node().children.length - 1;
1614
if (!device_valid) {
1615
device = Number(gpu.node().value);
1617
const key = [f, view_value, device];
1618
if (!(key in selection_to_div)) {
1619
selection_to_div[key] = d3.select('body').append('div');
1620
kinds[view_value](selection_to_div[key], snapshot, device);
1622
const selected_div = selection_to_div[key];
1624
selected_div.attr('style', 'display: float; height: 100%');
1627
function selected_change() {
1628
for (const d of Object.values(selection_to_div)) {
1629
d.attr('style', 'display: none; height: 100%');
1631
const f = snapshot_select.node().value;
1635
if (!(f in snapshot_cache)) {
1636
snapshot_to_loader[f](f);
1642
snapshot_select.on('change', selected_change);
1643
view.on('change', selected_change);
1644
gpu.on('change', selected_change);
1646
body.on('dragover', e => {
1647
event.preventDefault();
1650
body.on('drop', () => {
1651
console.log(event.dataTransfer.files);
1652
Array.from(event.dataTransfer.files).forEach(file => {
1653
add_snapshot(file.name, unique_name => {
1654
const reader = new FileReader();
1655
reader.onload = e => {
1656
finished_loading(unique_name, e.target.result);
1658
reader.readAsArrayBuffer(file);
1661
event.preventDefault();
1662
snapshot_select.node().selectedIndex =
1663
snapshot_select.node().options.length - 1;
1667
selection_to_div[''] = body
1670
'Drag and drop a file to load a local snapshot. No data from the snapshot is uploaded.',
1673
let next_unique_n = 1;
1674
function add_snapshot(name, loader) {
1675
if (name in snapshot_to_loader) {
1676
name = `${name} (${next_unique_n++})`;
1678
snapshot_select.append('option').text(name);
1679
snapshot_to_loader[name] = loader;
1682
function finished_loading(name, data) {
1683
snapshot_cache[name] = unpickle_and_annotate(data);
1684
snapshot_change(name);
1687
export function add_remote_files(files) {
1689
add_snapshot(f.name, unique_name => {
1690
console.log('fetching', f.url);
1692
.then(x => x.arrayBuffer())
1693
.then(data => finished_loading(unique_name, data));
1696
if (files.length > 0) {
1701
export function add_local_files(files, view_value) {
1702
view.node().value = view_value;
1704
add_snapshot(f.name, unique_name => {
1705
finished_loading(unique_name, decode_base64(f.base64));
1708
if (files.length > 0) {