div18 commited on
Commit ·
923f89f
1
Parent(s): 5a30495
chore(kubernetes): support unbounded max replicas and enhance local setup
Browse files- Allow ANTIATROPOS_MAX_REPLICAS to be empty or 'unbounded' for no scale-up limit
- Adjust scaling logic to handle unbounded max replicas correctly
- Update .env.example with local defaults and new Prometheus queries
- Simplify start-grafana.ps1 for local Grafana setup without AWS credentials
- Add kind cluster config with max pods set to 250 for local testing
- Enhance local-laptop.yaml with prometheus metrics exposition and python http server
- Update LOCAL_LAPTOP_FASTAPI_GUIDE.md to document unbounded max replicas and setup changes
- .env.example +15 -9
- control/kubernetes_executor.py +29 -3
- deploy/LOCAL_LAPTOP_FASTAPI_GUIDE.md +1 -1
- deploy/grafana/provisioning/dashboards/json/antiatropos-live.json +2 -2
- deploy/grafana/provisioning/dashboards/json/antiatropos-overview.json +21 -16
- deploy/grafana/provisioning/datasources/prometheus.yaml +1 -8
- deploy/kind-maxpods-250.yaml +11 -0
- deploy/local-laptop.yaml +272 -11
- deploy/local/grafana-local-values.yaml +34 -0
- deploy/local/prometheus-local-values.yaml +49 -0
- server/Dockerfile +1 -1
- server/app.py +4 -1
- start-grafana.ps1 +7 -24
- telemetry/mapping.py +5 -3
- telemetry/prometheus_client.py +30 -11
.env.example
CHANGED
|
@@ -1,34 +1,40 @@
|
|
| 1 |
# AntiAtropos runtime defaults (copy to .env and adjust values)
|
| 2 |
|
| 3 |
# Environment mode for FastAPI runtime
|
| 4 |
-
ANTIATROPOS_ENV_MODE=
|
| 5 |
|
| 6 |
# Reward output to agent
|
| 7 |
ANTIATROPOS_REWARD_OUTPUT_MODE=normalized
|
| 8 |
|
| 9 |
-
# Prometheus
|
| 10 |
-
PROMETHEUS_URL=
|
| 11 |
ANTIATROPOS_PROM_TIMEOUT_S=5.0
|
| 12 |
ANTIATROPOS_STRICT_REAL=false
|
| 13 |
ANTIATROPOS_METRIC_AGGREGATION=sum
|
| 14 |
|
| 15 |
# Kubernetes execution settings
|
| 16 |
-
KUBECONFIG=
|
| 17 |
ANTIATROPOS_K8S_NAMESPACE=prod-sre
|
| 18 |
ANTIATROPOS_MIN_REPLICAS=1
|
| 19 |
-
ANTIATROPOS_MAX_REPLICAS=
|
| 20 |
ANTIATROPOS_SCALE_STEP=3
|
| 21 |
|
| 22 |
# Node -> deployment map used by Kubernetes executor
|
| 23 |
ANTIATROPOS_WORKLOAD_MAP={"node-0":{"deployment":"payments","namespace":"prod-sre"},"node-1":{"deployment":"checkout","namespace":"prod-sre"},"node-2":{"deployment":"catalog","namespace":"prod-sre"},"node-3":{"deployment":"cart","namespace":"prod-sre"},"node-4":{"deployment":"auth","namespace":"prod-sre"}}
|
| 24 |
|
| 25 |
-
#
|
| 26 |
-
AWS_REGION=
|
| 27 |
-
ANTIATROPOS_GRAFANA_MODE=
|
| 28 |
|
| 29 |
# Agent LLM provider (optional): Groq (OpenAI-compatible)
|
| 30 |
# If GROQ_API_KEY is set and API_BASE_URL is not set, inference.py auto-uses Groq.
|
| 31 |
GROQ_API_KEY=
|
| 32 |
MODEL_NAME=llama-3.1-8b-instant
|
| 33 |
ENV_URL=http://localhost:8000
|
| 34 |
-
ANTIATROPOS_MODE=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# AntiAtropos runtime defaults (copy to .env and adjust values)
|
| 2 |
|
| 3 |
# Environment mode for FastAPI runtime
|
| 4 |
+
ANTIATROPOS_ENV_MODE=live
|
| 5 |
|
| 6 |
# Reward output to agent
|
| 7 |
ANTIATROPOS_REWARD_OUTPUT_MODE=normalized
|
| 8 |
|
| 9 |
+
# Local Prometheus endpoint (use kubectl port-forward to localhost:9090)
|
| 10 |
+
PROMETHEUS_URL=http://localhost:9090
|
| 11 |
ANTIATROPOS_PROM_TIMEOUT_S=5.0
|
| 12 |
ANTIATROPOS_STRICT_REAL=false
|
| 13 |
ANTIATROPOS_METRIC_AGGREGATION=sum
|
| 14 |
|
| 15 |
# Kubernetes execution settings
|
| 16 |
+
KUBECONFIG=C:/Users/your-user/.kube/config
|
| 17 |
ANTIATROPOS_K8S_NAMESPACE=prod-sre
|
| 18 |
ANTIATROPOS_MIN_REPLICAS=1
|
| 19 |
+
ANTIATROPOS_MAX_REPLICAS=
|
| 20 |
ANTIATROPOS_SCALE_STEP=3
|
| 21 |
|
| 22 |
# Node -> deployment map used by Kubernetes executor
|
| 23 |
ANTIATROPOS_WORKLOAD_MAP={"node-0":{"deployment":"payments","namespace":"prod-sre"},"node-1":{"deployment":"checkout","namespace":"prod-sre"},"node-2":{"deployment":"catalog","namespace":"prod-sre"},"node-3":{"deployment":"cart","namespace":"prod-sre"},"node-4":{"deployment":"auth","namespace":"prod-sre"}}
|
| 24 |
|
| 25 |
+
# Local defaults
|
| 26 |
+
AWS_REGION=
|
| 27 |
+
ANTIATROPOS_GRAFANA_MODE=local
|
| 28 |
|
| 29 |
# Agent LLM provider (optional): Groq (OpenAI-compatible)
|
| 30 |
# If GROQ_API_KEY is set and API_BASE_URL is not set, inference.py auto-uses Groq.
|
| 31 |
GROQ_API_KEY=
|
| 32 |
MODEL_NAME=llama-3.1-8b-instant
|
| 33 |
ENV_URL=http://localhost:8000
|
| 34 |
+
ANTIATROPOS_MODE=live
|
| 35 |
+
ANTIATROPOS_LABEL_NODE_MAP={"payments":"node-0","checkout":"node-1","catalog":"node-2","cart":"node-3","auth":"node-4"}
|
| 36 |
+
ANTIATROPOS_PROM_QUERY_REQUEST_RATE=sum(rate(http_requests_total[1m])) by (node_id)
|
| 37 |
+
ANTIATROPOS_PROM_QUERY_LATENCY_MS=histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[1m])) by (node_id, le)) * 1000
|
| 38 |
+
ANTIATROPOS_PROM_QUERY_ERROR_RATE=sum(rate(http_requests_total{status=~"5.."}[1m])) by (node_id) / clamp_min(sum(rate(http_requests_total[1m])) by (node_id), 1)
|
| 39 |
+
ANTIATROPOS_PROM_QUERY_CPU=avg(rate(container_cpu_usage_seconds_total[1m])) by (node_id)
|
| 40 |
+
ANTIATROPOS_PROM_QUERY_QUEUE_DEPTH=avg(queue_depth) by (node_id)
|
control/kubernetes_executor.py
CHANGED
|
@@ -18,12 +18,34 @@ class KubernetesExecutor:
|
|
| 18 |
self.is_mock = not self.kubeconfig or self.kubeconfig.lower() == "mock"
|
| 19 |
self.namespace = os.getenv("ANTIATROPOS_K8S_NAMESPACE", "default")
|
| 20 |
self.min_replicas = int(os.getenv("ANTIATROPOS_MIN_REPLICAS", "1"))
|
| 21 |
-
self.max_replicas =
|
| 22 |
self.scale_step = int(os.getenv("ANTIATROPOS_SCALE_STEP", "3"))
|
| 23 |
self._apps_v1_api = None
|
| 24 |
self._node_workload_map = self._load_node_workload_map()
|
| 25 |
self._live_supported_actions = {"NO_OP", "SCALE_UP", "SCALE_DOWN"}
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
@staticmethod
|
| 28 |
def _normalize_action_type(action_type) -> str:
|
| 29 |
if hasattr(action_type, "value"):
|
|
@@ -116,14 +138,18 @@ class KubernetesExecutor:
|
|
| 116 |
current = int(scale_obj.spec.replicas or self.min_replicas)
|
| 117 |
delta = max(1, int(float(parameter) * self.scale_step))
|
| 118 |
if action_type == "SCALE_UP":
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
| 120 |
else:
|
| 121 |
desired = max(self.min_replicas, current - delta)
|
| 122 |
|
| 123 |
if desired == current:
|
|
|
|
| 124 |
return (
|
| 125 |
f"Ack: {action_type} for {target} - replicas unchanged at {current} "
|
| 126 |
-
f"(bounds {self.min_replicas}-{
|
| 127 |
)
|
| 128 |
|
| 129 |
apps_v1.patch_namespaced_deployment_scale(
|
|
|
|
| 18 |
self.is_mock = not self.kubeconfig or self.kubeconfig.lower() == "mock"
|
| 19 |
self.namespace = os.getenv("ANTIATROPOS_K8S_NAMESPACE", "default")
|
| 20 |
self.min_replicas = int(os.getenv("ANTIATROPOS_MIN_REPLICAS", "1"))
|
| 21 |
+
self.max_replicas = self._parse_max_replicas(os.getenv("ANTIATROPOS_MAX_REPLICAS"))
|
| 22 |
self.scale_step = int(os.getenv("ANTIATROPOS_SCALE_STEP", "3"))
|
| 23 |
self._apps_v1_api = None
|
| 24 |
self._node_workload_map = self._load_node_workload_map()
|
| 25 |
self._live_supported_actions = {"NO_OP", "SCALE_UP", "SCALE_DOWN"}
|
| 26 |
|
| 27 |
+
@staticmethod
|
| 28 |
+
def _parse_max_replicas(raw: Optional[str]) -> Optional[int]:
|
| 29 |
+
"""
|
| 30 |
+
Parse optional max replicas.
|
| 31 |
+
|
| 32 |
+
Returns:
|
| 33 |
+
- int when a positive explicit cap is provided
|
| 34 |
+
- None when scale-up should be unbounded
|
| 35 |
+
"""
|
| 36 |
+
if raw is None:
|
| 37 |
+
return None
|
| 38 |
+
value = str(raw).strip().lower()
|
| 39 |
+
if value in ("", "none", "unbounded", "inf", "infinite"):
|
| 40 |
+
return None
|
| 41 |
+
try:
|
| 42 |
+
parsed = int(value)
|
| 43 |
+
except ValueError:
|
| 44 |
+
return None
|
| 45 |
+
if parsed <= 0:
|
| 46 |
+
return None
|
| 47 |
+
return parsed
|
| 48 |
+
|
| 49 |
@staticmethod
|
| 50 |
def _normalize_action_type(action_type) -> str:
|
| 51 |
if hasattr(action_type, "value"):
|
|
|
|
| 138 |
current = int(scale_obj.spec.replicas or self.min_replicas)
|
| 139 |
delta = max(1, int(float(parameter) * self.scale_step))
|
| 140 |
if action_type == "SCALE_UP":
|
| 141 |
+
if self.max_replicas is None:
|
| 142 |
+
desired = current + delta
|
| 143 |
+
else:
|
| 144 |
+
desired = min(self.max_replicas, current + delta)
|
| 145 |
else:
|
| 146 |
desired = max(self.min_replicas, current - delta)
|
| 147 |
|
| 148 |
if desired == current:
|
| 149 |
+
upper = "unbounded" if self.max_replicas is None else str(self.max_replicas)
|
| 150 |
return (
|
| 151 |
f"Ack: {action_type} for {target} - replicas unchanged at {current} "
|
| 152 |
+
f"(bounds {self.min_replicas}-{upper})"
|
| 153 |
)
|
| 154 |
|
| 155 |
apps_v1.patch_namespaced_deployment_scale(
|
deploy/LOCAL_LAPTOP_FASTAPI_GUIDE.md
CHANGED
|
@@ -24,7 +24,7 @@ The controller requires `KUBECONFIG` and `ANTIATROPOS_WORKLOAD_MAP`.
|
|
| 24 |
$env:KUBECONFIG = "$HOME/.kube/config"
|
| 25 |
$env:ANTIATROPOS_K8S_NAMESPACE = "prod-sre"
|
| 26 |
$env:ANTIATROPOS_MIN_REPLICAS = "1"
|
| 27 |
-
$env:ANTIATROPOS_MAX_REPLICAS = "
|
| 28 |
$env:ANTIATROPOS_SCALE_STEP = "3"
|
| 29 |
$env:ANTIATROPOS_WORKLOAD_MAP = '{"node-0":{"deployment":"payments","namespace":"prod-sre"},"node-1":{"deployment":"checkout","namespace":"prod-sre"},"node-2":{"deployment":"catalog","namespace":"prod-sre"},"node-3":{"deployment":"cart","namespace":"prod-sre"},"node-4":{"deployment":"auth","namespace":"prod-sre"}}'
|
| 30 |
```
|
|
|
|
| 24 |
$env:KUBECONFIG = "$HOME/.kube/config"
|
| 25 |
$env:ANTIATROPOS_K8S_NAMESPACE = "prod-sre"
|
| 26 |
$env:ANTIATROPOS_MIN_REPLICAS = "1"
|
| 27 |
+
$env:ANTIATROPOS_MAX_REPLICAS = "" # empty => unbounded scale-up
|
| 28 |
$env:ANTIATROPOS_SCALE_STEP = "3"
|
| 29 |
$env:ANTIATROPOS_WORKLOAD_MAP = '{"node-0":{"deployment":"payments","namespace":"prod-sre"},"node-1":{"deployment":"checkout","namespace":"prod-sre"},"node-2":{"deployment":"catalog","namespace":"prod-sre"},"node-3":{"deployment":"cart","namespace":"prod-sre"},"node-4":{"deployment":"auth","namespace":"prod-sre"}}'
|
| 30 |
```
|
deploy/grafana/provisioning/dashboards/json/antiatropos-live.json
CHANGED
|
@@ -54,7 +54,7 @@
|
|
| 54 |
},
|
| 55 |
"targets": [
|
| 56 |
{
|
| 57 |
-
"expr": "sum by (action_type, ack_class) (rate(antiatropos_actions_total{task_id=~\"$task\",mode=~\"$mode\"}[
|
| 58 |
"legendFormat": "{{action_type}} {{ack_class}}",
|
| 59 |
"refId": "A"
|
| 60 |
}
|
|
@@ -261,7 +261,7 @@
|
|
| 261 |
},
|
| 262 |
"targets": [
|
| 263 |
{
|
| 264 |
-
"expr": "histogram_quantile(0.95, sum(rate(antiatropos_executor_latency_ms_bucket{mode=~\"$mode\"}[
|
| 265 |
"legendFormat": "p95 {{mode}}",
|
| 266 |
"refId": "A"
|
| 267 |
},
|
|
|
|
| 54 |
},
|
| 55 |
"targets": [
|
| 56 |
{
|
| 57 |
+
"expr": "sum by (action_type, ack_class) (rate(antiatropos_actions_total{task_id=~\"$task\",mode=~\"$mode\"}[5m]))",
|
| 58 |
"legendFormat": "{{action_type}} {{ack_class}}",
|
| 59 |
"refId": "A"
|
| 60 |
}
|
|
|
|
| 261 |
},
|
| 262 |
"targets": [
|
| 263 |
{
|
| 264 |
+
"expr": "histogram_quantile(0.95, sum(rate(antiatropos_executor_latency_ms_bucket{mode=~\"$mode\"}[5m])) by (le, mode))",
|
| 265 |
"legendFormat": "p95 {{mode}}",
|
| 266 |
"refId": "A"
|
| 267 |
},
|
deploy/grafana/provisioning/dashboards/json/antiatropos-overview.json
CHANGED
|
@@ -76,8 +76,8 @@
|
|
| 76 |
"targets": [
|
| 77 |
{
|
| 78 |
"editorMode": "code",
|
| 79 |
-
"expr": "scalar(avg(last_over_time(antiatropos_reward{mode=\"simulated\"}[1m])))",
|
| 80 |
-
"legendFormat": "reward (
|
| 81 |
"range": true,
|
| 82 |
"refId": "A"
|
| 83 |
}
|
|
@@ -143,8 +143,8 @@
|
|
| 143 |
"targets": [
|
| 144 |
{
|
| 145 |
"editorMode": "code",
|
| 146 |
-
"expr": "scalar(avg(last_over_time(antiatropos_total_queue_backlog{mode=\"simulated\"}[1m])))",
|
| 147 |
-
"legendFormat": "queue backlog (
|
| 148 |
"range": true,
|
| 149 |
"refId": "A"
|
| 150 |
}
|
|
@@ -210,8 +210,8 @@
|
|
| 210 |
"targets": [
|
| 211 |
{
|
| 212 |
"editorMode": "code",
|
| 213 |
-
"expr": "scalar(avg(last_over_time(antiatropos_average_latency_norm{mode=\"simulated\"}[1m])))",
|
| 214 |
-
"legendFormat": "latency (
|
| 215 |
"range": true,
|
| 216 |
"refId": "A"
|
| 217 |
}
|
|
@@ -277,8 +277,8 @@
|
|
| 277 |
"targets": [
|
| 278 |
{
|
| 279 |
"editorMode": "code",
|
| 280 |
-
"expr": "scalar(avg(last_over_time(antiatropos_lyapunov_energy{mode=\"simulated\"}[1m])))",
|
| 281 |
-
"legendFormat": "lyapunov energy (
|
| 282 |
"range": true,
|
| 283 |
"refId": "A"
|
| 284 |
}
|
|
@@ -369,14 +369,14 @@
|
|
| 369 |
"targets": [
|
| 370 |
{
|
| 371 |
"editorMode": "code",
|
| 372 |
-
"expr": "antiatropos_reward{mode=\"simulated\"}",
|
| 373 |
"legendFormat": "reward {{task_id}} ({{mode}})",
|
| 374 |
"range": true,
|
| 375 |
"refId": "A"
|
| 376 |
},
|
| 377 |
{
|
| 378 |
"editorMode": "code",
|
| 379 |
-
"expr": "antiatropos_lyapunov_energy{mode=\"simulated\"}",
|
| 380 |
"legendFormat": "lyapunov {{task_id}} ({{mode}})",
|
| 381 |
"range": true,
|
| 382 |
"refId": "B"
|
|
@@ -468,14 +468,14 @@
|
|
| 468 |
"targets": [
|
| 469 |
{
|
| 470 |
"editorMode": "code",
|
| 471 |
-
"expr": "antiatropos_total_queue_backlog{mode=\"simulated\"}",
|
| 472 |
"legendFormat": "queue {{task_id}} ({{mode}})",
|
| 473 |
"range": true,
|
| 474 |
"refId": "A"
|
| 475 |
},
|
| 476 |
{
|
| 477 |
"editorMode": "code",
|
| 478 |
-
"expr": "antiatropos_average_latency_norm{mode=\"simulated\"}",
|
| 479 |
"legendFormat": "latency {{task_id}} ({{mode}})",
|
| 480 |
"range": true,
|
| 481 |
"refId": "B"
|
|
@@ -535,14 +535,14 @@
|
|
| 535 |
"targets": [
|
| 536 |
{
|
| 537 |
"editorMode": "code",
|
| 538 |
-
"expr": "sum by (task_id, mode) (rate(antiatropos_steps_total{mode=\"simulated\"}[1m]))",
|
| 539 |
"legendFormat": "steps/sec {{task_id}} ({{mode}})",
|
| 540 |
"range": true,
|
| 541 |
"refId": "A"
|
| 542 |
},
|
| 543 |
{
|
| 544 |
"editorMode": "code",
|
| 545 |
-
"expr": "sum by (task_id, mode, action_type) (rate(antiatropos_actions_total{mode=\"simulated\"}[1m]))",
|
| 546 |
"legendFormat": "actions/sec {{action_type}} ({{task_id}}, {{mode}})",
|
| 547 |
"range": true,
|
| 548 |
"refId": "B"
|
|
@@ -602,14 +602,14 @@
|
|
| 602 |
"targets": [
|
| 603 |
{
|
| 604 |
"editorMode": "code",
|
| 605 |
-
"expr": "sum by (mode, error_code) (rate(antiatropos_executor_errors_total{mode=\"simulated\"}[5m]))",
|
| 606 |
"legendFormat": "executor errors {{error_code}} ({{mode}})",
|
| 607 |
"range": true,
|
| 608 |
"refId": "A"
|
| 609 |
},
|
| 610 |
{
|
| 611 |
"editorMode": "code",
|
| 612 |
-
"expr": "histogram_quantile(0.95, sum(rate(antiatropos_executor_latency_ms_bucket{mode=\"simulated\"}[5m])) by (le, mode))",
|
| 613 |
"legendFormat": "p95 executor latency {{mode}}",
|
| 614 |
"range": true,
|
| 615 |
"refId": "B"
|
|
@@ -640,3 +640,8 @@
|
|
| 640 |
"version": 2,
|
| 641 |
"weekStart": ""
|
| 642 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
"targets": [
|
| 77 |
{
|
| 78 |
"editorMode": "code",
|
| 79 |
+
"expr": "scalar(avg(last_over_time(antiatropos_reward{mode=~\"live|simulated|hybrid|aws\"}[1m])))",
|
| 80 |
+
"legendFormat": "reward (all modes)",
|
| 81 |
"range": true,
|
| 82 |
"refId": "A"
|
| 83 |
}
|
|
|
|
| 143 |
"targets": [
|
| 144 |
{
|
| 145 |
"editorMode": "code",
|
| 146 |
+
"expr": "scalar(avg(last_over_time(antiatropos_total_queue_backlog{mode=~\"live|simulated|hybrid|aws\"}[1m])))",
|
| 147 |
+
"legendFormat": "queue backlog (all modes)",
|
| 148 |
"range": true,
|
| 149 |
"refId": "A"
|
| 150 |
}
|
|
|
|
| 210 |
"targets": [
|
| 211 |
{
|
| 212 |
"editorMode": "code",
|
| 213 |
+
"expr": "scalar(avg(last_over_time(antiatropos_average_latency_norm{mode=~\"live|simulated|hybrid|aws\"}[1m])))",
|
| 214 |
+
"legendFormat": "latency (all modes)",
|
| 215 |
"range": true,
|
| 216 |
"refId": "A"
|
| 217 |
}
|
|
|
|
| 277 |
"targets": [
|
| 278 |
{
|
| 279 |
"editorMode": "code",
|
| 280 |
+
"expr": "scalar(avg(last_over_time(antiatropos_lyapunov_energy{mode=~\"live|simulated|hybrid|aws\"}[1m])))",
|
| 281 |
+
"legendFormat": "lyapunov energy (all modes)",
|
| 282 |
"range": true,
|
| 283 |
"refId": "A"
|
| 284 |
}
|
|
|
|
| 369 |
"targets": [
|
| 370 |
{
|
| 371 |
"editorMode": "code",
|
| 372 |
+
"expr": "antiatropos_reward{mode=~\"live|simulated|hybrid|aws\"}",
|
| 373 |
"legendFormat": "reward {{task_id}} ({{mode}})",
|
| 374 |
"range": true,
|
| 375 |
"refId": "A"
|
| 376 |
},
|
| 377 |
{
|
| 378 |
"editorMode": "code",
|
| 379 |
+
"expr": "antiatropos_lyapunov_energy{mode=~\"live|simulated|hybrid|aws\"}",
|
| 380 |
"legendFormat": "lyapunov {{task_id}} ({{mode}})",
|
| 381 |
"range": true,
|
| 382 |
"refId": "B"
|
|
|
|
| 468 |
"targets": [
|
| 469 |
{
|
| 470 |
"editorMode": "code",
|
| 471 |
+
"expr": "antiatropos_total_queue_backlog{mode=~\"live|simulated|hybrid|aws\"}",
|
| 472 |
"legendFormat": "queue {{task_id}} ({{mode}})",
|
| 473 |
"range": true,
|
| 474 |
"refId": "A"
|
| 475 |
},
|
| 476 |
{
|
| 477 |
"editorMode": "code",
|
| 478 |
+
"expr": "antiatropos_average_latency_norm{mode=~\"live|simulated|hybrid|aws\"}",
|
| 479 |
"legendFormat": "latency {{task_id}} ({{mode}})",
|
| 480 |
"range": true,
|
| 481 |
"refId": "B"
|
|
|
|
| 535 |
"targets": [
|
| 536 |
{
|
| 537 |
"editorMode": "code",
|
| 538 |
+
"expr": "sum by (task_id, mode) (rate(antiatropos_steps_total{mode=~\"live|simulated|hybrid|aws\"}[1m]))",
|
| 539 |
"legendFormat": "steps/sec {{task_id}} ({{mode}})",
|
| 540 |
"range": true,
|
| 541 |
"refId": "A"
|
| 542 |
},
|
| 543 |
{
|
| 544 |
"editorMode": "code",
|
| 545 |
+
"expr": "sum by (task_id, mode, action_type) (rate(antiatropos_actions_total{mode=~\"live|simulated|hybrid|aws\"}[1m]))",
|
| 546 |
"legendFormat": "actions/sec {{action_type}} ({{task_id}}, {{mode}})",
|
| 547 |
"range": true,
|
| 548 |
"refId": "B"
|
|
|
|
| 602 |
"targets": [
|
| 603 |
{
|
| 604 |
"editorMode": "code",
|
| 605 |
+
"expr": "sum by (mode, error_code) (rate(antiatropos_executor_errors_total{mode=~\"live|simulated|hybrid|aws\"}[5m]))",
|
| 606 |
"legendFormat": "executor errors {{error_code}} ({{mode}})",
|
| 607 |
"range": true,
|
| 608 |
"refId": "A"
|
| 609 |
},
|
| 610 |
{
|
| 611 |
"editorMode": "code",
|
| 612 |
+
"expr": "histogram_quantile(0.95, sum(rate(antiatropos_executor_latency_ms_bucket{mode=~\"live|simulated|hybrid|aws\"}[5m])) by (le, mode))",
|
| 613 |
"legendFormat": "p95 executor latency {{mode}}",
|
| 614 |
"range": true,
|
| 615 |
"refId": "B"
|
|
|
|
| 640 |
"version": 2,
|
| 641 |
"weekStart": ""
|
| 642 |
}
|
| 643 |
+
|
| 644 |
+
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
|
deploy/grafana/provisioning/datasources/prometheus.yaml
CHANGED
|
@@ -5,13 +5,6 @@ datasources:
|
|
| 5 |
uid: PBFA97CFB590B2093
|
| 6 |
type: prometheus
|
| 7 |
access: proxy
|
| 8 |
-
url:
|
| 9 |
isDefault: true
|
| 10 |
editable: true
|
| 11 |
-
jsonData:
|
| 12 |
-
sigV4Auth: true
|
| 13 |
-
sigV4AuthType: keys
|
| 14 |
-
sigV4Region: ap-south-1
|
| 15 |
-
secureJsonData:
|
| 16 |
-
sigV4AccessKey: "${AWS_ACCESS_KEY_ID}"
|
| 17 |
-
sigV4SecretKey: "${AWS_SECRET_ACCESS_KEY}"
|
|
|
|
| 5 |
uid: PBFA97CFB590B2093
|
| 6 |
type: prometheus
|
| 7 |
access: proxy
|
| 8 |
+
url: http://host.docker.internal:9090
|
| 9 |
isDefault: true
|
| 10 |
editable: true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
deploy/kind-maxpods-250.yaml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
kind: Cluster
|
| 2 |
+
apiVersion: kind.x-k8s.io/v1alpha4
|
| 3 |
+
name: antiatropos-local
|
| 4 |
+
nodes:
|
| 5 |
+
- role: control-plane
|
| 6 |
+
kubeadmConfigPatches:
|
| 7 |
+
- |
|
| 8 |
+
kind: InitConfiguration
|
| 9 |
+
nodeRegistration:
|
| 10 |
+
kubeletExtraArgs:
|
| 11 |
+
max-pods: "250"
|
deploy/local-laptop.yaml
CHANGED
|
@@ -17,11 +17,63 @@ spec:
|
|
| 17 |
metadata:
|
| 18 |
labels:
|
| 19 |
app: auth
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
spec:
|
| 21 |
containers:
|
| 22 |
- name: auth
|
| 23 |
-
image:
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
---
|
| 26 |
apiVersion: apps/v1
|
| 27 |
kind: Deployment
|
|
@@ -37,11 +89,63 @@ spec:
|
|
| 37 |
metadata:
|
| 38 |
labels:
|
| 39 |
app: cart
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
spec:
|
| 41 |
containers:
|
| 42 |
- name: cart
|
| 43 |
-
image:
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
---
|
| 46 |
apiVersion: apps/v1
|
| 47 |
kind: Deployment
|
|
@@ -57,11 +161,63 @@ spec:
|
|
| 57 |
metadata:
|
| 58 |
labels:
|
| 59 |
app: catalog
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
spec:
|
| 61 |
containers:
|
| 62 |
- name: catalog
|
| 63 |
-
image:
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
---
|
| 66 |
apiVersion: apps/v1
|
| 67 |
kind: Deployment
|
|
@@ -77,11 +233,63 @@ spec:
|
|
| 77 |
metadata:
|
| 78 |
labels:
|
| 79 |
app: checkout
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
spec:
|
| 81 |
containers:
|
| 82 |
- name: checkout
|
| 83 |
-
image:
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
---
|
| 86 |
apiVersion: apps/v1
|
| 87 |
kind: Deployment
|
|
@@ -89,7 +297,7 @@ metadata:
|
|
| 89 |
name: payments
|
| 90 |
namespace: prod-sre
|
| 91 |
spec:
|
| 92 |
-
replicas:
|
| 93 |
selector:
|
| 94 |
matchLabels:
|
| 95 |
app: payments
|
|
@@ -97,8 +305,61 @@ spec:
|
|
| 97 |
metadata:
|
| 98 |
labels:
|
| 99 |
app: payments
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
spec:
|
| 101 |
containers:
|
| 102 |
- name: payments
|
| 103 |
-
image:
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
metadata:
|
| 18 |
labels:
|
| 19 |
app: auth
|
| 20 |
+
annotations:
|
| 21 |
+
prometheus.io/scrape: "true"
|
| 22 |
+
prometheus.io/port: "8080"
|
| 23 |
+
prometheus.io/path: "/metrics"
|
| 24 |
spec:
|
| 25 |
containers:
|
| 26 |
- name: auth
|
| 27 |
+
image: python:3.12-alpine
|
| 28 |
+
env:
|
| 29 |
+
- name: NODE_ID
|
| 30 |
+
value: node-4
|
| 31 |
+
- name: BASE_QUEUE
|
| 32 |
+
value: "6"
|
| 33 |
+
command: ["/bin/sh", "-lc"]
|
| 34 |
+
args:
|
| 35 |
+
- |
|
| 36 |
+
mkdir -p /www
|
| 37 |
+
echo ok > /www/index.html
|
| 38 |
+
python -m http.server 8080 --directory /www >/tmp/http.log 2>&1 &
|
| 39 |
+
req=0; err=0; cpu_total=0
|
| 40 |
+
while true; do
|
| 41 |
+
t=$(date +%s)
|
| 42 |
+
noise=$((t % 11))
|
| 43 |
+
req=$((req + 30 + noise))
|
| 44 |
+
q=$((BASE_QUEUE + (t % 20) - 10))
|
| 45 |
+
if [ "$q" -lt 0 ]; then q=0; fi
|
| 46 |
+
err=$((err + q / 20))
|
| 47 |
+
cpu_inc=$((10 + q / 10))
|
| 48 |
+
cpu_total=$((cpu_total + cpu_inc))
|
| 49 |
+
lat_ms=$((35 + q * 3))
|
| 50 |
+
b005=$((req / 5)); b01=$((req / 3)); b025=$((req / 2)); b05=$((req * 3 / 4)); b1=$req; b2=$req
|
| 51 |
+
lat_sum=$(awk "BEGIN {printf \"%.3f\", $req * $lat_ms / 1000.0}")
|
| 52 |
+
{
|
| 53 |
+
echo "# HELP http_requests_total Synthetic request counter"
|
| 54 |
+
echo "# TYPE http_requests_total counter"
|
| 55 |
+
echo "http_requests_total{node_id=\"${NODE_ID}\",status=\"200\"} ${req}"
|
| 56 |
+
echo "http_requests_total{node_id=\"${NODE_ID}\",status=\"500\"} ${err}"
|
| 57 |
+
echo "# HELP queue_depth Synthetic queue depth"
|
| 58 |
+
echo "# TYPE queue_depth gauge"
|
| 59 |
+
echo "queue_depth{node_id=\"${NODE_ID}\"} ${q}"
|
| 60 |
+
echo "# HELP container_cpu_usage_seconds_total Synthetic CPU counter"
|
| 61 |
+
echo "# TYPE container_cpu_usage_seconds_total counter"
|
| 62 |
+
echo "container_cpu_usage_seconds_total{node_id=\"${NODE_ID}\"} ${cpu_total}"
|
| 63 |
+
echo "# HELP http_request_duration_seconds Synthetic request duration histogram"
|
| 64 |
+
echo "# TYPE http_request_duration_seconds histogram"
|
| 65 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.05\"} ${b005}"
|
| 66 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.1\"} ${b01}"
|
| 67 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.25\"} ${b025}"
|
| 68 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.5\"} ${b05}"
|
| 69 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"1\"} ${b1}"
|
| 70 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"2\"} ${b2}"
|
| 71 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"+Inf\"} ${req}"
|
| 72 |
+
echo "http_request_duration_seconds_count{node_id=\"${NODE_ID}\"} ${req}"
|
| 73 |
+
echo "http_request_duration_seconds_sum{node_id=\"${NODE_ID}\"} ${lat_sum}"
|
| 74 |
+
} > /www/metrics
|
| 75 |
+
sleep 2
|
| 76 |
+
done
|
| 77 |
---
|
| 78 |
apiVersion: apps/v1
|
| 79 |
kind: Deployment
|
|
|
|
| 89 |
metadata:
|
| 90 |
labels:
|
| 91 |
app: cart
|
| 92 |
+
annotations:
|
| 93 |
+
prometheus.io/scrape: "true"
|
| 94 |
+
prometheus.io/port: "8080"
|
| 95 |
+
prometheus.io/path: "/metrics"
|
| 96 |
spec:
|
| 97 |
containers:
|
| 98 |
- name: cart
|
| 99 |
+
image: python:3.12-alpine
|
| 100 |
+
env:
|
| 101 |
+
- name: NODE_ID
|
| 102 |
+
value: node-3
|
| 103 |
+
- name: BASE_QUEUE
|
| 104 |
+
value: "14"
|
| 105 |
+
command: ["/bin/sh", "-lc"]
|
| 106 |
+
args:
|
| 107 |
+
- |
|
| 108 |
+
mkdir -p /www
|
| 109 |
+
echo ok > /www/index.html
|
| 110 |
+
python -m http.server 8080 --directory /www >/tmp/http.log 2>&1 &
|
| 111 |
+
req=0; err=0; cpu_total=0
|
| 112 |
+
while true; do
|
| 113 |
+
t=$(date +%s)
|
| 114 |
+
noise=$((t % 11))
|
| 115 |
+
req=$((req + 30 + noise))
|
| 116 |
+
q=$((BASE_QUEUE + (t % 20) - 10))
|
| 117 |
+
if [ "$q" -lt 0 ]; then q=0; fi
|
| 118 |
+
err=$((err + q / 20))
|
| 119 |
+
cpu_inc=$((10 + q / 10))
|
| 120 |
+
cpu_total=$((cpu_total + cpu_inc))
|
| 121 |
+
lat_ms=$((35 + q * 3))
|
| 122 |
+
b005=$((req / 5)); b01=$((req / 3)); b025=$((req / 2)); b05=$((req * 3 / 4)); b1=$req; b2=$req
|
| 123 |
+
lat_sum=$(awk "BEGIN {printf \"%.3f\", $req * $lat_ms / 1000.0}")
|
| 124 |
+
{
|
| 125 |
+
echo "# HELP http_requests_total Synthetic request counter"
|
| 126 |
+
echo "# TYPE http_requests_total counter"
|
| 127 |
+
echo "http_requests_total{node_id=\"${NODE_ID}\",status=\"200\"} ${req}"
|
| 128 |
+
echo "http_requests_total{node_id=\"${NODE_ID}\",status=\"500\"} ${err}"
|
| 129 |
+
echo "# HELP queue_depth Synthetic queue depth"
|
| 130 |
+
echo "# TYPE queue_depth gauge"
|
| 131 |
+
echo "queue_depth{node_id=\"${NODE_ID}\"} ${q}"
|
| 132 |
+
echo "# HELP container_cpu_usage_seconds_total Synthetic CPU counter"
|
| 133 |
+
echo "# TYPE container_cpu_usage_seconds_total counter"
|
| 134 |
+
echo "container_cpu_usage_seconds_total{node_id=\"${NODE_ID}\"} ${cpu_total}"
|
| 135 |
+
echo "# HELP http_request_duration_seconds Synthetic request duration histogram"
|
| 136 |
+
echo "# TYPE http_request_duration_seconds histogram"
|
| 137 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.05\"} ${b005}"
|
| 138 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.1\"} ${b01}"
|
| 139 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.25\"} ${b025}"
|
| 140 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.5\"} ${b05}"
|
| 141 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"1\"} ${b1}"
|
| 142 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"2\"} ${b2}"
|
| 143 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"+Inf\"} ${req}"
|
| 144 |
+
echo "http_request_duration_seconds_count{node_id=\"${NODE_ID}\"} ${req}"
|
| 145 |
+
echo "http_request_duration_seconds_sum{node_id=\"${NODE_ID}\"} ${lat_sum}"
|
| 146 |
+
} > /www/metrics
|
| 147 |
+
sleep 2
|
| 148 |
+
done
|
| 149 |
---
|
| 150 |
apiVersion: apps/v1
|
| 151 |
kind: Deployment
|
|
|
|
| 161 |
metadata:
|
| 162 |
labels:
|
| 163 |
app: catalog
|
| 164 |
+
annotations:
|
| 165 |
+
prometheus.io/scrape: "true"
|
| 166 |
+
prometheus.io/port: "8080"
|
| 167 |
+
prometheus.io/path: "/metrics"
|
| 168 |
spec:
|
| 169 |
containers:
|
| 170 |
- name: catalog
|
| 171 |
+
image: python:3.12-alpine
|
| 172 |
+
env:
|
| 173 |
+
- name: NODE_ID
|
| 174 |
+
value: node-2
|
| 175 |
+
- name: BASE_QUEUE
|
| 176 |
+
value: "20"
|
| 177 |
+
command: ["/bin/sh", "-lc"]
|
| 178 |
+
args:
|
| 179 |
+
- |
|
| 180 |
+
mkdir -p /www
|
| 181 |
+
echo ok > /www/index.html
|
| 182 |
+
python -m http.server 8080 --directory /www >/tmp/http.log 2>&1 &
|
| 183 |
+
req=0; err=0; cpu_total=0
|
| 184 |
+
while true; do
|
| 185 |
+
t=$(date +%s)
|
| 186 |
+
noise=$((t % 11))
|
| 187 |
+
req=$((req + 30 + noise))
|
| 188 |
+
q=$((BASE_QUEUE + (t % 20) - 10))
|
| 189 |
+
if [ "$q" -lt 0 ]; then q=0; fi
|
| 190 |
+
err=$((err + q / 20))
|
| 191 |
+
cpu_inc=$((10 + q / 10))
|
| 192 |
+
cpu_total=$((cpu_total + cpu_inc))
|
| 193 |
+
lat_ms=$((35 + q * 3))
|
| 194 |
+
b005=$((req / 5)); b01=$((req / 3)); b025=$((req / 2)); b05=$((req * 3 / 4)); b1=$req; b2=$req
|
| 195 |
+
lat_sum=$(awk "BEGIN {printf \"%.3f\", $req * $lat_ms / 1000.0}")
|
| 196 |
+
{
|
| 197 |
+
echo "# HELP http_requests_total Synthetic request counter"
|
| 198 |
+
echo "# TYPE http_requests_total counter"
|
| 199 |
+
echo "http_requests_total{node_id=\"${NODE_ID}\",status=\"200\"} ${req}"
|
| 200 |
+
echo "http_requests_total{node_id=\"${NODE_ID}\",status=\"500\"} ${err}"
|
| 201 |
+
echo "# HELP queue_depth Synthetic queue depth"
|
| 202 |
+
echo "# TYPE queue_depth gauge"
|
| 203 |
+
echo "queue_depth{node_id=\"${NODE_ID}\"} ${q}"
|
| 204 |
+
echo "# HELP container_cpu_usage_seconds_total Synthetic CPU counter"
|
| 205 |
+
echo "# TYPE container_cpu_usage_seconds_total counter"
|
| 206 |
+
echo "container_cpu_usage_seconds_total{node_id=\"${NODE_ID}\"} ${cpu_total}"
|
| 207 |
+
echo "# HELP http_request_duration_seconds Synthetic request duration histogram"
|
| 208 |
+
echo "# TYPE http_request_duration_seconds histogram"
|
| 209 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.05\"} ${b005}"
|
| 210 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.1\"} ${b01}"
|
| 211 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.25\"} ${b025}"
|
| 212 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.5\"} ${b05}"
|
| 213 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"1\"} ${b1}"
|
| 214 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"2\"} ${b2}"
|
| 215 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"+Inf\"} ${req}"
|
| 216 |
+
echo "http_request_duration_seconds_count{node_id=\"${NODE_ID}\"} ${req}"
|
| 217 |
+
echo "http_request_duration_seconds_sum{node_id=\"${NODE_ID}\"} ${lat_sum}"
|
| 218 |
+
} > /www/metrics
|
| 219 |
+
sleep 2
|
| 220 |
+
done
|
| 221 |
---
|
| 222 |
apiVersion: apps/v1
|
| 223 |
kind: Deployment
|
|
|
|
| 233 |
metadata:
|
| 234 |
labels:
|
| 235 |
app: checkout
|
| 236 |
+
annotations:
|
| 237 |
+
prometheus.io/scrape: "true"
|
| 238 |
+
prometheus.io/port: "8080"
|
| 239 |
+
prometheus.io/path: "/metrics"
|
| 240 |
spec:
|
| 241 |
containers:
|
| 242 |
- name: checkout
|
| 243 |
+
image: python:3.12-alpine
|
| 244 |
+
env:
|
| 245 |
+
- name: NODE_ID
|
| 246 |
+
value: node-1
|
| 247 |
+
- name: BASE_QUEUE
|
| 248 |
+
value: "24"
|
| 249 |
+
command: ["/bin/sh", "-lc"]
|
| 250 |
+
args:
|
| 251 |
+
- |
|
| 252 |
+
mkdir -p /www
|
| 253 |
+
echo ok > /www/index.html
|
| 254 |
+
python -m http.server 8080 --directory /www >/tmp/http.log 2>&1 &
|
| 255 |
+
req=0; err=0; cpu_total=0
|
| 256 |
+
while true; do
|
| 257 |
+
t=$(date +%s)
|
| 258 |
+
noise=$((t % 11))
|
| 259 |
+
req=$((req + 30 + noise))
|
| 260 |
+
q=$((BASE_QUEUE + (t % 20) - 10))
|
| 261 |
+
if [ "$q" -lt 0 ]; then q=0; fi
|
| 262 |
+
err=$((err + q / 20))
|
| 263 |
+
cpu_inc=$((10 + q / 10))
|
| 264 |
+
cpu_total=$((cpu_total + cpu_inc))
|
| 265 |
+
lat_ms=$((35 + q * 3))
|
| 266 |
+
b005=$((req / 5)); b01=$((req / 3)); b025=$((req / 2)); b05=$((req * 3 / 4)); b1=$req; b2=$req
|
| 267 |
+
lat_sum=$(awk "BEGIN {printf \"%.3f\", $req * $lat_ms / 1000.0}")
|
| 268 |
+
{
|
| 269 |
+
echo "# HELP http_requests_total Synthetic request counter"
|
| 270 |
+
echo "# TYPE http_requests_total counter"
|
| 271 |
+
echo "http_requests_total{node_id=\"${NODE_ID}\",status=\"200\"} ${req}"
|
| 272 |
+
echo "http_requests_total{node_id=\"${NODE_ID}\",status=\"500\"} ${err}"
|
| 273 |
+
echo "# HELP queue_depth Synthetic queue depth"
|
| 274 |
+
echo "# TYPE queue_depth gauge"
|
| 275 |
+
echo "queue_depth{node_id=\"${NODE_ID}\"} ${q}"
|
| 276 |
+
echo "# HELP container_cpu_usage_seconds_total Synthetic CPU counter"
|
| 277 |
+
echo "# TYPE container_cpu_usage_seconds_total counter"
|
| 278 |
+
echo "container_cpu_usage_seconds_total{node_id=\"${NODE_ID}\"} ${cpu_total}"
|
| 279 |
+
echo "# HELP http_request_duration_seconds Synthetic request duration histogram"
|
| 280 |
+
echo "# TYPE http_request_duration_seconds histogram"
|
| 281 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.05\"} ${b005}"
|
| 282 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.1\"} ${b01}"
|
| 283 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.25\"} ${b025}"
|
| 284 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.5\"} ${b05}"
|
| 285 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"1\"} ${b1}"
|
| 286 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"2\"} ${b2}"
|
| 287 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"+Inf\"} ${req}"
|
| 288 |
+
echo "http_request_duration_seconds_count{node_id=\"${NODE_ID}\"} ${req}"
|
| 289 |
+
echo "http_request_duration_seconds_sum{node_id=\"${NODE_ID}\"} ${lat_sum}"
|
| 290 |
+
} > /www/metrics
|
| 291 |
+
sleep 2
|
| 292 |
+
done
|
| 293 |
---
|
| 294 |
apiVersion: apps/v1
|
| 295 |
kind: Deployment
|
|
|
|
| 297 |
name: payments
|
| 298 |
namespace: prod-sre
|
| 299 |
spec:
|
| 300 |
+
replicas: 2
|
| 301 |
selector:
|
| 302 |
matchLabels:
|
| 303 |
app: payments
|
|
|
|
| 305 |
metadata:
|
| 306 |
labels:
|
| 307 |
app: payments
|
| 308 |
+
annotations:
|
| 309 |
+
prometheus.io/scrape: "true"
|
| 310 |
+
prometheus.io/port: "8080"
|
| 311 |
+
prometheus.io/path: "/metrics"
|
| 312 |
spec:
|
| 313 |
containers:
|
| 314 |
- name: payments
|
| 315 |
+
image: python:3.12-alpine
|
| 316 |
+
env:
|
| 317 |
+
- name: NODE_ID
|
| 318 |
+
value: node-0
|
| 319 |
+
- name: BASE_QUEUE
|
| 320 |
+
value: "30"
|
| 321 |
+
command: ["/bin/sh", "-lc"]
|
| 322 |
+
args:
|
| 323 |
+
- |
|
| 324 |
+
mkdir -p /www
|
| 325 |
+
echo ok > /www/index.html
|
| 326 |
+
python -m http.server 8080 --directory /www >/tmp/http.log 2>&1 &
|
| 327 |
+
req=0; err=0; cpu_total=0
|
| 328 |
+
while true; do
|
| 329 |
+
t=$(date +%s)
|
| 330 |
+
noise=$((t % 11))
|
| 331 |
+
req=$((req + 30 + noise))
|
| 332 |
+
q=$((BASE_QUEUE + (t % 20) - 10))
|
| 333 |
+
if [ "$q" -lt 0 ]; then q=0; fi
|
| 334 |
+
err=$((err + q / 20))
|
| 335 |
+
cpu_inc=$((10 + q / 10))
|
| 336 |
+
cpu_total=$((cpu_total + cpu_inc))
|
| 337 |
+
lat_ms=$((35 + q * 3))
|
| 338 |
+
b005=$((req / 5)); b01=$((req / 3)); b025=$((req / 2)); b05=$((req * 3 / 4)); b1=$req; b2=$req
|
| 339 |
+
lat_sum=$(awk "BEGIN {printf \"%.3f\", $req * $lat_ms / 1000.0}")
|
| 340 |
+
{
|
| 341 |
+
echo "# HELP http_requests_total Synthetic request counter"
|
| 342 |
+
echo "# TYPE http_requests_total counter"
|
| 343 |
+
echo "http_requests_total{node_id=\"${NODE_ID}\",status=\"200\"} ${req}"
|
| 344 |
+
echo "http_requests_total{node_id=\"${NODE_ID}\",status=\"500\"} ${err}"
|
| 345 |
+
echo "# HELP queue_depth Synthetic queue depth"
|
| 346 |
+
echo "# TYPE queue_depth gauge"
|
| 347 |
+
echo "queue_depth{node_id=\"${NODE_ID}\"} ${q}"
|
| 348 |
+
echo "# HELP container_cpu_usage_seconds_total Synthetic CPU counter"
|
| 349 |
+
echo "# TYPE container_cpu_usage_seconds_total counter"
|
| 350 |
+
echo "container_cpu_usage_seconds_total{node_id=\"${NODE_ID}\"} ${cpu_total}"
|
| 351 |
+
echo "# HELP http_request_duration_seconds Synthetic request duration histogram"
|
| 352 |
+
echo "# TYPE http_request_duration_seconds histogram"
|
| 353 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.05\"} ${b005}"
|
| 354 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.1\"} ${b01}"
|
| 355 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.25\"} ${b025}"
|
| 356 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"0.5\"} ${b05}"
|
| 357 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"1\"} ${b1}"
|
| 358 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"2\"} ${b2}"
|
| 359 |
+
echo "http_request_duration_seconds_bucket{node_id=\"${NODE_ID}\",le=\"+Inf\"} ${req}"
|
| 360 |
+
echo "http_request_duration_seconds_count{node_id=\"${NODE_ID}\"} ${req}"
|
| 361 |
+
echo "http_request_duration_seconds_sum{node_id=\"${NODE_ID}\"} ${lat_sum}"
|
| 362 |
+
} > /www/metrics
|
| 363 |
+
sleep 2
|
| 364 |
+
done
|
| 365 |
+
|
deploy/local/grafana-local-values.yaml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
adminUser: admin
|
| 2 |
+
adminPassword: antiatropos
|
| 3 |
+
|
| 4 |
+
service:
|
| 5 |
+
type: ClusterIP
|
| 6 |
+
|
| 7 |
+
persistence:
|
| 8 |
+
enabled: false
|
| 9 |
+
|
| 10 |
+
resources:
|
| 11 |
+
requests:
|
| 12 |
+
cpu: 100m
|
| 13 |
+
memory: 192Mi
|
| 14 |
+
limits:
|
| 15 |
+
cpu: 400m
|
| 16 |
+
memory: 384Mi
|
| 17 |
+
|
| 18 |
+
datasources:
|
| 19 |
+
datasources.yaml:
|
| 20 |
+
apiVersion: 1
|
| 21 |
+
datasources:
|
| 22 |
+
- name: Prometheus
|
| 23 |
+
type: prometheus
|
| 24 |
+
access: proxy
|
| 25 |
+
url: http://prometheus-server.monitoring.svc.cluster.local
|
| 26 |
+
isDefault: true
|
| 27 |
+
editable: true
|
| 28 |
+
|
| 29 |
+
sidecar:
|
| 30 |
+
dashboards:
|
| 31 |
+
enabled: true
|
| 32 |
+
label: grafana_dashboard
|
| 33 |
+
labelValue: "1"
|
| 34 |
+
searchNamespace: ALL
|
deploy/local/prometheus-local-values.yaml
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
alertmanager:
|
| 2 |
+
enabled: false
|
| 3 |
+
|
| 4 |
+
kube-state-metrics:
|
| 5 |
+
enabled: false
|
| 6 |
+
|
| 7 |
+
prometheus-node-exporter:
|
| 8 |
+
enabled: false
|
| 9 |
+
|
| 10 |
+
prometheus-pushgateway:
|
| 11 |
+
enabled: false
|
| 12 |
+
|
| 13 |
+
extraScrapeConfigs: |
|
| 14 |
+
- job_name: 'antiatropos-fastapi'
|
| 15 |
+
metrics_path: /metrics
|
| 16 |
+
static_configs:
|
| 17 |
+
- targets: ['host.docker.internal:8000']
|
| 18 |
+
|
| 19 |
+
- job_name: 'prod-sre-annotated-pods'
|
| 20 |
+
kubernetes_sd_configs:
|
| 21 |
+
- role: pod
|
| 22 |
+
namespaces:
|
| 23 |
+
names: ['prod-sre']
|
| 24 |
+
relabel_configs:
|
| 25 |
+
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
|
| 26 |
+
action: keep
|
| 27 |
+
regex: true
|
| 28 |
+
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
|
| 29 |
+
action: replace
|
| 30 |
+
target_label: __metrics_path__
|
| 31 |
+
regex: (.+)
|
| 32 |
+
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
|
| 33 |
+
action: replace
|
| 34 |
+
regex: ([^:]+)(?::\d+)?;(\d+)
|
| 35 |
+
replacement: $1:$2
|
| 36 |
+
target_label: __address__
|
| 37 |
+
|
| 38 |
+
server:
|
| 39 |
+
persistentVolume:
|
| 40 |
+
enabled: false
|
| 41 |
+
resources:
|
| 42 |
+
requests:
|
| 43 |
+
cpu: 100m
|
| 44 |
+
memory: 256Mi
|
| 45 |
+
limits:
|
| 46 |
+
cpu: 500m
|
| 47 |
+
memory: 512Mi
|
| 48 |
+
service:
|
| 49 |
+
type: ClusterIP
|
server/Dockerfile
CHANGED
|
@@ -85,7 +85,7 @@ ENV KUBECONFIG="mock"
|
|
| 85 |
ENV ANTIATROPOS_K8S_NAMESPACE="default"
|
| 86 |
ENV ANTIATROPOS_DEPLOYMENT_PREFIX=""
|
| 87 |
ENV ANTIATROPOS_MIN_REPLICAS="1"
|
| 88 |
-
ENV ANTIATROPOS_MAX_REPLICAS="
|
| 89 |
ENV ANTIATROPOS_SCALE_STEP="3"
|
| 90 |
ENV ANTIATROPOS_WORKLOAD_MAP="{}"
|
| 91 |
ENV ANTIATROPOS_NODE_DEPLOYMENT_MAP="{}"
|
|
|
|
| 85 |
ENV ANTIATROPOS_K8S_NAMESPACE="default"
|
| 86 |
ENV ANTIATROPOS_DEPLOYMENT_PREFIX=""
|
| 87 |
ENV ANTIATROPOS_MIN_REPLICAS="1"
|
| 88 |
+
ENV ANTIATROPOS_MAX_REPLICAS=""
|
| 89 |
ENV ANTIATROPOS_SCALE_STEP="3"
|
| 90 |
ENV ANTIATROPOS_WORKLOAD_MAP="{}"
|
| 91 |
ENV ANTIATROPOS_NODE_DEPLOYMENT_MAP="{}"
|
server/app.py
CHANGED
|
@@ -84,6 +84,9 @@ def runtime_config():
|
|
| 84 |
except Exception:
|
| 85 |
mapped_nodes = []
|
| 86 |
|
|
|
|
|
|
|
|
|
|
| 87 |
return {
|
| 88 |
"env_mode": os.getenv("ANTIATROPOS_ENV_MODE", "simulated"),
|
| 89 |
"reward_output_mode": os.getenv("ANTIATROPOS_REWARD_OUTPUT_MODE", "normalized"),
|
|
@@ -91,7 +94,7 @@ def runtime_config():
|
|
| 91 |
"kubeconfig_configured": bool(os.getenv("KUBECONFIG")),
|
| 92 |
"k8s_namespace": os.getenv("ANTIATROPOS_K8S_NAMESPACE", "default"),
|
| 93 |
"min_replicas": os.getenv("ANTIATROPOS_MIN_REPLICAS", "1"),
|
| 94 |
-
"max_replicas":
|
| 95 |
"scale_step": os.getenv("ANTIATROPOS_SCALE_STEP", "3"),
|
| 96 |
"strict_real": os.getenv("ANTIATROPOS_STRICT_REAL", "false"),
|
| 97 |
"workload_map_configured": bool(raw_map),
|
|
|
|
| 84 |
except Exception:
|
| 85 |
mapped_nodes = []
|
| 86 |
|
| 87 |
+
raw_max_replicas = os.getenv("ANTIATROPOS_MAX_REPLICAS", "")
|
| 88 |
+
max_replicas_display = raw_max_replicas if raw_max_replicas.strip() else "unbounded"
|
| 89 |
+
|
| 90 |
return {
|
| 91 |
"env_mode": os.getenv("ANTIATROPOS_ENV_MODE", "simulated"),
|
| 92 |
"reward_output_mode": os.getenv("ANTIATROPOS_REWARD_OUTPUT_MODE", "normalized"),
|
|
|
|
| 94 |
"kubeconfig_configured": bool(os.getenv("KUBECONFIG")),
|
| 95 |
"k8s_namespace": os.getenv("ANTIATROPOS_K8S_NAMESPACE", "default"),
|
| 96 |
"min_replicas": os.getenv("ANTIATROPOS_MIN_REPLICAS", "1"),
|
| 97 |
+
"max_replicas": max_replicas_display,
|
| 98 |
"scale_step": os.getenv("ANTIATROPOS_SCALE_STEP", "3"),
|
| 99 |
"strict_real": os.getenv("ANTIATROPOS_STRICT_REAL", "false"),
|
| 100 |
"workload_map_configured": bool(raw_map),
|
start-grafana.ps1
CHANGED
|
@@ -1,29 +1,12 @@
|
|
| 1 |
docker stop antiatropos-grafana 2>$null
|
| 2 |
docker rm antiatropos-grafana 2>$null
|
| 3 |
|
| 4 |
-
Write-Host "
|
| 5 |
-
$AccessKey = (aws configure get aws_access_key_id).Trim()
|
| 6 |
-
$SecretKey = (aws configure get aws_secret_access_key).Trim()
|
| 7 |
-
$SessionToken = aws configure get aws_session_token
|
| 8 |
-
if ($SessionToken) { $SessionToken = $SessionToken.Trim() }
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
|
| 13 |
-
"-v ""$PWD\deploy\grafana\provisioning:/etc/grafana/provisioning:ro"" " + `
|
| 14 |
-
"-e GF_AUTH_ANONYMOUS_ENABLED=true " + `
|
| 15 |
-
"-e GF_AUTH_ANONYMOUS_ORG_ROLE=Admin " + `
|
| 16 |
-
"-e GF_AUTH_SIGV4_AUTH_ENABLED=true " + `
|
| 17 |
-
"-e AWS_ACCESS_KEY_ID=""$AccessKey"" " + `
|
| 18 |
-
"-e AWS_SECRET_ACCESS_KEY=""$SecretKey"" " + `
|
| 19 |
-
"-e AWS_REGION=ap-south-1 "
|
| 20 |
-
|
| 21 |
-
if ($SessionToken) {
|
| 22 |
-
$DockerCmd += "-e AWS_SESSION_TOKEN=""$SessionToken"" "
|
| 23 |
-
}
|
| 24 |
-
|
| 25 |
-
$DockerCmd += "grafana/grafana:latest"
|
| 26 |
-
|
| 27 |
-
Invoke-Expression $DockerCmd
|
| 28 |
-
|
| 29 |
-
Write-Host "Grafana is running! Open http://localhost:3000 in your browser."
|
|
|
|
| 1 |
docker stop antiatropos-grafana 2>$null
|
| 2 |
docker rm antiatropos-grafana 2>$null
|
| 3 |
|
| 4 |
+
Write-Host "Starting local Grafana (datasource -> host.docker.internal:9090)..."
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
docker run -d --name antiatropos-grafana -p 3000:3000 `
|
| 7 |
+
-v "$PWD\deploy\grafana\provisioning:/etc/grafana/provisioning:ro" `
|
| 8 |
+
-e GF_AUTH_ANONYMOUS_ENABLED=true `
|
| 9 |
+
-e GF_AUTH_ANONYMOUS_ORG_ROLE=Admin `
|
| 10 |
+
grafana/grafana:latest | Out-Null
|
| 11 |
|
| 12 |
+
Write-Host "Grafana is running at http://localhost:3000"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
telemetry/mapping.py
CHANGED
|
@@ -25,9 +25,11 @@ class MetricMapper:
|
|
| 25 |
|
| 26 |
# Safe default mapping for local demos.
|
| 27 |
return {
|
| 28 |
-
"
|
| 29 |
-
"
|
| 30 |
-
"
|
|
|
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
def _resolve_node_id(self, labels: Dict[str, Any]) -> Optional[str]:
|
|
|
|
| 25 |
|
| 26 |
# Safe default mapping for local demos.
|
| 27 |
return {
|
| 28 |
+
"payments": "node-0",
|
| 29 |
+
"checkout": "node-1",
|
| 30 |
+
"catalog": "node-2",
|
| 31 |
+
"cart": "node-3",
|
| 32 |
+
"auth": "node-4",
|
| 33 |
}
|
| 34 |
|
| 35 |
def _resolve_node_id(self, labels: Dict[str, Any]) -> Optional[str]:
|
telemetry/prometheus_client.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
import os
|
| 2 |
import random
|
|
|
|
| 3 |
from typing import Any, Dict, List, Optional
|
| 4 |
import requests
|
| 5 |
from pydantic import BaseModel
|
| 6 |
from .mapping import MetricMapper
|
| 7 |
|
|
|
|
|
|
|
| 8 |
class TelemetryRecord(BaseModel):
|
| 9 |
node_id: str
|
| 10 |
latency_ms: float
|
|
@@ -49,7 +52,7 @@ class PrometheusClient:
|
|
| 49 |
'sum(queue_depth) by (pod)'
|
| 50 |
)
|
| 51 |
|
| 52 |
-
def fetch_latest_metrics(self, node_ids: List[str]) -> Dict[str,
|
| 53 |
"""
|
| 54 |
Query Prometheus for the latest metrics for the given nodes.
|
| 55 |
Returns a mapping from node_id to TelemetryRecord.
|
|
@@ -65,9 +68,9 @@ class PrometheusClient:
|
|
| 65 |
raise
|
| 66 |
return self._generate_mock_metrics(node_ids)
|
| 67 |
|
| 68 |
-
def _fetch_real_metrics(self, node_ids: List[str]) -> Dict[str,
|
| 69 |
"""Fetches node telemetry from Prometheus instant queries."""
|
| 70 |
-
metrics: Dict[str,
|
| 71 |
saw_any_real_signal = False
|
| 72 |
|
| 73 |
req_by_node = self._collect_metric_values("request_rate", self.request_rate_query, node_ids)
|
|
@@ -85,19 +88,35 @@ class PrometheusClient:
|
|
| 85 |
|
| 86 |
if any(v is not None for v in (req_rate, lat_ms, err_rate, cpu, q_depth)):
|
| 87 |
saw_any_real_signal = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
latency_ms=float(lat_ms
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
if self.strict_real and not saw_any_real_signal:
|
| 99 |
raise RuntimeError("Prometheus returned no usable real telemetry for requested node IDs.")
|
| 100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
return metrics
|
| 102 |
|
| 103 |
def _collect_metric_values(
|
|
|
|
| 1 |
import os
|
| 2 |
import random
|
| 3 |
+
import logging
|
| 4 |
from typing import Any, Dict, List, Optional
|
| 5 |
import requests
|
| 6 |
from pydantic import BaseModel
|
| 7 |
from .mapping import MetricMapper
|
| 8 |
|
| 9 |
+
logger = logging.getLogger("antiatropos.telemetry")
|
| 10 |
+
|
| 11 |
class TelemetryRecord(BaseModel):
|
| 12 |
node_id: str
|
| 13 |
latency_ms: float
|
|
|
|
| 52 |
'sum(queue_depth) by (pod)'
|
| 53 |
)
|
| 54 |
|
| 55 |
+
def fetch_latest_metrics(self, node_ids: List[str]) -> Dict[str, Any]:
|
| 56 |
"""
|
| 57 |
Query Prometheus for the latest metrics for the given nodes.
|
| 58 |
Returns a mapping from node_id to TelemetryRecord.
|
|
|
|
| 68 |
raise
|
| 69 |
return self._generate_mock_metrics(node_ids)
|
| 70 |
|
| 71 |
+
def _fetch_real_metrics(self, node_ids: List[str]) -> Dict[str, Any]:
|
| 72 |
"""Fetches node telemetry from Prometheus instant queries."""
|
| 73 |
+
metrics: Dict[str, Any] = {}
|
| 74 |
saw_any_real_signal = False
|
| 75 |
|
| 76 |
req_by_node = self._collect_metric_values("request_rate", self.request_rate_query, node_ids)
|
|
|
|
| 88 |
|
| 89 |
if any(v is not None for v in (req_rate, lat_ms, err_rate, cpu, q_depth)):
|
| 90 |
saw_any_real_signal = True
|
| 91 |
+
else:
|
| 92 |
+
# No usable sample for this node this cycle; skip reconciliation
|
| 93 |
+
# so simulator dynamics are preserved instead of being collapsed
|
| 94 |
+
# toward zero by synthetic defaults.
|
| 95 |
+
continue
|
| 96 |
|
| 97 |
+
node_payload: Dict[str, float] = {}
|
| 98 |
+
if lat_ms is not None:
|
| 99 |
+
node_payload["latency_ms"] = float(lat_ms)
|
| 100 |
+
if req_rate is not None:
|
| 101 |
+
node_payload["request_rate"] = float(req_rate)
|
| 102 |
+
if err_rate is not None:
|
| 103 |
+
node_payload["error_rate"] = max(0.0, min(1.0, float(err_rate)))
|
| 104 |
+
if cpu is not None:
|
| 105 |
+
node_payload["cpu_utilization"] = max(0.0, min(1.0, float(cpu)))
|
| 106 |
+
if q_depth is not None:
|
| 107 |
+
node_payload["queue_depth"] = max(0.0, float(q_depth))
|
| 108 |
+
if node_payload:
|
| 109 |
+
metrics[node_id] = node_payload
|
| 110 |
|
| 111 |
if self.strict_real and not saw_any_real_signal:
|
| 112 |
raise RuntimeError("Prometheus returned no usable real telemetry for requested node IDs.")
|
| 113 |
|
| 114 |
+
if not saw_any_real_signal:
|
| 115 |
+
logger.warning(
|
| 116 |
+
"No per-node Prometheus samples found for configured queries; "
|
| 117 |
+
"skipping telemetry reconciliation for this step."
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
return metrics
|
| 121 |
|
| 122 |
def _collect_metric_values(
|