SamDNX commited on
Commit
cfb8abe
·
1 Parent(s): bbcafae

Deploy MoBis: live planner API + web app + dataset

Browse files
hf_space/grafana/dashboards/mobaz.json CHANGED
@@ -4,69 +4,92 @@
4
  "tags": ["mobaz"],
5
  "timezone": "browser",
6
  "schemaVersion": 39,
7
- "version": 1,
8
  "editable": true,
9
  "refresh": "30s",
10
  "time": { "from": "now-6h", "to": "now" },
11
  "annotations": { "list": [] },
12
  "templating": { "list": [] },
13
  "panels": [
 
14
  {
15
- "id": 1,
16
- "type": "stat",
17
- "title": "Uptime",
18
  "datasource": { "type": "prometheus", "uid": "prometheus" },
19
- "gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 },
20
  "fieldConfig": { "defaults": { "unit": "s", "color": { "mode": "fixed", "fixedColor": "green" } }, "overrides": [] },
21
  "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "none" },
22
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_uptime_seconds" } ]
23
  },
24
  {
25
- "id": 2,
26
- "type": "stat",
27
- "title": "Requests / sec (5m)",
28
  "datasource": { "type": "prometheus", "uid": "prometheus" },
29
- "gridPos": { "h": 4, "w": 6, "x": 6, "y": 0 },
30
  "fieldConfig": { "defaults": { "unit": "reqps", "decimals": 2, "color": { "mode": "fixed", "fixedColor": "blue" } }, "overrides": [] },
31
  "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area" },
32
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "sum(rate(mobaz_http_requests_total[5m]))" } ]
33
  },
34
  {
35
- "id": 3,
36
- "type": "stat",
37
- "title": "p95 latency (5m)",
38
  "datasource": { "type": "prometheus", "uid": "prometheus" },
39
- "gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 },
40
  "fieldConfig": { "defaults": { "unit": "s", "decimals": 3, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "orange", "value": 1 }, { "color": "red", "value": 3 } ] } }, "overrides": [] },
41
  "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "none" },
42
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "histogram_quantile(0.95, sum(rate(mobaz_http_request_duration_seconds_bucket[5m])) by (le))" } ]
43
  },
44
  {
45
- "id": 4,
46
- "type": "stat",
47
- "title": "Client errors (total)",
48
  "datasource": { "type": "prometheus", "uid": "prometheus" },
49
- "gridPos": { "h": 4, "w": 6, "x": 18, "y": 0 },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  "fieldConfig": { "defaults": { "unit": "short", "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 1 } ] } }, "overrides": [] },
51
  "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "background", "graphMode": "none" },
52
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_client_errors_total" } ]
53
  },
 
 
54
  {
55
- "id": 5,
56
- "type": "timeseries",
57
- "title": "Request rate by route",
58
  "datasource": { "type": "prometheus", "uid": "prometheus" },
59
- "gridPos": { "h": 9, "w": 12, "x": 0, "y": 4 },
60
  "fieldConfig": { "defaults": { "unit": "reqps", "custom": { "drawStyle": "line", "fillOpacity": 15, "stacking": { "mode": "normal" } } }, "overrides": [] },
61
  "options": { "legend": { "displayMode": "table", "placement": "right", "calcs": ["mean", "max"] }, "tooltip": { "mode": "multi" } },
62
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "sum by (route) (rate(mobaz_http_requests_total[5m]))", "legendFormat": "{{route}}" } ]
63
  },
64
  {
65
- "id": 6,
66
- "type": "timeseries",
67
- "title": "Latency quantiles",
68
  "datasource": { "type": "prometheus", "uid": "prometheus" },
69
- "gridPos": { "h": 9, "w": 12, "x": 12, "y": 4 },
70
  "fieldConfig": { "defaults": { "unit": "s", "custom": { "drawStyle": "line", "fillOpacity": 10 } }, "overrides": [] },
71
  "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
72
  "targets": [
@@ -76,21 +99,81 @@
76
  ]
77
  },
