Skip to content

Commit 3dbdeca

Browse files
Fixed live latency graph display visualization
1 parent a701510 commit 3dbdeca

File tree

2 files changed

+135
-50
lines changed

2 files changed

+135
-50
lines changed

internal/stats/collector.go

Lines changed: 89 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func (h *LatencyHistogram) GetStats() map[string]float64 {
106106
defer h.mu.Unlock()
107107

108108
if h.Count == 0 {
109-
return map[string]float64{"avg": 0, "min": 0, "max": 0, "p95": 0, "p99": 0}
109+
return map[string]float64{"avg": 0, "min": 0, "max": 0, "p95": 0, "p99": 0, "sum": 0, "count": 0}
110110
}
111111

112112
min := h.Min
@@ -128,11 +128,13 @@ func (h *LatencyHistogram) GetStats() map[string]float64 {
128128
}
129129

130130
return map[string]float64{
131-
"avg": h.Sum / float64(h.Count),
132-
"min": min,
133-
"max": h.Max,
134-
"p95": getPerc(95.0),
135-
"p99": getPerc(99.0),
131+
"avg": h.Sum / float64(h.Count),
132+
"min": min,
133+
"max": h.Max,
134+
"p95": getPerc(95.0),
135+
"p99": getPerc(99.0),
136+
"sum": h.Sum,
137+
"count": float64(h.Count),
136138
}
137139
}
138140

