diff --git a/docker/telemetry/grafana/dashboards/fix_server_info_panels.py b/docker/telemetry/grafana/dashboards/fix_server_info_panels.py deleted file mode 100644 index 7dd902bdf8..0000000000 --- a/docker/telemetry/grafana/dashboards/fix_server_info_panels.py +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env python3 -"""Fix three Server Info panels on node-health (legends + colors), matching the -conventions of the user's reference "Validated Ledger Seq — Current (Stat)" -panel. Throwaway helper (not committed). - -Operates on a dashboard JSON that was pulled FRESH from Grafana Cloud, so the -user's live layout and their Validated Ledger Seq / Build Version panels are -preserved verbatim -- only these three panels are edited in place: - - - Server State : pie -> horizontal bargauge, one bar per state (node count), - each bar coloured by state via value mappings (red/orange/yellow/blue/green). - - Peer Count : bargauge -> stat, value_and_name + per-node legend + gradient - colour (clones the reference stat panel's options/color). - - Uptime : bargauge -> stat, same stat treatment, unit seconds. - -Build Version and the Validated Ledger Seq * panels are intentionally left -untouched (the user owns those). -""" - -import json -import sys - -FILTERS = ( - 'service_instance_id=~"$node", deployment_environment=~"$deployment_environment", ' - 'xrpl_network_type=~"$xrpl_network_type", service_name=~"$service_name", ' - 'xrpl_work_item=~"$xrpl_work_item", xrpl_branch=~"$xrpl_branch", ' - 'xrpl_node_role=~"$xrpl_node_role"' -) - -DS = {"type": "prometheus", "uid": "${DS_PROMETHEUS}"} - -# state value -> (name, colour) for the Server State bars. -STATES = [ - ("0", "DISCONNECTED", "red"), - ("1", "CONNECTED", "orange"), - ("2", "SYNCING", "yellow"), - ("3", "TRACKING", "blue"), - ("4", "FULL", "green"), -] - - -def find(panels, title): - for p in panels: - if p.get("title") == title: - return p - if "panels" in p: - r = find(p["panels"], title) - if r: - return r - return None - - -def fix_server_state(p): - """Pie -> bargauge, one bar per state (node count), state-coloured.""" - expr = 'count_values("state", server_info{%s, metric="server_state"})' % FILTERS - for num, name, _ in STATES: - expr = 'label_replace(%s, "state", "%s", "state", "%s")' % (expr, name, num) - p["type"] = "bargauge" - p["targets"] = [ - { - "datasource": DS, - "expr": expr, - "instant": True, - "legendFormat": "{{state}}", - "refId": "A", - } - ] - p["options"] = { - "displayMode": "gradient", - "orientation": "horizontal", - "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": False}, - "showUnfilled": True, - "valueMode": "color", - } - # colour each bar by its state name (value mapping sets text colour). - p["fieldConfig"] = { - "defaults": { - "color": {"mode": "fixed"}, - "displayName": "${__field.labels.state}", - "mappings": [ - {"type": "value", "options": {name: {"index": i, "color": colour}}} - for i, (_, name, colour) in enumerate(STATES) - ], - "unit": "short", - }, - "overrides": [ - { - "matcher": {"id": "byName", "options": name}, - "properties": [ - {"id": "color", "value": {"mode": "fixed", "fixedColor": colour}} - ], - } - for _, name, colour in STATES - ], - } - - -def to_reference_stat(p, expr, unit): - """bargauge -> stat, cloning the reference 'Current (Stat)' panel: per-node - legend, value_and_name text, continuous colour.""" - p["type"] = "stat" - p["targets"] = [ - { - "datasource": DS, - "expr": expr, - "instant": True, - "legendFormat": "{{service_instance_id}}", - "refId": "A", - } - ] - p["options"] = { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "center", - "orientation": "auto", - "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": False}, - "textMode": "value_and_name", - "wideLayout": True, - } - p["fieldConfig"] = { - "defaults": { - "color": {"mode": "continuous-RdYlGr"}, - "thresholds": { - "mode": "absolute", - "steps": [{"color": "green", "value": 0}], - }, - "unit": unit, - }, - "overrides": [], - } - - -def fix(dash): - panels = dash["panels"] - fix_server_state(find(panels, "Server State")) - to_reference_stat( - find(panels, "Peer Count"), 'server_info{%s, metric="peers"}' % FILTERS, "short" - ) - to_reference_stat( - find(panels, "Uptime"), 'server_info{%s, metric="uptime"}' % FILTERS, "s" - ) - return dash - - -def _dump(obj, lvl=0): - pad = " " * lvl - pad1 = " " * (lvl + 1) - if isinstance(obj, dict): - if not obj: - return "{}" - return ( - "{\n" - + ",\n".join( - "%s%s: %s" % (pad1, json.dumps(k), _dump(v, lvl + 1)) - for k, v in obj.items() - ) - + "\n" - + pad - + "}" - ) - if isinstance(obj, list): - if not obj: - return "[]" - if all(isinstance(x, (str, int, float, bool)) or x is None for x in obj): - return "[" + ", ".join(json.dumps(x) for x in obj) + "]" - return ( - "[\n" - + ",\n".join("%s%s" % (pad1, _dump(x, lvl + 1)) for x in obj) - + "\n" - + pad - + "]" - ) - return json.dumps(obj) - - -# Runtime-only keys the Grafana API returns that must NOT be written to the -# repo file (they are instance state, not dashboard definition). -_RUNTIME_KEYS = ("id", "version", "liveNow") - - -def main(): - """argv: [dest2.json ...] - - The live pull is the source of truth for layout + the user's panels. For each - dest we start from that dest's EXISTING top-level shape (to keep repo-only - keys like ``links`` and avoid importing runtime keys), then replace only the - ``panels`` array with the fixed live layout.""" - live = json.load(open(sys.argv[1])) - fix(live) - for dest in sys.argv[2:]: - base = json.load(open(dest)) # keep dest's own top-level keys - base["panels"] = live["panels"] # adopt live layout + fixed panels - for k in _RUNTIME_KEYS: - base.pop(k, None) - out = _dump(base) + "\n" - json.loads(out) - open(dest, "w").write(out) - print("wrote", dest, "(%d top-level panels)" % len(base["panels"])) - - -if __name__ == "__main__": - main() diff --git a/docker/telemetry/grafana/dashboards/retype_server_info.py b/docker/telemetry/grafana/dashboards/retype_server_info.py deleted file mode 100644 index 960b5827e6..0000000000 --- a/docker/telemetry/grafana/dashboards/retype_server_info.py +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env python3 -"""Change the render type of four Server Info tiles on node-health, and append -the user's live-authored ledger-seq panels. Throwaway helper (not committed). - -Transforms (type + query/options only; gridPos kept): - - Build Version : stat -> piechart, slices = count by (version, xrpl_branch) - - Server State : stat -> piechart, slices = node count per state (keep 0-4 - value/color mappings) - - Peer Count : stat -> bargauge (horizontal), one bar per node - - Uptime : stat -> bargauge (horizontal), one bar per node - -The user added three panels directly on Grafana Cloud (a row + two ledger-seq -stat panels); those are re-appended verbatim from /tmp/nh_added_panels.json so a -repo write does not drop them. -""" - -import json -import sys - -FILTERS = ( - 'service_instance_id=~"$node", deployment_environment=~"$deployment_environment", ' - 'xrpl_network_type=~"$xrpl_network_type", service_name=~"$service_name", ' - 'xrpl_work_item=~"$xrpl_work_item", xrpl_branch=~"$xrpl_branch", ' - 'xrpl_node_role=~"$xrpl_node_role"' -) - -ADDED_PANELS = "/tmp/nh_added_panels.json" - - -def find(panels, title): - for p in panels: - if p.get("title") == title: - return p - if "panels" in p: - r = find(p["panels"], title) - if r: - return r - return None - - -def to_piechart(p, expr, legend): - """Convert a panel to a piechart with one target + legend.""" - p["type"] = "piechart" - p["targets"] = [ - { - "datasource": {"type": "prometheus", "uid": "${DS_PROMETHEUS}"}, - "expr": expr, - "legendFormat": legend, - "refId": "A", - } - ] - p["options"] = { - "legend": {"displayMode": "list", "placement": "right", "values": ["value"]}, - "pieType": "pie", - "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": False}, - "tooltip": {"mode": "single", "sort": "desc"}, - } - # piechart uses field displayName for slice labels, not the xrpl_ident stat form - p.setdefault("fieldConfig", {}).setdefault("defaults", {}).pop("displayName", None) - - -def to_bargauge(p, expr): - """Convert a panel to a horizontal bar gauge, one bar per node.""" - p["type"] = "bargauge" - for t in p["targets"]: - t["expr"] = expr - t["instant"] = False - t["legendFormat"] = "{{service_instance_id}}" - p["options"] = { - "displayMode": "gradient", - "orientation": "horizontal", - "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": False}, - "showUnfilled": True, - "valueMode": "color", - } - # bar label is the per-node legend, not the stat xrpl_ident bracket - p.setdefault("fieldConfig", {}).setdefault("defaults", {})[ - "displayName" - ] = "{{service_instance_id}}" - - -def retype(dash): - panels = dash["panels"] - - # 1) Build Version -> pie by (version, xrpl_branch) - bv = find(panels, "Build Version") - to_piechart( - bv, - "count by (version, xrpl_branch) (build_info{%s})" % FILTERS, - "{{version}} {{xrpl_branch}}", - ) - - # 2) Server State -> pie of node count per state. server_state is a metric - # VALUE (0-4), not a label, so count_values buckets nodes by that value - # into a new "state" label; the nested label_replace chain then renames - # 0-4 to DISCONNECTED..FULL so the pie slices read as state names. (Value - # mappings can't help here -- they map field values, not legend labels.) - ss = find(panels, "Server State") - state_expr = ( - 'count_values("state", server_info{%s, metric="server_state"})' % FILTERS - ) - for num, name in ( - ("0", "DISCONNECTED"), - ("1", "CONNECTED"), - ("2", "SYNCING"), - ("3", "TRACKING"), - ("4", "FULL"), - ): - state_expr = 'label_replace(%s, "state", "%s", "state", "%s")' % ( - state_expr, - name, - num, - ) - to_piechart(ss, state_expr, "{{state}}") - - # 3) Peer Count -> bargauge per node - pc = find(panels, "Peer Count") - to_bargauge(pc, 'server_info{%s, metric="peers"}' % FILTERS) - - # 4) Uptime -> bargauge per node - up = find(panels, "Uptime") - to_bargauge(up, 'server_info{%s, metric="uptime"}' % FILTERS) - - # 5) Re-append the user's live-authored panels if not already present. - have = {p.get("title") for p in panels} - added = json.load(open(ADDED_PANELS)) - for p in added: - if p.get("title") not in have: - panels.append(p) - - return dash - - -# --- serializer matching the dashboards' on-disk byte format --------------- -def _dump(obj, lvl=0): - pad = " " * lvl - pad1 = " " * (lvl + 1) - if isinstance(obj, dict): - if not obj: - return "{}" - return ( - "{\n" - + ",\n".join( - "%s%s: %s" % (pad1, json.dumps(k), _dump(v, lvl + 1)) - for k, v in obj.items() - ) - + "\n" - + pad - + "}" - ) - if isinstance(obj, list): - if not obj: - return "[]" - if all(isinstance(x, (str, int, float, bool)) or x is None for x in obj): - return "[" + ", ".join(json.dumps(x) for x in obj) + "]" - return ( - "[\n" - + ",\n".join("%s%s" % (pad1, _dump(x, lvl + 1)) for x in obj) - + "\n" - + pad - + "]" - ) - return json.dumps(obj) - - -def main(): - for path in sys.argv[1:]: - d = json.load(open(path)) - retype(d) - out = _dump(d) + "\n" - json.loads(out) - open(path, "w").write(out) - print("%s: retyped 4 panels, +%d user panels" % (path, 3)) - - -if __name__ == "__main__": - main()