mirror of
https://github.com/zebrajr/pytorch.git
synced 2025-12-07 12:21:27 +01:00
Summary: When we print the addr we append an "s" or a "b" to the beginning of an addr. Since the addr is in hex, a user might be confused and think the "b" is part of the address. Added an approstrophe to clear this up Test Plan: CI Differential Revision: D69828538 Pull Request resolved: https://github.com/pytorch/pytorch/pull/147461 Approved by: https://github.com/zdevito
1738 lines
47 KiB
JavaScript
1738 lines
47 KiB
JavaScript
'use strict';
|
|
|
|
import * as d3 from "https://cdn.skypack.dev/d3@5";
|
|
import {axisLeft} from "https://cdn.skypack.dev/d3-axis@1";
|
|
import {scaleLinear} from "https://cdn.skypack.dev/d3-scale@1";
|
|
import {zoom, zoomIdentity} from "https://cdn.skypack.dev/d3-zoom@1";
|
|
import {brushX} from "https://cdn.skypack.dev/d3-brush@1";
|
|
|
|
const schemeTableau10 = [
|
|
'#4e79a7',
|
|
'#f28e2c',
|
|
'#e15759',
|
|
'#76b7b2',
|
|
'#59a14f',
|
|
'#edc949',
|
|
'#af7aa1',
|
|
'#ff9da7',
|
|
'#9c755f',
|
|
'#bab0ab',
|
|
];
|
|
|
|
function version_space() {
|
|
const version = {};
|
|
return (addr, increment) => {
|
|
if (!(addr in version)) {
|
|
version[addr] = 0;
|
|
}
|
|
const r = version[addr];
|
|
if (increment) {
|
|
version[addr]++;
|
|
}
|
|
return r;
|
|
};
|
|
}
|
|
|
|
function Segment(addr, size, stream, frames, version) {
|
|
return {addr, size, stream, version, frames};
|
|
}
|
|
|
|
function Block(addr, size, requested_size, frames, free_requested, version) {
|
|
return {addr, size, requested_size, frames, free_requested, version};
|
|
}
|
|
|
|
function EventSelector(outer, events, stack_info, memory_view) {
|
|
const events_div = outer
|
|
.append('div')
|
|
.attr(
|
|
'style',
|
|
'grid-column: 1; grid-row: 1; overflow: auto; font-family: monospace',
|
|
);
|
|
|
|
const events_selection = events_div
|
|
.selectAll('pre')
|
|
.data(events)
|
|
.enter()
|
|
.append('pre')
|
|
.text(e => formatEvent(e))
|
|
.attr('style', '');
|
|
|
|
let selected_event_idx = null;
|
|
|
|
const es = {
|
|
select(idx) {
|
|
if (selected_event_idx !== null) {
|
|
const selected_event = d3.select(
|
|
events_div.node().children[selected_event_idx],
|
|
);
|
|
selected_event.attr('style', '');
|
|
}
|
|
if (idx !== null) {
|
|
const div = d3.select(events_div.node().children[idx]);
|
|
div.attr('style', `background-color: ${schemeTableau10[5]}`);
|
|
const [reserved, allocated] = memory_view.draw(idx);
|
|
const enter = () => eventStack(div.datum(), allocated, reserved);
|
|
stack_info.highlight(enter);
|
|
div.node().scrollIntoViewIfNeeded(false);
|
|
} else {
|
|
memory_view.draw(0);
|
|
}
|
|
selected_event_idx = idx;
|
|
},
|
|
};
|
|
d3.select('body').on('keydown', _e => {
|
|
const key = d3.event.key;
|
|
const actions = {ArrowDown: 1, ArrowUp: -1};
|
|
if (selected_event_idx !== null && key in actions) {
|
|
const new_idx = selected_event_idx + actions[key];
|
|
es.select(Math.max(0, Math.min(new_idx, events.length - 1)));
|
|
d3.event.preventDefault();
|
|
}
|
|
});
|
|
|
|
stack_info.register(
|
|
events_selection,
|
|
t => eventStack(t.datum()),
|
|
_t => {},
|
|
d => es.select(d.datum().idx),
|
|
);
|
|
|
|
return es;
|
|
}
|
|
|
|
function formatSize(num) {
|
|
const orig = num;
|
|
// https://stackoverflow.com/questions/1094841/get-human-readable-version-of-file-size
|
|
const units = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi'];
|
|
for (const unit of units) {
|
|
if (Math.abs(num) < 1024.0) {
|
|
return `${num.toFixed(1)}${unit}B (${orig} bytes)`;
|
|
}
|
|
num /= 1024.0;
|
|
}
|
|
return `${num.toFixed(1)}YiB`;
|
|
}
|
|
function formatAddr(event) {
|
|
const prefix = event.action.startsWith('segment') ? 's\'' : 'b\'';
|
|
return `${prefix}${event.addr.toString(16)}_${event.version}`;
|
|
}
|
|
function formatEvent(event) {
|
|
const stream =
|
|
event.stream === null ? '' : `\n (stream ${event.stream})`;
|
|
switch (event.action) {
|
|
case 'oom':
|
|
return `OOM (requested ${formatSize(event.size)}, CUDA has ${formatSize(
|
|
event.device_free,
|
|
)} memory free)${stream}`;
|
|
case 'snapshot':
|
|
return 'snapshot';
|
|
default:
|
|
return `${event.action.padEnd(14)} ${formatAddr(event).padEnd(
|
|
18,
|
|
)} ${formatSize(event.size)}${stream}`;
|
|
}
|
|
}
|
|
|
|
function eventStack(e, allocated, reserved) {
|
|
let event = formatEvent(e);
|
|
if (reserved !== undefined) {
|
|
event = `(${formatSize(allocated)} allocated / ${formatSize(
|
|
reserved,
|
|
)} reserved)\n${event}`;
|
|
}
|
|
return event + '\n' + format_frames(e.frames);
|
|
}
|
|
|
|
function hashCode(num) {
|
|
const numStr = num.toString();
|
|
let hash = 0;
|
|
for (let i = 0; i < numStr.length; i++) {
|
|
const charCode = numStr.charCodeAt(i);
|
|
hash = (hash << 5) - hash + charCode;
|
|
hash = hash & hash; // Convert to 32-bit integer
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
function addStroke(d) {
|
|
d.attr('stroke', 'red')
|
|
.attr('stroke-width', '2')
|
|
.attr('vector-effect', 'non-scaling-stroke');
|
|
}
|
|
|
|
function removeStroke(d) {
|
|
d.attr('stroke', '');
|
|
}
|
|
|
|
function calculate_fragmentation(blocks, sorted_segments) {
|
|
const sorted_blocks = Object.values(blocks).sort((a, b) => a.addr - b.addr);
|
|
let block_i = 0;
|
|
let total_size = 0;
|
|
let sum_squared_free = 0;
|
|
for (const seg of sorted_segments) {
|
|
let addr = seg.addr;
|
|
total_size += seg.size;
|
|
while (
|
|
block_i < sorted_blocks.length &&
|
|
sorted_blocks[block_i].addr < seg.addr + seg.size
|
|
) {
|
|
const block = sorted_blocks[block_i];
|
|
if (block.addr > addr) {
|
|
sum_squared_free += (block.addr - addr) ** 2;
|
|
}
|
|
addr = block.addr + block.size;
|
|
block_i += 1;
|
|
}
|
|
if (addr < seg.addr + seg.size) {
|
|
sum_squared_free += (seg.addr + seg.size - addr) ** 2;
|
|
}
|
|
}
|
|
console.log(sum_squared_free / total_size ** 2);
|
|
}
|
|
|
|
function MemoryView(outer, stack_info, snapshot, device) {
|
|
const svg = outer
|
|
.append('svg')
|
|
.attr('style', 'grid-column: 2; grid-row: 1; width: 100%; height: 100%;')
|
|
.attr('viewBox', '0 0 200 100')
|
|
.attr('preserveAspectRatio', 'xMinYMin meet');
|
|
const g = svg.append('g');
|
|
const seg_zoom = zoom();
|
|
seg_zoom.on('zoom', () => {
|
|
g.attr('transform', d3.event.transform);
|
|
});
|
|
svg.call(seg_zoom);
|
|
|
|
const sorted_segments = [];
|
|
const block_map = {};
|
|
for (const seg of snapshot.segments) {
|
|
if (seg.device !== device) {
|
|
continue;
|
|
}
|
|
sorted_segments.push(
|
|
Segment(
|
|
seg.address,
|
|
seg.total_size,
|
|
seg.stream,
|
|
seg.frames || [],
|
|
seg.version,
|
|
),
|
|
);
|
|
for (const b of seg.blocks) {
|
|
if (b.state !== 'active_pending_free' && b.state !== 'active_allocated') {
|
|
continue;
|
|
}
|
|
block_map[b.addr] = Block(
|
|
b.addr,
|
|
b.size,
|
|
b.requested_size,
|
|
b.frames,
|
|
b.state === 'active_pending_free',
|
|
b.version,
|
|
);
|
|
}
|
|
}
|
|
sorted_segments.sort((x, y) => x.addr - y.addr);
|
|
|
|
function simulate_memory(idx) {
|
|
// create a copy of segments because we edit size properties below
|
|
const l_segments = sorted_segments.map(x => {
|
|
return {...x};
|
|
});
|
|
const l_block_map = {...block_map};
|
|
|
|
function map_segment(merge, seg) {
|
|
let idx = l_segments.findIndex(e => e.addr > seg.addr);
|
|
if (!merge) {
|
|
l_segments.splice(idx, 0, seg);
|
|
return;
|
|
}
|
|
if (idx === -1) {
|
|
idx = l_segments.length;
|
|
}
|
|
l_segments.splice(idx, 0, seg);
|
|
if (idx + 1 < l_segments.length) {
|
|
const next = l_segments[idx + 1];
|
|
if (seg.addr + seg.size === next.addr && seg.stream === next.stream) {
|
|
seg.size += next.size;
|
|
l_segments.splice(idx + 1, 1);
|
|
}
|
|
}
|
|
if (idx > 0) {
|
|
const prev = l_segments[idx - 1];
|
|
if (prev.addr + prev.size === seg.addr && prev.stream === seg.stream) {
|
|
prev.size += seg.size;
|
|
l_segments.splice(idx, 1);
|
|
}
|
|
}
|
|
}
|
|
function unmap_segment(merge, seg) {
|
|
if (!merge) {
|
|
l_segments.splice(
|
|
l_segments.findIndex(x => x.addr === seg.addr),
|
|
1,
|
|
);
|
|
return;
|
|
}
|
|
const seg_end = seg.addr + seg.size;
|
|
const idx = l_segments.findIndex(
|
|
e => e.addr <= seg.addr && seg_end <= e.addr + e.size,
|
|
);
|
|
const existing = l_segments[idx];
|
|
const existing_end = existing.addr + existing.size;
|
|
if (existing.addr === seg.addr) {
|
|
existing.addr += seg.size;
|
|
existing.size -= seg.size;
|
|
if (existing.size === 0) {
|
|
l_segments.splice(idx, 1);
|
|
}
|
|
} else if (existing_end === seg_end) {
|
|
existing.size -= seg.size;
|
|
} else {
|
|
existing.size = seg.addr - existing.addr;
|
|
seg.addr = seg_end;
|
|
seg.size = existing_end - seg_end;
|
|
l_segments.splice(idx + 1, 0, seg);
|
|
}
|
|
}
|
|
const events = snapshot.device_traces[device];
|
|
for (let i = events.length - 1; i > idx; i--) {
|
|
const event = events[i];
|
|
switch (event.action) {
|
|
case 'free':
|
|
l_block_map[event.addr] = Block(
|
|
event.addr,
|
|
event.size,
|
|
event.size,
|
|
event.frames,
|
|
false,
|
|
event.version,
|
|
);
|
|
break;
|
|
case 'free_requested':
|
|
l_block_map[event.addr].free_requested = false;
|
|
break;
|
|
case 'free_completed':
|
|
l_block_map[event.addr] = Block(
|
|
event.addr,
|
|
event.size,
|
|
event.size,
|
|
event.frames,
|
|
true,
|
|
event.version,
|
|
);
|
|
break;
|
|
case 'alloc':
|
|
delete l_block_map[event.addr];
|
|
break;
|
|
case 'segment_free':
|
|
case 'segment_unmap':
|
|
map_segment(
|
|
event.action === 'segment_unmap',
|
|
Segment(
|
|
event.addr,
|
|
event.size,
|
|
event.stream,
|
|
event.frames,
|
|
event.version,
|
|
),
|
|
);
|
|
break;
|
|
case 'segment_alloc':
|
|
case 'segment_map':
|
|
unmap_segment(
|
|
event.action === 'segment_map',
|
|
Segment(
|
|
event.addr,
|
|
event.size,
|
|
event.stream,
|
|
event.frames,
|
|
event.version,
|
|
),
|
|
);
|
|
break;
|
|
case 'oom':
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
const new_blocks = Object.values(l_block_map);
|
|
return [l_segments, new_blocks];
|
|
}
|
|
|
|
return {
|
|
draw(idx) {
|
|
const [segments_unsorted, blocks] = simulate_memory(idx);
|
|
g.selectAll('g').remove();
|
|
|
|
const segment_d = g.append('g');
|
|
const block_g = g.append('g');
|
|
const block_r = g.append('g');
|
|
|
|
segment_d.selectAll('rect').remove();
|
|
block_g.selectAll('rect').remove();
|
|
block_r.selectAll('rect').remove();
|
|
const segments = [...segments_unsorted].sort((x, y) =>
|
|
x.size === y.size ? x.addr - y.addr : x.size - y.size,
|
|
);
|
|
|
|
const segments_by_addr = [...segments].sort((x, y) => x.addr - y.addr);
|
|
|
|
const max_size = segments.length === 0 ? 0 : segments.at(-1).size;
|
|
|
|
const xScale = scaleLinear().domain([0, max_size]).range([0, 200]);
|
|
const padding = xScale.invert(1);
|
|
|
|
let cur_row = 0;
|
|
let cur_row_size = 0;
|
|
for (const seg of segments) {
|
|
seg.occupied = 0;
|
|
seg.internal_free = 0;
|
|
if (cur_row_size + seg.size > max_size) {
|
|
cur_row_size = 0;
|
|
cur_row += 1;
|
|
}
|
|
seg.offset = cur_row_size;
|
|
seg.row = cur_row;
|
|
cur_row_size += seg.size + padding;
|
|
}
|
|
|
|
const num_rows = cur_row + 1;
|
|
|
|
const yScale = scaleLinear().domain([0, num_rows]).range([0, 100]);
|
|
|
|
const segments_selection = segment_d
|
|
.selectAll('rect')
|
|
.data(segments)
|
|
.enter()
|
|
.append('rect')
|
|
.attr('x', x => xScale(x.offset))
|
|
.attr('y', x => yScale(x.row))
|
|
.attr('width', x => xScale(x.size))
|
|
.attr('height', yScale(4 / 5))
|
|
.attr('stroke', 'black')
|
|
.attr('stroke-width', '1')
|
|
.attr('vector-effect', 'non-scaling-stroke')
|
|
.attr('fill', 'white');
|
|
|
|
stack_info.register(
|
|
segments_selection,
|
|
d => {
|
|
addStroke(d);
|
|
const t = d.datum();
|
|
const free = t.size - t.occupied;
|
|
let internal = '';
|
|
if (t.internal_free > 0) {
|
|
internal = ` (${(t.internal_free / free) * 100}% internal)`;
|
|
}
|
|
return (
|
|
`s${t.addr.toString(16)}_${t.version}: segment ${formatSize(
|
|
t.size,
|
|
)} allocated, ` +
|
|
`${formatSize(free)} free${internal} (stream ${
|
|
t.stream
|
|
})\n${format_frames(t.frames)}`
|
|
);
|
|
},
|
|
d => {
|
|
d.attr('stroke', 'black')
|
|
.attr('stroke-width', '1')
|
|
.attr('vector-effect', 'non-scaling-stroke');
|
|
},
|
|
);
|
|
|
|
function find_segment(addr) {
|
|
let left = 0;
|
|
let right = segments_by_addr.length - 1;
|
|
while (left <= right) {
|
|
const mid = Math.floor((left + right) / 2);
|
|
if (addr < segments_by_addr[mid].addr) {
|
|
right = mid - 1;
|
|
} else if (
|
|
addr >=
|
|
segments_by_addr[mid].addr + segments_by_addr[mid].size
|
|
) {
|
|
left = mid + 1;
|
|
} else {
|
|
return segments_by_addr[mid];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
for (const b of blocks) {
|
|
b.segment = find_segment(b.addr);
|
|
b.segment.occupied += b.requested_size;
|
|
b.segment.internal_free += b.size - b.requested_size;
|
|
}
|
|
|
|
const block_selection = block_g
|
|
.selectAll('rect')
|
|
.data(blocks)
|
|
.enter()
|
|
.append('rect')
|
|
.attr('x', x => xScale(x.segment.offset + (x.addr - x.segment.addr)))
|
|
.attr('y', x => yScale(x.segment.row))
|
|
.attr('width', x => xScale(x.requested_size))
|
|
.attr('height', yScale(4 / 5))
|
|
.attr('fill', (x, _i) =>
|
|
x.free_requested
|
|
? 'red'
|
|
: schemeTableau10[
|
|
Math.abs(hashCode(x.addr)) % schemeTableau10.length
|
|
],
|
|
);
|
|
|
|
stack_info.register(
|
|
block_selection,
|
|
d => {
|
|
addStroke(d);
|
|
const t = d.datum();
|
|
let requested = '';
|
|
if (t.free_requested) {
|
|
requested = ' (block freed but waiting due to record_stream)';
|
|
}
|
|
return (
|
|
`b${t.addr.toString(16)}_${t.version} ` +
|
|
`${formatSize(t.requested_size)} allocation${requested} (stream ${
|
|
t.segment.stream
|
|
})\n` +
|
|
format_frames(t.frames)
|
|
);
|
|
},
|
|
removeStroke,
|
|
);
|
|
|
|
const free_selection = block_r
|
|
.selectAll('rect')
|
|
.data(blocks)
|
|
.enter()
|
|
.append('rect')
|
|
.attr('x', x =>
|
|
xScale(
|
|
x.segment.offset + (x.addr - x.segment.addr) + x.requested_size,
|
|
),
|
|
)
|
|
.attr('y', x => yScale(x.segment.row))
|
|
.attr('width', x => xScale(x.size - x.requested_size))
|
|
.attr('height', yScale(4 / 5))
|
|
.attr('fill', (_x, _i) => 'red');
|
|
|
|
stack_info.register(
|
|
free_selection,
|
|
d => {
|
|
addStroke(d);
|
|
const t = d.datum();
|
|
return (
|
|
`Free space lost due to rounding ${formatSize(
|
|
t.size - t.requested_size,
|
|
)}` +
|
|
` (stream ${t.segment.stream})\n` +
|
|
format_frames(t.frames)
|
|
);
|
|
},
|
|
removeStroke,
|
|
);
|
|
|
|
const reserved = segments.reduce((x, y) => x + y.size, 0);
|
|
const allocated = blocks.reduce((x, y) => x + y.requested_size, 0);
|
|
return [reserved, allocated];
|
|
},
|
|
};
|
|
}
|
|
|
|
function StackInfo(outer) {
|
|
const stack_trace = outer
|
|
.append('pre')
|
|
.attr('style', 'grid-column: 1 / 3; grid-row: 2; overflow: auto');
|
|
let selected = {
|
|
enter: () => {
|
|
stack_trace.text('');
|
|
},
|
|
leave: () => {},
|
|
};
|
|
return {
|
|
register(dom, enter, leave = _e => {}, select = _e => {}) {
|
|
dom
|
|
.on('mouseover', _e => {
|
|
selected.leave();
|
|
stack_trace.text(enter(d3.select(d3.event.target)));
|
|
})
|
|
.on('mousedown', _e => {
|
|
const obj = d3.select(d3.event.target);
|
|
selected = {
|
|
enter: () => stack_trace.text(enter(obj)),
|
|
leave: () => leave(obj),
|
|
};
|
|
select(obj);
|
|
})
|
|
.on('mouseleave', _e => {
|
|
leave(d3.select(d3.event.target));
|
|
selected.enter();
|
|
});
|
|
},
|
|
highlight(enter, leave = () => {}) {
|
|
selected = {enter: () => stack_trace.text(enter()), leave};
|
|
selected.enter();
|
|
},
|
|
};
|
|
}
|
|
|
|
function create_segment_view(dst, snapshot, device) {
|
|
const outer = dst
|
|
.append('div')
|
|
.attr(
|
|
'style',
|
|
'display: grid; grid-template-columns: 1fr 2fr; grid-template-rows: 2fr 1fr; height: 100%; gap: 10px',
|
|
);
|
|
|
|
const events = snapshot.device_traces[device];
|
|
const stack_info = StackInfo(outer);
|
|
const memory_view = MemoryView(outer, stack_info, snapshot, device);
|
|
const event_selector = EventSelector(outer, events, stack_info, memory_view);
|
|
|
|
window.requestAnimationFrame(function () {
|
|
event_selector.select(events.length > 0 ? events.length - 1 : null);
|
|
});
|
|
}
|
|
|
|
function annotate_snapshot(snapshot) {
|
|
snapshot.segment_version = version_space();
|
|
snapshot.block_version = version_space();
|
|
snapshot.categories = [];
|
|
const empty_list = [];
|
|
let next_stream = 1;
|
|
const stream_names = {0: 0};
|
|
function stream_name(s) {
|
|
if (!(s in stream_names)) {
|
|
stream_names[s] = next_stream++;
|
|
}
|
|
return stream_names[s];
|
|
}
|
|
const new_traces = [];
|
|
for (const device_trace of snapshot.device_traces) {
|
|
const new_trace = [];
|
|
new_traces.push(new_trace);
|
|
for (const t of device_trace) {
|
|
if (!('frames' in t)) {
|
|
t.frames = empty_list;
|
|
}
|
|
// set unique version for each time an address is used
|
|
// so that ctrl-f can be used to search for the beginning
|
|
// and end of allocations and segments
|
|
t.stream = stream_name(t.stream);
|
|
switch (t.action) {
|
|
case 'free_completed':
|
|
t.version = snapshot.block_version(t.addr, true);
|
|
if (new_trace.length > 0) {
|
|
// elide free_requested/free_completed into a single event
|
|
const prev = new_trace.at(-1);
|
|
if (prev.action === 'free_requested' && prev.addr === t.addr) {
|
|
prev.action = 'free';
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
case 'free_requested':
|
|
case 'alloc':
|
|
t.version = snapshot.block_version(t.addr, false);
|
|
break;
|
|
case 'segment_free':
|
|
case 'segment_unmap':
|
|
t.version = snapshot.segment_version(t.addr, true);
|
|
break;
|
|
case 'segment_alloc':
|
|
case 'segment_map':
|
|
t.version = snapshot.segment_version(t.addr, false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if ('category' in t && !snapshot.categories.includes(t.category)) {
|
|
snapshot.categories.push(t.category);
|
|
}
|
|
t.idx = new_trace.length;
|
|
new_trace.push(t);
|
|
}
|
|
}
|
|
snapshot.device_traces = new_traces;
|
|
// if every event was on the default stream, we elide stream printing
|
|
if (next_stream == 1) {
|
|
for (const device_trace of snapshot.device_traces) {
|
|
for (const t of device_trace) {
|
|
t.stream = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const seg of snapshot.segments) {
|
|
seg.stream = stream_name(seg.stream);
|
|
seg.version = snapshot.segment_version(seg.address, false);
|
|
let addr = seg.address;
|
|
for (const b of seg.blocks) {
|
|
b.addr = addr;
|
|
if (!('frames' in b)) {
|
|
// legacy format where 'requested_size' may be missing
|
|
// and frames might be in history rather than directly on block
|
|
if ('history' in b) {
|
|
b.frames = b.history[0].frames || empty_list;
|
|
b.requested_size = b.requested_size || b.history[0].real_size;
|
|
} else {
|
|
b.frames = empty_list;
|
|
b.requested_size = b.requested_size || b.size;
|
|
}
|
|
}
|
|
b.version = snapshot.block_version(b.addr, false);
|
|
addr += b.size;
|
|
}
|
|
}
|
|
|
|
if (
|
|
snapshot.categories.length > 0 &&
|
|
!snapshot.categories.includes('unknown')
|
|
) {
|
|
snapshot.categores.push('unknown');
|
|
}
|
|
}
|
|
|
|
function elideRepeats(frames) {
|
|
const result = [];
|
|
const length = frames.length;
|
|
for (let i = 0; i < length; ) {
|
|
let j = i + 1;
|
|
const f = frames[i];
|
|
while (j < length && f === frames[j]) {
|
|
j++;
|
|
}
|
|
switch (j - i) {
|
|
case 1:
|
|
result.push(f);
|
|
break;
|
|
case 2:
|
|
result.push(f, f);
|
|
break;
|
|
default:
|
|
result.push(f, `<repeats ${j - i - 1} times>`);
|
|
break;
|
|
}
|
|
i = j;
|
|
}
|
|
return result;
|
|
}
|
|
function frameFilter({name, filename}) {
|
|
const omitFunctions = [
|
|
'unwind::unwind',
|
|
'CapturedTraceback::gather',
|
|
'gather_with_cpp',
|
|
'_start',
|
|
'__libc_start_main',
|
|
'PyEval_',
|
|
'PyObject_',
|
|
'PyFunction_',
|
|
];
|
|
|
|
const omitFilenames = [
|
|
'core/boxing',
|
|
'/Register',
|
|
'/Redispatch',
|
|
'pythonrun.c',
|
|
'Modules/main.c',
|
|
'Objects/call.c',
|
|
'Objects/methodobject.c',
|
|
'pycore_ceval.h',
|
|
'ceval.c',
|
|
'cpython/abstract.h',
|
|
];
|
|
|
|
for (const of of omitFunctions) {
|
|
if (name.includes(of)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (const of of omitFilenames) {
|
|
if (filename.includes(of)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function format_frames(frames) {
|
|
if (frames.length === 0) {
|
|
return (
|
|
`This block has no frames. Potential causes:\n` +
|
|
`1) This block was allocated before _record_memory_history was enabled.\n` +
|
|
`2) The context or stacks passed to _record_memory_history does not include this block. Consider changing context to 'state', 'alloc', or 'all', or changing stacks to 'all'.\n` +
|
|
`3) This event occurred during backward, which has no python frames, and memory history did not include C++ frames. Use stacks='all' to record both C++ and python frames.`
|
|
);
|
|
}
|
|
const frame_strings = frames
|
|
.filter(frameFilter)
|
|
.map(f => `${f.filename}:${f.line}:${f.name}`);
|
|
return elideRepeats(frame_strings).join('\n');
|
|
}
|
|
|
|
function process_alloc_data(snapshot, device, plot_segments, max_entries) {
|
|
const elements = [];
|
|
const initially_allocated = [];
|
|
const actions = [];
|
|
const addr_to_alloc = {};
|
|
|
|
const alloc = plot_segments ? 'segment_alloc' : 'alloc';
|
|
const [free, free_completed] = plot_segments
|
|
? ['segment_free', 'segment_free']
|
|
: ['free', 'free_completed'];
|
|
for (const e of snapshot.device_traces[device]) {
|
|
switch (e.action) {
|
|
case alloc:
|
|
elements.push(e);
|
|
addr_to_alloc[e.addr] = elements.length - 1;
|
|
actions.push(elements.length - 1);
|
|
break;
|
|
case free:
|
|
case free_completed:
|
|
if (e.addr in addr_to_alloc) {
|
|
actions.push(addr_to_alloc[e.addr]);
|
|
delete addr_to_alloc[e.addr];
|
|
} else {
|
|
elements.push(e);
|
|
initially_allocated.push(elements.length - 1);
|
|
actions.push(elements.length - 1);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
for (const seg of snapshot.segments) {
|
|
if (seg.device !== device) {
|
|
continue;
|
|
}
|
|
if (plot_segments) {
|
|
if (!(seg.address in addr_to_alloc)) {
|
|
const element = {
|
|
action: 'alloc',
|
|
addr: seg.address,
|
|
size: seg.total_size,
|
|
frames: [],
|
|
stream: seg.stream,
|
|
version: seg.version,
|
|
};
|
|
elements.push(element);
|
|
initially_allocated.push(elements.length - 1);
|
|
}
|
|
} else {
|
|
for (const b of seg.blocks) {
|
|
if (b.state === 'active_allocated' && !(b.addr in addr_to_alloc)) {
|
|
const element = {
|
|
action: 'alloc',
|
|
addr: b.addr,
|
|
size: b.requested_size,
|
|
frames: b.frames,
|
|
stream: seg.stream,
|
|
version: b.version,
|
|
};
|
|
elements.push(element);
|
|
initially_allocated.push(elements.length - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
initially_allocated.reverse();
|
|
// if there are no actions, the graph will be blank,
|
|
// but if there are existing allocations we do not want to hide them
|
|
// by having just one allocate action it will show a flat graph with all segments
|
|
if (actions.length === 0 && initially_allocated.length > 0) {
|
|
actions.push(initially_allocated.pop());
|
|
}
|
|
|
|
const current = [];
|
|
const current_data = [];
|
|
const data = [];
|
|
let max_size = 0;
|
|
|
|
let total_mem = 0;
|
|
let total_summarized_mem = 0;
|
|
let timestep = 0;
|
|
|
|
const max_at_time = [];
|
|
|
|
const summarized_mem = {
|
|
elem: 'summarized',
|
|
timesteps: [],
|
|
offsets: [total_mem],
|
|
size: [],
|
|
color: 0,
|
|
};
|
|
const summarized_elems = {};
|
|
|
|
function advance(n) {
|
|
summarized_mem.timesteps.push(timestep);
|
|
summarized_mem.offsets.push(total_mem);
|
|
summarized_mem.size.push(total_summarized_mem);
|
|
timestep += n;
|
|
for (let i = 0; i < n; i++) {
|
|
max_at_time.push(total_mem + total_summarized_mem);
|
|
}
|
|
}
|
|
|
|
const sizes = elements
|
|
.map((x, i) => [x.size, i])
|
|
.sort(([x, _xi], [y, _yi]) => y - x);
|
|
|
|
const draw_elem = {};
|
|
for (const [_s, e] of sizes.slice(0, max_entries)) {
|
|
draw_elem[e] = true;
|
|
}
|
|
|
|
function add_allocation(elem) {
|
|
const element_obj = elements[elem];
|
|
const size = element_obj.size;
|
|
current.push(elem);
|
|
let color = elem;
|
|
if (snapshot.categories.length > 0) {
|
|
color = snapshot.categories.indexOf(element_obj.category || 'unknown');
|
|
}
|
|
const e = {
|
|
elem,
|
|
timesteps: [timestep],
|
|
offsets: [total_mem],
|
|
size,
|
|
color,
|
|
};
|
|
current_data.push(e);
|
|
data.push(e);
|
|
total_mem += size;
|
|
element_obj.max_allocated_mem = total_mem + total_summarized_mem;
|
|
}
|
|
|
|
for (const elem of initially_allocated) {
|
|
if (elem in draw_elem) {
|
|
add_allocation(elem);
|
|
} else {
|
|
total_summarized_mem += elements[elem].size;
|
|
summarized_elems[elem] = true;
|
|
}
|
|
}
|
|
|
|
for (const elem of actions) {
|
|
const size = elements[elem].size;
|
|
if (!(elem in draw_elem)) {
|
|
if (elem in summarized_elems) {
|
|
advance(1);
|
|
total_summarized_mem -= size;
|
|
summarized_elems[elem] = null;
|
|
} else {
|
|
total_summarized_mem += size;
|
|
summarized_elems[elem] = true;
|
|
advance(1);
|
|
}
|
|
continue;
|
|
}
|
|
const idx = current.findLastIndex(x => x === elem);
|
|
// first time we see an action we add it
|
|
// second time we remove it
|
|
if (idx === -1) {
|
|
add_allocation(elem);
|
|
advance(1);
|
|
} else {
|
|
advance(1);
|
|
const removed = current_data[idx];
|
|
removed.timesteps.push(timestep);
|
|
removed.offsets.push(removed.offsets.at(-1));
|
|
current.splice(idx, 1);
|
|
current_data.splice(idx, 1);
|
|
|
|
if (idx < current.length) {
|
|
for (let j = idx; j < current.length; j++) {
|
|
const e = current_data[j];
|
|
e.timesteps.push(timestep);
|
|
e.offsets.push(e.offsets.at(-1));
|
|
e.timesteps.push(timestep + 3);
|
|
e.offsets.push(e.offsets.at(-1) - size);
|
|
}
|
|
advance(3);
|
|
}
|
|
total_mem -= size;
|
|
}
|
|
max_size = Math.max(total_mem + total_summarized_mem, max_size);
|
|
}
|
|
|
|
for (const elem of current_data) {
|
|
elem.timesteps.push(timestep);
|
|
elem.offsets.push(elem.offsets.at(-1));
|
|
}
|
|
data.push(summarized_mem);
|
|
|
|
return {
|
|
max_size,
|
|
allocations_over_time: data,
|
|
max_at_time,
|
|
summarized_mem,
|
|
elements_length: elements.length,
|
|
context_for_id: id => {
|
|
const elem = elements[id];
|
|
let text = `Addr: ${formatAddr(elem)}`;
|
|
text = `${text}, Size: ${formatSize(elem.size)} allocation`;
|
|
text = `${text}, Total memory used after allocation: ${formatSize(
|
|
elem.max_allocated_mem,
|
|
)}`;
|
|
if (elem.stream !== null) {
|
|
text = `${text}, stream ${elem.stream}`;
|
|
}
|
|
if (elem.timestamp !== null) {
|
|
var d = new Date(elem.time_us / 1000);
|
|
text = `${text}, timestamp ${d}`;
|
|
}
|
|
if (!elem.action.includes('alloc')) {
|
|
text = `${text}\nalloc not recorded, stack trace for free:`;
|
|
}
|
|
text = `${text}\n${format_frames(elem.frames)}`;
|
|
return text;
|
|
},
|
|
};
|
|
}
|
|
|
|
function MemoryPlot(
|
|
svg,
|
|
data,
|
|
left_pad,
|
|
width,
|
|
height,
|
|
colors = schemeTableau10,
|
|
) {
|
|
function format_points(d) {
|
|
const size = d.size;
|
|
const xs = d.timesteps.map(t => xscale(t));
|
|
const bottom = d.offsets.map(t => yscale(t));
|
|
const m = Array.isArray(size)
|
|
? (t, i) => yscale(t + size[i])
|
|
: t => yscale(t + size);
|
|
const top = d.offsets.map(m);
|
|
const p0 = xs.map((x, i) => `${x},${bottom[i]}`);
|
|
const p1 = xs.map((x, i) => `${x},${top[i]}`).reverse();
|
|
return `${p0.join(' ')} ${p1.join(' ')}`;
|
|
}
|
|
|
|
const max_timestep = data.max_at_time.length;
|
|
const max_size = data.max_size;
|
|
|
|
const plot_width = width - left_pad;
|
|
const plot_height = height;
|
|
|
|
const yscale = scaleLinear().domain([0, max_size]).range([plot_height, 0]);
|
|
const yaxis = axisLeft(yscale).tickFormat(d3.format('.3s'));
|
|
const xscale = scaleLinear().domain([0, max_timestep]).range([0, plot_width]);
|
|
const plot_coordinate_space = svg
|
|
.append('g')
|
|
.attr('transform', `translate(${left_pad}, ${0})`);
|
|
const plot_outer = plot_coordinate_space.append('g');
|
|
|
|
function view_rect(a) {
|
|
return a
|
|
.append('rect')
|
|
.attr('x', 0)
|
|
.attr('y', 0)
|
|
.attr('width', plot_width)
|
|
.attr('height', plot_height)
|
|
.attr('fill', 'white');
|
|
}
|
|
|
|
view_rect(plot_outer);
|
|
|
|
const cp = svg.append('clipPath').attr('id', 'clip');
|
|
view_rect(cp);
|
|
plot_outer.attr('clip-path', 'url(#clip)');
|
|
|
|
const zoom_group = plot_outer.append('g');
|
|
const scrub_group = zoom_group.append('g');
|
|
|
|
const plot = scrub_group
|
|
.selectAll('polygon')
|
|
.data(data.allocations_over_time)
|
|
.enter()
|
|
.append('polygon')
|
|
.attr('points', format_points)
|
|
.attr('fill', d => colors[d.color % colors.length]);
|
|
|
|
const axis = plot_coordinate_space.append('g').call(yaxis);
|
|
|
|
function handleZoom() {
|
|
const t = d3.event.transform;
|
|
zoom_group.attr('transform', t);
|
|
axis.call(yaxis.scale(d3.event.transform.rescaleY(yscale)));
|
|
}
|
|
|
|
const thezoom = zoom().on('zoom', handleZoom);
|
|
plot_outer.call(thezoom);
|
|
|
|
return {
|
|
select_window: (stepbegin, stepend, max) => {
|
|
const begin = xscale(stepbegin);
|
|
const size = xscale(stepend) - xscale(stepbegin);
|
|
const scale = plot_width / size;
|
|
const translate = -begin;
|
|
const yscale = max_size / max;
|
|
scrub_group.attr(
|
|
'transform',
|
|
`scale(${scale / yscale}, 1) translate(${translate}, 0)`,
|
|
);
|
|
plot_outer.call(
|
|
thezoom.transform,
|
|
zoomIdentity
|
|
.scale(yscale)
|
|
.translate(0, -(plot_height - plot_height / yscale)),
|
|
);
|
|
},
|
|
set_delegate: delegate => {
|
|
plot
|
|
.on('mouseover', function (_e, _d) {
|
|
delegate.set_selected(d3.select(this));
|
|
})
|
|
.on('mousedown', function (_e, _d) {
|
|
delegate.default_selected = d3.select(this);
|
|
})
|
|
.on('mouseleave', function (_e, _d) {
|
|
delegate.set_selected(delegate.default_selected);
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
function ContextViewer(text, data) {
|
|
let current_selected = null;
|
|
|
|
return {
|
|
default_selected: null,
|
|
set_selected: d => {
|
|
if (current_selected !== null) {
|
|
current_selected.attr('stroke', null).attr('stroke-width', null);
|
|
}
|
|
if (d === null) {
|
|
text.text('');
|
|
} else {
|
|
const dd = d.datum();
|
|
if (dd.elem === 'summarized') {
|
|
text.html(
|
|
'Small tensors that were not plotted to cutdown on render time.\n' +
|
|
'Use detail slider to see smaller allocations.',
|
|
);
|
|
} else {
|
|
text.text(`${dd.elem} ${data.context_for_id(dd.elem)}`);
|
|
}
|
|
d.attr('stroke', 'black')
|
|
.attr('stroke-width', 1)
|
|
.attr('vector-effect', 'non-scaling-stroke');
|
|
}
|
|
current_selected = d;
|
|
},
|
|
};
|
|
}
|
|
|
|
function MiniMap(mini_svg, plot, data, left_pad, width, height = 70) {
|
|
const max_at_time = data.max_at_time;
|
|
const plot_width = width - left_pad;
|
|
const yscale = scaleLinear().domain([0, data.max_size]).range([height, 0]);
|
|
const minixscale = scaleLinear()
|
|
.domain([0, max_at_time.length])
|
|
.range([left_pad, width]);
|
|
|
|
const mini_points = [
|
|
[max_at_time.length, 0],
|
|
[0, 0],
|
|
];
|
|
|
|
for (const [i, m] of max_at_time.entries()) {
|
|
const [_lastx, lasty] = mini_points[mini_points.length - 1];
|
|
if (m !== lasty) {
|
|
mini_points.push([i, lasty]);
|
|
mini_points.push([i, m]);
|
|
} else if (i === max_at_time.length - 1) {
|
|
mini_points.push([i, m]);
|
|
}
|
|
}
|
|
|
|
let points = mini_points.map(([t, o]) => `${minixscale(t)}, ${yscale(o)}`);
|
|
points = points.join(' ');
|
|
mini_svg
|
|
.append('polygon')
|
|
.attr('points', points)
|
|
.attr('fill', schemeTableau10[0]);
|
|
|
|
const xscale = scaleLinear()
|
|
.domain([0, max_at_time.length])
|
|
.range([0, plot_width]);
|
|
|
|
const brush = brushX();
|
|
brush.extent([
|
|
[left_pad, 0],
|
|
[width, height],
|
|
]);
|
|
brush.on('brush', function () {
|
|
const [begin, end] = d3.event.selection.map(x => x - left_pad);
|
|
|
|
const stepbegin = Math.floor(xscale.invert(begin));
|
|
const stepend = Math.floor(xscale.invert(end));
|
|
let max = 0;
|
|
for (let i = stepbegin; i < stepend; i++) {
|
|
max = Math.max(max, max_at_time[i]);
|
|
}
|
|
plot.select_window(stepbegin, stepend, max);
|
|
});
|
|
mini_svg.call(brush);
|
|
return {};
|
|
}
|
|
|
|
function Legend(plot_svg, categories) {
|
|
const xstart = 100;
|
|
const ystart = 5;
|
|
plot_svg
|
|
.append('g')
|
|
.selectAll('rect')
|
|
.data(categories)
|
|
.enter()
|
|
.append('rect')
|
|
.attr('x', (c, i) => xstart)
|
|
.attr('y', (c, i) => ystart + i * 15)
|
|
.attr('width', 10)
|
|
.attr('height', 10)
|
|
.attr('fill', (c, i) => schemeTableau10[i % schemeTableau10.length]);
|
|
plot_svg
|
|
.append('g')
|
|
.selectAll('text')
|
|
.data(categories)
|
|
.enter()
|
|
.append('text')
|
|
.attr('x', (c, i) => xstart + 20)
|
|
.attr('y', (c, i) => ystart + i * 15 + 8)
|
|
.attr('font-family', 'helvetica')
|
|
.attr('font-size', 10)
|
|
.text(c => c);
|
|
return {};
|
|
}
|
|
|
|
function create_trace_view(
|
|
dst,
|
|
snapshot,
|
|
device,
|
|
plot_segments = false,
|
|
max_entries = 15000,
|
|
) {
|
|
const left_pad = 70;
|
|
const data = process_alloc_data(snapshot, device, plot_segments, max_entries);
|
|
dst.selectAll('svg').remove();
|
|
dst.selectAll('div').remove();
|
|
|
|
const d = dst.append('div');
|
|
d.append('input')
|
|
.attr('type', 'range')
|
|
.attr('min', 0)
|
|
.attr('max', data.elements_length)
|
|
.attr('value', max_entries)
|
|
.on('change', function () {
|
|
create_trace_view(dst, snapshot, device, plot_segments, this.value);
|
|
});
|
|
d.append('label').text('Detail');
|
|
|
|
const grid_container = dst
|
|
.append('div')
|
|
.attr(
|
|
'style',
|
|
'display: grid; grid-template-columns: 1fr; grid-template-rows: 10fr 1fr 8fr; height: 100%; gap: 10px',
|
|
);
|
|
|
|
const plot_svg = grid_container
|
|
.append('svg')
|
|
.attr('display', 'block')
|
|
.attr('viewBox', '0 0 1024 576')
|
|
.attr('preserveAspectRatio', 'none')
|
|
.attr('style', 'grid-column: 1; grid-row: 1; width: 100%; height: 100%;');
|
|
|
|
const plot = MemoryPlot(plot_svg, data, left_pad, 1024, 576);
|
|
|
|
if (snapshot.categories.length !== 0) {
|
|
Legend(plot_svg.append('g'), snapshot.categories);
|
|
}
|
|
|
|
const mini_svg = grid_container
|
|
.append('svg')
|
|
.attr('display', 'block')
|
|
.attr('viewBox', '0 0 1024 60')
|
|
.attr('preserveAspectRatio', 'none')
|
|
.attr('style', 'grid-column: 1; grid-row: 2; width: 100%; height: 100%;');
|
|
|
|
MiniMap(mini_svg, plot, data, left_pad, 1024);
|
|
const context_div = grid_container
|
|
.append('div')
|
|
.attr(
|
|
'style',
|
|
'grid-column: 1; grid-row: 3; width: 100%; height: 100%; overflow: auto;',
|
|
);
|
|
const delegate = ContextViewer(context_div.append('pre').text('none'), data);
|
|
plot.set_delegate(delegate);
|
|
}
|
|
|
|
function create_settings_view(dst, snapshot, device) {
|
|
dst.selectAll('svg').remove();
|
|
dst.selectAll('div').remove();
|
|
const settings_div = dst.append('div');
|
|
settings_div.append('p').text('CUDA Caching Allocator Settings:');
|
|
|
|
// Check if allocator_settings exists in snapshot
|
|
if ('allocator_settings' in snapshot) {
|
|
settings_div
|
|
.append('pre')
|
|
.text(JSON.stringify(snapshot.allocator_settings, null, 2));
|
|
} else {
|
|
settings_div.append('p').text('No allocator settings found.');
|
|
}
|
|
}
|
|
|
|
function unpickle(buffer) {
|
|
const bytebuffer = new Uint8Array(buffer);
|
|
const decoder = new TextDecoder();
|
|
|
|
const stack = [];
|
|
const marks = [];
|
|
const memo = [];
|
|
let offset = 0;
|
|
let memo_id = 0;
|
|
|
|
const APPENDS = 'e'.charCodeAt(0);
|
|
const BINGET = 'h'.charCodeAt(0);
|
|
const BININT = 'J'.charCodeAt(0);
|
|
const BININT1 = 'K'.charCodeAt(0);
|
|
const BININT2 = 'M'.charCodeAt(0);
|
|
const EMPTY_DICT = '}'.charCodeAt(0);
|
|
const EMPTY_LIST = ']'.charCodeAt(0);
|
|
const FRAME = 0x95;
|
|
const LONG1 = 0x8a;
|
|
const LONG_BINGET = 'j'.charCodeAt(0);
|
|
const MARK = '('.charCodeAt(0);
|
|
const MEMOIZE = 0x94;
|
|
const PROTO = 0x80;
|
|
const SETITEMS = 'u'.charCodeAt(0);
|
|
const SHORT_BINUNICODE = 0x8c;
|
|
const STOP = '.'.charCodeAt(0);
|
|
const TUPLE2 = 0x86;
|
|
const APPEND = 'a'.charCodeAt(0);
|
|
const NEWFALSE = 0x89;
|
|
const BINPUT = 'q'.charCodeAt(0);
|
|
const BINUNICODE = 'X'.charCodeAt(0);
|
|
const EMPTY_TUPLE = ')'.charCodeAt(0);
|
|
const NEWTRUE = 0x88;
|
|
const NONE = 'N'.charCodeAt(0);
|
|
const BINFLOAT = 'G'.charCodeAt(0);
|
|
const TUPLE = 't'.charCodeAt(0);
|
|
const TUPLE1 = 0x85;
|
|
const TUPLE3 = 0x87;
|
|
// untested
|
|
const LONG_BINPUT = 'r'.charCodeAt(0);
|
|
const LIST = 'l'.charCodeAt(0);
|
|
const DICT = 'd'.charCodeAt(0);
|
|
const SETITEM = 's'.charCodeAt(0);
|
|
|
|
const scratch_buffer = new ArrayBuffer(8);
|
|
const scratch_bytes = new Uint8Array(scratch_buffer);
|
|
const big = new BigInt64Array(scratch_buffer);
|
|
const float64 = new Float64Array(scratch_buffer);
|
|
|
|
function read_uint4() {
|
|
const n =
|
|
bytebuffer[offset] +
|
|
bytebuffer[offset + 1] * 256 +
|
|
bytebuffer[offset + 2] * 65536 +
|
|
bytebuffer[offset + 3] * 16777216;
|
|
offset += 4;
|
|
return n;
|
|
}
|
|
function setitems(d, mark) {
|
|
for (let i = mark; i < stack.length; i += 2) {
|
|
d[stack[i]] = stack[i + 1];
|
|
}
|
|
stack.splice(mark, Infinity);
|
|
}
|
|
|
|
while (true) {
|
|
const opcode = bytebuffer[offset++];
|
|
switch (opcode) {
|
|
case PROTO:
|
|
{
|
|
const version = bytebuffer[offset++];
|
|
if (version < 2 || version > 4) {
|
|
throw new Error(`Unhandled version ${version}`);
|
|
}
|
|
}
|
|
break;
|
|
case APPEND:
|
|
{
|
|
const v = stack.pop();
|
|
stack.at(-1).push(v);
|
|
}
|
|
break;
|
|
case APPENDS:
|
|
{
|
|
const mark = marks.pop();
|
|
const arr = stack[mark - 1];
|
|
arr.push(...stack.splice(mark, Infinity));
|
|
}
|
|
break;
|
|
case LIST:
|
|
case TUPLE:
|
|
{
|
|
const mark = marks.pop();
|
|
stack.push([...stack.splice(mark, Infinity)]);
|
|
}
|
|
break;
|
|
case NEWFALSE:
|
|
stack.push(false);
|
|
break;
|
|
case NEWTRUE:
|
|
stack.push(true);
|
|
break;
|
|
case NONE:
|
|
stack.push(null);
|
|
break;
|
|
case BINGET:
|
|
stack.push(memo[bytebuffer[offset++]]);
|
|
break;
|
|
case BININT:
|
|
{
|
|
let i32 = read_uint4();
|
|
if (i32 > 0x7fffffff) {
|
|
i32 -= 0x100000000;
|
|
}
|
|
stack.push(i32);
|
|
}
|
|
break;
|
|
case BININT1:
|
|
stack.push(bytebuffer[offset++]);
|
|
break;
|
|
case BININT2:
|
|
{
|
|
const v = bytebuffer[offset] + bytebuffer[offset + 1] * 256;
|
|
stack.push(v);
|
|
offset += 2;
|
|
}
|
|
break;
|
|
case EMPTY_DICT:
|
|
stack.push({});
|
|
break;
|
|
case EMPTY_LIST:
|
|
stack.push([]);
|
|
break;
|
|
case FRAME:
|
|
offset += 8;
|
|
break;
|
|
case LONG1:
|
|
{
|
|
const s = bytebuffer[offset++];
|
|
if (s <= 8) {
|
|
for (let i = 0; i < s; i++) {
|
|
scratch_bytes[i] = bytebuffer[offset++];
|
|
}
|
|
const fill = scratch_bytes[s - 1] >= 128 ? 0xff : 0x0;
|
|
for (let i = s; i < 8; i++) {
|
|
scratch_bytes[i] = fill;
|
|
}
|
|
stack.push(Number(big[0]));
|
|
} else { // BigInt
|
|
let scratch_bytes_unbounded = [];
|
|
for (let i = 0; i < s; i++) {
|
|
scratch_bytes_unbounded.push(bytebuffer[offset++]);
|
|
}
|
|
|
|
// BigInt can only convert from unsigned hex, thus we need to
|
|
// convert from twos-complement if negative
|
|
const negative = scratch_bytes_unbounded[s - 1] >= 128;
|
|
if (negative) {
|
|
// implements scratch_bytes_unbounded = ~scratch_bytes_unbounded + 1
|
|
// byte-by-byte.
|
|
let carry = 1;
|
|
for (let i = 0; i < s; i++) {
|
|
const twos_complement = (0xff ^ scratch_bytes_unbounded[i]) + carry;
|
|
carry = twos_complement > 0xff ? 1 : 0;
|
|
scratch_bytes_unbounded[i] = 0xff & twos_complement;
|
|
}
|
|
}
|
|
|
|
const hex_str = Array.from(scratch_bytes_unbounded.reverse(), byte => {
|
|
return byte.toString(16).padStart(2, '0');
|
|
}).join('');
|
|
|
|
const big_int = negative ? -BigInt(`0x${hex_str}`) : BigInt(`0x${hex_str}`);
|
|
stack.push(big_int);
|
|
}
|
|
}
|
|
break;
|
|
case LONG_BINGET:
|
|
{
|
|
const idx = read_uint4();
|
|
stack.push(memo[idx]);
|
|
}
|
|
break;
|
|
case MARK:
|
|
marks.push(stack.length);
|
|
break;
|
|
case MEMOIZE:
|
|
memo[memo_id++] = stack.at(-1);
|
|
break;
|
|
case BINPUT:
|
|
memo[bytebuffer[offset++]] = stack.at(-1);
|
|
break;
|
|
case LONG_BINPUT:
|
|
memo[read_uint4()] = stack.at(-1);
|
|
break;
|
|
case SETITEMS:
|
|
{
|
|
const mark = marks.pop();
|
|
const d = stack[mark - 1];
|
|
setitems(d, mark);
|
|
}
|
|
break;
|
|
case SETITEM: {
|
|
const v = stack.pop();
|
|
const k = stack.pop();
|
|
stack.at(-1)[k] = v;
|
|
break;
|
|
}
|
|
case DICT:
|
|
{
|
|
const mark = marks.pop();
|
|
const d = {};
|
|
setitems(d, mark);
|
|
stack.push(d);
|
|
}
|
|
break;
|
|
case SHORT_BINUNICODE:
|
|
{
|
|
const n = bytebuffer[offset++];
|
|
stack.push(decoder.decode(new Uint8Array(buffer, offset, n)));
|
|
offset += n;
|
|
}
|
|
break;
|
|
case BINUNICODE:
|
|
{
|
|
const n = read_uint4();
|
|
stack.push(decoder.decode(new Uint8Array(buffer, offset, n)));
|
|
offset += n;
|
|
}
|
|
break;
|
|
case STOP:
|
|
return stack.pop();
|
|
case EMPTY_TUPLE:
|
|
stack.push([]);
|
|
break;
|
|
case TUPLE1:
|
|
stack.push([stack.pop()]);
|
|
break;
|
|
case TUPLE2:
|
|
stack.push(stack.splice(-2, Infinity));
|
|
break;
|
|
case TUPLE3:
|
|
stack.push(stack.splice(-3, Infinity));
|
|
break;
|
|
case BINFLOAT:
|
|
for (let i = 7; i >= 0; i--) {
|
|
// stored in big-endian order
|
|
scratch_bytes[i] = bytebuffer[offset++];
|
|
}
|
|
stack.push(float64[0]);
|
|
break;
|
|
default:
|
|
throw new Error(`UNKNOWN OPCODE: ${opcode}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function decode_base64(input) {
|
|
function decode_char(i, shift) {
|
|
const nChr = input.charCodeAt(i);
|
|
const r =
|
|
nChr > 64 && nChr < 91
|
|
? nChr - 65
|
|
: nChr > 96 && nChr < 123
|
|
? nChr - 71
|
|
: nChr > 47 && nChr < 58
|
|
? nChr + 4
|
|
: nChr === 43
|
|
? 62
|
|
: nChr === 47
|
|
? 63
|
|
: 0;
|
|
return r << shift;
|
|
}
|
|
const output = new Uint8Array((input.length / 4) * 3);
|
|
for (let i = 0, j = 0; i < input.length; i += 4, j += 3) {
|
|
const u24 =
|
|
decode_char(i, 18) +
|
|
decode_char(i + 1, 12) +
|
|
decode_char(i + 2, 6) +
|
|
decode_char(i + 3);
|
|
output[j] = u24 >> 16;
|
|
output[j + 1] = (u24 >> 8) & 0xff;
|
|
output[j + 2] = u24 & 0xff;
|
|
}
|
|
return output.buffer;
|
|
}
|
|
|
|
const kinds = {
|
|
'Active Memory Timeline': create_trace_view,
|
|
'Allocator State History': create_segment_view,
|
|
'Active Cached Segment Timeline': (dst, snapshot, device) =>
|
|
create_trace_view(dst, snapshot, device, true),
|
|
'Allocator Settings': create_settings_view,
|
|
};
|
|
|
|
const snapshot_cache = {};
|
|
const snapshot_to_loader = {};
|
|
const snapshot_to_url = {};
|
|
const selection_to_div = {};
|
|
|
|
const style = `
|
|
pre {
|
|
margin: 0px;
|
|
}
|
|
html, body {
|
|
height: 100%;
|
|
overflow: clip;
|
|
}`;
|
|
|
|
const head = d3.select('head');
|
|
head.append('style').text(style);
|
|
const body = d3.select('body');
|
|
const snapshot_select = body.append('select');
|
|
const view = body.append('select');
|
|
for (const x in kinds) {
|
|
view.append('option').text(x);
|
|
}
|
|
const gpu = body.append('select');
|
|
|
|
function unpickle_and_annotate(data) {
|
|
data = unpickle(data);
|
|
console.log(data);
|
|
annotate_snapshot(data);
|
|
return data;
|
|
}
|
|
|
|
function snapshot_change(f) {
|
|
const view_value = view.node().value;
|
|
let device = Number(gpu.node().value);
|
|
const snapshot = snapshot_cache[f];
|
|
gpu.selectAll('option').remove();
|
|
const has_segments = {};
|
|
for (const s of snapshot.segments) {
|
|
has_segments[s.device] = true;
|
|
}
|
|
let device_valid = false;
|
|
for (const [i, trace] of snapshot.device_traces.entries()) {
|
|
if (trace.length > 0 || i in has_segments) {
|
|
gpu.append('option').text(i);
|
|
if (i === device) {
|
|
device_valid = true;
|
|
gpu.node().selectedIndex = gpu.node().children.length - 1;
|
|
}
|
|
}
|
|
}
|
|
if (!device_valid) {
|
|
device = Number(gpu.node().value);
|
|
}
|
|
const key = [f, view_value, device];
|
|
if (!(key in selection_to_div)) {
|
|
selection_to_div[key] = d3.select('body').append('div');
|
|
kinds[view_value](selection_to_div[key], snapshot, device);
|
|
}
|
|
const selected_div = selection_to_div[key];
|
|
|
|
selected_div.attr('style', 'display: float; height: 100%');
|
|
}
|
|
|
|
function selected_change() {
|
|
for (const d of Object.values(selection_to_div)) {
|
|
d.attr('style', 'display: none; height: 100%');
|
|
}
|
|
const f = snapshot_select.node().value;
|
|
if (f === '') {
|
|
return;
|
|
}
|
|
if (!(f in snapshot_cache)) {
|
|
snapshot_to_loader[f](f);
|
|
} else {
|
|
snapshot_change(f);
|
|
}
|
|
}
|
|
|
|
snapshot_select.on('change', selected_change);
|
|
view.on('change', selected_change);
|
|
gpu.on('change', selected_change);
|
|
|
|
body.on('dragover', e => {
|
|
event.preventDefault();
|
|
});
|
|
|
|
body.on('drop', () => {
|
|
console.log(event.dataTransfer.files);
|
|
Array.from(event.dataTransfer.files).forEach(file => {
|
|
add_snapshot(file.name, unique_name => {
|
|
const reader = new FileReader();
|
|
reader.onload = e => {
|
|
finished_loading(unique_name, e.target.result);
|
|
};
|
|
reader.readAsArrayBuffer(file);
|
|
});
|
|
});
|
|
event.preventDefault();
|
|
snapshot_select.node().selectedIndex =
|
|
snapshot_select.node().options.length - 1;
|
|
selected_change();
|
|
});
|
|
|
|
selection_to_div[''] = body
|
|
.append('div')
|
|
.text(
|
|
'Drag and drop a file to load a local snapshot. No data from the snapshot is uploaded.',
|
|
);
|
|
|
|
let next_unique_n = 1;
|
|
function add_snapshot(name, loader) {
|
|
if (name in snapshot_to_loader) {
|
|
name = `${name} (${next_unique_n++})`;
|
|
}
|
|
snapshot_select.append('option').text(name);
|
|
snapshot_to_loader[name] = loader;
|
|
}
|
|
|
|
function finished_loading(name, data) {
|
|
snapshot_cache[name] = unpickle_and_annotate(data);
|
|
snapshot_change(name);
|
|
}
|
|
|
|
export function add_remote_files(files) {
|
|
files.forEach(f =>
|
|
add_snapshot(f.name, unique_name => {
|
|
console.log('fetching', f.url);
|
|
fetch(f.url)
|
|
.then(x => x.arrayBuffer())
|
|
.then(data => finished_loading(unique_name, data));
|
|
}),
|
|
);
|
|
if (files.length > 0) {
|
|
selected_change();
|
|
}
|
|
}
|
|
|
|
export function add_local_files(files, view_value) {
|
|
view.node().value = view_value;
|
|
files.forEach(f =>
|
|
add_snapshot(f.name, unique_name => {
|
|
finished_loading(unique_name, decode_base64(f.base64));
|
|
}),
|
|
);
|
|
if (files.length > 0) {
|
|
selected_change();
|
|
}
|
|
}
|