78
  {
79
- "id": 7,
80
- "type": "timeseries",
81
- "title": "Responses by status code",
82
  "datasource": { "type": "prometheus", "uid": "prometheus" },
83
- "gridPos": { "h": 9, "w": 12, "x": 0, "y": 13 },
84
  "fieldConfig": { "defaults": { "unit": "reqps", "custom": { "drawStyle": "line", "fillOpacity": 15, "stacking": { "mode": "normal" } } }, "overrides": [] },
85
  "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
86
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "sum by (code) (rate(mobaz_http_requests_total[5m]))", "legendFormat": "{{code}}" } ]
87
  },
88
  {
89
- "id": 8,
90
- "type": "timeseries",
91
- "title": "Cache & data gauges",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  "datasource": { "type": "prometheus", "uid": "prometheus" },
93
- "gridPos": { "h": 9, "w": 12, "x": 12, "y": 13 },
94
  "fieldConfig": { "defaults": { "unit": "short", "custom": { "drawStyle": "line", "fillOpacity": 5 } }, "overrides": [] },
95
  "options": { "legend": { "displayMode": "table", "placement": "right", "calcs": ["lastNotNull"] }, "tooltip": { "mode": "multi" } },
96
  "targets": [
 
4
  "tags": ["mobaz"],
5
  "timezone": "browser",
6
  "schemaVersion": 39,
7
+ "version": 2,
8
  "editable": true,
9
  "refresh": "30s",
10
  "time": { "from": "now-6h", "to": "now" },
11
  "annotations": { "list": [] },
12
  "templating": { "list": [] },
13
  "panels": [
14
+ { "id": 100, "type": "row", "title": "Overview", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "collapsed": false },
15
  {
16
+ "id": 1, "type": "stat", "title": "Uptime",
 
 
17
  "datasource": { "type": "prometheus", "uid": "prometheus" },
18
+ "gridPos": { "h": 4, "w": 6, "x": 0, "y": 1 },
19
  "fieldConfig": { "defaults": { "unit": "s", "color": { "mode": "fixed", "fixedColor": "green" } }, "overrides": [] },
20
  "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "none" },
21
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_uptime_seconds" } ]
22
  },
23
  {
24
+ "id": 2, "type": "stat", "title": "Requests / sec (5m)",
 
 
25
  "datasource": { "type": "prometheus", "uid": "prometheus" },
26
+ "gridPos": { "h": 4, "w": 6, "x": 6, "y": 1 },
27
  "fieldConfig": { "defaults": { "unit": "reqps", "decimals": 2, "color": { "mode": "fixed", "fixedColor": "blue" } }, "overrides": [] },
28
  "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area" },
29
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "sum(rate(mobaz_http_requests_total[5m]))" } ]
30
  },
31
  {
32
+ "id": 3, "type": "stat", "title": "p95 latency (5m)",
 
 
33
  "datasource": { "type": "prometheus", "uid": "prometheus" },
34
+ "gridPos": { "h": 4, "w": 6, "x": 12, "y": 1 },
35
  "fieldConfig": { "defaults": { "unit": "s", "decimals": 3, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "orange", "value": 1 }, { "color": "red", "value": 3 } ] } }, "overrides": [] },
36
  "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "none" },
37
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "histogram_quantile(0.95, sum(rate(mobaz_http_request_duration_seconds_bucket[5m])) by (le))" } ]
38
  },
