mod-wsgi-telemetry 1.0.0.dev2__tar.gz → 1.0.0.dev4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/PKG-INFO +1 -1
- mod_wsgi_telemetry-1.0.0.dev4/src/mod_wsgi/telemetry/__init__.py +1 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/static/index.html +117 -32
- mod_wsgi_telemetry-1.0.0.dev2/src/mod_wsgi/telemetry/__init__.py +0 -1
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/.gitignore +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/LICENSE +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/README-telemetry.rst +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/README.md +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/pyproject.toml +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/__init__.py +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/cli.py +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/contention.py +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/dump.py +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/ingest.py +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/server.py +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/simulate.py +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/tui.py +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/wire.py +0 -0
- {mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/tests/test_wire.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mod_wsgi-telemetry
|
|
3
|
-
Version: 1.0.0.
|
|
3
|
+
Version: 1.0.0.dev4
|
|
4
4
|
Summary: Ingestion service and live UI for mod_wsgi telemetry samples.
|
|
5
5
|
Project-URL: Homepage, https://www.modwsgi.org/
|
|
6
6
|
Project-URL: Documentation, https://modwsgi.readthedocs.io/en/latest/user-guides/external-telemetry-service.html
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0.dev4"
|
|
@@ -1206,8 +1206,8 @@
|
|
|
1206
1206
|
<div class="gc-status" id="gc_status"></div>
|
|
1207
1207
|
<div class="charts">
|
|
1208
1208
|
<div class="chart">
|
|
1209
|
-
<div class="header-row"><h3>
|
|
1210
|
-
<div class="header-row"><div class="unit">
|
|
1209
|
+
<div class="header-row"><h3>GC overhead</h3></div>
|
|
1210
|
+
<div class="header-row"><div class="unit">per-interval fraction of wall-clock spent in GC pauses (sum of pause durations / interval length); invariant across CPython GC algorithms</div><div class="live" id="live_gc_pressure"></div></div>
|
|
1211
1211
|
<canvas id="c_gc_pressure"></canvas>
|
|
1212
1212
|
</div>
|
|
1213
1213
|
<div class="chart">
|
|
@@ -5494,6 +5494,96 @@ function gcDrawLines(canvas, series, extract, opts = {}) {
|
|
|
5494
5494
|
}
|
|
5495
5495
|
}
|
|
5496
5496
|
|
|
5497
|
+
function gcDrawOverhead(canvas, series, opts = {}) {
|
|
5498
|
+
// Per (pid, interpreter) series: one line whose y-value at each
|
|
5499
|
+
// snapshot is the fraction of wall-clock time spent inside GC
|
|
5500
|
+
// pauses over the interval ending at that snapshot, expressed as
|
|
5501
|
+
// a percentage. Interval is [prev_snapshot.t, this_snapshot.t];
|
|
5502
|
+
// events are binned by gc_event_start_stamp (the collection's
|
|
5503
|
+
// actual start time) and fall back to the datagram stamp when
|
|
5504
|
+
// that field is absent.
|
|
5505
|
+
//
|
|
5506
|
+
// Same calculation in every CPython GC regime (pre-3.13
|
|
5507
|
+
// generational, 3.14.0-3.14.4 incremental, 3.14.5 reverted,
|
|
5508
|
+
// free-threaded), so a chart that aggregates across mixed-build
|
|
5509
|
+
// process groups stays honest. No gc.get_count() / threshold
|
|
5510
|
+
// arithmetic is involved.
|
|
5511
|
+
const ctx = canvas.getContext("2d");
|
|
5512
|
+
const w = canvas.width = canvas.clientWidth * devicePixelRatio;
|
|
5513
|
+
const h = canvas.height = canvas.clientHeight * devicePixelRatio;
|
|
5514
|
+
ctx.clearRect(0, 0, w, h);
|
|
5515
|
+
|
|
5516
|
+
const now = gcNowSec();
|
|
5517
|
+
const windowSec = opts.windowSec || LINE_WINDOW_SEC;
|
|
5518
|
+
const tmin = now - windowSec;
|
|
5519
|
+
const tmax = now;
|
|
5520
|
+
|
|
5521
|
+
let vmax = 0;
|
|
5522
|
+
const lines = [];
|
|
5523
|
+
for (const s of series) {
|
|
5524
|
+
const ring = s.snapshots.filter(p => p.t >= tmin);
|
|
5525
|
+
if (ring.length < 2) continue;
|
|
5526
|
+
const evs = s.events
|
|
5527
|
+
.map(e => ({
|
|
5528
|
+
t: e.fields.gc_event_start_stamp || e.t,
|
|
5529
|
+
d: e.fields.gc_event_duration || 0,
|
|
5530
|
+
}))
|
|
5531
|
+
.sort((a, b) => a.t - b.t);
|
|
5532
|
+
let ei = 0;
|
|
5533
|
+
const pts = [];
|
|
5534
|
+
for (let i = 1; i < ring.length; i++) {
|
|
5535
|
+
const t0 = ring[i - 1].t;
|
|
5536
|
+
const t1 = ring[i].t;
|
|
5537
|
+
const dt = t1 - t0;
|
|
5538
|
+
if (dt <= 0) continue;
|
|
5539
|
+
while (ei < evs.length && evs[ei].t < t0) ei++;
|
|
5540
|
+
let sum = 0;
|
|
5541
|
+
let j = ei;
|
|
5542
|
+
while (j < evs.length && evs[j].t < t1) {
|
|
5543
|
+
sum += evs[j].d;
|
|
5544
|
+
j++;
|
|
5545
|
+
}
|
|
5546
|
+
const pct = (sum / dt) * 100;
|
|
5547
|
+
if (pct > vmax) vmax = pct;
|
|
5548
|
+
pts.push([t1, pct]);
|
|
5549
|
+
}
|
|
5550
|
+
if (pts.length === 0) continue;
|
|
5551
|
+
lines.push({ color: s.color, pts, label: s.label });
|
|
5552
|
+
}
|
|
5553
|
+
if (vmax === 0) vmax = opts.fallbackMax || 1;
|
|
5554
|
+
vmax *= 1.1;
|
|
5555
|
+
|
|
5556
|
+
ctx.strokeStyle = "#333";
|
|
5557
|
+
ctx.lineWidth = 1;
|
|
5558
|
+
ctx.beginPath();
|
|
5559
|
+
ctx.moveTo(0, h - 1);
|
|
5560
|
+
ctx.lineTo(w, h - 1);
|
|
5561
|
+
ctx.stroke();
|
|
5562
|
+
|
|
5563
|
+
ctx.fillStyle = "#666";
|
|
5564
|
+
ctx.font = (10 * devicePixelRatio) + "px -apple-system, sans-serif";
|
|
5565
|
+
ctx.textAlign = "left";
|
|
5566
|
+
ctx.textBaseline = "top";
|
|
5567
|
+
ctx.fillText(vmax.toFixed(2) + "%", 4, 4);
|
|
5568
|
+
ctx.textBaseline = "bottom";
|
|
5569
|
+
ctx.fillText("0%", 4, h - 4);
|
|
5570
|
+
|
|
5571
|
+
const xOf = t => ((t - tmin) / (tmax - tmin)) * w;
|
|
5572
|
+
const yOf = v => h - (v / vmax) * h;
|
|
5573
|
+
|
|
5574
|
+
ctx.lineWidth = 1.5 * devicePixelRatio;
|
|
5575
|
+
for (const l of lines) {
|
|
5576
|
+
ctx.strokeStyle = l.color;
|
|
5577
|
+
ctx.beginPath();
|
|
5578
|
+
l.pts.forEach(([t, v], i) => {
|
|
5579
|
+
const x = xOf(t), y = yOf(v);
|
|
5580
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
5581
|
+
else ctx.lineTo(x, y);
|
|
5582
|
+
});
|
|
5583
|
+
ctx.stroke();
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
|
|
5497
5587
|
function gcDrawEventTimeline(canvas, series, windowSec, opts = {}) {
|
|
5498
5588
|
// opts.gens filters which generations contribute dots; opts.region
|
|
5499
5589
|
// and opts.skipClear behave the same as gcDrawLines so the caller
|
|
@@ -5698,37 +5788,32 @@ function renderGc() {
|
|
|
5698
5788
|
|
|
5699
5789
|
renderGcStatus(series);
|
|
5700
5790
|
|
|
5701
|
-
//
|
|
5702
|
-
//
|
|
5703
|
-
//
|
|
5704
|
-
//
|
|
5705
|
-
//
|
|
5706
|
-
//
|
|
5707
|
-
//
|
|
5708
|
-
// on its own y-axis instead.
|
|
5791
|
+
// GC overhead: per (pid, interpreter) series, fraction of wall-clock
|
|
5792
|
+
// spent in GC pauses over each snapshot interval, as a percentage.
|
|
5793
|
+
// Built from the per-event durations in the gc_event stream, so the
|
|
5794
|
+
// calculation is identical across CPython GC algorithms (pre-3.13
|
|
5795
|
+
// generational, 3.14.0-3.14.4 incremental, 3.14.5 reverted, free-
|
|
5796
|
+
// threaded). No reliance on gc.get_count() / threshold semantics,
|
|
5797
|
+
// which diverge between those algorithms.
|
|
5709
5798
|
const pressureCanvas = document.getElementById("c_gc_pressure");
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
setLive("live_gc_pressure",
|
|
5729
|
-
`<span class="label" style="color:${GC_GEN_COLORS[0]}">gen0</span> ` +
|
|
5730
|
-
`<span class="label" style="color:${GC_GEN_COLORS[1]}">gen1</span> ` +
|
|
5731
|
-
`<span class="label" style="color:${GC_GEN_COLORS[2]}">gen2</span>`);
|
|
5799
|
+
gcDrawOverhead(pressureCanvas, series, { windowSec });
|
|
5800
|
+
// Live readout: per-series window-average overhead %, coloured to
|
|
5801
|
+
// match each line on the chart.
|
|
5802
|
+
const overheadWinFloor = gcNowSec() - windowSec;
|
|
5803
|
+
const overheadLabels = [];
|
|
5804
|
+
for (const s of series) {
|
|
5805
|
+
let sum = 0;
|
|
5806
|
+
for (const e of s.events) {
|
|
5807
|
+
const t = e.fields.gc_event_start_stamp || e.t;
|
|
5808
|
+
if (t < overheadWinFloor) continue;
|
|
5809
|
+
sum += e.fields.gc_event_duration || 0;
|
|
5810
|
+
}
|
|
5811
|
+
const pct = (sum / windowSec) * 100;
|
|
5812
|
+
overheadLabels.push(
|
|
5813
|
+
`<span class="label" style="color:${s.color}">${s.label}</span>` +
|
|
5814
|
+
`${pct.toFixed(2)}%`);
|
|
5815
|
+
}
|
|
5816
|
+
setLive("live_gc_pressure", overheadLabels.join(" "));
|
|
5732
5817
|
|
|
5733
5818
|
// Collections per second: rate of cumulative gc_collections* over the
|
|
5734
5819
|
// delta between successive snapshots.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.0.0dev2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/cli.py
RENAMED
|
File without changes
|
{mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/contention.py
RENAMED
|
File without changes
|
{mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/dump.py
RENAMED
|
File without changes
|
{mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/ingest.py
RENAMED
|
File without changes
|
{mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/server.py
RENAMED
|
File without changes
|
{mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/simulate.py
RENAMED
|
File without changes
|
{mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/tui.py
RENAMED
|
File without changes
|
{mod_wsgi_telemetry-1.0.0.dev2 → mod_wsgi_telemetry-1.0.0.dev4}/src/mod_wsgi/telemetry/wire.py
RENAMED
|
File without changes
|
|
File without changes
|