@@ -141,7 +143,7 @@ func (h *LatencyHistogram) GetStatsAndReset() map[string]float64 {
141143
defer h.mu.Unlock()
142144

143145
if h.Count == 0 {
144-
return map[string]float64{"avg": 0, "min": 0, "max": 0, "p95": 0, "p99": 0}
146+
return map[string]float64{"avg": 0, "min": 0, "max": 0, "p95": 0, "p99": 0, "sum": 0, "count": 0}
145147
}
146148

147149
min := h.Min
@@ -162,11 +164,13 @@ func (h *LatencyHistogram) GetStatsAndReset() map[string]float64 {
162164
}
163165

164166
stats := map[string]float64{
165-
"avg": h.Sum / float64(h.Count),
166-
"min": min,
167-
"max": h.Max,
168-
"p95": getPerc(95.0),
169-
"p99": getPerc(99.0),
167+
"avg": h.Sum / float64(h.Count),
168+
"min": min,
169+
"max": h.Max,
170+
"p95": getPerc(95.0),
171+
"p99": getPerc(99.0),
172+
"sum": h.Sum,
173+
"count": float64(h.Count),
170174
}
171175

172176
// Reset the histogram for the next interval
@@ -189,15 +193,22 @@ type Collector struct {
189193
AggOps uint64
190194
TransOps uint64
191195

192-
FindHist *LatencyHistogram
193-
InsertHist *LatencyHistogram
194-
UpsertHist *LatencyHistogram
195-
UpdateHist *LatencyHistogram
196-
DeleteHist *LatencyHistogram
197-
AggHist *LatencyHistogram
198-
TransHist *LatencyHistogram
199-
TotalHist *LatencyHistogram
200-
IntervalTotalHist *LatencyHistogram
196+
FindHist *LatencyHistogram
197+
InsertHist *LatencyHistogram
198+
UpsertHist *LatencyHistogram
199+
UpdateHist *LatencyHistogram
200+
DeleteHist *LatencyHistogram
201+
AggHist *LatencyHistogram
202+
TransHist *LatencyHistogram
203+
TotalHist *LatencyHistogram
204+
IntervalFindHist *LatencyHistogram
205+
IntervalInsertHist *LatencyHistogram
206+
IntervalUpsertHist *LatencyHistogram
207+
IntervalUpdateHist *LatencyHistogram
208+
IntervalDeleteHist *LatencyHistogram
209+
IntervalAggHist *LatencyHistogram
210+
IntervalTransHist *LatencyHistogram
211+
IntervalTotalHist *LatencyHistogram
201212

202213
CurrentIteration int
203214

@@ -213,16 +224,23 @@ type Collector struct {
213224

214225
func NewCollector() *Collector {
215226
return &Collector{
216-
FindHist: &LatencyHistogram{Min: math.MaxFloat64},
217-
InsertHist: &LatencyHistogram{Min: math.MaxFloat64},
218-
UpsertHist: &LatencyHistogram{Min: math.MaxFloat64},
219-
UpdateHist: &LatencyHistogram{Min: math.MaxFloat64},
220-
DeleteHist: &LatencyHistogram{Min: math.MaxFloat64},
221-
AggHist: &LatencyHistogram{Min: math.MaxFloat64},
222-
TransHist: &LatencyHistogram{Min: math.MaxFloat64},
223-
TotalHist: &LatencyHistogram{Min: math.MaxFloat64},
224-
IntervalTotalHist: &LatencyHistogram{Min: math.MaxFloat64},
225-
startTime: time.Now(),
227+
FindHist: &LatencyHistogram{Min: math.MaxFloat64},
228+
InsertHist: &LatencyHistogram{Min: math.MaxFloat64},
229+
UpsertHist: &LatencyHistogram{Min: math.MaxFloat64},
230+
UpdateHist: &LatencyHistogram{Min: math.MaxFloat64},
231+
DeleteHist: &LatencyHistogram{Min: math.MaxFloat64},
232+
AggHist: &LatencyHistogram{Min: math.MaxFloat64},
233+
TransHist: &LatencyHistogram{Min: math.MaxFloat64},
234+
TotalHist: &LatencyHistogram{Min: math.MaxFloat64},
235+
IntervalFindHist: &LatencyHistogram{Min: math.MaxFloat64},
236+
IntervalInsertHist: &LatencyHistogram{Min: math.MaxFloat64},
237+
IntervalUpsertHist: &LatencyHistogram{Min: math.MaxFloat64},
238+
IntervalUpdateHist: &LatencyHistogram{Min: math.MaxFloat64},
239+
IntervalDeleteHist: &LatencyHistogram{Min: math.MaxFloat64},
240+
IntervalAggHist: &LatencyHistogram{Min: math.MaxFloat64},
241+
IntervalTransHist: &LatencyHistogram{Min: math.MaxFloat64},
242+
IntervalTotalHist: &LatencyHistogram{Min: math.MaxFloat64},
243+
startTime: time.Now(),
226244
}
227245
}
228246

@@ -234,24 +252,31 @@ func (c *Collector) Track(opType string, duration time.Duration) {
234252
case "find":
235253
atomic.AddUint64(&c.FindOps, 1)
236254
c.FindHist.Record(ms)
255+
c.IntervalFindHist.Record(ms)
237256
case "insert":
238257
atomic.AddUint64(&c.InsertOps, 1)
239258
c.InsertHist.Record(ms)
259+
c.IntervalInsertHist.Record(ms)
240260
case "upsert":
241261
atomic.AddUint64(&c.UpsertOps, 1)
242262
c.UpsertHist.Record(ms)
263+
c.IntervalUpsertHist.Record(ms)
243264
case "update", "updateOne", "updateMany":
244265
atomic.AddUint64(&c.UpdateOps, 1)
245266
c.UpdateHist.Record(ms)
267+
c.IntervalUpdateHist.Record(ms)
246268
case "delete", "deleteOne", "deleteMany":
247269
atomic.AddUint64(&c.DeleteOps, 1)
248270
c.DeleteHist.Record(ms)
271+
c.IntervalDeleteHist.Record(ms)
249272
case "aggregate":
250273
atomic.AddUint64(&c.AggOps, 1)
251274
c.AggHist.Record(ms)
275+
c.IntervalAggHist.Record(ms)
252276
case "transaction":
253277
atomic.AddUint64(&c.TransOps, 1)
254278
c.TransHist.Record(ms)
279+
c.IntervalTransHist.Record(ms)
255280
}
256281
}
257282

@@ -263,24 +288,31 @@ func (c *Collector) Add(opType string, count int64, duration time.Duration) {
263288
case "find":
264289
atomic.AddUint64(&c.FindOps, uint64(count))
265290
c.FindHist.RecordBatch(ms, count)
291+
c.IntervalFindHist.RecordBatch(ms, count)
266292
case "insert":
267293
atomic.AddUint64(&c.InsertOps, uint64(count))
268294
c.InsertHist.RecordBatch(ms, count)
295+
c.IntervalInsertHist.RecordBatch(ms, count)
269296
case "upsert":
270297
atomic.AddUint64(&c.UpsertOps, uint64(count))
271298
c.UpsertHist.RecordBatch(ms, count)
299+
c.IntervalUpsertHist.RecordBatch(ms, count)
272300
case "update", "updateOne", "updateMany":
273301
atomic.AddUint64(&c.UpdateOps, uint64(count))
274302
c.UpdateHist.RecordBatch(ms, count)
303+
c.IntervalUpdateHist.RecordBatch(ms, count)
275304
case "delete", "deleteOne", "deleteMany":
276305
atomic.AddUint64(&c.DeleteOps, uint64(count))
277306
c.DeleteHist.RecordBatch(ms, count)
307+
c.IntervalDeleteHist.RecordBatch(ms, count)
278308
case "aggregate":
279309
atomic.AddUint64(&c.AggOps, uint64(count))
280310
c.AggHist.RecordBatch(ms, count)
311+
c.IntervalAggHist.RecordBatch(ms, count)
281312
case "transaction":
282313
atomic.AddUint64(&c.TransOps, uint64(count))
283314
c.TransHist.RecordBatch(ms, count)
315+
c.IntervalTransHist.RecordBatch(ms, count)
284316
}
285317
}
286318

@@ -321,12 +353,17 @@ func (c *Collector) Monitor(done <-chan struct{}, refreshRateSec int, concurrenc
321353
csvWriter = csv.NewWriter(csvFile)
322354

323355
if needsHeader {
324-
// Write the CSV Header row only if it's a new or empty file
325356
csvWriter.Write([]string{
326357
"Timestamp", "ElapsedSec",
327-
"Select_OpsSec", "Insert_OpsSec", "Upsert_OpsSec",
328-
"Update_OpsSec", "Delete_OpsSec", "Agg_OpsSec", "Trans_OpsSec",
329-
"Lat_Avg_ms", "Lat_Min_ms", "Lat_Max_ms", "Lat_P95_ms", "Lat_P99_ms",
358+
"Select_OpsSec", "Insert_OpsSec", "Upsert_OpsSec", "Update_OpsSec", "Delete_OpsSec", "Agg_OpsSec", "Trans_OpsSec",
359+
"Total_Lat_Avg", "Total_Lat_P99",
360+
"Select_Lat_Avg", "Select_Lat_P99",
361+
"Insert_Lat_Avg", "Insert_Lat_P99",
362+
"Upsert_Lat_Avg", "Upsert_Lat_P99",
363+
"Update_Lat_Avg", "Update_Lat_P99",
364+
"Delete_Lat_Avg", "Delete_Lat_P99",
365+
"Agg_Lat_Avg", "Agg_Lat_P99",
366+
"Trans_Lat_Avg", "Trans_Lat_P99",
330367
"Iteration",
331368
})
332369
csvWriter.Flush()
@@ -377,7 +414,7 @@ func (c *Collector) Monitor(done <-chan struct{}, refreshRateSec int, concurrenc
377414
currentUpdate := atomic.LoadUint64(&c.UpdateOps)
378415
currentDelete := atomic.LoadUint64(&c.DeleteOps)
379416
currentAgg := atomic.LoadUint64(&c.AggOps)
380-
currentTrans := atomic.LoadUint64(&c.TransOps) // Fixed field name
417+
currentTrans := atomic.LoadUint64(&c.TransOps)
381418

382419
// Calculate Ops/Sec for this specific window
383420
rateFind := float64(currentFind-lastFind) / float64(refreshRateSec)
@@ -390,6 +427,14 @@ func (c *Collector) Monitor(done <-chan struct{}, refreshRateSec int, concurrenc
390427

391428
// Fetch cumulative latency stats at this exact point in time
392429
latStats := c.IntervalTotalHist.GetStatsAndReset()
430+
totLat := c.IntervalTotalHist.GetStatsAndReset()
431+
fndLat := c.IntervalFindHist.GetStatsAndReset()
432+
insLat := c.IntervalInsertHist.GetStatsAndReset()
433+
upsLat := c.IntervalUpsertHist.GetStatsAndReset()
434+
updLat := c.IntervalUpdateHist.GetStatsAndReset()
435+
delLat := c.IntervalDeleteHist.GetStatsAndReset()
436+
aggLat := c.IntervalAggHist.GetStatsAndReset()
437+
trnLat := c.IntervalTransHist.GetStatsAndReset()
393438

394439
iter := c.CurrentIteration
395440
if iter < 1 {
@@ -411,6 +456,14 @@ func (c *Collector) Monitor(done <-chan struct{}, refreshRateSec int, concurrenc
411456
fmt.Sprintf("%.2f", latStats["max"]),
412457
fmt.Sprintf("%.2f", latStats["p95"]),
413458
fmt.Sprintf("%.2f", latStats["p99"]),
459+
fmt.Sprintf("%.2f", totLat["avg"]), fmt.Sprintf("%.2f", totLat["p99"]),
460+
fmt.Sprintf("%.2f", fndLat["avg"]), fmt.Sprintf("%.2f", fndLat["p99"]),
461+
fmt.Sprintf("%.2f", insLat["avg"]), fmt.Sprintf("%.2f", insLat["p99"]),
462+
fmt.Sprintf("%.2f", upsLat["avg"]), fmt.Sprintf("%.2f", upsLat["p99"]),
463+
fmt.Sprintf("%.2f", updLat["avg"]), fmt.Sprintf("%.2f", updLat["p99"]),
464+
fmt.Sprintf("%.2f", delLat["avg"]), fmt.Sprintf("%.2f", delLat["p99"]),
465+
fmt.Sprintf("%.2f", aggLat["avg"]), fmt.Sprintf("%.2f", aggLat["p99"]),
466+
fmt.Sprintf("%.2f", trnLat["avg"]), fmt.Sprintf("%.2f", trnLat["p99"]),
414467
strconv.Itoa(iter),
415468
})
416469
csvWriter.Flush()

internal/webui/static/index.html

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,14 @@ <h3 style="display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
12951295
let targetSeconds = 0;
12961296
let lastFormData = null;
12971297
let lastStats = { total: 0, find: 0, insert: 0, update: 0, delete: 0, aggregate: 0 };
1298+
let lastLat = {
1299+
total: { sum: 0, count: 0 },
1300+
find: { sum: 0, count: 0 },
1301+
insert: { sum: 0, count: 0 },
1302+
update: { sum: 0, count: 0 },
1303+
delete: { sum: 0, count: 0 },
1304+
aggregate: { sum: 0, count: 0 }
1305+
};
12981306
let finalReportData = null;
12991307

13001308
function parseDuration(str) {
@@ -1455,6 +1463,14 @@ <h3 style="display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
14551463
histLats = { total: [], find: [], insert: [], update: [], delete: [], aggregate: [] };
14561464
histTotals = { total: [], find: [], insert: [], update: [], delete: [], aggregate: [] };
14571465
lastStats = { total: 0, find: 0, insert: 0, update: 0, delete: 0, aggregate: 0 };
1466+
lastLat = {
1467+
total: { sum: 0, count: 0 },
1468+
find: { sum: 0, count: 0 },
1469+
insert: { sum: 0, count: 0 },
1470+
update: { sum: 0, count: 0 },
1471+
delete: { sum: 0, count: 0 },
1472+
aggregate: { sum: 0, count: 0 }
1473+
};
14581474

14591475
globalMarkers = [];
14601476
lastKnownIteration = 0;
@@ -1543,14 +1559,30 @@ <h3 style="display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
15431559
const rAggregate = Math.max(0, Math.round((cAggregate - (lastStats.aggregate || 0)) * rateMultiplier));
15441560
const rTotal = rFind + rInsert + rUpdate + rDelete + rAggregate;
15451561

1546-
let tLatAvg = 0;
1547-
if (rTotal > 0) {
1548-
tLatAvg = ((rFind * (stats.findLatAvg || 0)) +
1549-
(rInsert * (stats.insertLatAvg || 0)) +
1550-
(rUpdate * (stats.updateLatAvg || 0)) +
1551-
(rDelete * (stats.deleteLatAvg || 0)) +
1552-
(rAggregate * (stats.aggLatAvg || 0))) / rTotal;
1553-
}
1562+
const calcIntervalAvg = (current, last) => {
1563+
if (!current) return 0;
1564+
const diffCount = (current.count || 0) - last.count;
1565+
const diffSum = (current.sum || 0) - last.sum;
1566+
last.count = current.count || 0;
1567+
last.sum = current.sum || 0;
1568+
return diffCount > 0 ? diffSum / diffCount : 0;
1569+
};
1570+
1571+
const intTotalLat = calcIntervalAvg(stats.distTotal, lastLat.total);
1572+
const intFindLat = calcIntervalAvg(stats.distFind, lastLat.find);
1573+
const intInsertLat = calcIntervalAvg(stats.distInsert, lastLat.insert);
1574+
1575+
// Update and Upsert are combined in the UI ops, so combine their sums/counts
1576+
const curUpdateSum = (stats.distUpdate?.sum || 0) + (stats.distUpsert?.sum || 0);
1577+
const curUpdateCount = (stats.distUpdate?.count || 0) + (stats.distUpsert?.count || 0);
1578+
const diffUpdateCount = curUpdateCount - lastLat.update.count;
1579+
const diffUpdateSum = curUpdateSum - lastLat.update.sum;
1580+
lastLat.update.count = curUpdateCount;
1581+
lastLat.update.sum = curUpdateSum;
1582+
const intUpdateLat = diffUpdateCount > 0 ? diffUpdateSum / diffUpdateCount : 0;
1583+
1584+
const intDeleteLat = calcIntervalAvg(stats.distDelete, lastLat.delete);
1585+
const intAggLat = calcIntervalAvg(stats.distAgg, lastLat.aggregate);
15541586

15551587
lastStats = { total: cTotal, find: cFind, insert: cInsert, update: cUpdate, delete: cDelete, aggregate: cAggregate };
15561588

@@ -1580,12 +1612,12 @@ <h3 style="display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
15801612
histTotals.delete.push(cDelete);
15811613
histTotals.aggregate.push(cAggregate);
15821614

1583-
histLats.total.push(tLatAvg);
1584-
histLats.find.push(rFind > 0 ? (stats.findLatAvg || 0) : 0);
1585-
histLats.insert.push(rInsert > 0 ? (stats.insertLatAvg || 0) : 0);
1586-
histLats.update.push(rUpdate > 0 ? (stats.updateLatAvg || 0) : 0);
1587-
histLats.delete.push(rDelete > 0 ? (stats.deleteLatAvg || 0) : 0);
1588-
histLats.aggregate.push(rAggregate > 0 ? (stats.aggLatAvg || 0) : 0);
1615+
histLats.total.push(intTotalLat);
1616+
histLats.find.push(intFindLat);
1617+
histLats.insert.push(intInsertLat);
1618+
histLats.update.push(intUpdateLat);
1619+
histLats.delete.push(intDeleteLat);
1620+
histLats.aggregate.push(intAggLat);
15891621

15901622
const maxIdx = histRates.find.length - 1;
15911623
const scrubber = document.getElementById('timeScrubber');

0 commit comments

Comments
 (0)