39
  {
40
+ "id": 9, "type": "stat", "title": "Active clients (5m)",
 
 
41
  "datasource": { "type": "prometheus", "uid": "prometheus" },
42
+ "gridPos": { "h": 4, "w": 6, "x": 18, "y": 1 },
43
+ "fieldConfig": { "defaults": { "unit": "short", "color": { "mode": "fixed", "fixedColor": "purple" } }, "overrides": [] },
44
+ "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area" },
45
+ "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_active_clients" } ]
46
+ },
47
+ {
48
+ "id": 10, "type": "stat", "title": "Avg response time (5m)",
49
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
50
+ "gridPos": { "h": 4, "w": 6, "x": 0, "y": 5 },
51
+ "fieldConfig": { "defaults": { "unit": "s", "decimals": 3, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "orange", "value": 0.5 }, { "color": "red", "value": 2 } ] } }, "overrides": [] },
52
+ "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "none" },
53
+ "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "sum(rate(mobaz_http_request_duration_seconds_sum[5m])) / sum(rate(mobaz_http_request_duration_seconds_count[5m]))" } ]
54
+ },
55
+ {
56
+ "id": 11, "type": "stat", "title": "In-flight requests",
57
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
58
+ "gridPos": { "h": 4, "w": 6, "x": 6, "y": 5 },
59
+ "fieldConfig": { "defaults": { "unit": "short", "color": { "mode": "fixed", "fixedColor": "blue" } }, "overrides": [] },
60
+ "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "value", "graphMode": "area" },
61
+ "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_inflight_requests" } ]
62
+ },
63
+ {
64
+ "id": 12, "type": "stat", "title": "Error rate (5xx, 5m)",
65
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
66
+ "gridPos": { "h": 4, "w": 6, "x": 12, "y": 5 },
67
+ "fieldConfig": { "defaults": { "unit": "percent", "decimals": 2, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "orange", "value": 1 }, { "color": "red", "value": 5 } ] } }, "overrides": [] },
68
+ "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "background", "graphMode": "none" },
69
+ "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "100 * sum(rate(mobaz_http_requests_total{code=~\"5..\"}[5m])) / clamp_min(sum(rate(mobaz_http_requests_total[5m])), 0.0001)" } ]
70
+ },
71
+ {
72
+ "id": 4, "type": "stat", "title": "Client errors (total)",
73
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
74
+ "gridPos": { "h": 4, "w": 6, "x": 18, "y": 5 },
75
  "fieldConfig": { "defaults": { "unit": "short", "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 1 } ] } }, "overrides": [] },
76
  "options": { "reduceOptions": { "calcs": ["lastNotNull"] }, "colorMode": "background", "graphMode": "none" },
77
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_client_errors_total" } ]
78
  },
79
+
80
+ { "id": 101, "type": "row", "title": "Traffic & latency", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 9 }, "collapsed": false },
81
  {
82
+ "id": 5, "type": "timeseries", "title": "Request rate by route",
 
 
83
  "datasource": { "type": "prometheus", "uid": "prometheus" },
84
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 10 },
85
  "fieldConfig": { "defaults": { "unit": "reqps", "custom": { "drawStyle": "line", "fillOpacity": 15, "stacking": { "mode": "normal" } } }, "overrides": [] },
86
  "options": { "legend": { "displayMode": "table", "placement": "right", "calcs": ["mean", "max"] }, "tooltip": { "mode": "multi" } },
87
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "sum by (route) (rate(mobaz_http_requests_total[5m]))", "legendFormat": "{{route}}" } ]
88
  },
89
  {
90
+ "id": 6, "type": "timeseries", "title": "Latency quantiles",
 
 
91
  "datasource": { "type": "prometheus", "uid": "prometheus" },
92
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 10 },
93
  "fieldConfig": { "defaults": { "unit": "s", "custom": { "drawStyle": "line", "fillOpacity": 10 } }, "overrides": [] },
94
  "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
95
  "targets": [
 
99
  ]
100
  },
101
  {
102
+ "id": 7, "type": "timeseries", "title": "Responses by status code",
 
 
103
  "datasource": { "type": "prometheus", "uid": "prometheus" },
104
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 18 },
105
  "fieldConfig": { "defaults": { "unit": "reqps", "custom": { "drawStyle": "line", "fillOpacity": 15, "stacking": { "mode": "normal" } } }, "overrides": [] },
106
  "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
107
  "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "sum by (code) (rate(mobaz_http_requests_total[5m]))", "legendFormat": "{{code}}" } ]
108
  },
