diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/c/generated_tracepoints.c | 46 | ||||
| -rw-r--r-- | internal/flamegraph/nativesvg.go | 11 | ||||
| -rw-r--r-- | internal/flamegraph/svgwriter.go | 16 | ||||
| -rw-r--r-- | internal/flamegraph/svgwriter_js.go | 155 | ||||
| -rw-r--r-- | internal/flamegraph/svgwriter_jscode.go | 155 | ||||
| -rw-r--r-- | internal/flamegraph/tool.go | 4 | ||||
| -rw-r--r-- | internal/flamegraph/webserver.go | 108 | ||||
| -rw-r--r-- | internal/ior.go | 10 |
8 files changed, 453 insertions, 52 deletions
diff --git a/internal/c/generated_tracepoints.c b/internal/c/generated_tracepoints.c index 06f8c39..b4d4e0f 100644 --- a/internal/c/generated_tracepoints.c +++ b/internal/c/generated_tracepoints.c @@ -1,7 +1,7 @@ // Code generated - don't change manually! -/// Ignoring sys_enter_accept sys_exit_accept as possibly not file I/O related /// Ignoring sys_enter_accept4 sys_exit_accept4 as possibly not file I/O related +/// Ignoring sys_enter_accept sys_exit_accept as possibly not file I/O related /// Ignoring sys_enter_acct sys_exit_acct as possibly not file I/O related /// Ignoring sys_enter_add_key sys_exit_add_key as possibly not file I/O related /// Ignoring sys_enter_adjtimex sys_exit_adjtimex as possibly not file I/O related @@ -17,20 +17,20 @@ /// Ignoring sys_enter_clock_gettime sys_exit_clock_gettime as possibly not file I/O related /// Ignoring sys_enter_clock_nanosleep sys_exit_clock_nanosleep as possibly not file I/O related /// Ignoring sys_enter_clock_settime sys_exit_clock_settime as possibly not file I/O related -/// Ignoring sys_enter_clone sys_exit_clone as possibly not file I/O related /// Ignoring sys_enter_clone3 sys_exit_clone3 as possibly not file I/O related +/// Ignoring sys_enter_clone sys_exit_clone as possibly not file I/O related /// Ignoring sys_enter_connect sys_exit_connect as possibly not file I/O related /// Ignoring sys_enter_delete_module sys_exit_delete_module as possibly not file I/O related -/// Ignoring sys_enter_epoll_create sys_exit_epoll_create as possibly not file I/O related /// Ignoring sys_enter_epoll_create1 sys_exit_epoll_create1 as possibly not file I/O related +/// Ignoring sys_enter_epoll_create sys_exit_epoll_create as possibly not file I/O related /// Ignoring sys_enter_epoll_ctl sys_exit_epoll_ctl as possibly not file I/O related -/// Ignoring sys_enter_epoll_pwait sys_exit_epoll_pwait as possibly not file I/O related /// Ignoring sys_enter_epoll_pwait2 sys_exit_epoll_pwait2 as possibly not file I/O related +/// Ignoring sys_enter_epoll_pwait sys_exit_epoll_pwait as possibly not file I/O related /// Ignoring sys_enter_epoll_wait sys_exit_epoll_wait as possibly not file I/O related -/// Ignoring sys_enter_eventfd sys_exit_eventfd as possibly not file I/O related /// Ignoring sys_enter_eventfd2 sys_exit_eventfd2 as possibly not file I/O related -/// Ignoring sys_enter_execve sys_exit_execve as possibly not file I/O related +/// Ignoring sys_enter_eventfd sys_exit_eventfd as possibly not file I/O related /// Ignoring sys_enter_execveat sys_exit_execveat as possibly not file I/O related +/// Ignoring sys_enter_execve sys_exit_execve as possibly not file I/O related /// Ignoring sys_enter_exit sys_exit_exit as possibly not file I/O related /// Ignoring sys_enter_exit_group sys_exit_exit_group as possibly not file I/O related /// Ignoring sys_enter_fanotify_init sys_exit_fanotify_init as possibly not file I/O related @@ -42,14 +42,13 @@ /// Ignoring sys_enter_futex_wait sys_exit_futex_wait as possibly not file I/O related /// Ignoring sys_enter_futex_waitv sys_exit_futex_waitv as possibly not file I/O related /// Ignoring sys_enter_futex_wake sys_exit_futex_wake as possibly not file I/O related -/// Ignoring sys_enter_get_mempolicy sys_exit_get_mempolicy as possibly not file I/O related -/// Ignoring sys_enter_get_robust_list sys_exit_get_robust_list as possibly not file I/O related /// Ignoring sys_enter_getcpu sys_exit_getcpu as possibly not file I/O related /// Ignoring sys_enter_getegid sys_exit_getegid as possibly not file I/O related /// Ignoring sys_enter_geteuid sys_exit_geteuid as possibly not file I/O related /// Ignoring sys_enter_getgid sys_exit_getgid as possibly not file I/O related /// Ignoring sys_enter_getgroups sys_exit_getgroups as possibly not file I/O related /// Ignoring sys_enter_getitimer sys_exit_getitimer as possibly not file I/O related +/// Ignoring sys_enter_get_mempolicy sys_exit_get_mempolicy as possibly not file I/O related /// Ignoring sys_enter_getpeername sys_exit_getpeername as possibly not file I/O related /// Ignoring sys_enter_getpgid sys_exit_getpgid as possibly not file I/O related /// Ignoring sys_enter_getpgrp sys_exit_getpgrp as possibly not file I/O related @@ -60,6 +59,7 @@ /// Ignoring sys_enter_getresgid sys_exit_getresgid as possibly not file I/O related /// Ignoring sys_enter_getresuid sys_exit_getresuid as possibly not file I/O related /// Ignoring sys_enter_getrlimit sys_exit_getrlimit as possibly not file I/O related +/// Ignoring sys_enter_get_robust_list sys_exit_get_robust_list as possibly not file I/O related /// Ignoring sys_enter_getrusage sys_exit_getrusage as possibly not file I/O related /// Ignoring sys_enter_getsid sys_exit_getsid as possibly not file I/O related /// Ignoring sys_enter_getsockname sys_exit_getsockname as possibly not file I/O related @@ -69,8 +69,8 @@ /// Ignoring sys_enter_getuid sys_exit_getuid as possibly not file I/O related /// Ignoring sys_enter_init_module sys_exit_init_module as possibly not file I/O related /// Ignoring sys_enter_inotify_add_watch sys_exit_inotify_add_watch as possibly not file I/O related -/// Ignoring sys_enter_inotify_init sys_exit_inotify_init as possibly not file I/O related /// Ignoring sys_enter_inotify_init1 sys_exit_inotify_init1 as possibly not file I/O related +/// Ignoring sys_enter_inotify_init sys_exit_inotify_init as possibly not file I/O related /// Ignoring sys_enter_inotify_rm_watch sys_exit_inotify_rm_watch as possibly not file I/O related /// Ignoring sys_enter_ioperm sys_exit_ioperm as possibly not file I/O related /// Ignoring sys_enter_iopl sys_exit_iopl as possibly not file I/O related @@ -97,11 +97,11 @@ /// Ignoring sys_enter_memfd_secret sys_exit_memfd_secret as possibly not file I/O related /// Ignoring sys_enter_migrate_pages sys_exit_migrate_pages as possibly not file I/O related /// Ignoring sys_enter_mincore sys_exit_mincore as possibly not file I/O related -/// Ignoring sys_enter_mknod sys_exit_mknod as possibly not file I/O related /// Ignoring sys_enter_mknodat sys_exit_mknodat as possibly not file I/O related -/// Ignoring sys_enter_mlock sys_exit_mlock as possibly not file I/O related +/// Ignoring sys_enter_mknod sys_exit_mknod as possibly not file I/O related /// Ignoring sys_enter_mlock2 sys_exit_mlock2 as possibly not file I/O related /// Ignoring sys_enter_mlockall sys_exit_mlockall as possibly not file I/O related +/// Ignoring sys_enter_mlock sys_exit_mlock as possibly not file I/O related /// Ignoring sys_enter_modify_ldt sys_exit_modify_ldt as possibly not file I/O related /// Ignoring sys_enter_mount sys_exit_mount as possibly not file I/O related /// Ignoring sys_enter_move_mount sys_exit_move_mount as possibly not file I/O related @@ -119,8 +119,8 @@ /// Ignoring sys_enter_msgget sys_exit_msgget as possibly not file I/O related /// Ignoring sys_enter_msgrcv sys_exit_msgrcv as possibly not file I/O related /// Ignoring sys_enter_msgsnd sys_exit_msgsnd as possibly not file I/O related -/// Ignoring sys_enter_munlock sys_exit_munlock as possibly not file I/O related /// Ignoring sys_enter_munlockall sys_exit_munlockall as possibly not file I/O related +/// Ignoring sys_enter_munlock sys_exit_munlock as possibly not file I/O related /// Ignoring sys_enter_munmap sys_exit_munmap as possibly not file I/O related /// Ignoring sys_enter_nanosleep sys_exit_nanosleep as possibly not file I/O related /// Ignoring sys_enter_newuname sys_exit_newuname as possibly not file I/O related @@ -129,8 +129,8 @@ /// Ignoring sys_enter_personality sys_exit_personality as possibly not file I/O related /// Ignoring sys_enter_pidfd_open sys_exit_pidfd_open as possibly not file I/O related /// Ignoring sys_enter_pidfd_send_signal sys_exit_pidfd_send_signal as possibly not file I/O related -/// Ignoring sys_enter_pipe sys_exit_pipe as possibly not file I/O related /// Ignoring sys_enter_pipe2 sys_exit_pipe2 as possibly not file I/O related +/// Ignoring sys_enter_pipe sys_exit_pipe as possibly not file I/O related /// Ignoring sys_enter_pivot_root sys_exit_pivot_root as possibly not file I/O related /// Ignoring sys_enter_pkey_alloc sys_exit_pkey_alloc as possibly not file I/O related /// Ignoring sys_enter_pkey_free sys_exit_pkey_free as possibly not file I/O related @@ -162,11 +162,11 @@ /// Ignoring sys_enter_rt_sigsuspend sys_exit_rt_sigsuspend as possibly not file I/O related /// Ignoring sys_enter_rt_sigtimedwait sys_exit_rt_sigtimedwait as possibly not file I/O related /// Ignoring sys_enter_rt_tgsigqueueinfo sys_exit_rt_tgsigqueueinfo as possibly not file I/O related -/// Ignoring sys_enter_sched_get_priority_max sys_exit_sched_get_priority_max as possibly not file I/O related -/// Ignoring sys_enter_sched_get_priority_min sys_exit_sched_get_priority_min as possibly not file I/O related /// Ignoring sys_enter_sched_getaffinity sys_exit_sched_getaffinity as possibly not file I/O related /// Ignoring sys_enter_sched_getattr sys_exit_sched_getattr as possibly not file I/O related /// Ignoring sys_enter_sched_getparam sys_exit_sched_getparam as possibly not file I/O related +/// Ignoring sys_enter_sched_get_priority_max sys_exit_sched_get_priority_max as possibly not file I/O related +/// Ignoring sys_enter_sched_get_priority_min sys_exit_sched_get_priority_min as possibly not file I/O related /// Ignoring sys_enter_sched_getscheduler sys_exit_sched_getscheduler as possibly not file I/O related /// Ignoring sys_enter_sched_rr_get_interval sys_exit_sched_rr_get_interval as possibly not file I/O related /// Ignoring sys_enter_sched_setaffinity sys_exit_sched_setaffinity as possibly not file I/O related @@ -184,10 +184,6 @@ /// Ignoring sys_enter_sendmmsg sys_exit_sendmmsg as possibly not file I/O related /// Ignoring sys_enter_sendmsg sys_exit_sendmsg as possibly not file I/O related /// Ignoring sys_enter_sendto sys_exit_sendto as possibly not file I/O related -/// Ignoring sys_enter_set_mempolicy sys_exit_set_mempolicy as possibly not file I/O related -/// Ignoring sys_enter_set_mempolicy_home_node sys_exit_set_mempolicy_home_node as possibly not file I/O related -/// Ignoring sys_enter_set_robust_list sys_exit_set_robust_list as possibly not file I/O related -/// Ignoring sys_enter_set_tid_address sys_exit_set_tid_address as possibly not file I/O related /// Ignoring sys_enter_setdomainname sys_exit_setdomainname as possibly not file I/O related /// Ignoring sys_enter_setfsgid sys_exit_setfsgid as possibly not file I/O related /// Ignoring sys_enter_setfsuid sys_exit_setfsuid as possibly not file I/O related @@ -195,6 +191,8 @@ /// Ignoring sys_enter_setgroups sys_exit_setgroups as possibly not file I/O related /// Ignoring sys_enter_sethostname sys_exit_sethostname as possibly not file I/O related /// Ignoring sys_enter_setitimer sys_exit_setitimer as possibly not file I/O related +/// Ignoring sys_enter_set_mempolicy sys_exit_set_mempolicy as possibly not file I/O related +/// Ignoring sys_enter_set_mempolicy_home_node sys_exit_set_mempolicy_home_node as possibly not file I/O related /// Ignoring sys_enter_setns sys_exit_setns as possibly not file I/O related /// Ignoring sys_enter_setpgid sys_exit_setpgid as possibly not file I/O related /// Ignoring sys_enter_setpriority sys_exit_setpriority as possibly not file I/O related @@ -203,8 +201,10 @@ /// Ignoring sys_enter_setresuid sys_exit_setresuid as possibly not file I/O related /// Ignoring sys_enter_setreuid sys_exit_setreuid as possibly not file I/O related /// Ignoring sys_enter_setrlimit sys_exit_setrlimit as possibly not file I/O related +/// Ignoring sys_enter_set_robust_list sys_exit_set_robust_list as possibly not file I/O related /// Ignoring sys_enter_setsid sys_exit_setsid as possibly not file I/O related /// Ignoring sys_enter_setsockopt sys_exit_setsockopt as possibly not file I/O related +/// Ignoring sys_enter_set_tid_address sys_exit_set_tid_address as possibly not file I/O related /// Ignoring sys_enter_settimeofday sys_exit_settimeofday as possibly not file I/O related /// Ignoring sys_enter_setuid sys_exit_setuid as possibly not file I/O related /// Ignoring sys_enter_shmat sys_exit_shmat as possibly not file I/O related @@ -213,8 +213,8 @@ /// Ignoring sys_enter_shmget sys_exit_shmget as possibly not file I/O related /// Ignoring sys_enter_shutdown sys_exit_shutdown as possibly not file I/O related /// Ignoring sys_enter_sigaltstack sys_exit_sigaltstack as possibly not file I/O related -/// Ignoring sys_enter_signalfd sys_exit_signalfd as possibly not file I/O related /// Ignoring sys_enter_signalfd4 sys_exit_signalfd4 as possibly not file I/O related +/// Ignoring sys_enter_signalfd sys_exit_signalfd as possibly not file I/O related /// Ignoring sys_enter_socket sys_exit_socket as possibly not file I/O related /// Ignoring sys_enter_socketpair sys_exit_socketpair as possibly not file I/O related /// Ignoring sys_enter_splice sys_exit_splice as possibly not file I/O related @@ -228,12 +228,12 @@ /// Ignoring sys_enter_time sys_exit_time as possibly not file I/O related /// Ignoring sys_enter_timer_create sys_exit_timer_create as possibly not file I/O related /// Ignoring sys_enter_timer_delete sys_exit_timer_delete as possibly not file I/O related -/// Ignoring sys_enter_timer_getoverrun sys_exit_timer_getoverrun as possibly not file I/O related -/// Ignoring sys_enter_timer_gettime sys_exit_timer_gettime as possibly not file I/O related -/// Ignoring sys_enter_timer_settime sys_exit_timer_settime as possibly not file I/O related /// Ignoring sys_enter_timerfd_create sys_exit_timerfd_create as possibly not file I/O related /// Ignoring sys_enter_timerfd_gettime sys_exit_timerfd_gettime as possibly not file I/O related /// Ignoring sys_enter_timerfd_settime sys_exit_timerfd_settime as possibly not file I/O related +/// Ignoring sys_enter_timer_getoverrun sys_exit_timer_getoverrun as possibly not file I/O related +/// Ignoring sys_enter_timer_gettime sys_exit_timer_gettime as possibly not file I/O related +/// Ignoring sys_enter_timer_settime sys_exit_timer_settime as possibly not file I/O related /// Ignoring sys_enter_times sys_exit_times as possibly not file I/O related /// Ignoring sys_enter_tkill sys_exit_tkill as possibly not file I/O related /// Ignoring sys_enter_umask sys_exit_umask as possibly not file I/O related diff --git a/internal/flamegraph/nativesvg.go b/internal/flamegraph/nativesvg.go index 2c76a7d..de0364b 100644 --- a/internal/flamegraph/nativesvg.go +++ b/internal/flamegraph/nativesvg.go @@ -23,7 +23,7 @@ func NewNativeSVG(fields []string, countField string) NativeSVG { } } -func (n NativeSVG) WriteSVGFromFile(iorDataFile string) error { +func (n NativeSVG) WriteSVGFromFile(iorDataFile string) (string, error) { outFile := fmt.Sprintf("%s.%s-by-%s.svg", strings.TrimSuffix(iorDataFile, ".ior.zst"), strings.Join(n.fields, ":"), @@ -32,16 +32,19 @@ func (n NativeSVG) WriteSVGFromFile(iorDataFile string) error { iod, err := newIorDataFromFile(iorDataFile) if err != nil { - return fmt.Errorf("read ior data: %w", err) + return outFile, fmt.Errorf("read ior data: %w", err) } fd, err := os.Create(outFile) if err != nil { - return fmt.Errorf("create output %s: %w", outFile, err) + return outFile, fmt.Errorf("create output %s: %w", outFile, err) } defer fd.Close() - return n.WriteSVGFromIter(iod.iter(), fd) + if err := n.WriteSVGFromIter(iod.iter(), fd); err != nil { + return outFile, err + } + return outFile, nil } func (n NativeSVG) WriteSVGFromIter(records iter.Seq[IterRecord], w io.Writer) error { diff --git a/internal/flamegraph/svgwriter.go b/internal/flamegraph/svgwriter.go index 26e203e..5ab8e50 100644 --- a/internal/flamegraph/svgwriter.go +++ b/internal/flamegraph/svgwriter.go @@ -68,7 +68,7 @@ func writeSVGHeader(bw *bufio.Writer, cfg SVGConfig, height int) error { if err != nil { return err } - _, err = fmt.Fprintf(bw, `<g class="controls"><text x="10" y="42" onclick="fgSearch()">Search</text><text x="80" y="42" onclick="fgResetSearch()">Reset Search</text><text x="190" y="42" onclick="fgResetZoom()">Reset Zoom</text><text id="fg-info" x="320" y="42"></text></g>`+"\n") + _, err = fmt.Fprintf(bw, `<g class="controls"><text x="10" y="42" onclick="fgSearch()">Search</text><text x="80" y="42" onclick="fgResetSearch()">Reset Search</text><text x="190" y="42" onclick="fgUndoZoom()">Undo Zoom</text><text x="280" y="42" onclick="fgResetZoom()">Reset Zoom</text><text id="fg-info" x="390" y="42"></text></g>`+"\n") return err } @@ -113,11 +113,14 @@ func writeFrame(bw *bufio.Writer, name, title, fill string, x, y, w, h float64, svgEscape(title), x, y, w, h, fill); err != nil { return err } - if w > float64(fontSize*2) { - _, err = fmt.Fprintf(bw, `<text x="%.3f" y="%.3f">%s</text>`+"\n", x+3, y+float64(fontSize), svgEscape(name)) - if err != nil { - return err - } + labelStyle := "" + if w <= float64(fontSize*2) { + labelStyle = ` style="display:none"` + } + _, err = fmt.Fprintf(bw, `<text x="%.3f" y="%.3f"%s>%s</text>`+"\n", + x+3, y+float64(fontSize), labelStyle, svgEscape(name)) + if err != nil { + return err } _, err = fmt.Fprintln(bw, "</g>") return err @@ -139,6 +142,7 @@ func flamegraphCSS(cfg SVGConfig) string { .controls text { font-size: %dpx; font-family: monospace; cursor: pointer; fill: #444; } .frame text { font-size: %dpx; font-family: monospace; pointer-events: none; fill: #111; } .frame rect { stroke: rgba(0,0,0,0.18); stroke-width: 0.5; } +.title, .controls text, .frame text { user-select: none; -webkit-user-select: none; } `, cfg.FontSize+2, cfg.FontSize, cfg.FontSize-1) } diff --git a/internal/flamegraph/svgwriter_js.go b/internal/flamegraph/svgwriter_js.go index 7b9183f..bf8bfd2 100644 --- a/internal/flamegraph/svgwriter_js.go +++ b/internal/flamegraph/svgwriter_js.go @@ -5,16 +5,33 @@ const fg = { frames: [], info: null, matchColor: "rgb(220, 30, 70)", + zoomStack: [], + zoomRange: null, + rootWidth: 0, }; function fgInit() { fg.frames = Array.from(document.querySelectorAll("g.frame")); fg.info = document.getElementById("fg-info"); + fg.rootWidth = fgDetectRootWidth(); fg.frames.forEach((frame) => { - frame.addEventListener("click", (ev) => fgZoom(ev.currentTarget)); + fgSnapshotOriginalGeometry(frame); + frame.addEventListener("click", (ev) => { + if (ev.detail > 1) return; + ev.stopPropagation(); + fgZoom(ev.currentTarget); + }); + frame.addEventListener("dblclick", (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + fgResetZoom(); + }); frame.addEventListener("mouseenter", (ev) => fgHover(ev.currentTarget)); }); - document.addEventListener("dblclick", () => fgResetZoom()); + document.addEventListener("dblclick", (ev) => { + ev.preventDefault(); + fgResetZoom(); + }); } function fgHover(frame) { @@ -24,15 +41,42 @@ function fgHover(frame) { } function fgZoom(frame) { - const x = Number(frame.dataset.x || "0"); - const w = Number(frame.dataset.w || "0"); + const x = fgOriginalX(frame); + const w = fgOriginalW(frame); if (w <= 0) return; - const end = x + w; + if (fg.zoomRange) { + fg.zoomStack.push(fg.zoomRange); + } + fg.zoomRange = { x: x, w: w, depth: Number(frame.dataset.depth || "0") }; + fgApplyZoom(); +} + +function fgApplyZoom() { + if (!fg.zoomRange) { + fg.frames.forEach((frame) => { + frame.style.display = ""; + }); + return; + } + const x = fg.zoomRange.x; + const end = x + fg.zoomRange.w; + const width = fg.zoomRange.w; + const minDepth = fg.zoomRange.depth; + const eps = 1e-6; + const scale = fg.rootWidth / width; fg.frames.forEach((other) => { - const ox = Number(other.dataset.x || "0"); - const ow = Number(other.dataset.w || "0"); - const sameBand = Number(other.dataset.depth || "0") >= Number(frame.dataset.depth || "0"); - if (sameBand && ox >= x && ox + ow <= end) { + const ox = fgOriginalX(other); + const ow = fgOriginalW(other); + const depth = Number(other.dataset.depth || "0"); + const inSelectedRange = ox >= x-eps && ox+ow <= end+eps; + const isAncestor = depth < minDepth && ox <= x+eps && ox+ow >= end-eps; + + if (isAncestor || (depth >= minDepth && inSelectedRange)) { + if (isAncestor) { + fgSetFrameGeometry(other, 0, fg.rootWidth); + } else { + fgSetFrameGeometry(other, (ox-x)*scale, ow*scale); + } other.style.display = ""; } else { other.style.display = "none"; @@ -40,8 +84,20 @@ function fgZoom(frame) { }); } +function fgUndoZoom() { + if (fg.zoomStack.length === 0) { + fgResetZoom(); + return; + } + fg.zoomRange = fg.zoomStack.pop(); + fgApplyZoom(); +} + function fgResetZoom() { + fg.zoomStack = []; + fg.zoomRange = null; fg.frames.forEach((frame) => { + fgRestoreFrameGeometry(frame); frame.style.display = ""; }); } @@ -71,5 +127,86 @@ function fgResetSearch() { }); } +function fgDetectRootWidth() { + let maxEnd = 0; + fg.frames.forEach((frame) => { + const x = Number(frame.dataset.x || "0"); + const w = Number(frame.dataset.w || "0"); + maxEnd = Math.max(maxEnd, x + w); + }); + return maxEnd; +} + +function fgSnapshotOriginalGeometry(frame) { + const rect = frame.querySelector("rect"); + const text = frame.querySelector("text"); + frame.dataset.ox = frame.dataset.x || "0"; + frame.dataset.ow = frame.dataset.w || "0"; + if (rect) { + rect.dataset.ox = rect.getAttribute("x") || "0"; + rect.dataset.ow = rect.getAttribute("width") || "0"; + } + if (text) { + text.dataset.ox = text.getAttribute("x") || "0"; + text.dataset.hidden = text.style.display === "none" ? "1" : "0"; + text.dataset.full = text.textContent || frame.dataset.name || ""; + } +} + +function fgOriginalX(frame) { + return Number(frame.dataset.ox || frame.dataset.x || "0"); +} + +function fgOriginalW(frame) { + return Number(frame.dataset.ow || frame.dataset.w || "0"); +} + +function fgSetFrameGeometry(frame, x, w) { + const rect = frame.querySelector("rect"); + const text = frame.querySelector("text"); + if (rect) { + rect.setAttribute("x", String(x)); + rect.setAttribute("width", String(w)); + } + if (text) { + text.setAttribute("x", String(x + 3)); + fgFitLabel(text, w); + } +} + +function fgRestoreFrameGeometry(frame) { + const rect = frame.querySelector("rect"); + const text = frame.querySelector("text"); + if (rect) { + rect.setAttribute("x", rect.dataset.ox || "0"); + rect.setAttribute("width", rect.dataset.ow || "0"); + } + if (text) { + text.setAttribute("x", text.dataset.ox || "0"); + if (text.dataset.hidden === "1") { + text.style.display = "none"; + text.textContent = text.dataset.full || ""; + } else { + fgFitLabel(text, Number(rect ? (rect.dataset.ow || "0") : "0")); + } + } +} + +function fgFitLabel(text, width) { + const full = text.dataset.full || text.textContent || ""; + const maxChars = Math.floor((width - 6) / 7); + if (maxChars < 3) { + text.style.display = "none"; + text.textContent = full; + return; + } + text.style.display = ""; + if (full.length <= maxChars) { + text.textContent = full; + return; + } + text.textContent = full.slice(0, maxChars - 1) + "…"; +} + window.addEventListener("DOMContentLoaded", fgInit); ` diff --git a/internal/flamegraph/svgwriter_jscode.go b/internal/flamegraph/svgwriter_jscode.go index 52f8818..3ac00fd 100644 --- a/internal/flamegraph/svgwriter_jscode.go +++ b/internal/flamegraph/svgwriter_jscode.go @@ -7,16 +7,33 @@ const fg = { frames: [], info: null, matchColor: "rgb(220, 30, 70)", + zoomStack: [], + zoomRange: null, + rootWidth: 0, }; function fgInit() { fg.frames = Array.from(document.querySelectorAll("g.frame")); fg.info = document.getElementById("fg-info"); + fg.rootWidth = fgDetectRootWidth(); fg.frames.forEach((frame) => { - frame.addEventListener("click", (ev) => fgZoom(ev.currentTarget)); + fgSnapshotOriginalGeometry(frame); + frame.addEventListener("click", (ev) => { + if (ev.detail > 1) return; + ev.stopPropagation(); + fgZoom(ev.currentTarget); + }); + frame.addEventListener("dblclick", (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + fgResetZoom(); + }); frame.addEventListener("mouseenter", (ev) => fgHover(ev.currentTarget)); }); - document.addEventListener("dblclick", () => fgResetZoom()); + document.addEventListener("dblclick", (ev) => { + ev.preventDefault(); + fgResetZoom(); + }); } function fgHover(frame) { @@ -26,15 +43,42 @@ function fgHover(frame) { } function fgZoom(frame) { - const x = Number(frame.dataset.x || "0"); - const w = Number(frame.dataset.w || "0"); + const x = fgOriginalX(frame); + const w = fgOriginalW(frame); if (w <= 0) return; - const end = x + w; + if (fg.zoomRange) { + fg.zoomStack.push(fg.zoomRange); + } + fg.zoomRange = { x: x, w: w, depth: Number(frame.dataset.depth || "0") }; + fgApplyZoom(); +} + +function fgApplyZoom() { + if (!fg.zoomRange) { + fg.frames.forEach((frame) => { + frame.style.display = ""; + }); + return; + } + const x = fg.zoomRange.x; + const end = x + fg.zoomRange.w; + const width = fg.zoomRange.w; + const minDepth = fg.zoomRange.depth; + const eps = 1e-6; + const scale = fg.rootWidth / width; fg.frames.forEach((other) => { - const ox = Number(other.dataset.x || "0"); - const ow = Number(other.dataset.w || "0"); - const sameBand = Number(other.dataset.depth || "0") >= Number(frame.dataset.depth || "0"); - if (sameBand && ox >= x && ox + ow <= end) { + const ox = fgOriginalX(other); + const ow = fgOriginalW(other); + const depth = Number(other.dataset.depth || "0"); + const inSelectedRange = ox >= x-eps && ox+ow <= end+eps; + const isAncestor = depth < minDepth && ox <= x+eps && ox+ow >= end-eps; + + if (isAncestor || (depth >= minDepth && inSelectedRange)) { + if (isAncestor) { + fgSetFrameGeometry(other, 0, fg.rootWidth); + } else { + fgSetFrameGeometry(other, (ox-x)*scale, ow*scale); + } other.style.display = ""; } else { other.style.display = "none"; @@ -42,8 +86,20 @@ function fgZoom(frame) { }); } +function fgUndoZoom() { + if (fg.zoomStack.length === 0) { + fgResetZoom(); + return; + } + fg.zoomRange = fg.zoomStack.pop(); + fgApplyZoom(); +} + function fgResetZoom() { + fg.zoomStack = []; + fg.zoomRange = null; fg.frames.forEach((frame) => { + fgRestoreFrameGeometry(frame); frame.style.display = ""; }); } @@ -73,5 +129,86 @@ function fgResetSearch() { }); } +function fgDetectRootWidth() { + let maxEnd = 0; + fg.frames.forEach((frame) => { + const x = Number(frame.dataset.x || "0"); + const w = Number(frame.dataset.w || "0"); + maxEnd = Math.max(maxEnd, x + w); + }); + return maxEnd; +} + +function fgSnapshotOriginalGeometry(frame) { + const rect = frame.querySelector("rect"); + const text = frame.querySelector("text"); + frame.dataset.ox = frame.dataset.x || "0"; + frame.dataset.ow = frame.dataset.w || "0"; + if (rect) { + rect.dataset.ox = rect.getAttribute("x") || "0"; + rect.dataset.ow = rect.getAttribute("width") || "0"; + } + if (text) { + text.dataset.ox = text.getAttribute("x") || "0"; + text.dataset.hidden = text.style.display === "none" ? "1" : "0"; + text.dataset.full = text.textContent || frame.dataset.name || ""; + } +} + +function fgOriginalX(frame) { + return Number(frame.dataset.ox || frame.dataset.x || "0"); +} + +function fgOriginalW(frame) { + return Number(frame.dataset.ow || frame.dataset.w || "0"); +} + +function fgSetFrameGeometry(frame, x, w) { + const rect = frame.querySelector("rect"); + const text = frame.querySelector("text"); + if (rect) { + rect.setAttribute("x", String(x)); + rect.setAttribute("width", String(w)); + } + if (text) { + text.setAttribute("x", String(x + 3)); + fgFitLabel(text, w); + } +} + +function fgRestoreFrameGeometry(frame) { + const rect = frame.querySelector("rect"); + const text = frame.querySelector("text"); + if (rect) { + rect.setAttribute("x", rect.dataset.ox || "0"); + rect.setAttribute("width", rect.dataset.ow || "0"); + } + if (text) { + text.setAttribute("x", text.dataset.ox || "0"); + if (text.dataset.hidden === "1") { + text.style.display = "none"; + text.textContent = text.dataset.full || ""; + } else { + fgFitLabel(text, Number(rect ? (rect.dataset.ow || "0") : "0")); + } + } +} + +function fgFitLabel(text, width) { + const full = text.dataset.full || text.textContent || ""; + const maxChars = Math.floor((width - 6) / 7); + if (maxChars < 3) { + text.style.display = "none"; + text.textContent = full; + return; + } + text.style.display = ""; + if (full.length <= maxChars) { + text.textContent = full; + return; + } + text.textContent = full.slice(0, maxChars - 1) + "…"; +} + window.addEventListener("DOMContentLoaded", fgInit); ` diff --git a/internal/flamegraph/tool.go b/internal/flamegraph/tool.go index 3719b0a..a83c44f 100644 --- a/internal/flamegraph/tool.go +++ b/internal/flamegraph/tool.go @@ -69,6 +69,10 @@ func (t Tool) WriteSVG() error { return nil } +func (t Tool) OutFile() string { + return t.outFile +} + func decompress(compressedFile string) (string, error) { decompressedFile := strings.TrimSuffix(compressedFile, ".zst") diff --git a/internal/flamegraph/webserver.go b/internal/flamegraph/webserver.go new file mode 100644 index 0000000..7925011 --- /dev/null +++ b/internal/flamegraph/webserver.go @@ -0,0 +1,108 @@ +package flamegraph + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "os/signal" + "path/filepath" + "strings" + "syscall" + "time" +) + +func ServeSVG(svgFile string) error { + absPath, err := filepath.Abs(svgFile) + if err != nil { + return fmt.Errorf("resolve svg path: %w", err) + } + urlPath := buildURLPath(absPath) + srv := &http.Server{Handler: buildSVGHandler(absPath, urlPath)} + + listener, err := listenRandomPort() + if err != nil { + return err + } + defer listener.Close() + + hostname, port := serverHostPort(listener) + printServerURL(hostname, port, urlPath) + + errCh := make(chan error, 1) + go func() { + errCh <- srv.Serve(listener) + }() + + return waitForStop(srv, errCh) +} + +func buildURLPath(absPath string) string { + urlPath := filepath.ToSlash(absPath) + if !strings.HasPrefix(urlPath, "/") { + return "/" + urlPath + } + return urlPath +} + +func buildSVGHandler(absPath, urlPath string) http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, urlPath, http.StatusFound) + }) + mux.HandleFunc(urlPath, func(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, absPath) + }) + return mux +} + +func listenRandomPort() (net.Listener, error) { + listener, err := net.Listen("tcp", ":0") + if err != nil { + return nil, fmt.Errorf("start web server: %w", err) + } + return listener, nil +} + +func serverHostPort(listener net.Listener) (string, int) { + hostname, err := os.Hostname() + if err != nil { + hostname = "localhost" + } + port := listener.Addr().(*net.TCPAddr).Port + return hostname, port +} + +func printServerURL(hostname string, port int, urlPath string) { + fmt.Printf("Flamegraph available at http://%s:%d%s\n", hostname, port, urlPath) + fmt.Println("Press Ctrl+C to stop the web server.") +} + +func waitForStop(srv *http.Server, errCh <-chan error) error { + stopCh := make(chan os.Signal, 1) + signal.Notify(stopCh, os.Interrupt, syscall.SIGTERM) + defer signal.Stop(stopCh) + + select { + case sig := <-stopCh: + _ = sig + case serveErr := <-errCh: + if serveErr != nil && serveErr != http.ErrServerClosed { + return serveErr + } + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + return fmt.Errorf("shutdown web server: %w", err) + } + + serveErr := <-errCh + if serveErr != nil && serveErr != http.ErrServerClosed { + return serveErr + } + return nil +} diff --git a/internal/ior.go b/internal/ior.go index ee1f988..599736d 100644 --- a/internal/ior.go +++ b/internal/ior.go @@ -99,6 +99,7 @@ func Run() error { if iorFile != "" { noTraceRun = true + var svgFile string if cfg.FlamegraphTool != "" { collapsed := flamegraph.NewCollapsed(iorFile, cfg.CollapsedFields, cfg.CountField) collapsedFile, err := collapsed.Write(iorFile) @@ -113,12 +114,19 @@ func Run() error { if err := tool.WriteSVG(); err != nil { return err } + svgFile = tool.OutFile() } else { native := flamegraph.NewNativeSVG(cfg.CollapsedFields, cfg.CountField) - if err := native.WriteSVGFromFile(iorFile); err != nil { + var err error + svgFile, err = native.WriteSVGFromFile(iorFile) + if err != nil { return err } } + + if err := flamegraph.ServeSVG(svgFile); err != nil { + return err + } } if noTraceRun { |
