sgnmon 0.1.0__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.
- sgnmon-0.1.0/.gitignore +11 -0
- sgnmon-0.1.0/.gitlab-ci.yml +67 -0
- sgnmon-0.1.0/CHANGELOG.md +19 -0
- sgnmon-0.1.0/CONSOLIDATION-TRACKING.md +180 -0
- sgnmon-0.1.0/CONSOLIDATION.md +273 -0
- sgnmon-0.1.0/LICENSE +195 -0
- sgnmon-0.1.0/Makefile +42 -0
- sgnmon-0.1.0/PKG-INFO +196 -0
- sgnmon-0.1.0/PROMQL-GRAFANA-NOTES.md +135 -0
- sgnmon-0.1.0/README.md +128 -0
- sgnmon-0.1.0/VM-PILOT-PLAN.md +125 -0
- sgnmon-0.1.0/docs/gen_ref_nav.py +58 -0
- sgnmon-0.1.0/docs/index.md +64 -0
- sgnmon-0.1.0/docs/tutorials/getting-started.md +114 -0
- sgnmon-0.1.0/docs/user/attaching.md +176 -0
- sgnmon-0.1.0/docs/user/custom-metrics.md +99 -0
- sgnmon-0.1.0/docs/user/health.md +144 -0
- sgnmon-0.1.0/docs/user/index.md +25 -0
- sgnmon-0.1.0/docs/user/metrics.md +131 -0
- sgnmon-0.1.0/docs/user/server.md +122 -0
- sgnmon-0.1.0/mkdocs.yml +69 -0
- sgnmon-0.1.0/pyproject.toml +213 -0
- sgnmon-0.1.0/src/sgnmon/__init__.py +62 -0
- sgnmon-0.1.0/src/sgnmon/_version.py +24 -0
- sgnmon-0.1.0/src/sgnmon/cli.py +256 -0
- sgnmon-0.1.0/src/sgnmon/elements.py +201 -0
- sgnmon-0.1.0/src/sgnmon/health.py +538 -0
- sgnmon-0.1.0/src/sgnmon/metrics.py +237 -0
- sgnmon-0.1.0/src/sgnmon/monitor.py +685 -0
- sgnmon-0.1.0/src/sgnmon/probes.py +776 -0
- sgnmon-0.1.0/src/sgnmon/py.typed +0 -0
- sgnmon-0.1.0/src/sgnmon/server.py +376 -0
- sgnmon-0.1.0/src/sgnmon/static/dashboard.css +219 -0
- sgnmon-0.1.0/src/sgnmon/static/dashboard.html +38 -0
- sgnmon-0.1.0/src/sgnmon/static/dashboard.js +963 -0
- sgnmon-0.1.0/src/sgnmon/tests/__init__.py +0 -0
- sgnmon-0.1.0/src/sgnmon/tests/helpers.py +169 -0
- sgnmon-0.1.0/src/sgnmon/tests/js/harness.js +431 -0
- sgnmon-0.1.0/src/sgnmon/tests/js/stubs.js +62 -0
- sgnmon-0.1.0/src/sgnmon/tests/test_browser.py +172 -0
- sgnmon-0.1.0/src/sgnmon/tests/test_cli.py +93 -0
- sgnmon-0.1.0/src/sgnmon/tests/test_composed.py +41 -0
- sgnmon-0.1.0/src/sgnmon/tests/test_dashboard.py +48 -0
- sgnmon-0.1.0/src/sgnmon/tests/test_elements.py +237 -0
- sgnmon-0.1.0/src/sgnmon/tests/test_health.py +357 -0
- sgnmon-0.1.0/src/sgnmon/tests/test_metrics.py +126 -0
- sgnmon-0.1.0/src/sgnmon/tests/test_monitor.py +230 -0
- sgnmon-0.1.0/src/sgnmon/tests/test_probes.py +221 -0
- sgnmon-0.1.0/src/sgnmon/tests/test_server.py +196 -0
sgnmon-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
include:
|
|
2
|
+
# -- python
|
|
3
|
+
- component: $CI_SERVER_FQDN/computing/gitlab/components/python/sdist@1
|
|
4
|
+
- component: $CI_SERVER_FQDN/computing/gitlab/components/python/wheel@1
|
|
5
|
+
- component: $CI_SERVER_FQDN/computing/gitlab/components/python/code-quality@1
|
|
6
|
+
inputs:
|
|
7
|
+
analyzer: "ruff"
|
|
8
|
+
requirements: "-r requirements-lint.txt"
|
|
9
|
+
- component: $CI_SERVER_FQDN/computing/gitlab/components/python/dependency-scanning@1
|
|
10
|
+
- component: $CI_SERVER_FQDN/computing/gitlab/components/python/type-checking@1
|
|
11
|
+
inputs:
|
|
12
|
+
project_dir: "src"
|
|
13
|
+
- component: $CI_SERVER_FQDN/computing/gitlab/components/python/test@1
|
|
14
|
+
inputs:
|
|
15
|
+
install_extra: test
|
|
16
|
+
python_versions:
|
|
17
|
+
- "3.11"
|
|
18
|
+
- "3.12"
|
|
19
|
+
- "3.13"
|
|
20
|
+
# test against sgn and sgn-ts from git (main)
|
|
21
|
+
- component: $CI_SERVER_FQDN/computing/gitlab/components/python/test@1
|
|
22
|
+
inputs:
|
|
23
|
+
job_name: "python_test_latest"
|
|
24
|
+
install_extra: test
|
|
25
|
+
python_versions:
|
|
26
|
+
- "3.11"
|
|
27
|
+
- "3.12"
|
|
28
|
+
- "3.13"
|
|
29
|
+
# -- docs
|
|
30
|
+
- component: $CI_SERVER_FQDN/computing/gitlab/components/mkdocs/build@1
|
|
31
|
+
inputs:
|
|
32
|
+
requirements: "-r requirements-docs.txt"
|
|
33
|
+
- component: $CI_SERVER_FQDN/computing/gitlab/components/mkdocs/pages@1
|
|
34
|
+
inputs:
|
|
35
|
+
pages_when: "default"
|
|
36
|
+
|
|
37
|
+
# -- customizations
|
|
38
|
+
|
|
39
|
+
# install latest sgn and sgn-ts from git (main) to catch any
|
|
40
|
+
# prerelease changes before they ship
|
|
41
|
+
python_test_latest:
|
|
42
|
+
before_script:
|
|
43
|
+
- pip install git+https://git.ligo.org/greg/sgn.git@main
|
|
44
|
+
- pip install git+https://git.ligo.org/greg/sgn-ts.git@main
|
|
45
|
+
|
|
46
|
+
ruff:
|
|
47
|
+
needs:
|
|
48
|
+
- requirements
|
|
49
|
+
|
|
50
|
+
mkdocs:
|
|
51
|
+
needs:
|
|
52
|
+
- requirements
|
|
53
|
+
|
|
54
|
+
# -- requirements
|
|
55
|
+
requirements:
|
|
56
|
+
stage: build
|
|
57
|
+
image: python:3.12
|
|
58
|
+
script:
|
|
59
|
+
- python -m pip install pipx
|
|
60
|
+
- pipx ensurepath
|
|
61
|
+
- source ~/.bashrc
|
|
62
|
+
- pipx install hatch
|
|
63
|
+
- hatch dep show requirements --feature docs > requirements-docs.txt
|
|
64
|
+
- hatch dep show requirements --feature lint > requirements-lint.txt
|
|
65
|
+
artifacts:
|
|
66
|
+
paths:
|
|
67
|
+
- requirements-*.txt
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-07-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Initial release: monitoring for SGN pipelines — non-invasive taps and
|
|
10
|
+
in-graph elements feeding Prometheus metrics (data flow, latency,
|
|
11
|
+
per-pad execution time, freshness, EOS; opt-in process metrics), a
|
|
12
|
+
declarative custom-metrics API, per-event latency for event streams,
|
|
13
|
+
self-pacing health checks, and a monitoring server with a live
|
|
14
|
+
dashboard, `/metrics`, `/health`, `/status`, and server-sent events,
|
|
15
|
+
plus a CLI for zero-code attach (`sgnmon run`), demoing, and health
|
|
16
|
+
checks with plugin-friendly exit codes
|
|
17
|
+
|
|
18
|
+
[unreleased]: https://git.ligo.org/greg/sgnmon/-/compare/0.1.0...main
|
|
19
|
+
[0.1.0]: https://git.ligo.org/greg/sgnmon/-/tags/0.1.0
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Consolidation tracking
|
|
2
|
+
|
|
3
|
+
Live tracker for the remaining decisions and work in the monitoring
|
|
4
|
+
consolidation ([CONSOLIDATION.md](CONSOLIDATION.md)). Analysis and rationale
|
|
5
|
+
live there; this file only tracks state. Last updated 2026-07-22.
|
|
6
|
+
|
|
7
|
+
## Done
|
|
8
|
+
|
|
9
|
+
- [x] Comparative analysis of sgnmon / sgn-skig / sgn testpoints (CONSOLIDATION.md §1–§5)
|
|
10
|
+
- [x] sgnmon gap-closing, four commits on `main` (§6):
|
|
11
|
+
- [x] `0b61ebe` pad execution-time histogram (`sgnmon_exec_seconds`)
|
|
12
|
+
- [x] `f97881e` opt-in process metrics (`Monitor(process_metrics=True)`)
|
|
13
|
+
- [x] `048f98e` custom metrics API (`MetricSpec`/`MetricsMixin`, `Monitor` factories)
|
|
14
|
+
- [x] `dbb144e` `EventLatency` + `PadProbe.record_latency`
|
|
15
|
+
- [x] Storage decision (§8): Prometheus stays the only query layer; single-node
|
|
16
|
+
VictoriaMetrics as long-term backend for both ops history (remote_write)
|
|
17
|
+
and sgnl scientific results (line-protocol push); InfluxDB, scald retention
|
|
18
|
+
policies, ginterval, and the Grafana exporter all retire
|
|
19
|
+
- [x] Health-check integration decision: monitor `/health` with stock Nagios
|
|
20
|
+
`check_http` (200 → OK, 503/refused → CRITICAL). No igwn-monitoring-plugins
|
|
21
|
+
packaging needed — it would put a Python sgnmon install on monitoring hosts
|
|
22
|
+
just to enrich alert text. Revisit only if on-call wants failing-check
|
|
23
|
+
names in the page itself; `sgnmon check` remains a dev/debug convenience.
|
|
24
|
+
|
|
25
|
+
## Decisions open
|
|
26
|
+
|
|
27
|
+
- [x] **`IGWNAlertSource` home**: moved to **sgn-llai** (decided 2026-07-23,
|
|
28
|
+
new `sgnllai/sources/` package) — alert-driven low-latency work is
|
|
29
|
+
sgn-llai's domain, and repo-wide grep found zero current consumers
|
|
30
|
+
anywhere (verified twice: exact class name across all ligo repos, and
|
|
31
|
+
case-insensitive igwn-alert terms across sgnl/sgn-cal/sgn-ligo/
|
|
32
|
+
sgn-llai src+tests+bin+pyproject). Removed from sgn-ligo along with
|
|
33
|
+
its `igwn-alert` dependency — sgn-ligo's dependency delta from the
|
|
34
|
+
transport move is now just `igwn-auth-utils` + `jsonschema`;
|
|
35
|
+
`igwn-alert` is declared by sgn-llai instead.
|
|
36
|
+
|
|
37
|
+
- [x] **Home for sgn-skig's transport elements**: **sgn-ligo** (decided
|
|
38
|
+
2026-07-22). Dependency delta is small — sgn-ligo already has
|
|
39
|
+
`confluent-kafka`, `sgn >= 0.8`, and `sgn-ts`; net-new declarations are
|
|
40
|
+
`igwn-alert` (lazy-imported by `IGWNAlertSource`), `igwn-auth-utils`
|
|
41
|
+
(SciToken discovery in `ScitokenKafkaSource`; likely already transitive
|
|
42
|
+
via gwpy, declare explicitly anyway), and `jsonschema` (`json_frames.py`,
|
|
43
|
+
which the Kafka elements use). skig's `pyyaml`/`requests` are used only
|
|
44
|
+
by the Grafana/pipeline layer and stay behind. Migration caveat:
|
|
45
|
+
`KafkaSource`/`KafkaSink`/`IGWNAlertSource`/`json_frames` are
|
|
46
|
+
metrics-free and move as-is, but `DelayBuffer`/`RoundRobinDistributor`
|
|
47
|
+
mix in skig's `MetricsCollectorMixin` — port their metrics to
|
|
48
|
+
`sgnmon.MetricsMixin` (adds a sgnmon dep wherever they land) or strip
|
|
49
|
+
them; `EventLatency` is NOT moved — already reimplemented in sgnmon.
|
|
50
|
+
Merge plan (decided 2026-07-22, supersedes the earlier rename idea):
|
|
51
|
+
- **Sources — merge.** `ScitokenKafkaSource` folds into `KafkaSource`
|
|
52
|
+
as an auth option (OAUTHBEARER token callback via lazy-imported
|
|
53
|
+
`igwn-auth-utils`); it only overrides consumer config. sgn-llai's
|
|
54
|
+
`run_superevent.py` already auto-picks between the two classes by
|
|
55
|
+
broker hostname — that logic becomes an `auth="auto"` default.
|
|
56
|
+
- **Sinks — merge on one base.** scald's kafka `Client.write()` is
|
|
57
|
+
just `json.dumps` + `produce(key=".".join(tags))` (~15 lines), so
|
|
58
|
+
both sinks are confluent-kafka JSON producers differing only in
|
|
59
|
+
payload schema and batching: sgn-ligo's writes scald-consumer format
|
|
60
|
+
(`{time: [...], data: [...]}`, interval batching; used by sgnl
|
|
61
|
+
`inspiral.py` + `ll_dq.py`), skig's writes generic per-frame JSON
|
|
62
|
+
(per-topic pads, lz4, delivery modes; used by sgn-llai). Plan
|
|
63
|
+
(2026-07-22, Olivia OK'd sgnl adapting): a single `KafkaSink` class,
|
|
64
|
+
confluent-kafka direct, generic JSON by default with a scald-format
|
|
65
|
+
payload mode (`{topic: {time, data}}` accumulation, interval flush,
|
|
66
|
+
tags-as-dotted-key) — no compatibility front-ends; sgnl's two call
|
|
67
|
+
sites update natively with the migration. Drops `ligo.scald` from
|
|
68
|
+
the kafka sink.
|
|
69
|
+
- Consumer census: sgnl uses sgnligo's scald-format sink; sgn-llai uses
|
|
70
|
+
skig's generic pair; **sgn-cal uses no Kafka at all**.
|
|
71
|
+
- Discovered: `sgnligo/sinks/influx_sink.py` is a *second* scald→Influx
|
|
72
|
+
writer outside skig (sgn-ligo keeps `ligo-scald` for it regardless).
|
|
73
|
+
Out of scope for the transport move, but it is another Influx client
|
|
74
|
+
to fold into the VM/Prometheus story eventually — added to
|
|
75
|
+
follow-ups.
|
|
76
|
+
- [ ] **Shared Grafana dashboard ownership**: one static dashboard with
|
|
77
|
+
`element`/`pad`/`job` variables replaces generated per-pipeline JSON.
|
|
78
|
+
Who builds/owns it, and where dashboards-as-code live in IGWN Grafana
|
|
79
|
+
provisioning. sgn-llai's custom-metric panels get hand-built once.
|
|
80
|
+
- [ ] **Manifest question**: does sgn-llai CI depend on skig's
|
|
81
|
+
`write_metrics_manifest()` YAML export? Dropped from the port; if needed,
|
|
82
|
+
it is a small addition to the sgnmon mixin. Ask sgnl folks before the
|
|
83
|
+
migration starts.
|
|
84
|
+
|
|
85
|
+
## Work queue (in order)
|
|
86
|
+
|
|
87
|
+
- [ ] **VM prototype** — gates the §8 storage plan. Sysadmin handoff plan:
|
|
88
|
+
[VM-PILOT-PLAN.md](VM-PILOT-PLAN.md). PromQL/Grafana aggregation notes
|
|
89
|
+
(how query-time reduction replaces scald's write-time tiers):
|
|
90
|
+
[PROMQL-GRAFANA-NOTES.md](PROMQL-GRAFANA-NOTES.md). Push a few weeks of one
|
|
91
|
+
representative sgnl result series (include the sparsest one) into a
|
|
92
|
+
scratch VictoriaMetrics via InfluxDB line protocol; rebuild the Grafana
|
|
93
|
+
panel beside the Influx original. Verify: (a) GPS-vs-Unix timestamp
|
|
94
|
+
convention the scald dashboards assume (VM sample timestamps must be Unix;
|
|
95
|
+
GPS as value or companion series); (b) sparse series query shape
|
|
96
|
+
(`max_over_time(x[$__interval])` / points panels vs the ~5m gauge
|
|
97
|
+
staleness window); (c) whether any Influx data is event *records* rather
|
|
98
|
+
than time series — those belong in a proper service (e.g. GraceDB).
|
|
99
|
+
- [ ] **Thin results writer** for sgnl (~50 lines, HTTP line-protocol POST to
|
|
100
|
+
VM). Lives in sgnl or a small dedicated package — not sgnmon.
|
|
101
|
+
- [ ] **sgn-llai migration, metrics first**: `MetricsCollectorMixin` →
|
|
102
|
+
`sgnmon.MetricsMixin`; `MetricsPipeline` → plain `Pipeline` +
|
|
103
|
+
`monitor.tap()`. DONE 2026-07-23: `DelayBuffer` +
|
|
104
|
+
`RoundRobinDistributor` moved into `sgnllai/transforms/` with metrics
|
|
105
|
+
ported to `sgnmon.MetricsMixin` — Prometheus-shaped families
|
|
106
|
+
(`delay_buffer_events_total{state=buffered|released|flushed_eos}`,
|
|
107
|
+
`delay_buffer_size`, `events_distributed_total{worker=N}`), skig's
|
|
108
|
+
auto-elapsed timing dropped (superseded by `sgnmon_exec_seconds`
|
|
109
|
+
taps); `metrics_enabled` kwarg kept for bin compatibility, optional
|
|
110
|
+
`monitor=` for test isolation; sgnmon added to sgn-llai deps; bins'
|
|
111
|
+
imports updated (EventLatency etc. still from sgneskig until the
|
|
112
|
+
metrics migration); 29 ported tests + full suite green (test_make_dag
|
|
113
|
+
collection error is a pre-existing missing `ezdag` dep), flake8/isort
|
|
114
|
+
clean, all uncommitted. With this, EVERY skig element needing a home
|
|
115
|
+
has one; what remains of skig is superseded (metrics layer,
|
|
116
|
+
MetricsPipeline, EventLatency) or dies with the Influx retirement
|
|
117
|
+
(writer, ScaldMetricsSink, influxdb/grafana modules, Grafana exporter,
|
|
118
|
+
all five bin scripts). Remaining here: swap sgn-llai's own
|
|
119
|
+
MetricsCollectorMixin/EventLatency/MetricsPipeline usage to sgnmon. Gotchas: sgnmon `EventLatency` defaults
|
|
120
|
+
`time_field="time"` (skig: `"gpstime"`) — pass explicitly; push/flusher
|
|
121
|
+
model becomes scrape — each service needs a `MonitorServer` port and a
|
|
122
|
+
Prometheus scrape-config entry; GPS point timestamps and
|
|
123
|
+
`storage_aggregate` have no equivalent — affected dashboards get rebuilt
|
|
124
|
+
against PromQL.
|
|
125
|
+
- [ ] **Ops wiring**: scrape configs for pipeline services on IGWN Prometheus;
|
|
126
|
+
`remote_write` → VM; `check_http` `/health` checks in IGWN monitoring.
|
|
127
|
+
- [x] **Transport element move** — COMMITTED 2026-07-23 on feature branches:
|
|
128
|
+
sgn-ligo `kafka-transport` (`fd94c82` unified KafkaSink w/ provenance
|
|
129
|
+
+ derivation from Yun-Jing Huang's implementation in the message,
|
|
130
|
+
`35e921b` KafkaSource + json_frames), sgn-llai `skig-elements`
|
|
131
|
+
(`8cc4d8c` DelayBuffer/RoundRobinDistributor on sgnmon metrics,
|
|
132
|
+
`a7a1719` IGWNAlertSource), sgnl `kafka-sink-args` (`8c9ea6f` sink
|
|
133
|
+
kwargs). Module docstrings carry no port notes per Olivia — commit
|
|
134
|
+
messages hold provenance. Not pushed; MRs when Olivia is ready.
|
|
135
|
+
Original notes: Done: merged `KafkaSource` (auth="none|scitoken|auto", helpers +
|
|
136
|
+
lazy igwn-auth-utils) in `sgnligo/sources/kafka_source.py`;
|
|
137
|
+
`IGWNAlertSource` and `json_frames.py` copied; unified `KafkaSink`
|
|
138
|
+
(payload_format="json"|"scald", stdout dry-run mode, LIGOTimeGPS-safe
|
|
139
|
+
encoding on all paths) replacing the scald-client sink; three deps added
|
|
140
|
+
to sgn-ligo pyproject; sgnl call sites (`inspiral.py`, `ll_dq.py`)
|
|
141
|
+
updated to `bootstrap_servers=` + `payload_format="scald"`; smoke tests
|
|
142
|
+
pass. Note: sgn-ligo checkout moved from `compat` branch to `main`
|
|
143
|
+
(compat's sgnts.compat layer isn't in the installed sgnts).
|
|
144
|
+
2026-07-23: test port complete — tests/test_kafka_source.py (71),
|
|
145
|
+
test_kafka_sink.py (39, incl. new scald-mode coverage: accumulation,
|
|
146
|
+
trigger flush, interval batching, LIGOTimeGPS, stdout mode),
|
|
147
|
+
test_igwn_alert_source.py (13), test_json_frames.py (ported, minus
|
|
148
|
+
skig's version-fallback test); sgn-ligo's OWN bins also migrated
|
|
149
|
+
(`sgnligo/bin/ll_dq.py`, `gwistat.py` used the old sink kwargs — found
|
|
150
|
+
via test failure); one pre-existing test assertion updated
|
|
151
|
+
(test_ll_dq test_no_kafka_server, asserts new kwargs). mypy + flake8
|
|
152
|
+
clean, 182 tests green across affected files, full suite passes (gwosc
|
|
153
|
+
live-data tests are network-flaky, unrelated). All uncommitted.
|
|
154
|
+
Remaining: sgn-llai import updates (deferred to the metrics-first
|
|
155
|
+
sgn-llai migration), commit/MR when Olivia is ready.
|
|
156
|
+
COMMIT-MESSAGE NOTE (Olivia, 2026-07-23): module docstrings carry no
|
|
157
|
+
port/provenance notes — the sgn-ligo commit message must say: ported
|
|
158
|
+
from sgn-skig; KafkaSource merges sgneskig's KafkaSource +
|
|
159
|
+
ScitokenKafkaSource (auth folded into an option); KafkaSink unifies
|
|
160
|
+
sgnligo's scald-format sink (scald wire format now produced directly,
|
|
161
|
+
dropping ligo.scald.io.kafka) with sgneskig's generic JSON sink;
|
|
162
|
+
IGWNAlertSource and json_frames ported as-is.
|
|
163
|
+
- [ ] **Archive sgn-skig** with a README pointer — only after the migration is
|
|
164
|
+
*deployed*, not merely merged.
|
|
165
|
+
|
|
166
|
+
## Follow-ups (no urgency)
|
|
167
|
+
|
|
168
|
+
- [ ] Upstream conversation with sgn maintainers: sgnmon consuming sgn's
|
|
169
|
+
built-in `record_frame`/`record_exec` hooks instead of wrapping
|
|
170
|
+
`pad.call` — would remove the third parallel interception mechanism
|
|
171
|
+
(CONSOLIDATION.md §3).
|
|
172
|
+
- [x] Document the exec-metric coverage limitation in the sgnmon user docs —
|
|
173
|
+
done 2026-07-23 (metrics.md + attaching.md: `src`/`snk` taps only, not
|
|
174
|
+
`adp` wraps or in-graph elements; absence means "not measured").
|
|
175
|
+
- [ ] `sgnligo/sinks/influx_sink.py` (scald→Influx, separate from skig):
|
|
176
|
+
grep across sgnl/sgn-cal/sgn-llai/sgn-ligo/gstlal found **zero
|
|
177
|
+
consumers** (only the `sgnligo.sinks` re-export) — likely deletable
|
|
178
|
+
rather than migratable. Confirm no external/site users, then remove;
|
|
179
|
+
that clears the last scald→Influx writer outside skig without needing
|
|
180
|
+
the VM push path for it.
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Monitoring consolidation: sgnmon, sgn-skig, and sgn testpoints
|
|
2
|
+
|
|
3
|
+
Comparative analysis of the three monitoring systems in the sgn ecosystem, with a
|
|
4
|
+
consolidation plan to deprecate sgn-skig and rally behind sgnmon as the monitoring
|
|
5
|
+
library. Written 2026-07-22.
|
|
6
|
+
|
|
7
|
+
## TL;DR
|
|
8
|
+
|
|
9
|
+
The three systems barely overlap in what they *collect* — they overlap in *mission*.
|
|
10
|
+
sgnmon is already the strongest general-purpose monitoring library of the three: it
|
|
11
|
+
collects far more out of the box, has health checks, a live dashboard, and a
|
|
12
|
+
Prometheus-native model. sgn-skig's real value is not its metrics (it ships almost
|
|
13
|
+
none) but its **custom-metrics ergonomics** and its **InfluxDB/scald/Grafana ops
|
|
14
|
+
story**; both have clean Prometheus-world equivalents. sgn's testpoints are a
|
|
15
|
+
terminal debugging tool, not a monitoring system — they stay, they're complementary.
|
|
16
|
+
The one hard prerequisite for deprecating sgn-skig: **sgn-llai is its only downstream
|
|
17
|
+
consumer**, and it uses everything — including the Kafka transport elements, which
|
|
18
|
+
are not monitoring code and need a new home before sgn-skig can be archived.
|
|
19
|
+
|
|
20
|
+
## 1. What each system is
|
|
21
|
+
|
|
22
|
+
**sgnmon** observes frames at pad granularity via two attachment styles —
|
|
23
|
+
non-invasive taps that wrap pad callables (including post-audioadapter taps), and
|
|
24
|
+
in-graph `MonitorTransform`/`MonitorSink` elements. It exposes ten Prometheus metric
|
|
25
|
+
families plus windowed gauges over an in-memory two-resolution bucket store (1 s
|
|
26
|
+
buckets for windows up to 300 s, 60 s buckets up to 6 h), six health checks (three on
|
|
27
|
+
by default, self-calibrating), a Starlette server with `/metrics`, `/health`,
|
|
28
|
+
`/status`, `/graph`, and `/events` SSE, a live SVG dashboard, and a Typer CLI with
|
|
29
|
+
zero-code attach (`sgnmon run -- <cmd>`) and a Nagios-style `sgnmon check`.
|
|
30
|
+
|
|
31
|
+
**sgn-skig** (Scald–Kafka–InfluxDB–Grafana) is a metrics *pipeline to storage*, not a
|
|
32
|
+
metrics *collector*. Elements mix in `MetricsCollectorMixin`, declare
|
|
33
|
+
`MetricDeclaration` schemas (timing/counter/gauge + tags), and record via
|
|
34
|
+
`time_operation()` / `increment_counter()` / `record_metric()`. A shared
|
|
35
|
+
background-flushing `MetricsWriter` pushes to InfluxDB through ligo.scald, which owns
|
|
36
|
+
the powers-of-10 reduction. A `GrafanaExporter` generates dashboards from the
|
|
37
|
+
declared schemas, and a small Flask "ginterval" service picks the right retention
|
|
38
|
+
policy for a Grafana time range. It also carries transport elements (KafkaSource,
|
|
39
|
+
KafkaSink, ScitokenKafkaSource, IGWNAlertSource) and utility transforms
|
|
40
|
+
(DelayBuffer, RoundRobinDistributor, EventLatency) that are unrelated to monitoring.
|
|
41
|
+
|
|
42
|
+
**sgn testpoints** (`src/sgn/testpoint.py` in sgn) are an env-var-activated
|
|
43
|
+
(`TESTPOINT=frame|exec`), in-process debugging facility that renders a live `rich`
|
|
44
|
+
terminal table. Frame mode keeps only the *last* frame per pad; exec mode keeps a
|
|
45
|
+
10-sample sliding window of per-pad execution time (mean/stdev). No HTTP, no export,
|
|
46
|
+
no retention. Its hooks are compiled into `SourcePad.__call__`/`SinkPad.__call__` and
|
|
47
|
+
the graph executor, so it sees things no external library can: actual frame objects
|
|
48
|
+
at every pad boundary and true per-node compute time inside the scheduler.
|
|
49
|
+
Separately, sgn also ships `StatsSource` (psutil process/system metrics emitted *as
|
|
50
|
+
frames*, i.e. data-plane), memory profiling, graphviz pipeline visualization, and a
|
|
51
|
+
bottle-based HTTP control plane.
|
|
52
|
+
|
|
53
|
+
## 2. Metrics collected — side by side
|
|
54
|
+
|
|
55
|
+
| What | sgnmon | sgn-skig | sgn testpoints |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| Frame/throughput counts | ✅ `frames`, `gap_frames`, `data/gap_seconds`, `samples` per pad | ❌ | ❌ (last frame snapshot only) |
|
|
58
|
+
| Data latency (GPS now − frame end) | ✅ histogram + last-value gauge per pad, plus `EventLatency` for event streams | ⚠️ only via the `EventLatency` element you must splice in | ❌ |
|
|
59
|
+
| Per-element/pad **execution time** | ✅ `sgnmon_exec_seconds` histogram from `src`/`snk` taps | ✅ auto elapsed-time per pull→new cycle | ✅ exec mode, 10-sample window, terminal only |
|
|
60
|
+
| Gap/liveness/EOS | ✅ gap ratio windows, EOS gauge, freshness timestamps | ❌ | ⚠️ `has gap` column of last frame |
|
|
61
|
+
| Process/system stats (CPU, RSS, IO) | ✅ opt-in `Monitor(process_metrics=True)` (Linux) | ❌ | ⚠️ `StatsSource` (as pipeline frames, not monitoring) |
|
|
62
|
+
| App-defined custom metrics | ✅ declarative `MetricSpec`/`MetricsMixin` + `Monitor.counter/gauge/histogram` | ✅ declarative schema + recording API | ❌ |
|
|
63
|
+
| Windowed rollups in-process | ✅ 5 s–6 h trailing windows | ❌ (delegated to scald/Influx) | ⚠️ 10-sample exec deque |
|
|
64
|
+
| Health checks | ✅ 6 checks, adaptive baselines, HTTP 503 semantics | ❌ | ❌ |
|
|
65
|
+
|
|
66
|
+
The striking asymmetry: sgn-skig ships only three metric-emitting transforms
|
|
67
|
+
(`events_buffered/released/flushed_eos`, `buffer_size`, `events_distributed`,
|
|
68
|
+
`event_latency`) plus the automatic per-cycle elapsed timer. Everything sgnmon
|
|
69
|
+
measures out of the box, sgn-skig simply doesn't measure. The two things sgnmon
|
|
70
|
+
originally did **not** measure — per-pad execution time and process resource
|
|
71
|
+
usage — existed only in the other two projects; both landed in sgnmon on
|
|
72
|
+
2026-07-22 (§6).
|
|
73
|
+
|
|
74
|
+
## 3. Feature overlap map
|
|
75
|
+
|
|
76
|
+
Genuine overlap across all three is narrow:
|
|
77
|
+
|
|
78
|
+
- **Pipeline topology visualization exists three times**: sgnmon's live SVG dashboard
|
|
79
|
+
+ `/graph` JSON, sgn-skig's Mermaid panel in generated Grafana dashboards, and
|
|
80
|
+
sgn's graphviz `Pipeline.visualize()`. sgnmon's `/graph` is the only
|
|
81
|
+
machine-readable, live one; the Mermaid generator can die with sgn-skig, and
|
|
82
|
+
graphviz stays as a static docs/debug tool.
|
|
83
|
+
- **Per-pad observation hooks exist three times**: sgnmon taps wrap `pad.call`;
|
|
84
|
+
testpoints have hooks compiled into sgn's `base.py`/`apps.py`; sgn-skig
|
|
85
|
+
monkey-wraps `pull()`/`new()` via `__init_subclass__`. Three interception
|
|
86
|
+
mechanisms doing the same job at three code layers. Longer-term, sgn's built-in
|
|
87
|
+
`record_frame`/`record_exec` hook points are the architecturally cleanest
|
|
88
|
+
interception layer — sgnmon could optionally consume them instead of wrapping pads
|
|
89
|
+
itself, but that is an optimization, not a blocker.
|
|
90
|
+
- **Timing**: sgn-skig's elapsed-time auto-tracking ≈ testpoints' exec mode ≈ nothing
|
|
91
|
+
in sgnmon. This is the one real *metric* overlap between skig and testpoints, and
|
|
92
|
+
the one gap in sgnmon.
|
|
93
|
+
|
|
94
|
+
Everything else is disjoint: health checks, SSE dashboard, and Prometheus exposition
|
|
95
|
+
exist only in sgnmon; durable storage, Grafana generation, and multi-resolution
|
|
96
|
+
retention exist only in sgn-skig; frame-content inspection exists only in testpoints.
|
|
97
|
+
|
|
98
|
+
## 4. Custom metrics in sgnmon
|
|
99
|
+
|
|
100
|
+
`Monitor` already owns a standard `prometheus_client.CollectorRegistry`, so custom
|
|
101
|
+
metrics are already *possible* — just undocumented and unergonomic. What is worth
|
|
102
|
+
porting from sgn-skig is the ergonomics, not the machinery:
|
|
103
|
+
|
|
104
|
+
1. **A small declarative layer on `Monitor`**: `monitor.counter(name, description,
|
|
105
|
+
labels)`, `monitor.gauge(...)`, `monitor.histogram(...)` returning
|
|
106
|
+
prometheus_client families registered in the monitor's registry.
|
|
107
|
+
2. **A mixin for custom elements** (the `MetricsCollectorMixin` replacement)
|
|
108
|
+
providing sgn-skig's recording verbs backed by those families:
|
|
109
|
+
`increment_counter(name, amount, tags)`, `set_gauge(...)`, and a
|
|
110
|
+
`time_operation(name, tags)` context manager backed by a Histogram. Keeping the
|
|
111
|
+
verb names close to sgn-skig's makes the sgn-llai migration mostly find-and-replace.
|
|
112
|
+
3. **Type mapping**: skig `counter` → Counter (its `storage_aggregate: sum` concern
|
|
113
|
+
evaporates — Prometheus counters are cumulative, `increase()` is exact at any
|
|
114
|
+
resolution); skig `timing` → Histogram, optionally with a last-value gauge
|
|
115
|
+
(histograms preserve tail behavior at any query resolution, which is what skig's
|
|
116
|
+
`max` aggregate was hand-rolling); skig `gauge` → Gauge.
|
|
117
|
+
|
|
118
|
+
Deliberately not carried over: `MetricsPipeline` (tapping means no Pipeline subclass
|
|
119
|
+
is needed), the writer/flusher thread (scraping replaces pushing), schema
|
|
120
|
+
auto-registration against Influx, and `storage_aggregate`.
|
|
121
|
+
|
|
122
|
+
## 5. Powers-of-10 aggregation — what it solves, and the alternatives
|
|
123
|
+
|
|
124
|
+
What the scald scheme is *for*: bounded query cost (~300–1000 points) over any
|
|
125
|
+
Grafana time range, achieved by write-side reduction every 300 s into six InfluxDB
|
|
126
|
+
retention policies (1 s → 100 000 s), with per-type aggregate functions (sum for
|
|
127
|
+
counters so counts don't inflate, max for timings so spikes survive), plus
|
|
128
|
+
sgn-skig's Flask ginterval service doing read-side resolution selection via a hidden
|
|
129
|
+
Grafana template variable.
|
|
130
|
+
|
|
131
|
+
In the Prometheus model sgnmon commits to, **most of that problem does not exist**,
|
|
132
|
+
and the rest is an ops decision outside the library:
|
|
133
|
+
|
|
134
|
+
- **Correctness needs no pre-aggregation.** Counters are cumulative, so
|
|
135
|
+
`rate()`/`increase()` are exact at any step size; Grafana auto-scales the query
|
|
136
|
+
step with the time range, so point counts are bounded by construction. Latency
|
|
137
|
+
tails survive via the histogram (quantiles compose over any range) and
|
|
138
|
+
`max_over_time` on the gauges. Both aggregate-correctness rationales documented in
|
|
139
|
+
skig's `concepts.md` are native PromQL semantics.
|
|
140
|
+
- **What remains is long-range query cost and retention** — a TSDB deployment
|
|
141
|
+
choice, in rough order of preference:
|
|
142
|
+
1. **Plain Prometheus with long retention.** sgnmon's cardinality is tiny (10
|
|
143
|
+
families × element × pad × a few jobs). A single Prometheus with 1–2 y
|
|
144
|
+
retention handles this scale trivially; query-time stepping does the
|
|
145
|
+
"resolution selection" for free. Start here; escalate only if range queries
|
|
146
|
+
actually get slow.
|
|
147
|
+
2. **Recording rules** for the handful of expensive range queries (e.g. 5 m
|
|
148
|
+
rollups of `rate(sgnmon_data_seconds[...])`). The moral equivalent of scald's
|
|
149
|
+
write-side reduction, expressed in ~20 lines of ops YAML instead of a library
|
|
150
|
+
feature plus a bespoke Flask service.
|
|
151
|
+
3. **Thanos**, if years of history in object storage are needed: its compactor
|
|
152
|
+
downsamples to 5 m and 1 h resolutions and its query layer auto-selects
|
|
153
|
+
resolution per time range — the maintained-infrastructure version of
|
|
154
|
+
powers-of-10 + ginterval. Mimir/Cortex are the scale-out alternatives but do
|
|
155
|
+
*not* downsample; VictoriaMetrics is very cheap for long raw retention but
|
|
156
|
+
gates downsampling behind its enterprise edition.
|
|
157
|
+
4. **Bridge option** if ops is institutionally committed to InfluxDB/Grafana:
|
|
158
|
+
scrape `/metrics` with Telegraf into InfluxDB and keep continuous queries
|
|
159
|
+
there. Retains the storage stack with zero sgn-skig code.
|
|
160
|
+
- **The genuinely open ops question is push vs. pull**, not aggregation: sgn-skig
|
|
161
|
+
pushes, which works from ephemeral Condor jobs; Prometheus pulls, which requires
|
|
162
|
+
the scraper to find the job. Standard answers: a Grafana Alloy/vmagent sidecar
|
|
163
|
+
that scrapes localhost and `remote_write`s out, or file-based service discovery
|
|
164
|
+
fed from job submission. This is the one item to settle with ops *before*
|
|
165
|
+
declaring sgn-skig deprecated — the only thing skig's architecture handles that
|
|
166
|
+
sgnmon does not yet have a designated answer for.
|
|
167
|
+
|
|
168
|
+
## 6. Gaps sgnmon should close — CLOSED 2026-07-22
|
|
169
|
+
|
|
170
|
+
1. **Per-pad execution time** — DONE (`0b61ebe`): taps time the original pad
|
|
171
|
+
callable into the `sgnmon_exec_seconds` histogram (buckets 100µs–5s,
|
|
172
|
+
`Monitor(exec_buckets=...)`). Known limitation: recorded on `src`/`snk` taps
|
|
173
|
+
only — not on `adp` internal-pad wraps (one wrap serves several probes) and
|
|
174
|
+
not by the in-graph elements — so absence of exec data for those probes means
|
|
175
|
+
"not measured", not "zero cost".
|
|
176
|
+
2. **Process metrics** — DONE (`f97881e`): `Monitor(process_metrics=True)` /
|
|
177
|
+
`sgnmon run --process-metrics` registers the prometheus_client process
|
|
178
|
+
collector (`/proc`-based, Linux only). sgn's `StatsSource` keeps its
|
|
179
|
+
data-plane role.
|
|
180
|
+
3. **Custom metrics API** — DONE (`048f98e`): `sgnmon.metrics` with
|
|
181
|
+
`MetricSpec`/`MetricsMixin` (skig-compatible verbs) plus
|
|
182
|
+
`Monitor.counter/gauge/histogram` factories.
|
|
183
|
+
4. **Event-frame latency** — DONE (`dbb144e`): `EventLatency` element records
|
|
184
|
+
per-event GPS latency through the new `PadProbe.record_latency` without
|
|
185
|
+
inflating frame counts. Note the default `time_field` is `"time"` (skig
|
|
186
|
+
defaulted to `"gpstime"`) — migrations must pass it explicitly.
|
|
187
|
+
|
|
188
|
+
## 7. Deprecation path for sgn-skig
|
|
189
|
+
|
|
190
|
+
Exactly one downstream consumer exists: **sgn-llai**, and it imports the full
|
|
191
|
+
surface — `MetricsCollectorMixin`/`MetricDeclaration` (heavily, for custom metrics),
|
|
192
|
+
`MetricsPipeline`, `EventLatency`, `DelayBuffer`, `RoundRobinDistributor`, and
|
|
193
|
+
`KafkaSource`/`KafkaSink`/`ScitokenKafkaSource`. The plan splits in three:
|
|
194
|
+
|
|
195
|
+
- **Monitoring surface → sgnmon** (§4/§6). After that, sgn-llai's `MetricsPipeline`
|
|
196
|
+
becomes plain `Pipeline` + `monitor.tap()`, and its mixin usage becomes the sgnmon
|
|
197
|
+
mixin.
|
|
198
|
+
- **Transport elements → a new home.** KafkaSource/KafkaSink/ScitokenKafkaSource/
|
|
199
|
+
IGWNAlertSource are ingestion, not monitoring — they cannot land in sgnmon without
|
|
200
|
+
recreating the scope creep that made skig awkward. Options: a new `sgn-kafka`
|
|
201
|
+
package, or fold into sgn-ligo; the SciToken/IGWN-alert pieces argue for the
|
|
202
|
+
LIGO-flavored home. `DelayBuffer`/`RoundRobinDistributor` go with them or move
|
|
203
|
+
into sgn-llai itself if no one else needs them.
|
|
204
|
+
- **Ops surface → dropped or replaced by config.** Grafana dashboard generation
|
|
205
|
+
becomes largely unnecessary: because sgnmon's families and labels are uniform
|
|
206
|
+
across every pipeline, one static, hand-maintained Grafana dashboard with
|
|
207
|
+
`element`/`pad`/`job` variables serves all deployments — strictly better than
|
|
208
|
+
per-pipeline generated JSON. The ginterval service, provisioning CLIs, and
|
|
209
|
+
netrc/Influx user tooling are replaced by whatever TSDB choice comes out of §5.
|
|
210
|
+
|
|
211
|
+
Sequencing: (1) land §6 items in sgnmon — DONE 2026-07-22; (2) settle the
|
|
212
|
+
push-vs-pull + TSDB question with ops — decided, see §8; (3) migrate sgn-llai
|
|
213
|
+
(metrics first, transports once they have a home); (4) archive sgn-skig with a
|
|
214
|
+
README pointer.
|
|
215
|
+
|
|
216
|
+
Two items discovered after the original analysis, tracked in
|
|
217
|
+
[CONSOLIDATION-TRACKING.md](CONSOLIDATION-TRACKING.md) with the rest of the open
|
|
218
|
+
work: whether sgn-llai's CI depends on skig's `write_metrics_manifest()` YAML
|
|
219
|
+
export (dropped from the port — confirm before migrating), and health-check
|
|
220
|
+
integration with IGWN monitoring (resolved: a stock Nagios `check_http` against
|
|
221
|
+
`/health` suffices; no igwn-monitoring-plugins packaging needed).
|
|
222
|
+
|
|
223
|
+
**Testpoints**: unchanged. They are a zero-config terminal debugger with access to
|
|
224
|
+
live frame *contents* — a different job than fleet monitoring, and the only
|
|
225
|
+
user-facing thing sgnmon could subsume (exec timing) is §6 item 1.
|
|
226
|
+
|
|
227
|
+
## 8. Storage decision (resolved 2026-07-22)
|
|
228
|
+
|
|
229
|
+
Deployment facts: production pipelines are long-lived services; IGWN ops already
|
|
230
|
+
runs Grafana and Prometheus; InfluxDB is maintained only for the sgn-skig stack.
|
|
231
|
+
Two workloads must be covered: operational metrics (weeks-long queries) and sgnl's
|
|
232
|
+
scientific results (GPS-stamped, sparse/backfilled series currently stored via
|
|
233
|
+
scald→InfluxDB and browsed in Grafana).
|
|
234
|
+
|
|
235
|
+
**Decision: Prometheus stays the only query layer; a single-node VictoriaMetrics
|
|
236
|
+
instance becomes the long-term backend; InfluxDB is retired.**
|
|
237
|
+
|
|
238
|
+
- Ops metrics: IGWN Prometheus scrapes each service's `/metrics` and
|
|
239
|
+
`remote_write`s to VictoriaMetrics for multi-year raw retention. No
|
|
240
|
+
downsampling needed at sgnmon's cardinality; recording rules are the escape
|
|
241
|
+
hatch if a specific range query ever gets slow.
|
|
242
|
+
- How this preserves scald's bounded-query property without pre-aggregation:
|
|
243
|
+
Grafana range queries carry a step ≈ range / max-data-points (~1000), so any
|
|
244
|
+
panel returns ~1000 points per series regardless of time span — the read-side
|
|
245
|
+
resolution selection ginterval provided, but automatic and per zoom level
|
|
246
|
+
(and zooming in reaches raw data, which scald expired after minutes). Scald's
|
|
247
|
+
write-time aggregate choice becomes a query-time choice via
|
|
248
|
+
`increase(x[$__interval])` / `max_over_time(x[$__interval])` etc. Server-side
|
|
249
|
+
scan cost of raw data is negligible at this scale (an 8-week, 20-series panel
|
|
250
|
+
scans ~10M samples; VM processes tens of millions per second per core), and
|
|
251
|
+
raw retention is single-digit GB/year. Caveat: sparse science series should
|
|
252
|
+
be queried with range functions or points panels — plain gauge queries only
|
|
253
|
+
fill forward within the ~5m staleness window; verify during the prototype.
|
|
254
|
+
- Scientific results: plain Prometheus scraping is the wrong tool (wall-clock
|
|
255
|
+
scrape timestamps, no backfill, no per-event fidelity). VictoriaMetrics accepts
|
|
256
|
+
pushed data with arbitrary timestamps via the InfluxDB v1 line-protocol `/write`
|
|
257
|
+
API and a timestamped Prometheus-text import endpoint, and serves the
|
|
258
|
+
Prometheus query API, so it appears in Grafana as just another Prometheus
|
|
259
|
+
datasource. sgnl pushes results directly; a thin line-protocol writer (~50
|
|
260
|
+
lines) should live in sgnl or a small dedicated package — not in sgnmon.
|
|
261
|
+
- "Current state" science values (latest range, live thresholds) can instead be
|
|
262
|
+
sgnmon custom gauges and ride the scrape path for free.
|
|
263
|
+
- Thanos was rejected (object-storage retention only, no push/backfill story —
|
|
264
|
+
would still require a second system for science data); Mimir likewise, plus
|
|
265
|
+
heavier operations.
|
|
266
|
+
- Migration checks: (a) prototype one representative sgnl series end to end
|
|
267
|
+
(line-protocol push of a few weeks of GPS-stamped values into a scratch VM
|
|
268
|
+
instance, rebuild the Grafana panel, compare against the Influx original);
|
|
269
|
+
(b) confirm the GPS-vs-Unix timestamp convention the existing scald dashboards
|
|
270
|
+
assume — VM/Grafana sample timestamps must be Unix time, with GPS kept as the
|
|
271
|
+
value or a companion series; (c) audit whether anything in sgnl's Influx data
|
|
272
|
+
is event *records* rather than time series — those belong in a proper service
|
|
273
|
+
(e.g. GraceDB), not any TSDB.
|