109
  {
110
+ "id": 13, "type": "timeseries", "title": "Avg response time & in-flight",
111
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
112
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 18 },
113
+ "fieldConfig": { "defaults": { "unit": "s", "custom": { "drawStyle": "line", "fillOpacity": 10 } }, "overrides": [ { "matcher": { "id": "byName", "options": "in-flight" }, "properties": [ { "id": "unit", "value": "short" }, { "id": "custom.axisPlacement", "value": "right" } ] } ] },
114
+ "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
115
+ "targets": [
116
+ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "sum(rate(mobaz_http_request_duration_seconds_sum[5m])) / sum(rate(mobaz_http_request_duration_seconds_count[5m]))", "legendFormat": "avg response" },
117
+ { "refId": "B", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_inflight_requests", "legendFormat": "in-flight" }
118
+ ]
119
+ },
120
+
121
+ { "id": 102, "type": "row", "title": "System (CPU / RAM / host)", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 26 }, "collapsed": false },
122
+ {
123
+ "id": 14, "type": "timeseries", "title": "CPU usage",
124
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
125
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 27 },
126
+ "fieldConfig": { "defaults": { "unit": "percent", "min": 0, "custom": { "drawStyle": "line", "fillOpacity": 15 } }, "overrides": [] },
127
+ "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
128
+ "targets": [
129
+ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_process_cpu_percent", "legendFormat": "process" },
130
+ { "refId": "B", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_system_cpu_percent", "legendFormat": "system" }
131
+ ]
132
+ },
133
+ {
134
+ "id": 15, "type": "timeseries", "title": "Memory",
135
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
136
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 27 },
137
+ "fieldConfig": { "defaults": { "unit": "bytes", "custom": { "drawStyle": "line", "fillOpacity": 15 } }, "overrides": [ { "matcher": { "id": "byName", "options": "system used %" }, "properties": [ { "id": "unit", "value": "percent" }, { "id": "custom.axisPlacement", "value": "right" }, { "id": "max", "value": 100 } ] } ] },
138
+ "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
139
+ "targets": [
140
+ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_process_resident_memory_bytes", "legendFormat": "process RSS" },
141
+ { "refId": "B", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_system_memory_used_percent", "legendFormat": "system used %" }
142
+ ]
143
+ },
144
+ {
145
+ "id": 16, "type": "timeseries", "title": "Threads & open file descriptors",
146
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
147
+ "gridPos": { "h": 8, "w": 8, "x": 0, "y": 35 },
148
+ "fieldConfig": { "defaults": { "unit": "short", "custom": { "drawStyle": "line", "fillOpacity": 10 } }, "overrides": [] },
149
+ "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "multi" } },
150
+ "targets": [
151
+ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_process_threads", "legendFormat": "threads" },
152
+ { "refId": "B", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_process_open_fds", "legendFormat": "open fds" }
153
+ ]
154
+ },
155
+ {
156
+ "id": 17, "type": "timeseries", "title": "System load (1m)",
157
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
158
+ "gridPos": { "h": 8, "w": 8, "x": 8, "y": 35 },
159
+ "fieldConfig": { "defaults": { "unit": "short", "custom": { "drawStyle": "line", "fillOpacity": 15 } }, "overrides": [] },
160
+ "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } },
161
+ "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_system_load1", "legendFormat": "load1" } ]
162
+ },
163
+ {
164
+ "id": 18, "type": "timeseries", "title": "Active clients over time",
165
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
166
+ "gridPos": { "h": 8, "w": 8, "x": 16, "y": 35 },
167
+ "fieldConfig": { "defaults": { "unit": "short", "custom": { "drawStyle": "line", "fillOpacity": 20 } }, "overrides": [] },
168
+ "options": { "legend": { "displayMode": "list", "placement": "bottom" }, "tooltip": { "mode": "single" } },
169
+ "targets": [ { "refId": "A", "datasource": { "type": "prometheus", "uid": "prometheus" }, "expr": "mobaz_active_clients", "legendFormat": "active clients" } ]
170
+ },
171
+
172
+ { "id": 103, "type": "row", "title": "Data & caches", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 43 }, "collapsed": false },
173
+ {
174
+ "id": 8, "type": "timeseries", "title": "Cache & data gauges",
175
  "datasource": { "type": "prometheus", "uid": "prometheus" },
176
+ "gridPos": { "h": 8, "w": 24, "x": 0, "y": 44 },
177
  "fieldConfig": { "defaults": { "unit": "short", "custom": { "drawStyle": "line", "fillOpacity": 5 } }, "overrides": [] },
178
  "options": { "legend": { "displayMode": "table", "placement": "right", "calcs": ["lastNotNull"] }, "tooltip": { "mode": "multi" } },
179
  "targets": [
requirements.txt CHANGED
@@ -1,2 +1,3 @@
1
  requests>=2.25
2
  beautifulsoup4>=4.9
 
 
1
  requests>=2.25
2
  beautifulsoup4>=4.9
3
+ psutil>=5.9
server.py CHANGED
@@ -39,6 +39,15 @@ import geocode
39
  import nearby
40
  import roads
41
 
 
 
 
 
 
 
 
 
 
42
  ROOT = os.path.dirname(os.path.abspath(__file__))
43
  PUBLIC = os.path.join(ROOT, "public")
44
  PORT = int(os.environ.get("PORT", "8000"))
@@ -94,6 +103,8 @@ class _Metrics:
94
  self.duration_count = 0
95
  self.client_errors = 0
96
  self.beacons_dropped = 0 # rate-limited client beacons
 
 
97
 
98
  def observe(self, route, method, code, dur):
99
  with self._lock:
@@ -112,6 +123,54 @@ class _Metrics:
112
  else:
113
  self.client_errors += 1
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  def _gauges(self):
116
  """Best-effort point-in-time gauges (never raise into /metrics)."""
117
  out = {}
@@ -141,6 +200,7 @@ class _Metrics:
141
  buckets = list(self.bucket_counts)
142
  dur_sum, dur_count = self.duration_sum, self.duration_count
143
  client_errors, dropped = self.client_errors, self.beacons_dropped
 
144
  uptime = time.time() - self.start
145
  L = []
146
  L.append("# HELP mobaz_up Whether the MoBaz backend is serving (always 1).")
@@ -169,9 +229,19 @@ class _Metrics:
169
  L.append("# HELP mobaz_client_error_beacons_dropped_total Beacons dropped by the rate limit.")
170
  L.append("# TYPE mobaz_client_error_beacons_dropped_total counter")
171
  L.append("mobaz_client_error_beacons_dropped_total %d" % dropped)
172
- for name, val in self._gauges().items():
 
 
 
 
 
 
 
 
 
 
173
  L.append("# TYPE %s gauge" % name)
174
- L.append("%s %d" % (name, val))
175
  return "\n".join(L) + "\n"
176
 
177
 
@@ -189,6 +259,16 @@ def _status_html():
189
  gauges = METRICS._gauges()
190
  total = sum(reqs.values())
191
  avg_ms = (dur_sum / dur_count * 1000) if dur_count else 0.0
 
 
 
 
 
 
 
 
 
 
192
  # Requests grouped by route (sum across method/code).
193
  by_route = {}
194
  for (route, _m, _c), n in reqs.items():
@@ -203,6 +283,9 @@ def _status_html():
203
  ("uptime", "%dh %dm" % (uptime // 3600, (uptime % 3600) // 60)),
204
  ("requests", total),
205
  ("avg latency", "%.1f ms" % avg_ms),
 
 
 
206
  ("stops", gauges.get("mobaz_stops", 0)),
207
  ("events cached", gauges.get("mobaz_events_cached", 0)),
208
  ("geocode cache", gauges.get("mobaz_geocode_cache_entries", 0)),
@@ -350,6 +433,17 @@ class Handler(BaseHTTPRequestHandler):
350
  if t0 is not None:
351
  route = getattr(self, "_route", _route_label(urlparse(self.path).path))
352
  METRICS.observe(route, self.command or "GET", code, time.perf_counter() - t0)
 
 
 
 
 
 
 
 
 
 
 
353
 
354
  def _cors(self):
355
  # The packaged app calls this API from a file:// origin, so allow any.
@@ -422,8 +516,7 @@ class Handler(BaseHTTPRequestHandler):
422
  def do_GET(self):
423
  parsed = urlparse(self.path)
424
  path = parsed.path
425
- self._t0 = time.perf_counter()
426
- self._route = _route_label(path)
427
  try:
428
  if path == "/metrics":
429
  return self._send_text(METRICS.render(), "text/plain; version=0.0.4")
@@ -540,8 +633,7 @@ class Handler(BaseHTTPRequestHandler):
540
 
541
  def do_POST(self):
542
  parsed = urlparse(self.path)
543
- self._t0 = time.perf_counter()
544
- self._route = _route_label(parsed.path)
545
  if parsed.path != "/api/clienterror":
546
  return self._send_json({"error": "not found"}, 404)
547
  # Read at most a few KB; oversized or malformed bodies are dropped quietly.
 
39
  import nearby
40
  import roads
41
 
42
+ try:
43
+ import psutil # process + system CPU/RAM metrics for /metrics
44
+ _PROC = psutil.Process()
45
+ _PROC.cpu_percent(interval=None) # prime the per-process CPU delta
46
+ psutil.cpu_percent(interval=None) # prime the system CPU delta
47
+ except Exception: # noqa: BLE001 — metrics are best-effort; never block startup
48
+ psutil = None
49
+ _PROC = None
50
+
51
  ROOT = os.path.dirname(os.path.abspath(__file__))
52
  PUBLIC = os.path.join(ROOT, "public")
53
  PORT = int(os.environ.get("PORT", "8000"))
 
103
  self.duration_count = 0
104
  self.client_errors = 0
105
  self.beacons_dropped = 0 # rate-limited client beacons
106
+ self.inflight = 0 # requests currently being served
107
+ self.clients = [] # sliding window of (ts, client_ip)
108
 
109
  def observe(self, route, method, code, dur):
110
  with self._lock:
 
123
  else:
124
  self.client_errors += 1
125
 
126
+ def inflight_inc(self):
127
+ with self._lock:
128
+ self.inflight += 1
129
+
130
+ def inflight_dec(self):
131
+ with self._lock:
132
+ self.inflight = max(0, self.inflight - 1)
133
+
134
+ def record_client(self, ip):
135
+ now = time.time()
136
+ with self._lock:
137
+ self.clients.append((now, ip))
138
+ if len(self.clients) > 5000: # hard cap so a flood can't grow it unbounded
139
+ self.clients = self.clients[-5000:]
140
+
141
+ def active_clients(self, window=300):
142
+ """Unique client IPs seen in the last `window` seconds (a stateless API
143
+ has no real 'connections', so this is the meaningful 'active clients')."""
144
+ cutoff = time.time() - window
145
+ with self._lock:
146
+ self.clients = [(t, ip) for (t, ip) in self.clients if t >= cutoff]
147
+ return len({ip for _, ip in self.clients})
148
+
149
+ def _system_metrics(self):
150
+ """Process + host CPU/RAM via psutil (best-effort; empty if unavailable)."""
151
+ out = {}
152
+ if psutil is None or _PROC is None:
153
+ return out
154
+ try:
155
+ out["mobaz_process_cpu_percent"] = float(_PROC.cpu_percent(interval=None))
156
+ out["mobaz_process_resident_memory_bytes"] = int(_PROC.memory_info().rss)
157
+ out["mobaz_process_threads"] = int(_PROC.num_threads())
158
+ try:
159
+ out["mobaz_process_open_fds"] = int(_PROC.num_fds())
160
+ except Exception: # noqa: BLE001 — not on every platform
161
+ pass
162
+ out["mobaz_system_cpu_percent"] = float(psutil.cpu_percent(interval=None))
163
+ vm = psutil.virtual_memory()
164
+ out["mobaz_system_memory_used_percent"] = float(vm.percent)
165
+ out["mobaz_system_memory_total_bytes"] = int(vm.total)
166
+ try:
167
+ out["mobaz_system_load1"] = float(os.getloadavg()[0])
168
+ except Exception: # noqa: BLE001 — no getloadavg on some platforms
169
+ pass
170
+ except Exception: # noqa: BLE001
171
+ pass
172
+ return out
173
+
174
  def _gauges(self):
175
  """Best-effort point-in-time gauges (never raise into /metrics)."""
176
  out = {}
 
200
  buckets = list(self.bucket_counts)
201
  dur_sum, dur_count = self.duration_sum, self.duration_count
202
  client_errors, dropped = self.client_errors, self.beacons_dropped
203
+ inflight = self.inflight
204
  uptime = time.time() - self.start
205
  L = []
206
  L.append("# HELP mobaz_up Whether the MoBaz backend is serving (always 1).")
 
229
  L.append("# HELP mobaz_client_error_beacons_dropped_total Beacons dropped by the rate limit.")
230
  L.append("# TYPE mobaz_client_error_beacons_dropped_total counter")
231
  L.append("mobaz_client_error_beacons_dropped_total %d" % dropped)
232
+ L.append("# HELP mobaz_inflight_requests Requests currently being served.")
233
+ L.append("# TYPE mobaz_inflight_requests gauge")
234
+ L.append("mobaz_inflight_requests %d" % inflight)
235
+ L.append("# HELP mobaz_active_clients Unique client IPs seen in the last 5 minutes.")
236
+ L.append("# TYPE mobaz_active_clients gauge")
237
+ L.append("mobaz_active_clients %d" % self.active_clients())
238
+ # Data/cache gauges + process & host CPU/RAM (psutil).
239
+ allg = {}
240
+ allg.update(self._gauges())
241
+ allg.update(self._system_metrics())
242
+ for name, val in allg.items():
243
  L.append("# TYPE %s gauge" % name)
244
+ L.append(("%s %d" if isinstance(val, int) else "%s %.4f") % (name, val))
245
  return "\n".join(L) + "\n"
246
 
247
 
 
259
  gauges = METRICS._gauges()
260
  total = sum(reqs.values())
261
  avg_ms = (dur_sum / dur_count * 1000) if dur_count else 0.0
262
+ active = METRICS.active_clients()
263
+ inflight = METRICS.inflight
264
+ # Memory only (reading RSS is side-effect-free; CPU% would reset the delta
265
+ # that the Prometheus scrape relies on, so leave CPU to Grafana).
266
+ rss_mb = 0
267
+ if _PROC is not None:
268
+ try:
269
+ rss_mb = _PROC.memory_info().rss / (1024 * 1024)
270
+ except Exception: # noqa: BLE001
271
+ rss_mb = 0
272
  # Requests grouped by route (sum across method/code).
273
  by_route = {}
274
  for (route, _m, _c), n in reqs.items():
 
283
  ("uptime", "%dh %dm" % (uptime // 3600, (uptime % 3600) // 60)),
284
  ("requests", total),
285
  ("avg latency", "%.1f ms" % avg_ms),
286
+ ("active clients (5m)", active),
287
+ ("in-flight", inflight),
288
+ ("memory", "%.0f MB" % rss_mb),
289
  ("stops", gauges.get("mobaz_stops", 0)),
290
  ("events cached", gauges.get("mobaz_events_cached", 0)),
291
  ("geocode cache", gauges.get("mobaz_geocode_cache_entries", 0)),
 
433
  if t0 is not None:
434
  route = getattr(self, "_route", _route_label(urlparse(self.path).path))
435
  METRICS.observe(route, self.command or "GET", code, time.perf_counter() - t0)
436
+ METRICS.inflight_dec()
437
+
438
+ def _begin(self, path):
439
+ """Stamp route/timer, count the request as in-flight, and note the client
440
+ IP (behind nginx the real IP is in X-Forwarded-For)."""
441
+ self._t0 = time.perf_counter()
442
+ self._route = _route_label(path)
443
+ METRICS.inflight_inc()
444
+ xff = self.headers.get("X-Forwarded-For")
445
+ ip = xff.split(",")[0].strip() if xff else (self.client_address[0] if self.client_address else "?")
446
+ METRICS.record_client(ip)
447
 
448
  def _cors(self):
449
  # The packaged app calls this API from a file:// origin, so allow any.
 
516
  def do_GET(self):
517
  parsed = urlparse(self.path)
518
  path = parsed.path
519
+ self._begin(path)
 
520
  try:
521
  if path == "/metrics":
522
  return self._send_text(METRICS.render(), "text/plain; version=0.0.4")
 
633
 
634
  def do_POST(self):
635
  parsed = urlparse(self.path)
636
+ self._begin(parsed.path)
 
637
  if parsed.path != "/api/clienterror":
638
  return self._send_json({"error": "not found"}, 404)
639
  # Read at most a few KB; oversized or malformed bodies are dropped quietly.