diff options
| -rw-r--r-- | dashboards/general.json | 279 | ||||
| -rwxr-xr-x | oexport.py | 42 | ||||
| -rw-r--r-- | ribreader.py | 5 |
3 files changed, 317 insertions, 9 deletions
diff --git a/dashboards/general.json b/dashboards/general.json index 08eae1e..4047d9e 100644 --- a/dashboards/general.json +++ b/dashboards/general.json @@ -2796,7 +2796,7 @@ "x": 12, "y": 76 }, - "id": 211, + "id": 216, "options": { "legend": { "calcs": [], @@ -12961,6 +12961,283 @@ ], "title": "FRCT \u2014 Tx ring drops", "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "df36a9e8-5205-4042-9bfb-0516cbcacb4f" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 2, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 6, + "x": 18, + "y": 76 + }, + "id": 215, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "df36a9e8-5205-4042-9bfb-0516cbcacb4f" + }, + "query": "from(bucket: \"ouroboros-metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"ouroboros_flow_allocator_loss_cuts_total\")\n |> filter(fn: (r) => r[\"_field\"] == \"loss_cuts_total\")\n |> filter(fn: (r) => contains(value: r[\"ipcp\"], set: ${_ipcp:json}))\n |> filter(fn: (r) => contains(value: r[\"system\"], set: ${_system:json}))\n |> filter(fn: (r) => contains(value: r[\"layer\"], set: ${_layer:json}))\n |> aggregateWindow(every: ${_interval}, fn: max, createEmpty: false)\n |> derivative(unit: 1s, nonNegative: true)\n |> yield(name: \"rate\")", + "refId": "A" + } + ], + "title": "Signal-loss cuts/s", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "df36a9e8-5205-4042-9bfb-0516cbcacb4f" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 2, + "scaleDistribution": { + "log": 2, + "type": "log" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ns" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 6, + "x": 12, + "y": 86 + }, + "id": 218, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "df36a9e8-5205-4042-9bfb-0516cbcacb4f" + }, + "query": "from(bucket: \"ouroboros-metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"ouroboros_flow_allocator_ss_tc_ns\")\n |> filter(fn: (r) => r[\"_field\"] == \"ss_tc_ns\")\n |> filter(fn: (r) => contains(value: r[\"ipcp\"], set: ${_ipcp:json}))\n |> filter(fn: (r) => contains(value: r[\"system\"], set: ${_system:json}))\n |> filter(fn: (r) => contains(value: r[\"layer\"], set: ${_layer:json}))\n |> aggregateWindow(every: ${_interval}, fn: last, createEmpty: false)\n |> yield(name: \"last\")", + "refId": "A" + } + ], + "title": "Ramp time constant (ss_tc)", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "df36a9e8-5205-4042-9bfb-0516cbcacb4f" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 2, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 6, + "x": 18, + "y": 86 + }, + "id": 217, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "df36a9e8-5205-4042-9bfb-0516cbcacb4f" + }, + "query": "from(bucket: \"ouroboros-metrics\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"ouroboros_flow_allocator_hb_rtt_samples_total\")\n |> filter(fn: (r) => r[\"_field\"] == \"hb_rtt_samples_total\")\n |> filter(fn: (r) => contains(value: r[\"ipcp\"], set: ${_ipcp:json}))\n |> filter(fn: (r) => contains(value: r[\"system\"], set: ${_system:json}))\n |> filter(fn: (r) => contains(value: r[\"layer\"], set: ${_layer:json}))\n |> aggregateWindow(every: ${_interval}, fn: max, createEmpty: false)\n |> derivative(unit: 1s, nonNegative: true)\n |> yield(name: \"rate\")", + "refId": "A" + } + ], + "title": "Heartbeat RTT samples/s", + "type": "timeseries" } ], "preload": false, @@ -86,6 +86,38 @@ def _jain_index(values): return (total * total) / (n * sq) +# Additive inc, proportional dec, loss recovery: the pacer is binding. +_FAIRNESS_REGIMES = {1, 2, 4} + + +def _fairness_rates(flows): + """Paced rates that count toward the Jain fairness index. + + Fairness is only meaningful over senders whose pacer constrains + real data traffic. Slow start (0) has no allocation yet; source + limited (3) covers sinks, where paced_rate is reverse-ACK pacing. + Older RIBs without a regime code fall back to a byte-ratio test + to weed out sinks: ACK-only traffic is a few percent of the + received volume, nowhere near the 25% cutoff. + """ + rates = [] + for flow in flows: + rate = flow.get('paced_rate', 0) + if rate <= 0: + continue + + regime = flow.get('cong_regime') + sent = flow.get('sent_bytes_total', 0) + rcvd = flow.get('recv_bytes_total', 0) + if regime is not None: + if regime in _FAIRNESS_REGIMES: + rates.append(rate) + elif sent * 4 > rcvd: + rates.append(rate) + + return rates + + def _make_timerfd(interval_ns: int) -> int: """Create and arm a CLOCK_MONOTONIC timerfd for *interval_ns* nanoseconds. @@ -366,7 +398,6 @@ class OuroborosExporter: ipcp_name, layer, metrics) -> None: - point = {} for metric in metrics: point = { 'measurement': f'ouroboros_data_transfer_{metric}', @@ -381,8 +412,8 @@ class OuroborosExporter: 'time': now, } - self.writer.write(bucket=self.bucket, - record=Point.from_dict(point)) + self.writer.write(bucket=self.bucket, + record=Point.from_dict(point)) def _write_ouroboros_process_frct_metric(self, now, @@ -503,7 +534,6 @@ class OuroborosExporter: for ipcp in ipcps: flows = self.ribreader.get_flow_allocator_flow_info_for_ipcp(ipcp['name']) - rates = [] for flow in flows: for metric, value in flow.items(): if metric == 'endpoint_id': @@ -513,11 +543,9 @@ class OuroborosExporter: metric, str(now), ipcp['name'], flow['endpoint_id'], ipcp['layer'], value) - if 'paced_rate' in flow: - rates.append(flow['paced_rate']) - # Per-IPCP Jain fairness over the CA-allocated (paced) rates; # a bottleneck-fairness proxy, meaningful with >=2 flows. + rates = _fairness_rates(flows) if len(rates) >= 2: jain = _jain_index(rates) if jain is not None: diff --git a/ribreader.py b/ribreader.py index fa40804..856ca1d 100644 --- a/ribreader.py +++ b/ribreader.py @@ -262,7 +262,7 @@ class OuroborosRIBReader: @staticmethod def _get_trailing_number(s: str) -> int: """Extract trailing integer from a string.""" - m = re.search(r'\d+$', s) + m = re.search(r'-?\d+$', s) return int(m.group()) if m else None def get_dht_stats_for_ipcp(self, @@ -344,6 +344,9 @@ class OuroborosRIBReader: 'Capacity rate floor (bytes/s)': 'cap_rate_floor', 'Capacity updates (count)': 'cap_updates_total', 'Slow start peak rate (bytes/s)': 'ss_peak_rate', + 'Signal-loss cuts (count)': 'loss_cuts_total', + 'Heartbeat RTT samples (count)': 'hb_rtt_samples_total', + 'Ramp time constant (ns)': 'ss_tc_ns', } ret = {} |
