@@ -7,7 +7,10 @@ def _extract_frames(frames: list[dict]) -> list[str]:
77 fn = f .get ("filename" , "" )
88 name = f .get ("name" , "" )
99 line = f .get ("line" , 0 )
10- if not name or name in ("torch::unwind::unwind()" , "torch::CapturedTraceback::gather(bool, bool, bool)" ):
10+ if not name or name in (
11+ "torch::unwind::unwind()" ,
12+ "torch::CapturedTraceback::gather(bool, bool, bool)" ,
13+ ):
1114 continue
1215 if fn and fn != "??" and fn != "" :
1316 result .append (f"{ fn } :{ line } { name } " )
@@ -48,7 +51,6 @@ def get_stack_idx(frames: list[dict]) -> int:
4851 reserved = 0
4952 hwm = 0
5053 timeline : list [dict ] = []
51- active_by_addr : dict [int , dict ] = {}
5254 last_time = 0
5355
5456 current_stack : list [int ] = []
@@ -70,12 +72,14 @@ def get_stack_idx(frames: list[dict]) -> int:
7072 allocated += size
7173 offset = allocated - size
7274 alloc_id = len (alloc_polys )
73- alloc_polys .append ({
74- "si" : si ,
75- "s" : size ,
76- "ts" : [timestep ],
77- "offsets" : [offset ],
78- })
75+ alloc_polys .append (
76+ {
77+ "si" : si ,
78+ "s" : size ,
79+ "ts" : [timestep ],
80+ "offsets" : [offset ],
81+ }
82+ )
7983 current_stack .append (alloc_id )
8084 alloc_id_by_addr [addr ] = alloc_id
8185 timestep += 1
@@ -110,10 +114,17 @@ def get_stack_idx(frames: list[dict]) -> int:
110114 pass
111115
112116 hwm = max (hwm , allocated )
113- timeline .append ({
114- "t" : time_us , "a" : allocated , "r" : reserved ,
115- "h" : hwm , "act" : action , "s" : size , "si" : si ,
116- })
117+ timeline .append (
118+ {
119+ "t" : time_us ,
120+ "a" : allocated ,
121+ "r" : reserved ,
122+ "h" : hwm ,
123+ "act" : action ,
124+ "s" : size ,
125+ "si" : si ,
126+ }
127+ )
117128
118129 for alloc_id in current_stack :
119130 poly = alloc_polys [alloc_id ]
@@ -149,8 +160,7 @@ def generate_memory_html(
149160 }
150161
151162 return (
152- _MEMORY_VIZ_TEMPLATE
153- .replace ("__TITLE__" , title )
163+ _MEMORY_VIZ_TEMPLATE .replace ("__TITLE__" , title )
154164 .replace ("__TIMELINE__" , json .dumps (timeline ))
155165 .replace ("__ALLOCS__" , json .dumps (alloc_polys ))
156166 .replace ("__STACKS__" , json .dumps (stacks ))
@@ -566,18 +576,60 @@ def generate_memory_html(
566576 .extent([[0, 0], [width, height]])
567577 .on('zoom', (event) => updateChart(event.transform));
568578
569- chartArea.append('rect')
579+ const zoomRect = chartArea.append('rect')
570580 .attr('width', width).attr('height', height)
571581 .attr('fill', 'none').attr('pointer-events', 'all')
572582 .call(zoom);
573583
574584// Raise polys above the zoom rect
575585polysG.raise();
576586
587+ // WASD / arrow key navigation (Perfetto-style)
588+ const PAN_STEP = 0.15;
589+ const ZOOM_STEP = 1.3;
590+
591+ document.addEventListener('keydown', function(event) {
592+ if (event.target.tagName === 'INPUT') return;
593+ const k = event.key.toLowerCase();
594+ let t = currentTransform;
595+
596+ switch (k) {
597+ case 'a':
598+ case 'arrowleft':
599+ t = t.translate(width * PAN_STEP, 0);
600+ break;
601+ case 'd':
602+ case 'arrowright':
603+ t = t.translate(-width * PAN_STEP, 0);
604+ break;
605+ case 'w':
606+ case 'arrowup':
607+ t = d3.zoomIdentity
608+ .translate(width / 2, 0)
609+ .scale(t.k * ZOOM_STEP)
610+ .translate(-width / 2, 0)
611+ .translate(t.x / (t.k * ZOOM_STEP), 0);
612+ break;
613+ case 's':
614+ case 'arrowdown':
615+ t = d3.zoomIdentity
616+ .translate(width / 2, 0)
617+ .scale(t.k / ZOOM_STEP)
618+ .translate(-width / 2, 0)
619+ .translate(t.x / (t.k / ZOOM_STEP), 0);
620+ break;
621+ default:
622+ return;
623+ }
624+
625+ event.preventDefault();
626+ zoomRect.call(zoom.transform, t);
627+ });
628+
577629document.getElementById('hwm-toggle').onchange = function() {
578630 hwmG.style('display', this.checked ? null : 'none');
579631};
580632</script>
581633</body>
582634</html>
583- """ ;
635+ """
0 commit comments