diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-06 15:35:24 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-06 15:35:24 +0200 |
| commit | 99b02bf8c389a793df5d5986db05eed7e459f7b1 (patch) | |
| tree | bc4e36cfcd3c9ef9b067beed2eb5b68a75a45aa2 /internal/flamegraph | |
| parent | 4ff17c30120d657b966f8a55188ba167dc875e64 (diff) | |
refactor: remove web flamegrapher and keep TUI-only
Diffstat (limited to 'internal/flamegraph')
22 files changed, 1 insertions, 4185 deletions
diff --git a/internal/flamegraph/doc.go b/internal/flamegraph/doc.go index 8ff27d2..02429d3 100644 --- a/internal/flamegraph/doc.go +++ b/internal/flamegraph/doc.go @@ -1,2 +1,2 @@ -// Package flamegraph builds aggregated call trees and rendering inputs for I/O flamegraphs. +// Package flamegraph provides TUI flamegraph aggregation primitives. package flamegraph diff --git a/internal/flamegraph/iordatacollector.go b/internal/flamegraph/iordatacollector.go deleted file mode 100644 index a2ae731..0000000 --- a/internal/flamegraph/iordatacollector.go +++ /dev/null @@ -1,65 +0,0 @@ -package flamegraph - -import ( - "context" - "fmt" - "runtime" - "sync" - - "ior/internal/event" -) - -type IorDataCollector struct { - flamegraphName string - Ch chan *event.Pair - Done chan error - workers []worker -} - -func New(flamegraphName ...string) IorDataCollector { - name := "default" - if len(flamegraphName) > 0 && flamegraphName[0] != "" { - name = flamegraphName[0] - } - - f := IorDataCollector{ - flamegraphName: name, - Ch: make(chan *event.Pair, 4096), - Done: make(chan error, 1), - } - numWorkers := runtime.NumCPU() / 4 - if numWorkers == 0 { - numWorkers = 1 - } - for range numWorkers { - f.workers = append(f.workers, newWorker()) - } - return f -} - -func (f IorDataCollector) Start(ctx context.Context) { - go func() { - defer close(f.Done) - var wg sync.WaitGroup - wg.Add(len(f.workers)) - - for i, worker := range f.workers { - fmt.Println("Starting flamegraph worker", i) - go worker.run(ctx, &wg, f.Ch) - } - wg.Wait() - - iod := f.workers[0].iod - if len(f.workers) > 1 { - for i, w := range f.workers[1:] { - iod = iod.merge(w.iod) - fmt.Println("Worker", i+1, "merged") - } - } - if err := iod.serializeToFile(f.flamegraphName); err != nil { - f.Done <- err - return - } - f.Done <- nil - }() -} diff --git a/internal/flamegraph/layout.go b/internal/flamegraph/layout.go deleted file mode 100644 index c319800..0000000 --- a/internal/flamegraph/layout.go +++ /dev/null @@ -1,78 +0,0 @@ -package flamegraph - -import "fmt" - -// FrameLayout captures renderer-agnostic flamegraph geometry for a single frame. -// -// The layout is reusable by non-SVG renderers (for example SDL or WASM UIs) so -// they can render the same hierarchy without depending on SVG internals. -type FrameLayout struct { - Name string - Title string - Fill string - X float64 - Y float64 - Width float64 - Height float64 - Depth int - Total uint64 - Percent float64 -} - -func sanitizeSVGConfig(cfg SVGConfig) SVGConfig { - if cfg.Width <= 0 || cfg.FrameHeight <= 0 || cfg.FontSize <= 0 || cfg.MinWidthPx <= 0 { - return defaultSVGConfig() - } - if cfg.Title == "" { - cfg.Title = defaultSVGConfig().Title - } - return cfg -} - -func canvasHeightFor(cfg SVGConfig, t *trie) int { - return cfg.FrameHeight*(t.maxDepth+1) + 80 -} - -// BuildFrameLayout builds renderer-agnostic frame coordinates from a flamegraph trie. -func BuildFrameLayout(t *trie, cfg SVGConfig) []FrameLayout { - if t == nil || t.root == nil || t.root.total == 0 { - return nil - } - cfg = sanitizeSVGConfig(cfg) - canvasHeight := canvasHeightFor(cfg, t) - out := make([]FrameLayout, 0, len(t.root.children)) - collectFrameLayout(&out, t.root, t.root.total, cfg, 0, 0, canvasHeight, true) - return out -} - -func collectFrameLayout(out *[]FrameLayout, node *trieNode, rootTotal uint64, - cfg SVGConfig, x float64, depth int, canvasHeight int, isRoot bool) { - - if !isRoot { - w := float64(cfg.Width) * (float64(node.total) / float64(rootTotal)) - if w < cfg.MinWidthPx { - return - } - y := float64(canvasHeight - (depth+1)*cfg.FrameHeight) - pct := 100 * float64(node.total) / float64(rootTotal) - *out = append(*out, FrameLayout{ - Name: node.name, - Title: fmt.Sprintf("%s (%d, %.2f%%)", node.name, node.total, pct), - Fill: frameColor(node.name), - X: x, - Y: y, - Width: w, - Height: float64(cfg.FrameHeight - 1), - Depth: depth, - Total: node.total, - Percent: pct, - }) - } - - cursor := x - for _, child := range node.children { - cw := float64(cfg.Width) * (float64(child.total) / float64(rootTotal)) - collectFrameLayout(out, child, rootTotal, cfg, cursor, depth+1, canvasHeight, false) - cursor += cw - } -} diff --git a/internal/flamegraph/layout_test.go b/internal/flamegraph/layout_test.go deleted file mode 100644 index 8fa7398..0000000 --- a/internal/flamegraph/layout_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package flamegraph - -import ( - "math" - "testing" -) - -func almostEqual(a, b float64) bool { - return math.Abs(a-b) < 1e-6 -} - -func TestBuildFrameLayoutBasicGeometry(t *testing.T) { - tr := newTrie() - tr.add([]string{"A"}, 4) - tr.add([]string{"B"}, 1) - tr.computeTotals() - - cfg := defaultSVGConfig() - cfg.Width = 100 - cfg.FrameHeight = 10 - cfg.FontSize = 10 - cfg.MinWidthPx = 1 - - frames := BuildFrameLayout(tr, cfg) - if len(frames) != 2 { - t.Fatalf("frames len = %d, want 2", len(frames)) - } - - a := frames[0] - if a.Name != "A" { - t.Fatalf("first frame name = %q, want %q", a.Name, "A") - } - if !almostEqual(a.X, 0) { - t.Fatalf("A x = %f, want 0", a.X) - } - if !almostEqual(a.Width, 80) { - t.Fatalf("A width = %f, want 80", a.Width) - } - if !almostEqual(a.Percent, 80) { - t.Fatalf("A percent = %f, want 80", a.Percent) - } - if a.Depth != 1 { - t.Fatalf("A depth = %d, want 1", a.Depth) - } - - b := frames[1] - if b.Name != "B" { - t.Fatalf("second frame name = %q, want %q", b.Name, "B") - } - if !almostEqual(b.X, 80) { - t.Fatalf("B x = %f, want 80", b.X) - } - if !almostEqual(b.Width, 20) { - t.Fatalf("B width = %f, want 20", b.Width) - } -} - -func TestBuildFrameLayoutSkipsFramesBelowMinWidth(t *testing.T) { - tr := newTrie() - tr.add([]string{"A"}, 999) - tr.add([]string{"B"}, 1) - tr.computeTotals() - - cfg := defaultSVGConfig() - cfg.Width = 100 - cfg.FrameHeight = 10 - cfg.FontSize = 10 - cfg.MinWidthPx = 1 - - frames := BuildFrameLayout(tr, cfg) - if len(frames) != 1 { - t.Fatalf("frames len = %d, want 1", len(frames)) - } - if frames[0].Name != "A" { - t.Fatalf("remaining frame name = %q, want %q", frames[0].Name, "A") - } -} diff --git a/internal/flamegraph/livehtml.go b/internal/flamegraph/livehtml.go deleted file mode 100644 index 71b955e..0000000 --- a/internal/flamegraph/livehtml.go +++ /dev/null @@ -1,842 +0,0 @@ -package flamegraph - -const liveHTML = `<!doctype html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>I/O Flame Graph (Live)</title> - <style> - :root { - --fg-bg: #f6f1ea; - --fg-panel: #fbf7f1; - --fg-border: #d8cdc0; - --fg-text: #232323; - --fg-muted: #5f5f5f; - --fg-accent: #7b2d1f; - --fg-btn: #efe2d2; - --fg-btn-hover: #e6d5c1; - --fg-paused: #b02222; - } - - * { box-sizing: border-box; } - - body { - margin: 0; - min-height: 100vh; - background: linear-gradient(180deg, #f8f2ea 0%, #f2e9dc 100%); - color: var(--fg-text); - font-family: monospace; - } - - #controls { - position: sticky; - top: 0; - z-index: 1; - display: flex; - gap: 8px; - align-items: center; - flex-wrap: wrap; - padding: 10px 12px; - background: var(--fg-panel); - border-bottom: 1px solid var(--fg-border); - } - - #controls button { - border: 1px solid var(--fg-border); - background: var(--fg-btn); - color: var(--fg-text); - font: inherit; - font-size: 12px; - line-height: 1.2; - padding: 6px 10px; - cursor: pointer; - } - - #controls button:hover { - background: var(--fg-btn-hover); - } - - #controls .order-toggle { - min-width: 220px; - text-align: left; - } - - #status { - margin-left: 8px; - font-size: 12px; - color: var(--fg-muted); - } - - .paused #status { - color: var(--fg-paused); - font-weight: 700; - letter-spacing: 0.03em; - text-transform: uppercase; - } - - #flamegraph { - display: block; - width: 100%; - height: calc(100vh - 56px); - min-height: calc(100vh - 56px); - background: transparent; - } - - .title { - font-size: 14px; - font-family: monospace; - } - - .controls text { - font-size: 12px; - font-family: monospace; - cursor: pointer; - fill: #444; - } - - .frame text { - font-size: 11px; - font-family: monospace; - pointer-events: none; - fill: #111; - } - - .frame rect { - stroke: rgba(0, 0, 0, 0.18); - stroke-width: 0.5; - } - </style> -</head> -<body> - <div id="controls"> - <button id="btn-pause" type="button">Pause</button> - <button id="btn-search" type="button">Search</button> - <button id="btn-reset-search" type="button">Reset Search</button> - <button id="btn-undo-zoom" type="button">Undo Zoom</button> - <button id="btn-reset-zoom" type="button">Reset Zoom</button> - <button id="btn-reset-baseline" type="button">Reset Baseline</button> - <button id="btn-toggle-order" class="order-toggle" type="button">Order: comm > tracepoint > path</button> - <span id="status">LIVE</span> - </div> - - <svg id="flamegraph" xmlns="http://www.w3.org/2000/svg"></svg> - - <script> - (function () { - var fg = { - paused: false, - resetting: false, - lastTreeData: null, - pendingData: null, - searchQuery: '', - zoomStack: [], - zoomRange: null, - frames: [], - rootWidth: 0, - matchColor: 'rgb(220,30,70)', - eventSource: null, - svg: document.getElementById('flamegraph'), - status: document.getElementById('status'), - pauseBtn: document.getElementById('btn-pause'), - searchBtn: document.getElementById('btn-search'), - resetSearchBtn: document.getElementById('btn-reset-search'), - undoZoomBtn: document.getElementById('btn-undo-zoom'), - resetZoomBtn: document.getElementById('btn-reset-zoom'), - resetBaselineBtn: document.getElementById('btn-reset-baseline'), - toggleOrderBtn: document.getElementById('btn-toggle-order'), - orderPresets: [ - 'comm,tracepoint,path', - 'path,tracepoint,comm', - 'tracepoint,comm,path', - 'pid,tracepoint,path', - 'comm,path,tracepoint' - ], - orderIndex: 0, - cfg: { - baseWidth: 1200, - baseFrameHeight: 16, - width: 1200, - frameHeight: 16, - fontSize: 12, - minWidthPx: 1.0 - } - }; - - function fgFrameColor(name) { - var bytes = new TextEncoder().encode(name || ''); - var h = 2166136261 >>> 0; - for (var i = 0; i < bytes.length; i++) { - h ^= bytes[i]; - h = Math.imul(h, 16777619) >>> 0; - } - var r = 200 + (h % 35); - var g = 80 + ((h >>> 8) % 120); - var b = 40 + ((h >>> 16) % 90); - return 'rgb(' + r + ',' + g + ',' + b + ')'; - } - - function fgMaxDepth(node, depth) { - if (!node || !Array.isArray(node.c) || node.c.length === 0) { - return depth; - } - var maxDepth = depth; - for (var i = 0; i < node.c.length; i++) { - var childDepth = fgMaxDepth(node.c[i], depth + 1); - if (childDepth > maxDepth) { - maxDepth = childDepth; - } - } - return maxDepth; - } - - function fgDefaultCanvasHeight(maxDepth) { - return (fg.cfg.baseFrameHeight * (maxDepth + 1)) + 80; - } - - function fgViewportLayout(maxDepth) { - var rows = Math.max(maxDepth + 1, 1); - var defaultCanvasHeight = fgDefaultCanvasHeight(maxDepth); - var viewportWidth = Number(window.innerWidth || 0); - if (viewportWidth <= 0 && document && document.documentElement) { - viewportWidth = Number(document.documentElement.clientWidth || 0); - } - if (viewportWidth <= 0) { - viewportWidth = fg.cfg.baseWidth; - } - var viewportHeight = Number(window.innerHeight || 0); - if (viewportHeight <= 0) { - return { - width: viewportWidth, - frameHeight: fg.cfg.baseFrameHeight, - canvasHeight: defaultCanvasHeight - }; - } - - var controls = document.getElementById('controls'); - var controlsHeight = 56; - if (controls && typeof controls.getBoundingClientRect === 'function') { - controlsHeight = Number(controls.getBoundingClientRect().height || controlsHeight); - } - - var availableHeight = viewportHeight - controlsHeight; - if (availableHeight <= 0) { - return { - width: viewportWidth, - frameHeight: fg.cfg.baseFrameHeight, - canvasHeight: defaultCanvasHeight - }; - } - - var canvasHeight = Math.max(defaultCanvasHeight, availableHeight); - var frameHeight = (canvasHeight - 80) / rows; - if (frameHeight < fg.cfg.baseFrameHeight) { - frameHeight = fg.cfg.baseFrameHeight; - } - return { - width: viewportWidth, - frameHeight: frameHeight, - canvasHeight: canvasHeight - }; - } - - function fgVisibleChildrenTotal(node) { - var children = Array.isArray(node && node.c) ? node.c : []; - var total = 0; - for (var i = 0; i < children.length; i++) { - total += Number(children[i].t || 0); - } - if (total > 0) { - return total; - } - return Number(node && node.t || 0); - } - - function fgBuildFrames(node, rootTotal, x, width, depth, canvasHeight, isRoot, out, path) { - if (!node || rootTotal <= 0 || width <= 0) { - return; - } - var currentPath = path || ''; - if (!isRoot) { - var w = width; - if (w < fg.cfg.minWidthPx) { - return; - } - var name = node.n || ''; - currentPath = currentPath ? (currentPath + '\u001f' + name) : name; - var y = canvasHeight - ((depth + 1) * fg.cfg.frameHeight); - var total = Number(node.t || 0); - var pct = 100 * total / Number(rootTotal); - out.push({ - name: name, - path: currentPath, - x: x, - y: y, - w: w, - h: fg.cfg.frameHeight - 1, - depth: depth, - total: total, - pct: pct, - fill: fgFrameColor(name) - }); - } - var cursor = x; - var children = Array.isArray(node.c) ? node.c : []; - var childrenTotal = fgVisibleChildrenTotal(node); - if (childrenTotal <= 0) { - return; - } - for (var i = 0; i < children.length; i++) { - var child = children[i]; - var childTotal = Number(child.t || 0); - if (childTotal <= 0) { - continue; - } - var childWidth = width * (childTotal / childrenTotal); - fgBuildFrames(child, rootTotal, cursor, childWidth, depth + 1, canvasHeight, false, out, currentPath); - cursor += childWidth; - } - } - - function fgEscape(value) { - return String(value || '') - .replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } - - function fgSetStatus(suffix) { - var prefix = fg.paused ? 'PAUSED' : 'LIVE'; - fg.status.textContent = suffix ? (prefix + ' | ' + suffix) : prefix; - } - - function fgOrderLabel(csv) { - return String(csv || '').split(',').join(' > '); - } - - function fgOrderFields(csv) { - return String(csv || '').split(',').filter(function (s) { return s; }); - } - - function fgSetOrderIndexByCSV(csv) { - for (var i = 0; i < fg.orderPresets.length; i++) { - if (fg.orderPresets[i] === csv) { - fg.orderIndex = i; - return; - } - } - } - - function fgUpdateOrderButton() { - fg.toggleOrderBtn.textContent = 'Order: ' + fgOrderLabel(fg.orderPresets[fg.orderIndex] || ''); - } - - function fgHover(frame) { - var title = frame.querySelector('title'); - fgSetStatus(title ? title.textContent : ''); - } - - function fgDetectRootWidth() { - var maxEnd = 0; - for (var i = 0; i < fg.frames.length; i++) { - var x = Number(fg.frames[i].dataset.x || '0'); - var w = Number(fg.frames[i].dataset.w || '0'); - if (x + w > maxEnd) { - maxEnd = x + w; - } - } - return maxEnd; - } - - function fgSnapshotOriginalGeometry(frame) { - var rect = frame.querySelector('rect'); - var 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 fgFitLabel(text, width) { - var full = text.dataset.full || text.textContent || ''; - var 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) + '...'; - } - - function fgSetFrameGeometry(frame, x, w) { - var rect = frame.querySelector('rect'); - var 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) { - var rect = frame.querySelector('rect'); - var 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 fgZoom(frame) { - var width = fgOriginalW(frame); - if (width <= 0) { - return; - } - if (fg.zoomRange) { - fg.zoomStack.push(fg.zoomRange); - } - fg.zoomRange = { - x: fgOriginalX(frame), - w: width, - depth: Number(frame.dataset.depth || '0'), - path: frame.dataset.path || '' - }; - fgApplyZoom(); - } - - function fgFindFrameByPath(path) { - for (var i = 0; i < fg.frames.length; i++) { - if ((fg.frames[i].dataset.path || '') === path) { - return fg.frames[i]; - } - } - return null; - } - - function fgRefreshZoomRange() { - if (!fg.zoomRange || !fg.zoomRange.path) { - return; - } - var candidatePath = fg.zoomRange.path; - var match = null; - while (candidatePath) { - match = fgFindFrameByPath(candidatePath); - if (match) { - break; - } - var cut = candidatePath.lastIndexOf('\u001f'); - if (cut < 0) { - break; - } - candidatePath = candidatePath.slice(0, cut); - } - if (!match) { - return; - } - var width = fgOriginalW(match); - if (width <= 0) { - return; - } - fg.zoomRange.path = match.dataset.path || candidatePath; - fg.zoomRange.x = fgOriginalX(match); - fg.zoomRange.w = width; - fg.zoomRange.depth = Number(match.dataset.depth || String(fg.zoomRange.depth || 0)); - } - - function fgApplyZoom() { - if (!fg.zoomRange) { - for (var i = 0; i < fg.frames.length; i++) { - fgRestoreFrameGeometry(fg.frames[i]); - fg.frames[i].style.display = ''; - } - return; - } - fgRefreshZoomRange(); - var x = fg.zoomRange.x; - var end = x + fg.zoomRange.w; - var width = fg.zoomRange.w; - var minDepth = fg.zoomRange.depth; - var scale = fg.rootWidth > 0 ? fg.rootWidth / width : 1; - var eps = 1e-6; - for (var i = 0; i < fg.frames.length; i++) { - var frame = fg.frames[i]; - var ox = fgOriginalX(frame); - var ow = fgOriginalW(frame); - var depth = Number(frame.dataset.depth || '0'); - var inRange = (ox >= x - eps) && (ox + ow <= end + eps); - var isAncestor = depth < minDepth && ox <= x + eps && ox + ow >= end - eps; - if (isAncestor || (depth >= minDepth && inRange)) { - if (isAncestor) { - fgSetFrameGeometry(frame, 0, fg.rootWidth); - } else { - fgSetFrameGeometry(frame, (ox - x) * scale, ow * scale); - } - frame.style.display = ''; - } else { - frame.style.display = 'none'; - } - } - } - - function fgUndoZoom() { - if (fg.zoomStack.length === 0) { - fgResetZoom(); - return; - } - fg.zoomRange = fg.zoomStack.pop(); - fgApplyZoom(); - } - - function fgResetZoom() { - fg.zoomStack = []; - fg.zoomRange = null; - fgApplyZoom(); - } - - function fgResetSearch() { - for (var i = 0; i < fg.frames.length; i++) { - var rect = fg.frames[i].querySelector('rect'); - if (!rect) { - continue; - } - rect.setAttribute('fill', fg.frames[i].dataset.baseFill || ''); - } - } - - function fgApplySearch() { - fgResetSearch(); - if (!fg.searchQuery) { - return; - } - var query = fg.searchQuery.toLowerCase(); - for (var i = 0; i < fg.frames.length; i++) { - var name = (fg.frames[i].dataset.name || '').toLowerCase(); - if (name.indexOf(query) < 0) { - continue; - } - var rect = fg.frames[i].querySelector('rect'); - if (rect) { - rect.setAttribute('fill', fg.matchColor); - } - } - } - - function fgSearch() { - var query = window.prompt('Search frame substring:', fg.searchQuery || ''); - if (query === null) { - return; - } - fg.searchQuery = query.trim(); - fgApplySearch(); - } - - function fgTogglePause() { - fg.paused = !fg.paused; - document.body.classList.toggle('paused', fg.paused); - fg.pauseBtn.textContent = fg.paused ? 'Resume' : 'Pause'; - fgSetStatus(''); - if (!fg.paused && fg.pendingData) { - var pending = fg.pendingData; - fg.pendingData = null; - requestAnimationFrame(function () { - fgProcessUpdate(pending); - }); - } - } - - function fgClearLocalState() { - fg.pendingData = null; - fg.searchQuery = ''; - fg.zoomStack = []; - fg.zoomRange = null; - } - - function fgResetBaseline() { - if (fg.resetting) { - return; - } - fg.resetting = true; - fgSetStatus('resetting baseline...'); - fetch('/reset', { method: 'POST' }) - .then(function (resp) { - if (!resp.ok) { - throw new Error('reset failed'); - } - return resp.text(); - }) - .then(function (payload) { - fgClearLocalState(); - fgProcessUpdate(payload); - fgSetStatus('baseline reset'); - }) - .catch(function () { - fgSetStatus('reset failed'); - }) - .then(function () { - fg.resetting = false; - }); - } - - function fgBindFrameEvents() { - for (var i = 0; i < fg.frames.length; i++) { - fg.frames[i].addEventListener('mouseenter', function () { fgHover(this); }); - fg.frames[i].addEventListener('mouseleave', function () { fgSetStatus(''); }); - fg.frames[i].addEventListener('click', function (ev) { - if (ev.detail > 1) { - return; - } - ev.stopPropagation(); - fgZoom(ev.currentTarget); - }); - fg.frames[i].addEventListener('dblclick', function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - fgResetZoom(); - }); - } - } - - function fgRender(treeData) { - var maxDepth = fgMaxDepth(treeData, 0); - var layout = fgViewportLayout(maxDepth); - fg.cfg.width = layout.width; - fg.cfg.frameHeight = layout.frameHeight; - - if (!treeData || Number(treeData.t || 0) <= 0) { - fg.svg.style.height = String(layout.canvasHeight) + 'px'; - fg.svg.setAttribute('viewBox', '0 0 ' + fg.cfg.width + ' ' + layout.canvasHeight); - fg.svg.setAttribute('preserveAspectRatio', 'xMinYMin meet'); - fg.frames = []; - fg.svg.innerHTML = ''; - fgSetStatus(''); - return; - } - - var rootTotal = fgVisibleChildrenTotal(treeData); - if (rootTotal <= 0) { - rootTotal = Number(treeData.t || 0); - } - var canvasHeight = layout.canvasHeight; - var frames = []; - fgBuildFrames(treeData, rootTotal, 0, fg.cfg.width, 0, canvasHeight, true, frames, ''); - - var parts = []; - parts.push('<text class="title" x="10" y="22">I/O Flame Graph (Live)</text>'); - for (var i = 0; i < frames.length; i++) { - var frame = frames[i]; - var textStyle = frame.w <= (fg.cfg.fontSize * 2) ? ' style="display:none"' : ''; - var title = fgEscape(frame.name + ' (' + frame.total + ', ' + frame.pct.toFixed(2) + '%)'); - parts.push('<g class="frame" data-name="' + fgEscape(frame.name) + '" data-path="' + fgEscape(frame.path) + - '" data-x="' + frame.x.toFixed(3) + '" data-w="' + frame.w.toFixed(3) + - '" data-depth="' + frame.depth + '" data-base-fill="' + frame.fill + '">'); - parts.push('<title>' + title + '</title>'); - parts.push('<rect x="' + frame.x.toFixed(3) + '" y="' + frame.y.toFixed(3) + '" width="' + frame.w.toFixed(3) + - '" height="' + frame.h.toFixed(3) + '" fill="' + frame.fill + '"></rect>'); - parts.push('<text x="' + (frame.x + 3).toFixed(3) + '" y="' + (frame.y + fg.cfg.fontSize).toFixed(3) + '"' + - textStyle + '>' + fgEscape(frame.name) + '</text>'); - parts.push('</g>'); - } - - fg.svg.setAttribute('viewBox', '0 0 ' + fg.cfg.width + ' ' + canvasHeight); - fg.svg.setAttribute('preserveAspectRatio', 'xMinYMin meet'); - fg.svg.style.height = String(canvasHeight) + 'px'; - fg.svg.innerHTML = parts.join(''); - fg.frames = Array.prototype.slice.call(fg.svg.querySelectorAll('g.frame')); - fg.rootWidth = fgDetectRootWidth(); - for (var j = 0; j < fg.frames.length; j++) { - fgSnapshotOriginalGeometry(fg.frames[j] |
