Improved all axpect of tracing / forcing arrays

Improved ui and tree export
This commit is contained in:
Martino Ferrari
2026-05-21 06:20:22 +02:00
parent 58abf98eea
commit bceab8607c
7 changed files with 1281 additions and 328 deletions
Binary file not shown.
+5 -87
View File
@@ -347,9 +347,11 @@ func (m *MarteClient) handleJSONResponse(tag, data string) {
return
case "TREE":
// Parse server-side and stream pre-digested flat-node batches to the
// browser so it never has to JSON.parse a multi-MB string.
go m.sendTreeBatches(data)
// Per-node response — forward to browser as-is.
broadcast(m.hub, map[string]any{
"type": "tree_node",
"data": data,
})
return
}
broadcast(m.hub, map[string]any{
@@ -359,90 +361,6 @@ func (m *MarteClient) handleJSONResponse(tag, data string) {
})
}
// ---------------------------------------------------------------------------
// TREE streaming
// ---------------------------------------------------------------------------
// TreeNode mirrors the DebugService TREE JSON structure.
type TreeNode struct {
Name string `json:"Name"`
Class string `json:"Class"`
IsTraceable bool `json:"IsTraceable"`
IsForcable bool `json:"IsForcable"`
Elements uint32 `json:"Elements"`
Children []*TreeNode `json:"Children"`
}
// FlatNode is a pre-digested, compact representation sent to the browser.
type FlatNode struct {
Path string `json:"p"`
Name string `json:"n"`
Class string `json:"c"`
Parent string `json:"par"`
Tr bool `json:"tr,omitempty"`
Fo bool `json:"fo,omitempty"`
El uint32 `json:"el,omitempty"`
HasCh bool `json:"ch,omitempty"`
}
func flattenTree(node *TreeNode, parentPath string, out *[]FlatNode) {
path := node.Name
if parentPath != "" {
path = parentPath + "." + node.Name
}
hasCh := len(node.Children) > 0
*out = append(*out, FlatNode{
Path: path,
Name: node.Name,
Class: node.Class,
Parent: parentPath,
Tr: node.IsTraceable,
Fo: node.IsForcable,
El: node.Elements,
HasCh: hasCh,
})
for _, c := range node.Children {
flattenTree(c, path, out)
}
}
// sendTreeBatches parses the raw TREE JSON and streams compact FlatNode
// batches to all browsers. Each browser processes one batch per animation
// frame, keeping the UI responsive during large tree loads.
func (m *MarteClient) sendTreeBatches(raw string) {
var root TreeNode
if err := json.Unmarshal([]byte(raw), &root); err != nil {
log.Printf("[TREE] parse error: %v — falling back to raw JSON", err)
broadcast(m.hub, map[string]any{"type": "response", "tag": "TREE", "data": raw})
return
}
var nodes []FlatNode
if root.Name == "Root" && len(root.Children) > 0 {
for _, c := range root.Children {
flattenTree(c, "", &nodes)
}
} else {
flattenTree(&root, "", &nodes)
}
total := len(nodes)
log.Printf("[TREE] streaming %d flat nodes to browser", total)
broadcast(m.hub, map[string]any{"type": "tree_clear", "total": total})
const batchSize = 50
for i := 0; i < total; i += batchSize {
end := i + batchSize
if end > total {
end = total
}
broadcast(m.hub, map[string]any{
"type": "tree_batch",
"nodes": nodes[i:end],
"done": end == total,
})
}
}
func (m *MarteClient) handleTextLine(line string) {
if strings.HasPrefix(line, "OK SERVICE_INFO") {
File diff suppressed because it is too large Load Diff
+98 -1
View File
@@ -81,6 +81,7 @@ details>summary:hover{background:#313244}
details>summary::before{content:'▶';font-size:9px;color:#585b70;width:10px;flex-shrink:0}
details[open]>summary::before{content:'▼'}
details>.children{padding-left:14px}
.tree-loading{padding:2px 4px;color:#585b70;font-size:10px;font-style:italic}
.tree-btn{background:transparent;border:1px solid #45475a;color:#a6adc8;padding:0 4px;border-radius:3px;cursor:pointer;font-size:10px;line-height:14px}
.tree-btn:hover{background:#45475a;color:#cdd6f4}
.tree-btn.t{border-color:#89b4fa;color:#89b4fa}
@@ -156,6 +157,21 @@ details>.children{padding-left:14px}
::-webkit-scrollbar-thumb{background:#45475a;border-radius:3px}
::-webkit-scrollbar-thumb:hover{background:#585b70}
/* ── grouped array signal in trace list ── */
.sig-group{display:flex;align-items:center;gap:4px;padding:3px 6px;border-bottom:1px solid #1e1e2e;user-select:none;cursor:grab}
.sig-group:hover{background:#313244}
.sig-group-exp{color:#585b70;font-size:10px;width:14px;text-align:center;flex-shrink:0;cursor:pointer}
.sig-group-exp:hover{color:#89b4fa}
.sig-item-sub{padding-left:24px;background:#11111b;border-left:2px solid #313244;margin-left:2px}
.sig-item-sub:hover{background:#1e1e2e}
/* ── array selection toggle ── */
.arr-seg{display:flex;gap:0;margin-bottom:12px;border-radius:4px;overflow:hidden;border:1px solid #45475a}
.arr-seg button{flex:1;border:none;border-radius:0;border-right:1px solid #45475a;color:#a6adc8;background:#313244;padding:4px 0;font-size:11px}
.arr-seg button:last-child{border-right:none}
.arr-seg button.active{background:#89b4fa;color:#1e1e2e}
.arr-seg button:hover:not(.active){background:#45475a;color:#cdd6f4}
/* ── misc ── */
.empty-hint{padding:16px;color:#45475a;text-align:center}
.break-item{padding:3px 6px;border-bottom:1px solid #181825;display:flex;align-items:center;gap:4px;font-size:11px}
@@ -276,7 +292,7 @@ select{background:#313244;border:1px solid #45475a;color:#cdd6f4;padding:2px 4px
<div id="log-toolbar">
<button class="panel-toggle" title="Collapse logs" onclick="togglePanel('log-panel','▼','▲',this)" id="log-toggle-btn"></button>
<span style="font-weight:600;color:#89b4fa">Logs</span>
<label><input type="checkbox" id="lf-service" checked onchange="renderLogs()">
<label><input type="checkbox" id="lf-service" onchange="renderLogs()">
Service</label>
<label><input type="checkbox" id="lf-debug" checked onchange="renderLogs()">
Debug</label>
@@ -369,6 +385,87 @@ select{background:#313244;border:1px solid #45475a;color:#cdd6f4;padding:2px 4px
</div>
</div>
</div>
<!-- Array signal dialog (Trace / Force / Break with index/range selection) -->
<div class="dialog-overlay" id="dlg-array" style="display:none" onclick="if(event.target===this)closeDlg('dlg-array')">
<div class="dialog" style="min-width:360px">
<h3 id="arr-dlg-title">Array Signal</h3>
<p style="font-size:11px;color:#a6adc8;margin-bottom:14px">
<b id="arr-dlg-sig" style="color:#cdd6f4"></b>
&nbsp;·&nbsp;<span id="arr-dlg-n" style="color:#89b4fa"></span> elements
</p>
<!-- Segmented selection toggle -->
<label style="margin-bottom:6px">Apply to</label>
<div class="arr-seg">
<button id="arr-sel-all" class="active" onclick="setArrSel('all')">All</button>
<button id="arr-sel-idx" onclick="setArrSel('idx')">Index</button>
<button id="arr-sel-rng" onclick="setArrSel('rng')">Range</button>
</div>
<!-- Index mode: single number input -->
<div id="arr-idx-sect" style="display:none">
<label>Element index &nbsp;<span style="color:#585b70;font-weight:normal">(0 <span id="arr-idx-max"></span>)</span></label>
<input id="arr-idx" type="number" min="0" value="0">
</div>
<!-- Range mode: start / end -->
<div id="arr-rng-sect" style="display:none">
<div class="form-row">
<div>
<label>From index</label>
<input id="arr-r0" type="number" min="0" value="0">
</div>
<div>
<label>To index <span style="color:#585b70;font-weight:normal">inclusive</span></label>
<input id="arr-r1" type="number" min="0" value="0">
</div>
</div>
</div>
<!-- Force value (shown only for Force action) -->
<div id="arr-force-sect" style="display:none">
<label>Force value</label>
<input id="arr-force-val" placeholder="e.g. 0">
</div>
<!-- Break condition (shown only for Break action) -->
<div id="arr-break-sect" style="display:none">
<div class="form-row">
<div>
<label>Condition</label>
<select id="arr-break-op">
<option>></option><option>>=</option>
<option>&lt;</option><option>&lt;=</option>
<option>==</option><option>!=</option>
</select>
</div>
<div>
<label>Threshold</label>
<input id="arr-break-thr" placeholder="0">
</div>
</div>
</div>
<div class="btns">
<button onclick="closeDlg('dlg-array')">Cancel</button>
<button id="arr-ok-btn" class="active" onclick="doArrayOp()">Apply</button>
</div>
</div>
</div>
<!-- Array → plot mode dialog (Sequential / Waterfall) -->
<div class="dialog-overlay" id="dlg-arr-plot" style="display:none" onclick="if(event.target===this)closeDlg('dlg-arr-plot')">
<div class="dialog" style="min-width:360px">
<h3>Plot Array Signal</h3>
<p style="font-size:11px;color:#a6adc8;margin-bottom:14px">
<b id="arrp-name" style="color:#cdd6f4"></b>
&nbsp;·&nbsp;<span id="arrp-n" style="color:#89b4fa"></span>
elements
</p>
<div class="arr-seg" style="margin-bottom:8px">
<button id="arrp-sel-sequential" class="active" onclick="setArrpMode('sequential')">Sequential</button>
<button id="arrp-sel-waterfall" onclick="setArrpMode('waterfall')">Waterfall</button>
</div>
<p id="arrp-desc" style="font-size:10px;color:#a6adc8;margin-bottom:16px;min-height:28px"></p>
<div class="btns">
<button onclick="closeDlg('dlg-arr-plot')">Cancel</button>
<button class="active" onclick="doArrayPlotMode()">Add</button>
</div>
</div>
</div>
<!-- Info dialog -->
<div class="dialog-overlay" id="dlg-info" style="display:none" onclick="if(event.target===this)closeDlg('dlg-info')">
<div class="dialog" style="max-width:600px;width:90vw">