prescale-agent 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prescale_agent-0.2.0/PKG-INFO +155 -0
- prescale_agent-0.2.0/README.md +107 -0
- prescale_agent-0.2.0/pyproject.toml +85 -0
- prescale_agent-0.2.0/setup.cfg +4 -0
- prescale_agent-0.2.0/src/prescale_agent/__init__.py +3 -0
- prescale_agent-0.2.0/src/prescale_agent/agent.py +245 -0
- prescale_agent-0.2.0/src/prescale_agent/cli.py +384 -0
- prescale_agent-0.2.0/src/prescale_agent/client.py +199 -0
- prescale_agent-0.2.0/src/prescale_agent/config.py +189 -0
- prescale_agent-0.2.0/src/prescale_agent/sources/__init__.py +15 -0
- prescale_agent-0.2.0/src/prescale_agent/sources/azure_monitor.py +190 -0
- prescale_agent-0.2.0/src/prescale_agent/sources/base.py +156 -0
- prescale_agent-0.2.0/src/prescale_agent/sources/cloudwatch.py +209 -0
- prescale_agent-0.2.0/src/prescale_agent/sources/datadog.py +204 -0
- prescale_agent-0.2.0/src/prescale_agent/sources/gcp_monitoring.py +259 -0
- prescale_agent-0.2.0/src/prescale_agent/sources/prometheus.py +206 -0
- prescale_agent-0.2.0/src/prescale_agent/sources/registry.py +111 -0
- prescale_agent-0.2.0/src/prescale_agent/sources/system.py +228 -0
- prescale_agent-0.2.0/src/prescale_agent.egg-info/PKG-INFO +155 -0
- prescale_agent-0.2.0/src/prescale_agent.egg-info/SOURCES.txt +23 -0
- prescale_agent-0.2.0/src/prescale_agent.egg-info/dependency_links.txt +1 -0
- prescale_agent-0.2.0/src/prescale_agent.egg-info/entry_points.txt +2 -0
- prescale_agent-0.2.0/src/prescale_agent.egg-info/requires.txt +32 -0
- prescale_agent-0.2.0/src/prescale_agent.egg-info/top_level.txt +1 -0
- prescale_agent-0.2.0/tests/test_agent.py +465 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prescale-agent
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Metrics collection agent for Prescale - Predictive Infrastructure Intelligence Platform
|
|
5
|
+
Author-email: Prescale Platform <maintainers@prescale.dev>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/pyjeebz/prescale
|
|
8
|
+
Project-URL: Repository, https://github.com/pyjeebz/prescale
|
|
9
|
+
Keywords: kubernetes,metrics,monitoring,prometheus,observability
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: System :: Monitoring
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: httpx>=0.24.0
|
|
25
|
+
Requires-Dist: click>=8.0.0
|
|
26
|
+
Requires-Dist: rich>=13.0.0
|
|
27
|
+
Requires-Dist: pyyaml>=6.0
|
|
28
|
+
Requires-Dist: psutil>=5.9.0
|
|
29
|
+
Provides-Extra: prometheus
|
|
30
|
+
Requires-Dist: prometheus-client>=0.17.0; extra == "prometheus"
|
|
31
|
+
Provides-Extra: datadog
|
|
32
|
+
Requires-Dist: datadog-api-client>=2.0.0; extra == "datadog"
|
|
33
|
+
Provides-Extra: aws
|
|
34
|
+
Requires-Dist: boto3>=1.26.0; extra == "aws"
|
|
35
|
+
Provides-Extra: azure
|
|
36
|
+
Requires-Dist: azure-identity>=1.12.0; extra == "azure"
|
|
37
|
+
Requires-Dist: azure-mgmt-monitor>=6.0.0; extra == "azure"
|
|
38
|
+
Provides-Extra: gcp
|
|
39
|
+
Requires-Dist: google-cloud-monitoring>=2.15.0; extra == "gcp"
|
|
40
|
+
Provides-Extra: kubernetes
|
|
41
|
+
Requires-Dist: kubernetes>=28.0.0; extra == "kubernetes"
|
|
42
|
+
Provides-Extra: all
|
|
43
|
+
Requires-Dist: prescale-agent[aws,azure,datadog,gcp,kubernetes,prometheus]; extra == "all"
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
46
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
47
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
48
|
+
|
|
49
|
+
# Prescale Agent
|
|
50
|
+
|
|
51
|
+
Metrics collection agent for [Prescale](https://github.com/pyjeebz/prescale) - Predictive Infrastructure Intelligence Platform.
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Base installation (system metrics + Prometheus)
|
|
57
|
+
pip install prescale-platform-agent
|
|
58
|
+
|
|
59
|
+
# With specific backends
|
|
60
|
+
pip install prescale-platform-agent[gcp] # + GCP Cloud Monitoring
|
|
61
|
+
pip install prescale-platform-agent[aws] # + AWS CloudWatch
|
|
62
|
+
pip install prescale-platform-agent[azure] # + Azure Monitor
|
|
63
|
+
pip install prescale-platform-agent[datadog] # + Datadog
|
|
64
|
+
pip install prescale-platform-agent[all] # All backends
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Quick Start
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Generate configuration file
|
|
71
|
+
prescale-agent init
|
|
72
|
+
|
|
73
|
+
# List available metric sources
|
|
74
|
+
prescale-agent sources
|
|
75
|
+
|
|
76
|
+
# Test configured sources
|
|
77
|
+
prescale-agent test
|
|
78
|
+
|
|
79
|
+
# Run the agent
|
|
80
|
+
prescale-agent run --config prescale-agent.yaml
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
Create a `prescale-agent.yaml` file:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
agent:
|
|
89
|
+
collection_interval: 60
|
|
90
|
+
log_level: INFO
|
|
91
|
+
|
|
92
|
+
sources:
|
|
93
|
+
# System metrics (always available)
|
|
94
|
+
- type: system
|
|
95
|
+
enabled: true
|
|
96
|
+
config:
|
|
97
|
+
collect_cpu: true
|
|
98
|
+
collect_memory: true
|
|
99
|
+
|
|
100
|
+
# GCP Cloud Monitoring
|
|
101
|
+
- type: gcp-monitoring
|
|
102
|
+
enabled: true
|
|
103
|
+
config:
|
|
104
|
+
project_id: your-gcp-project
|
|
105
|
+
metrics:
|
|
106
|
+
- kubernetes.io/container/cpu/limit_utilization
|
|
107
|
+
- kubernetes.io/container/memory/limit_utilization
|
|
108
|
+
|
|
109
|
+
# Prometheus
|
|
110
|
+
- type: prometheus
|
|
111
|
+
enabled: false
|
|
112
|
+
config:
|
|
113
|
+
url: http://prometheus:9090
|
|
114
|
+
queries:
|
|
115
|
+
- name: cpu_usage
|
|
116
|
+
query: rate(container_cpu_usage_seconds_total[5m])
|
|
117
|
+
|
|
118
|
+
prescale:
|
|
119
|
+
endpoint: http://prescale-inference:8080
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Supported Sources
|
|
123
|
+
|
|
124
|
+
| Source | Description | Extra Install |
|
|
125
|
+
|--------|-------------|---------------|
|
|
126
|
+
| `system` | Local CPU, memory, disk via psutil | Built-in |
|
|
127
|
+
| `prometheus` | Query Prometheus server | Built-in |
|
|
128
|
+
| `gcp-monitoring` | GCP Cloud Monitoring | `[gcp]` |
|
|
129
|
+
| `cloudwatch` | AWS CloudWatch | `[aws]` |
|
|
130
|
+
| `azure-monitor` | Azure Monitor | `[azure]` |
|
|
131
|
+
| `datadog` | Datadog API | `[datadog]` |
|
|
132
|
+
|
|
133
|
+
## CLI Commands
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
prescale-agent init # Generate config file
|
|
137
|
+
prescale-agent run # Start collecting metrics
|
|
138
|
+
prescale-agent run --once # Single collection (testing)
|
|
139
|
+
prescale-agent run --deployment my-deployment # Associate with deployment
|
|
140
|
+
prescale-agent sources # List available sources
|
|
141
|
+
prescale-agent test # Test source connections
|
|
142
|
+
prescale-agent status # Show agent status
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Environment Variables
|
|
146
|
+
|
|
147
|
+
| Variable | Description |
|
|
148
|
+
|----------|-------------|
|
|
149
|
+
| `PRESCALE_CONFIG_FILE` | Path to config file (default: `./prescale-agent.yaml`) |
|
|
150
|
+
| `PRESCALE_ENDPOINT` | Prescale inference endpoint |
|
|
151
|
+
| `PRESCALE_API_KEY` | API key for authentication |
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
Apache 2.0 - See [LICENSE](../LICENSE)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Prescale Agent
|
|
2
|
+
|
|
3
|
+
Metrics collection agent for [Prescale](https://github.com/pyjeebz/prescale) - Predictive Infrastructure Intelligence Platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Base installation (system metrics + Prometheus)
|
|
9
|
+
pip install prescale-platform-agent
|
|
10
|
+
|
|
11
|
+
# With specific backends
|
|
12
|
+
pip install prescale-platform-agent[gcp] # + GCP Cloud Monitoring
|
|
13
|
+
pip install prescale-platform-agent[aws] # + AWS CloudWatch
|
|
14
|
+
pip install prescale-platform-agent[azure] # + Azure Monitor
|
|
15
|
+
pip install prescale-platform-agent[datadog] # + Datadog
|
|
16
|
+
pip install prescale-platform-agent[all] # All backends
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Generate configuration file
|
|
23
|
+
prescale-agent init
|
|
24
|
+
|
|
25
|
+
# List available metric sources
|
|
26
|
+
prescale-agent sources
|
|
27
|
+
|
|
28
|
+
# Test configured sources
|
|
29
|
+
prescale-agent test
|
|
30
|
+
|
|
31
|
+
# Run the agent
|
|
32
|
+
prescale-agent run --config prescale-agent.yaml
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
Create a `prescale-agent.yaml` file:
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
agent:
|
|
41
|
+
collection_interval: 60
|
|
42
|
+
log_level: INFO
|
|
43
|
+
|
|
44
|
+
sources:
|
|
45
|
+
# System metrics (always available)
|
|
46
|
+
- type: system
|
|
47
|
+
enabled: true
|
|
48
|
+
config:
|
|
49
|
+
collect_cpu: true
|
|
50
|
+
collect_memory: true
|
|
51
|
+
|
|
52
|
+
# GCP Cloud Monitoring
|
|
53
|
+
- type: gcp-monitoring
|
|
54
|
+
enabled: true
|
|
55
|
+
config:
|
|
56
|
+
project_id: your-gcp-project
|
|
57
|
+
metrics:
|
|
58
|
+
- kubernetes.io/container/cpu/limit_utilization
|
|
59
|
+
- kubernetes.io/container/memory/limit_utilization
|
|
60
|
+
|
|
61
|
+
# Prometheus
|
|
62
|
+
- type: prometheus
|
|
63
|
+
enabled: false
|
|
64
|
+
config:
|
|
65
|
+
url: http://prometheus:9090
|
|
66
|
+
queries:
|
|
67
|
+
- name: cpu_usage
|
|
68
|
+
query: rate(container_cpu_usage_seconds_total[5m])
|
|
69
|
+
|
|
70
|
+
prescale:
|
|
71
|
+
endpoint: http://prescale-inference:8080
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Supported Sources
|
|
75
|
+
|
|
76
|
+
| Source | Description | Extra Install |
|
|
77
|
+
|--------|-------------|---------------|
|
|
78
|
+
| `system` | Local CPU, memory, disk via psutil | Built-in |
|
|
79
|
+
| `prometheus` | Query Prometheus server | Built-in |
|
|
80
|
+
| `gcp-monitoring` | GCP Cloud Monitoring | `[gcp]` |
|
|
81
|
+
| `cloudwatch` | AWS CloudWatch | `[aws]` |
|
|
82
|
+
| `azure-monitor` | Azure Monitor | `[azure]` |
|
|
83
|
+
| `datadog` | Datadog API | `[datadog]` |
|
|
84
|
+
|
|
85
|
+
## CLI Commands
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
prescale-agent init # Generate config file
|
|
89
|
+
prescale-agent run # Start collecting metrics
|
|
90
|
+
prescale-agent run --once # Single collection (testing)
|
|
91
|
+
prescale-agent run --deployment my-deployment # Associate with deployment
|
|
92
|
+
prescale-agent sources # List available sources
|
|
93
|
+
prescale-agent test # Test source connections
|
|
94
|
+
prescale-agent status # Show agent status
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Environment Variables
|
|
98
|
+
|
|
99
|
+
| Variable | Description |
|
|
100
|
+
|----------|-------------|
|
|
101
|
+
| `PRESCALE_CONFIG_FILE` | Path to config file (default: `./prescale-agent.yaml`) |
|
|
102
|
+
| `PRESCALE_ENDPOINT` | Prescale inference endpoint |
|
|
103
|
+
| `PRESCALE_API_KEY` | API key for authentication |
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
Apache 2.0 - See [LICENSE](../LICENSE)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "prescale-agent"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Metrics collection agent for Prescale - Predictive Infrastructure Intelligence Platform"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "Apache-2.0"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Prescale Platform", email = "maintainers@prescale.dev"}
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"kubernetes",
|
|
17
|
+
"metrics",
|
|
18
|
+
"monitoring",
|
|
19
|
+
"prometheus",
|
|
20
|
+
"observability"
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 4 - Beta",
|
|
24
|
+
"Environment :: Console",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Intended Audience :: System Administrators",
|
|
27
|
+
"License :: OSI Approved :: Apache Software License",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Topic :: System :: Monitoring",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
dependencies = [
|
|
38
|
+
"httpx>=0.24.0",
|
|
39
|
+
"click>=8.0.0",
|
|
40
|
+
"rich>=13.0.0",
|
|
41
|
+
"pyyaml>=6.0",
|
|
42
|
+
"psutil>=5.9.0",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
prometheus = [
|
|
47
|
+
"prometheus-client>=0.17.0",
|
|
48
|
+
]
|
|
49
|
+
datadog = [
|
|
50
|
+
"datadog-api-client>=2.0.0",
|
|
51
|
+
]
|
|
52
|
+
aws = [
|
|
53
|
+
"boto3>=1.26.0",
|
|
54
|
+
]
|
|
55
|
+
azure = [
|
|
56
|
+
"azure-identity>=1.12.0",
|
|
57
|
+
"azure-mgmt-monitor>=6.0.0",
|
|
58
|
+
]
|
|
59
|
+
gcp = [
|
|
60
|
+
"google-cloud-monitoring>=2.15.0",
|
|
61
|
+
]
|
|
62
|
+
kubernetes = [
|
|
63
|
+
"kubernetes>=28.0.0",
|
|
64
|
+
]
|
|
65
|
+
all = [
|
|
66
|
+
"prescale-agent[prometheus,datadog,aws,azure,gcp,kubernetes]",
|
|
67
|
+
]
|
|
68
|
+
dev = [
|
|
69
|
+
"pytest>=7.0.0",
|
|
70
|
+
"pytest-asyncio>=0.21.0",
|
|
71
|
+
"ruff>=0.1.0",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[project.scripts]
|
|
75
|
+
prescale-agent = "prescale_agent.cli:main"
|
|
76
|
+
|
|
77
|
+
[project.urls]
|
|
78
|
+
Homepage = "https://github.com/pyjeebz/prescale"
|
|
79
|
+
Repository = "https://github.com/pyjeebz/prescale"
|
|
80
|
+
|
|
81
|
+
[tool.setuptools.packages.find]
|
|
82
|
+
where = ["src"]
|
|
83
|
+
|
|
84
|
+
[tool.setuptools.package-dir]
|
|
85
|
+
"" = "src"
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Prescale Agent - Main agent runner with unified metrics sources."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import signal
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from .client import PrescaleClient
|
|
10
|
+
from .sources import MetricsSource, MetricSample, SourceRegistry
|
|
11
|
+
from .config import AgentConfig
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Agent:
|
|
17
|
+
"""
|
|
18
|
+
Main Prescale metrics collection agent.
|
|
19
|
+
|
|
20
|
+
Uses a unified source interface to collect metrics from any backend:
|
|
21
|
+
- system: Local host metrics via psutil
|
|
22
|
+
- prometheus: Prometheus server
|
|
23
|
+
- datadog: Datadog API
|
|
24
|
+
- cloudwatch: AWS CloudWatch
|
|
25
|
+
- azure_monitor: Azure Monitor
|
|
26
|
+
- gcp_monitoring: Google Cloud Monitoring
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, config: AgentConfig):
|
|
30
|
+
self.config = config
|
|
31
|
+
self.sources: list[MetricsSource] = []
|
|
32
|
+
self.client: Optional[PrescaleClient] = None
|
|
33
|
+
self._running = False
|
|
34
|
+
self._paused = False
|
|
35
|
+
self._interval_override: Optional[int] = None # Server-controlled interval
|
|
36
|
+
self._metrics_buffer: list[MetricSample] = []
|
|
37
|
+
self._last_flush = datetime.now(timezone.utc)
|
|
38
|
+
|
|
39
|
+
async def setup(self):
|
|
40
|
+
"""Initialize sources and client."""
|
|
41
|
+
# Setup Prescale client
|
|
42
|
+
self.client = PrescaleClient(
|
|
43
|
+
endpoint=self.config.endpoint.url,
|
|
44
|
+
api_key=self.config.endpoint.api_key,
|
|
45
|
+
timeout=self.config.endpoint.timeout,
|
|
46
|
+
retry_attempts=self.config.endpoint.retry_attempts,
|
|
47
|
+
retry_delay=self.config.endpoint.retry_delay,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Setup sources from config
|
|
51
|
+
for source_config in self.config.sources:
|
|
52
|
+
if not source_config.enabled:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
source = SourceRegistry.create(source_config)
|
|
56
|
+
if source is None:
|
|
57
|
+
logger.warning(f"Unknown source type: {source_config.type}")
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
# Initialize the source
|
|
61
|
+
try:
|
|
62
|
+
if await source.initialize():
|
|
63
|
+
self.sources.append(source)
|
|
64
|
+
logger.info(f"Initialized source: {source.name} (type: {source_config.type})")
|
|
65
|
+
else:
|
|
66
|
+
logger.warning(f"Failed to initialize source: {source.name}")
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.error(f"Error initializing source {source.name}: {e}")
|
|
69
|
+
|
|
70
|
+
logger.info(f"Agent initialized with {len(self.sources)} sources")
|
|
71
|
+
logger.info(f"Available source types: {SourceRegistry.list_types()}")
|
|
72
|
+
|
|
73
|
+
async def run(self):
|
|
74
|
+
"""Run the agent main loop."""
|
|
75
|
+
self._running = True
|
|
76
|
+
logger.info("Starting Prescale Agent...")
|
|
77
|
+
|
|
78
|
+
# Setup signal handlers
|
|
79
|
+
loop = asyncio.get_running_loop()
|
|
80
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
81
|
+
try:
|
|
82
|
+
loop.add_signal_handler(sig, lambda: asyncio.create_task(self.stop()))
|
|
83
|
+
except NotImplementedError:
|
|
84
|
+
# Windows doesn't support add_signal_handler
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# Check Prescale API health
|
|
88
|
+
if self.client:
|
|
89
|
+
healthy = await self.client.check_health()
|
|
90
|
+
if healthy:
|
|
91
|
+
logger.info(f"Connected to Prescale at {self.config.endpoint.url}")
|
|
92
|
+
else:
|
|
93
|
+
logger.warning(f"Could not connect to Prescale at {self.config.endpoint.url}")
|
|
94
|
+
|
|
95
|
+
# Start source collection tasks
|
|
96
|
+
tasks = []
|
|
97
|
+
for source in self.sources:
|
|
98
|
+
if source.is_enabled():
|
|
99
|
+
task = asyncio.create_task(self._run_source(source))
|
|
100
|
+
tasks.append(task)
|
|
101
|
+
|
|
102
|
+
# Start flush task
|
|
103
|
+
flush_task = asyncio.create_task(self._flush_loop())
|
|
104
|
+
tasks.append(flush_task)
|
|
105
|
+
|
|
106
|
+
# Wait for all tasks
|
|
107
|
+
try:
|
|
108
|
+
await asyncio.gather(*tasks)
|
|
109
|
+
except asyncio.CancelledError:
|
|
110
|
+
logger.info("Agent tasks cancelled")
|
|
111
|
+
|
|
112
|
+
async def stop(self):
|
|
113
|
+
"""Stop the agent gracefully."""
|
|
114
|
+
logger.info("Stopping Prescale Agent...")
|
|
115
|
+
self._running = False
|
|
116
|
+
|
|
117
|
+
# Flush remaining metrics
|
|
118
|
+
await self._flush_metrics()
|
|
119
|
+
|
|
120
|
+
# Close sources
|
|
121
|
+
for source in self.sources:
|
|
122
|
+
try:
|
|
123
|
+
await source.close()
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.warning(f"Error closing source {source.name}: {e}")
|
|
126
|
+
|
|
127
|
+
# Close client
|
|
128
|
+
if self.client:
|
|
129
|
+
await self.client.close()
|
|
130
|
+
|
|
131
|
+
async def _run_source(self, source: MetricsSource):
|
|
132
|
+
"""Run a source collection loop."""
|
|
133
|
+
while self._running:
|
|
134
|
+
# Check if paused
|
|
135
|
+
if self._paused:
|
|
136
|
+
logger.debug(f"Agent paused, skipping collection from {source.name}")
|
|
137
|
+
await asyncio.sleep(5) # Check pause state every 5s
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
result = await source.collect()
|
|
142
|
+
|
|
143
|
+
if result.success:
|
|
144
|
+
# Convert source metrics to buffer format
|
|
145
|
+
self._metrics_buffer.extend(result.metrics)
|
|
146
|
+
logger.debug(
|
|
147
|
+
f"Collected {len(result.metrics)} metrics from {source.name} "
|
|
148
|
+
f"in {result.duration_ms:.1f}ms"
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
logger.warning(f"Source {source.name} error: {result.error}")
|
|
152
|
+
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.error(f"Error running source {source.name}: {e}")
|
|
155
|
+
|
|
156
|
+
# Use server-controlled interval if available, else source default
|
|
157
|
+
interval = self._interval_override or source.config.interval
|
|
158
|
+
await asyncio.sleep(interval)
|
|
159
|
+
|
|
160
|
+
async def _flush_loop(self):
|
|
161
|
+
"""Periodically flush metrics to Prescale."""
|
|
162
|
+
while self._running:
|
|
163
|
+
await asyncio.sleep(self.config.flush_interval)
|
|
164
|
+
await self._flush_metrics()
|
|
165
|
+
|
|
166
|
+
async def _flush_metrics(self):
|
|
167
|
+
"""Flush buffered metrics to Prescale."""
|
|
168
|
+
if not self._metrics_buffer:
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
if not self.client:
|
|
172
|
+
logger.warning("No Prescale client configured, discarding metrics")
|
|
173
|
+
self._metrics_buffer.clear()
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
# Get metrics to send
|
|
177
|
+
metrics_to_send = self._metrics_buffer[:self.config.batch_size]
|
|
178
|
+
self._metrics_buffer = self._metrics_buffer[self.config.batch_size:]
|
|
179
|
+
|
|
180
|
+
# Send metrics
|
|
181
|
+
result = await self.client.send_metrics(metrics_to_send)
|
|
182
|
+
|
|
183
|
+
if result is not None:
|
|
184
|
+
logger.info(f"Sent {len(metrics_to_send)} metrics to Prescale")
|
|
185
|
+
|
|
186
|
+
# Apply control commands from server
|
|
187
|
+
commands = result.get("commands")
|
|
188
|
+
if commands:
|
|
189
|
+
self._apply_commands(commands)
|
|
190
|
+
else:
|
|
191
|
+
# Re-add failed metrics to buffer (at the front)
|
|
192
|
+
self._metrics_buffer = metrics_to_send + self._metrics_buffer
|
|
193
|
+
# Trim buffer if too large
|
|
194
|
+
max_buffer = self.config.batch_size * 10
|
|
195
|
+
if len(self._metrics_buffer) > max_buffer:
|
|
196
|
+
dropped = len(self._metrics_buffer) - max_buffer
|
|
197
|
+
self._metrics_buffer = self._metrics_buffer[:max_buffer]
|
|
198
|
+
logger.warning(f"Buffer full, dropped {dropped} oldest metrics")
|
|
199
|
+
|
|
200
|
+
def _apply_commands(self, commands: dict):
|
|
201
|
+
"""Apply control commands received from the server."""
|
|
202
|
+
if "paused" in commands:
|
|
203
|
+
new_paused = bool(commands["paused"])
|
|
204
|
+
if new_paused != self._paused:
|
|
205
|
+
self._paused = new_paused
|
|
206
|
+
state = "PAUSED" if new_paused else "RESUMED"
|
|
207
|
+
logger.info(f"Agent {state} by server command")
|
|
208
|
+
|
|
209
|
+
if "collection_interval" in commands:
|
|
210
|
+
new_interval = int(commands["collection_interval"])
|
|
211
|
+
if new_interval != self._interval_override:
|
|
212
|
+
old = self._interval_override or "default"
|
|
213
|
+
self._interval_override = new_interval
|
|
214
|
+
logger.info(f"Collection interval changed: {old} -> {new_interval}s")
|
|
215
|
+
|
|
216
|
+
async def collect_once(self) -> list[MetricSample]:
|
|
217
|
+
"""Run all sources once and return metrics."""
|
|
218
|
+
all_metrics = []
|
|
219
|
+
|
|
220
|
+
for source in self.sources:
|
|
221
|
+
if source.is_enabled():
|
|
222
|
+
result = await source.collect()
|
|
223
|
+
if result.success:
|
|
224
|
+
all_metrics.extend(result.metrics)
|
|
225
|
+
|
|
226
|
+
return all_metrics
|
|
227
|
+
|
|
228
|
+
async def health_check(self) -> dict:
|
|
229
|
+
"""Check health of all sources and client."""
|
|
230
|
+
status = {
|
|
231
|
+
"sources": {},
|
|
232
|
+
"client": False,
|
|
233
|
+
"metrics_buffered": len(self._metrics_buffer),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for source in self.sources:
|
|
237
|
+
try:
|
|
238
|
+
status["sources"][source.name] = await source.health_check()
|
|
239
|
+
except Exception:
|
|
240
|
+
status["sources"][source.name] = False
|
|
241
|
+
|
|
242
|
+
if self.client:
|
|
243
|
+
status["client"] = await self.client.check_health()
|
|
244
|
+
|
|
245
|
+
return status
|