Skip to content

Commit d97fc61

Browse files
committed
minimal impl
1 parent 05b2105 commit d97fc61

41 files changed

Lines changed: 2862 additions & 4 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/xds-example/build.gradle

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
plugins {
2+
id 'application'
3+
}
4+
5+
dependencies {
6+
implementation project(':xds')
7+
implementation project(':prometheus1')
8+
implementation libs.jackson.databind
9+
implementation libs.jackson.dataformat.yaml
10+
implementation libs.micrometer.prometheus
11+
implementation libs.protobuf.java.util
12+
runtimeOnly libs.slf4j.simple
13+
14+
testImplementation project(':junit5')
15+
testImplementation libs.assertj
16+
testImplementation libs.awaitility
17+
testImplementation libs.junit5.jupiter.api
18+
}
19+
20+
application {
21+
mainClass.set('example.armeria.xds.Main')
22+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Prometheus + Grafana for xDS metrics visualization.
2+
#
3+
# Usage:
4+
# 1. Start the xDS example: ./gradlew :examples:xds:run
5+
# 2. Start monitoring: docker compose up -d
6+
# 3. Open Grafana at http://localhost:3000 (anonymous access enabled)
7+
# 4. The "Armeria xDS Metrics" dashboard is auto-provisioned.
8+
9+
services:
10+
prometheus:
11+
image: prom/prometheus:latest
12+
ports:
13+
- "9090:9090"
14+
volumes:
15+
- ./prometheus.yml:/etc/prometheus/prometheus.yml
16+
extra_hosts:
17+
- "host.docker.internal:host-gateway"
18+
19+
grafana:
20+
image: grafana/grafana:latest
21+
ports:
22+
- "3000:3000"
23+
environment:
24+
- GF_SECURITY_ADMIN_PASSWORD=admin
25+
- GF_AUTH_ANONYMOUS_ENABLED=true
26+
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
27+
- GF_AUTH_DISABLE_LOGIN_FORM=true
28+
- GF_DASHBOARDS_MIN_REFRESH_INTERVAL=1s
29+
volumes:
30+
- ./grafana/provisioning:/etc/grafana/provisioning
31+
- ./grafana/dashboards:/var/lib/grafana/dashboards
32+
depends_on:
33+
- prometheus
Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
{
2+
"title": "Armeria xDS Metrics",
3+
"uid": "armeria-xds",
4+
"editable": true,
5+
"time": { "from": "now-5m", "to": "now" },
6+
"refresh": "1s",
7+
"timepicker": {
8+
"refresh_intervals": ["1s", "2s", "5s", "10s", "30s"]
9+
},
10+
"templating": {
11+
"list": [
12+
{
13+
"name": "cluster",
14+
"type": "query",
15+
"query": "label_values(armeria_xds_lb_membership, cluster)",
16+
"datasource": "Prometheus",
17+
"refresh": 2,
18+
"includeAll": true,
19+
"multi": true,
20+
"current": { "text": "All", "value": "$__all" }
21+
}
22+
]
23+
},
24+
"panels": [
25+
{
26+
"title": "Armeria",
27+
"type": "row",
28+
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 },
29+
"collapsed": false
30+
},
31+
{
32+
"title": "L1 Requests (by status)",
33+
"description": "Per-attempt counts (includes retry attempts)",
34+
"type": "timeseries",
35+
"gridPos": { "h": 6, "w": 8, "x": 0, "y": 1 },
36+
"interval": "1s",
37+
"targets": [
38+
{ "expr": "sum by (http_status)(rate(layer1_requests_total[3s]))", "legendFormat": "{{http_status}}" }
39+
],
40+
"fieldConfig": { "defaults": { "unit": "reqps" } }
41+
},
42+
{
43+
"title": "L2 Requests (by status)",
44+
"description": "Aggregate Layer 2 server requests from Layer 1",
45+
"type": "timeseries",
46+
"gridPos": { "h": 6, "w": 8, "x": 8, "y": 1 },
47+
"interval": "1s",
48+
"targets": [
49+
{ "expr": "sum by (http_status)(rate(layer2_requests_total{side=\"server\"}[3s]))", "legendFormat": "{{http_status}}" }
50+
],
51+
"fieldConfig": { "defaults": { "unit": "reqps" } }
52+
},
53+
{
54+
"title": "Retries",
55+
"description": "Rate of retry attempts (actual requests minus logical requests)",
56+
"type": "timeseries",
57+
"gridPos": { "h": 6, "w": 8, "x": 16, "y": 1 },
58+
"interval": "1s",
59+
"targets": [
60+
{ "expr": "sum(rate(layer1_actual_requests_attempts_sum[3s])) - sum(rate(layer1_actual_requests_attempts_count[3s]))", "legendFormat": "L1 retries" },
61+
{ "expr": "sum(rate(layer2_actual_requests_attempts_sum{side=\"client\"}[3s])) - sum(rate(layer2_actual_requests_attempts_count{side=\"client\"}[3s]))", "legendFormat": "L2 retries" }
62+
],
63+
"fieldConfig": { "defaults": { "unit": "reqps" } }
64+
},
65+
{
66+
"title": "Route Success (L1 → L2 → L3)",
67+
"description": "Successful request routing path (2xx)",
68+
"type": "timeseries",
69+
"gridPos": { "h": 6, "w": 8, "x": 0, "y": 7 },
70+
"interval": "1s",
71+
"targets": [
72+
{ "expr": "sum by (layer2_node, layer3_node)(rate(layer1_route_total{http_status=\"200\"}[3s]))", "legendFormat": "{{layer2_node}} → {{layer3_node}}" }
73+
],
74+
"fieldConfig": { "defaults": { "unit": "reqps" } }
75+
},
76+
{
77+
"title": "Route Failure (L1 → L2 → L3)",
78+
"description": "Failed request routing path (non-2xx)",
79+
"type": "timeseries",
80+
"gridPos": { "h": 6, "w": 8, "x": 8, "y": 7 },
81+
"interval": "1s",
82+
"targets": [
83+
{ "expr": "sum by (layer2_node, layer3_node, http_status)(rate(layer1_route_total{http_status!=\"200\"}[3s]))", "legendFormat": "{{layer2_node}} → {{layer3_node}} {{http_status}}" }
84+
],
85+
"fieldConfig": { "defaults": { "unit": "reqps" } }
86+
},
87+
{
88+
"title": "Active Connections (by protocol)",
89+
"description": "Client connection pool — h2 = TLS, h2c = cleartext. Cycles every 5s due to max connection age.",
90+
"type": "timeseries",
91+
"gridPos": { "h": 6, "w": 8, "x": 16, "y": 7 },
92+
"interval": "1s",
93+
"targets": [
94+
{ "expr": "layer1_client_active_connections", "legendFormat": "L1 {{protocol}} → {{remote_ip}}" },
95+
{ "expr": "layer2_client_active_connections", "legendFormat": "L2 {{protocol}} → {{remote_ip}}" }
96+
]
97+
},
98+
99+
{
100+
"title": "Control Plane",
101+
"type": "row",
102+
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 14 },
103+
"collapsed": false
104+
},
105+
{
106+
"title": "File Loaded",
107+
"type": "timeseries",
108+
"gridPos": { "h": 6, "w": 6, "x": 0, "y": 15 },
109+
"interval": "1s",
110+
"targets": [
111+
{ "expr": "sum by (node) (rate(armeria_xds_configsource_file_loaded_total[3s]))", "legendFormat": "{{node}}" }
112+
],
113+
"fieldConfig": { "defaults": { "unit": "ops" } }
114+
},
115+
{
116+
"title": "File Errors",
117+
"type": "timeseries",
118+
"gridPos": { "h": 6, "w": 6, "x": 6, "y": 15 },
119+
"interval": "1s",
120+
"targets": [
121+
{ "expr": "sum by (node) (rate(armeria_xds_configsource_file_error_total[3s]))", "legendFormat": "{{node}}" }
122+
],
123+
"fieldConfig": { "defaults": { "unit": "ops" } }
124+
},
125+
{
126+
"title": "Resources Active",
127+
"type": "timeseries",
128+
"gridPos": { "h": 6, "w": 6, "x": 12, "y": 15 },
129+
"interval": "1s",
130+
"targets": [
131+
{ "expr": "armeria_xds_resource_node_revision", "legendFormat": "{{type}}/{{name}}" }
132+
]
133+
},
134+
{
135+
"title": "Resource Errors",
136+
"type": "timeseries",
137+
"gridPos": { "h": 6, "w": 6, "x": 18, "y": 15 },
138+
"interval": "1s",
139+
"targets": [
140+
{ "expr": "rate(armeria_xds_resource_node_error_total[3s])", "legendFormat": "error {{type}}/{{name}}" },
141+
{ "expr": "rate(armeria_xds_resource_node_missing_total[3s])", "legendFormat": "missing {{type}}/{{name}}" }
142+
],
143+
"fieldConfig": { "defaults": { "unit": "ops" } }
144+
},
145+
146+
{
147+
"title": "Load Balancer",
148+
"type": "row",
149+
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 21 },
150+
"collapsed": false
151+
},
152+
{
153+
"title": "Membership per Locality",
154+
"type": "timeseries",
155+
"gridPos": { "h": 6, "w": 8, "x": 0, "y": 22 },
156+
"interval": "1s",
157+
"targets": [
158+
{
159+
"expr": "armeria_xds_lb_membership{cluster=~\"$cluster\"}",
160+
"legendFormat": "{{cluster}} / {{region}}"
161+
}
162+
]
163+
},
164+
{
165+
"title": "Healthy / Degraded Membership",
166+
"type": "timeseries",
167+
"gridPos": { "h": 6, "w": 8, "x": 8, "y": 22 },
168+
"interval": "1s",
169+
"targets": [
170+
{ "expr": "armeria_xds_lb_membership_healthy{cluster=~\"$cluster\"}", "legendFormat": "healthy {{cluster}}/{{region}}" },
171+
{ "expr": "armeria_xds_lb_membership_degraded{cluster=~\"$cluster\"}", "legendFormat": "degraded {{cluster}}/{{region}}" }
172+
]
173+
},
174+
{
175+
"title": "Locality Weights",
176+
"type": "timeseries",
177+
"gridPos": { "h": 6, "w": 8, "x": 16, "y": 22 },
178+
"interval": "1s",
179+
"targets": [
180+
{
181+
"expr": "armeria_xds_lb_locality_weight{cluster=~\"$cluster\"}",
182+
"legendFormat": "{{cluster}} / {{region}}"
183+
}
184+
]
185+
},
186+
{
187+
"title": "Priority Load",
188+
"description": "Traffic distribution across priorities. In panic mode, all endpoints are treated as healthy regardless of actual health status.",
189+
"type": "timeseries",
190+
"gridPos": { "h": 6, "w": 12, "x": 0, "y": 28 },
191+
"interval": "1s",
192+
"targets": [
193+
{ "expr": "armeria_xds_lb_state_load_healthy{cluster=~\"$cluster\"}", "legendFormat": "healthy p{{priority}} {{cluster}}" },
194+
{ "expr": "armeria_xds_lb_state_load_degraded{cluster=~\"$cluster\"}", "legendFormat": "degraded p{{priority}} {{cluster}}" }
195+
]
196+
},
197+
{
198+
"title": "Panic Mode",
199+
"type": "timeseries",
200+
"gridPos": { "h": 6, "w": 6, "x": 12, "y": 28 },
201+
"interval": "1s",
202+
"targets": [
203+
{
204+
"expr": "armeria_xds_lb_state_panic{cluster=~\"$cluster\"}",
205+
"legendFormat": "{{cluster}} p{{priority}}"
206+
}
207+
]
208+
},
209+
{
210+
"title": "Subsets",
211+
"type": "timeseries",
212+
"gridPos": { "h": 6, "w": 6, "x": 18, "y": 28 },
213+
"interval": "1s",
214+
"targets": [
215+
{ "expr": "armeria_xds_lb_state_subsets{cluster=~\"$cluster\"}", "legendFormat": "{{cluster}}" }
216+
]
217+
},
218+
219+
{
220+
"title": "Zone-Aware Routing",
221+
"type": "row",
222+
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 34 },
223+
"collapsed": false
224+
},
225+
{
226+
"title": "ZAR Local Percentage",
227+
"description": "Percentage of traffic routed to the local zone (computed by Armeria ZAR algorithm)",
228+
"type": "bargauge",
229+
"gridPos": { "h": 6, "w": 12, "x": 0, "y": 35 },
230+
"interval": "1s",
231+
"targets": [
232+
{
233+
"expr": "armeria_xds_lb_zar_local_percentage{cluster=~\"$cluster\",cluster!~\".*local.*\"}",
234+
"legendFormat": "{{node}} / {{cluster}}"
235+
}
236+
],
237+
"options": {
238+
"orientation": "horizontal",
239+
"displayMode": "gradient",
240+
"reduceOptions": { "calcs": ["lastNotNull"] }
241+
},
242+
"fieldConfig": { "defaults": { "unit": "percentunit", "min": 0, "max": 1 } }
243+
},
244+
{
245+
"title": "ZAR Residual Percentage per Locality",
246+
"description": "Residual traffic percentage for each locality after local zone allocation",
247+
"type": "table",
248+
"gridPos": { "h": 6, "w": 12, "x": 12, "y": 35 },
249+
"interval": "1s",
250+
"targets": [
251+
{
252+
"expr": "armeria_xds_lb_zar_residual_percentage{cluster=~\"$cluster\",cluster!~\".*local.*\"}",
253+
"legendFormat": "{{node}} / {{cluster}} / {{region}}",
254+
"instant": true,
255+
"format": "table"
256+
}
257+
],
258+
"transformations": [
259+
{ "id": "organize", "options": { "excludeByName": { "Time": true, "__name__": true, "instance": true, "job": true, "priority": true }, "renameByName": { "Value": "residual %" } } },
260+
],
261+
"fieldConfig": { "defaults": { "unit": "percentunit" }, "overrides": [] }
262+
},
263+
264+
{
265+
"title": "LB Selection",
266+
"type": "row",
267+
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 41 },
268+
"collapsed": false
269+
},
270+
{
271+
"title": "Selection Rate (hit vs miss)",
272+
"type": "timeseries",
273+
"gridPos": { "h": 6, "w": 12, "x": 0, "y": 42 },
274+
"interval": "1s",
275+
"targets": [
276+
{ "expr": "rate(armeria_xds_lb_select_total{result=\"hit\",cluster=~\"$cluster\"}[3s])", "legendFormat": "hit {{cluster}}/{{region}}" },
277+
{ "expr": "rate(armeria_xds_lb_select_total{result=\"miss\",cluster=~\"$cluster\"}[3s])", "legendFormat": "miss {{cluster}}/{{region}}" }
278+
],
279+
"fieldConfig": { "defaults": { "unit": "ops" } }
280+
},
281+
{
282+
"title": "Errors (no_endpoints / no_host_source)",
283+
"type": "timeseries",
284+
"gridPos": { "h": 6, "w": 12, "x": 12, "y": 42 },
285+
"interval": "1s",
286+
"targets": [
287+
{ "expr": "rate(armeria_xds_lb_select_total{result=\"no_endpoints\",cluster=~\"$cluster\"}[3s])", "legendFormat": "no_endpoints {{cluster}}" },
288+
{ "expr": "rate(armeria_xds_lb_select_total{result=\"no_host_source\",cluster=~\"$cluster\"}[3s])", "legendFormat": "no_host_source {{cluster}}" }
289+
],
290+
"fieldConfig": { "defaults": { "unit": "ops" } }
291+
},
292+
293+
{
294+
"title": "LB Update Events",
295+
"type": "row",
296+
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 48 },
297+
"collapsed": false
298+
},
299+
{
300+
"title": "Resource / Endpoint / State Updates",
301+
"type": "timeseries",
302+
"gridPos": { "h": 6, "w": 12, "x": 0, "y": 49 },
303+
"interval": "1s",
304+
"targets": [
305+
{ "expr": "rate(armeria_xds_lb_resource_updated_count_total{cluster=~\"$cluster\"}[3s])", "legendFormat": "resource {{cluster}}" },
306+
{ "expr": "rate(armeria_xds_lb_endpoints_updated_count_total{cluster=~\"$cluster\"}[3s])", "legendFormat": "endpoints {{cluster}}" },
307+
{ "expr": "rate(armeria_xds_lb_state_updated_count_total{cluster=~\"$cluster\"}[3s])", "legendFormat": "state {{cluster}}" },
308+
{ "expr": "rate(armeria_xds_lb_state_rejected_count_total{cluster=~\"$cluster\"}[3s])", "legendFormat": "rejected {{cluster}}" }
309+
],
310+
"fieldConfig": { "defaults": { "unit": "ops" } }
311+
},
312+
{
313+
"title": "Update Revisions",
314+
"type": "timeseries",
315+
"gridPos": { "h": 6, "w": 12, "x": 12, "y": 49 },
316+
"interval": "1s",
317+
"targets": [
318+
{ "expr": "armeria_xds_lb_resource_updated_revision{cluster=~\"$cluster\"}", "legendFormat": "resource {{cluster}}" },
319+
{ "expr": "armeria_xds_lb_state_updated_revision{cluster=~\"$cluster\"}", "legendFormat": "state {{cluster}}" }
320+
]
321+
}
322+
]
323+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: 1
2+
3+
providers:
4+
- name: 'default'
5+
orgId: 1
6+
folder: ''
7+
type: file
8+
allowUiUpdates: true
9+
options:
10+
path: /var/lib/grafana/dashboards

0 commit comments

Comments
 (0)