serial-alice-agent 1.0.1__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.
- serial_alice_agent-1.0.1/PKG-INFO +94 -0
- serial_alice_agent-1.0.1/README.md +54 -0
- serial_alice_agent-1.0.1/agent/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/adapters/__init__.py +161 -0
- serial_alice_agent-1.0.1/agent/adapters/adapters_init.py +141 -0
- serial_alice_agent-1.0.1/agent/adapters/base.py +244 -0
- serial_alice_agent-1.0.1/agent/adapters/cpu.py +280 -0
- serial_alice_agent-1.0.1/agent/adapters/gpu.py +307 -0
- serial_alice_agent-1.0.1/agent/adapters/ipmi_adapter.py +150 -0
- serial_alice_agent-1.0.1/agent/adapters/machine.py +416 -0
- serial_alice_agent-1.0.1/agent/adapters/pdu_snmp_adapter.py +159 -0
- serial_alice_agent-1.0.1/agent/adapters/signed_exporter.py +361 -0
- serial_alice_agent-1.0.1/agent/adapters/system.py +275 -0
- serial_alice_agent-1.0.1/agent/adapters/windows_rapl.py +421 -0
- serial_alice_agent-1.0.1/agent/agent.py +750 -0
- serial_alice_agent-1.0.1/agent/anomaly/__init__.py +26 -0
- serial_alice_agent-1.0.1/agent/anomaly/detector.py +222 -0
- serial_alice_agent-1.0.1/agent/anomaly/monitor.py +179 -0
- serial_alice_agent-1.0.1/agent/anomaly/power_source.py +166 -0
- serial_alice_agent-1.0.1/agent/attestation/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/attestation/nvidia_cc.py +407 -0
- serial_alice_agent-1.0.1/agent/attestation/payload_builder.py +646 -0
- serial_alice_agent-1.0.1/agent/attestation/tee_quote.py +147 -0
- serial_alice_agent-1.0.1/agent/attestation/tpm.py +377 -0
- serial_alice_agent-1.0.1/agent/attribution/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/attribution/workload.py +355 -0
- serial_alice_agent-1.0.1/agent/cli.py +511 -0
- serial_alice_agent-1.0.1/agent/client/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/client/serial_alice_api.py +284 -0
- serial_alice_agent-1.0.1/agent/config.py +332 -0
- serial_alice_agent-1.0.1/agent/energy/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/hardware/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/hardware/cpu_discovery.py +241 -0
- serial_alice_agent-1.0.1/agent/machine/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/machine/agent_identity.py +196 -0
- serial_alice_agent-1.0.1/agent/machine/fingerprint_v2.py +316 -0
- serial_alice_agent-1.0.1/agent/machine/release_signing.py +252 -0
- serial_alice_agent-1.0.1/agent/optimization/__init__.py +50 -0
- serial_alice_agent-1.0.1/agent/optimization/collector.py +328 -0
- serial_alice_agent-1.0.1/agent/optimization/metrics.py +184 -0
- serial_alice_agent-1.0.1/agent/optimization/models.py +268 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/__init__.py +43 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/classifier.py +279 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/cooldown.py +101 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/db_models.py +185 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/engine.py +386 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/explainability.py +248 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/guardrails.py +146 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/hardware_profile.py +207 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/hysteresis.py +87 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/models.py +315 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/policy.py +149 -0
- serial_alice_agent-1.0.1/agent/optimization/phase1/repository.py +272 -0
- serial_alice_agent-1.0.1/agent/optimization/phase2/__init__.py +41 -0
- serial_alice_agent-1.0.1/agent/optimization/phase2/anomaly_detector.py +325 -0
- serial_alice_agent-1.0.1/agent/optimization/phase2/canary.py +253 -0
- serial_alice_agent-1.0.1/agent/optimization/phase2/confidence_calibrator.py +177 -0
- serial_alice_agent-1.0.1/agent/optimization/phase2/engine.py +329 -0
- serial_alice_agent-1.0.1/agent/optimization/phase2/kpi.py +218 -0
- serial_alice_agent-1.0.1/agent/optimization/phase2/models.py +392 -0
- serial_alice_agent-1.0.1/agent/optimization/phase2/policy_version_manager.py +189 -0
- serial_alice_agent-1.0.1/agent/optimization/phase2/scorer.py +274 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/__init__.py +53 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/blast_radius.py +166 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/db_models.py +288 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/digital_twin.py +273 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/engine.py +232 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/explainability.py +344 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/hardware_benchmark.py +218 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/idle_node_detector.py +152 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/models.py +560 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/node_efficiency_scorer.py +198 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/rack_hotspot_detector.py +164 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/repository.py +357 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/scheduler_adapter.py +365 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/thermal_state_machine.py +223 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/twin_calibration.py +222 -0
- serial_alice_agent-1.0.1/agent/optimization/phase3/workload_consolidator.py +247 -0
- serial_alice_agent-1.0.1/agent/optimization/phase4/__init__.py +39 -0
- serial_alice_agent-1.0.1/agent/optimization/phase4/batch_shift_recommender.py +192 -0
- serial_alice_agent-1.0.1/agent/optimization/phase4/carbon_client.py +200 -0
- serial_alice_agent-1.0.1/agent/optimization/phase4/contextual_bandit.py +393 -0
- serial_alice_agent-1.0.1/agent/optimization/phase4/engine.py +317 -0
- serial_alice_agent-1.0.1/agent/optimization/phase4/governance.py +426 -0
- serial_alice_agent-1.0.1/agent/optimization/phase4/kill_switch.py +198 -0
- serial_alice_agent-1.0.1/agent/optimization/phase4/models.py +537 -0
- serial_alice_agent-1.0.1/agent/optimization/power_optimizer.py +221 -0
- serial_alice_agent-1.0.1/agent/optimization/safety.py +237 -0
- serial_alice_agent-1.0.1/agent/optimization/session_manager.py +397 -0
- serial_alice_agent-1.0.1/agent/rapl_signer/__init__.py +38 -0
- serial_alice_agent-1.0.1/agent/rapl_signer/cli.py +106 -0
- serial_alice_agent-1.0.1/agent/rapl_signer/proxy.py +115 -0
- serial_alice_agent-1.0.1/agent/rapl_signer/signer.py +132 -0
- serial_alice_agent-1.0.1/agent/reconciliation/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/reconciliation/engine.py +348 -0
- serial_alice_agent-1.0.1/agent/router_training/train_alice_router.py +362 -0
- serial_alice_agent-1.0.1/agent/runtime/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/runtime/job_detector.py +614 -0
- serial_alice_agent-1.0.1/agent/serial-alice-agent.service +46 -0
- serial_alice_agent-1.0.1/agent/session_runner.py +363 -0
- serial_alice_agent-1.0.1/agent/telemetry/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/__main__.py +197 -0
- serial_alice_agent-1.0.1/agent/telemetry/adapters/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/adapters/sources.py +551 -0
- serial_alice_agent-1.0.1/agent/telemetry/attribution/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/attribution/engine.py +261 -0
- serial_alice_agent-1.0.1/agent/telemetry/buffering/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/buffering/buffer.py +249 -0
- serial_alice_agent-1.0.1/agent/telemetry/capture/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/capture/collector.py +175 -0
- serial_alice_agent-1.0.1/agent/telemetry/config/manager.py +217 -0
- serial_alice_agent-1.0.1/agent/telemetry/core/contract.py +206 -0
- serial_alice_agent-1.0.1/agent/telemetry/cpu_telemetry.py +275 -0
- serial_alice_agent-1.0.1/agent/telemetry/errors/taxonomy.py +189 -0
- serial_alice_agent-1.0.1/agent/telemetry/gpu_telemetry.py +341 -0
- serial_alice_agent-1.0.1/agent/telemetry/health/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/health/monitor.py +239 -0
- serial_alice_agent-1.0.1/agent/telemetry/integrity/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/integrity/identity.py +225 -0
- serial_alice_agent-1.0.1/agent/telemetry/models/event.py +366 -0
- serial_alice_agent-1.0.1/agent/telemetry/normalization/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/normalization/normalizer.py +242 -0
- serial_alice_agent-1.0.1/agent/telemetry/pipeline.py +353 -0
- serial_alice_agent-1.0.1/agent/telemetry/pipeline_v2.py +381 -0
- serial_alice_agent-1.0.1/agent/telemetry/quality/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/quality/engine.py +211 -0
- serial_alice_agent-1.0.1/agent/telemetry/quality/scoring.py +433 -0
- serial_alice_agent-1.0.1/agent/telemetry/reconciliation/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/reconciliation/engine.py +392 -0
- serial_alice_agent-1.0.1/agent/telemetry/security/hardening.py +120 -0
- serial_alice_agent-1.0.1/agent/telemetry/signing/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/signing/signer.py +187 -0
- serial_alice_agent-1.0.1/agent/telemetry/temporal/integrity.py +280 -0
- serial_alice_agent-1.0.1/agent/telemetry/tests_pipeline.py +520 -0
- serial_alice_agent-1.0.1/agent/telemetry/transport/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/telemetry/transport/http.py +201 -0
- serial_alice_agent-1.0.1/agent/tests/__init__.py +0 -0
- serial_alice_agent-1.0.1/agent/tests/test_agent.py +486 -0
- serial_alice_agent-1.0.1/agent/tests/test_anomaly.py +181 -0
- serial_alice_agent-1.0.1/agent/tests/test_machine_identity.py +332 -0
- serial_alice_agent-1.0.1/agent/tests/test_rapl_signer.py +277 -0
- serial_alice_agent-1.0.1/agent/tests/test_standalone_no_app.py +109 -0
- serial_alice_agent-1.0.1/agent/verification/__init__.py +1 -0
- serial_alice_agent-1.0.1/agent/verification/offline_verifier.py +545 -0
- serial_alice_agent-1.0.1/agent/verification/sth_checker.py +307 -0
- serial_alice_agent-1.0.1/agent/windows_service.py +159 -0
- serial_alice_agent-1.0.1/pyproject.toml +80 -0
- serial_alice_agent-1.0.1/serial_alice_agent.egg-info/PKG-INFO +94 -0
- serial_alice_agent-1.0.1/serial_alice_agent.egg-info/SOURCES.txt +152 -0
- serial_alice_agent-1.0.1/serial_alice_agent.egg-info/dependency_links.txt +1 -0
- serial_alice_agent-1.0.1/serial_alice_agent.egg-info/entry_points.txt +2 -0
- serial_alice_agent-1.0.1/serial_alice_agent.egg-info/requires.txt +20 -0
- serial_alice_agent-1.0.1/serial_alice_agent.egg-info/top_level.txt +1 -0
- serial_alice_agent-1.0.1/setup.cfg +4 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: serial-alice-agent
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Serial Alice Agent — Verifiable energy and compute attestation
|
|
5
|
+
Author-email: Sirius GreenTech <contact@serialalice.pt>
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Homepage, https://serialalice.pt
|
|
8
|
+
Project-URL: Documentation, https://api.serialalice.pt/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/SerialAlicev3/serial-alice
|
|
10
|
+
Keywords: energy,compute,attestation,cryptography,gpu
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Operating System :: MacOS
|
|
19
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
20
|
+
Classifier: Topic :: System :: Monitoring
|
|
21
|
+
Classifier: Topic :: Security :: Cryptography
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: requests>=2.31
|
|
25
|
+
Requires-Dist: pyyaml>=6.0
|
|
26
|
+
Requires-Dist: psutil>=5.9
|
|
27
|
+
Requires-Dist: cryptography>=41.0
|
|
28
|
+
Provides-Extra: gpu
|
|
29
|
+
Requires-Dist: pynvml>=11.5; extra == "gpu"
|
|
30
|
+
Provides-Extra: windows
|
|
31
|
+
Requires-Dist: pywin32>=306; extra == "windows"
|
|
32
|
+
Requires-Dist: wmi>=1.5.1; extra == "windows"
|
|
33
|
+
Provides-Extra: full
|
|
34
|
+
Requires-Dist: serial-alice-agent[gpu,windows]; extra == "full"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
38
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
39
|
+
Requires-Dist: mypy>=1.5; extra == "dev"
|
|
40
|
+
|
|
41
|
+
# Serial Alice Agent
|
|
42
|
+
|
|
43
|
+
Verifiable energy and compute attestation agent for AI workloads.
|
|
44
|
+
Issues Ed25519-signed certificates of energy consumption, anchored on Polygon.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# From the self-hosted zip (no PyPI, no GitHub auth required):
|
|
50
|
+
pip install https://api.serialalice.pt/agent/download/linux
|
|
51
|
+
|
|
52
|
+
# Or download first:
|
|
53
|
+
curl -L https://api.serialalice.pt/agent/download/linux -o serial-alice-agent.zip
|
|
54
|
+
pip install serial-alice-agent.zip
|
|
55
|
+
|
|
56
|
+
# With NVIDIA GPU telemetry:
|
|
57
|
+
pip install "serial-alice-agent.zip[gpu]"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
export SA_AGENT_API_KEY=sa_live_... # from https://app.serialalice.pt
|
|
64
|
+
|
|
65
|
+
serial-alice-agent status # verify API connectivity
|
|
66
|
+
serial-alice-agent discover # list detected hardware
|
|
67
|
+
serial-alice-agent run --job-name my-training -- python train.py
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Run without installing
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
unzip serial-alice-agent.zip
|
|
74
|
+
cd serial-alice-agent
|
|
75
|
+
pip install requests pyyaml psutil cryptography # core deps
|
|
76
|
+
python -m agent.cli status
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
# agent.yaml
|
|
83
|
+
api_url: https://api.serialalice.pt
|
|
84
|
+
api_key: ${SA_AGENT_API_KEY}
|
|
85
|
+
measurement_interval_s: 10
|
|
86
|
+
hardware:
|
|
87
|
+
gpu: auto
|
|
88
|
+
cpu: true
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Links
|
|
92
|
+
|
|
93
|
+
- Documentation: https://api.serialalice.pt/docs
|
|
94
|
+
- Platform: https://serialalice.pt
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Serial Alice Agent
|
|
2
|
+
|
|
3
|
+
Verifiable energy and compute attestation agent for AI workloads.
|
|
4
|
+
Issues Ed25519-signed certificates of energy consumption, anchored on Polygon.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
# From the self-hosted zip (no PyPI, no GitHub auth required):
|
|
10
|
+
pip install https://api.serialalice.pt/agent/download/linux
|
|
11
|
+
|
|
12
|
+
# Or download first:
|
|
13
|
+
curl -L https://api.serialalice.pt/agent/download/linux -o serial-alice-agent.zip
|
|
14
|
+
pip install serial-alice-agent.zip
|
|
15
|
+
|
|
16
|
+
# With NVIDIA GPU telemetry:
|
|
17
|
+
pip install "serial-alice-agent.zip[gpu]"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
export SA_AGENT_API_KEY=sa_live_... # from https://app.serialalice.pt
|
|
24
|
+
|
|
25
|
+
serial-alice-agent status # verify API connectivity
|
|
26
|
+
serial-alice-agent discover # list detected hardware
|
|
27
|
+
serial-alice-agent run --job-name my-training -- python train.py
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Run without installing
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
unzip serial-alice-agent.zip
|
|
34
|
+
cd serial-alice-agent
|
|
35
|
+
pip install requests pyyaml psutil cryptography # core deps
|
|
36
|
+
python -m agent.cli status
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Configuration
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
# agent.yaml
|
|
43
|
+
api_url: https://api.serialalice.pt
|
|
44
|
+
api_key: ${SA_AGENT_API_KEY}
|
|
45
|
+
measurement_interval_s: 10
|
|
46
|
+
hardware:
|
|
47
|
+
gpu: auto
|
|
48
|
+
cpu: true
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Links
|
|
52
|
+
|
|
53
|
+
- Documentation: https://api.serialalice.pt/docs
|
|
54
|
+
- Platform: https://serialalice.pt
|
|
File without changes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Serial Alice — Adapter Registry v3
|
|
3
|
+
====================================
|
|
4
|
+
Registry de todos os adapters de medição multi-componente.
|
|
5
|
+
|
|
6
|
+
Uso:
|
|
7
|
+
from agent.adapters import collect_all_measurements, AdapterRegistry
|
|
8
|
+
|
|
9
|
+
registry = AdapterRegistry()
|
|
10
|
+
measurements = registry.collect(started_at, ended_at)
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from typing import List
|
|
17
|
+
|
|
18
|
+
from .base import Measurement, EnergyAdapter
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger("sa.adapters")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AdapterRegistry:
|
|
24
|
+
"""
|
|
25
|
+
Registo e orquestrador de todos os adapters activos.
|
|
26
|
+
|
|
27
|
+
Inicializa os adapters disponíveis e recolhe medições de todos em paralelo.
|
|
28
|
+
Adapters indisponíveis são ignorados silenciosamente.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
enable_gpu: bool = True,
|
|
34
|
+
enable_cpu: bool = True,
|
|
35
|
+
enable_ram: bool = True,
|
|
36
|
+
enable_disk: bool = True,
|
|
37
|
+
enable_network: bool = True,
|
|
38
|
+
enable_machine: bool = True,
|
|
39
|
+
):
|
|
40
|
+
self._adapters: list[EnergyAdapter] = []
|
|
41
|
+
self._init_adapters(enable_gpu, enable_cpu, enable_ram,
|
|
42
|
+
enable_disk, enable_network, enable_machine)
|
|
43
|
+
|
|
44
|
+
def _init_adapters(self, gpu, cpu, ram, disk, net, machine):
|
|
45
|
+
if gpu:
|
|
46
|
+
try:
|
|
47
|
+
from .gpu import NvmlGpuAdapter, DcgmGpuAdapter
|
|
48
|
+
nvml = NvmlGpuAdapter()
|
|
49
|
+
if nvml.is_available():
|
|
50
|
+
self._adapters.append(nvml)
|
|
51
|
+
logger.info("Adapter activo: nvml_gpu")
|
|
52
|
+
else:
|
|
53
|
+
dcgm = DcgmGpuAdapter()
|
|
54
|
+
if dcgm.is_available():
|
|
55
|
+
self._adapters.append(dcgm)
|
|
56
|
+
logger.info("Adapter activo: dcgm_gpu")
|
|
57
|
+
else:
|
|
58
|
+
# Tenta Windows RAPL para iGPU Intel
|
|
59
|
+
try:
|
|
60
|
+
from .windows_rapl import WindowsRaplAdapter
|
|
61
|
+
win_rapl = WindowsRaplAdapter()
|
|
62
|
+
if win_rapl.is_available():
|
|
63
|
+
self._adapters.append(win_rapl)
|
|
64
|
+
logger.info("Adapter activo: windows_rapl (Intel iGPU + CPU via LHM)")
|
|
65
|
+
else:
|
|
66
|
+
logger.debug("GPU adapters indisponíveis (sem NVML, DCGM, ou LHM)")
|
|
67
|
+
except Exception as we:
|
|
68
|
+
logger.debug("WindowsRaplAdapter init: %s", we)
|
|
69
|
+
logger.debug("GPU adapters indisponíveis")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.warning("GPU adapter init: %s", e)
|
|
72
|
+
|
|
73
|
+
if cpu:
|
|
74
|
+
try:
|
|
75
|
+
from .cpu import get_best_cpu_adapter
|
|
76
|
+
a = get_best_cpu_adapter()
|
|
77
|
+
self._adapters.append(a)
|
|
78
|
+
logger.info("Adapter activo: %s", a.adapter_name)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.warning("CPU adapter init: %s", e)
|
|
81
|
+
|
|
82
|
+
if ram:
|
|
83
|
+
try:
|
|
84
|
+
from .system import RamAdapter
|
|
85
|
+
a = RamAdapter()
|
|
86
|
+
if a.is_available():
|
|
87
|
+
self._adapters.append(a)
|
|
88
|
+
logger.info("Adapter activo: ram_model")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.warning("RAM adapter init: %s", e)
|
|
91
|
+
|
|
92
|
+
if disk:
|
|
93
|
+
try:
|
|
94
|
+
from .system import DiskAdapter
|
|
95
|
+
a = DiskAdapter()
|
|
96
|
+
if a.is_available():
|
|
97
|
+
self._adapters.append(a)
|
|
98
|
+
logger.info("Adapter activo: disk_model")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.warning("Disk adapter init: %s", e)
|
|
101
|
+
|
|
102
|
+
if net:
|
|
103
|
+
try:
|
|
104
|
+
from .system import NetworkAdapter
|
|
105
|
+
a = NetworkAdapter()
|
|
106
|
+
if a.is_available():
|
|
107
|
+
self._adapters.append(a)
|
|
108
|
+
logger.info("Adapter activo: network_model")
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.warning("Network adapter init: %s", e)
|
|
111
|
+
|
|
112
|
+
if machine:
|
|
113
|
+
try:
|
|
114
|
+
from .machine import get_best_machine_adapter
|
|
115
|
+
a = get_best_machine_adapter()
|
|
116
|
+
if a:
|
|
117
|
+
self._adapters.append(a)
|
|
118
|
+
logger.info("Adapter activo: %s", a.adapter_name)
|
|
119
|
+
else:
|
|
120
|
+
logger.info("Machine adapter: nenhum disponível (sem BMC/PDU configurado)")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.warning("Machine adapter init: %s", e)
|
|
123
|
+
|
|
124
|
+
# IPMI/DCMI — bare-metal system-level power (ipmitool)
|
|
125
|
+
try:
|
|
126
|
+
from .ipmi_adapter import IpmiDcmiAdapter
|
|
127
|
+
ipmi = IpmiDcmiAdapter()
|
|
128
|
+
if ipmi.is_available():
|
|
129
|
+
self._adapters.append(ipmi)
|
|
130
|
+
logger.info("Adapter activo: ipmi_dcmi")
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.debug("IPMI adapter init: %s", e)
|
|
133
|
+
|
|
134
|
+
# PDU/SNMP — rack-level power via SNMP
|
|
135
|
+
try:
|
|
136
|
+
from .pdu_snmp_adapter import PduSnmpAdapter
|
|
137
|
+
pdu = PduSnmpAdapter()
|
|
138
|
+
if pdu.is_available():
|
|
139
|
+
self._adapters.append(pdu)
|
|
140
|
+
logger.info("Adapter activo: pdu_snmp")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.debug("PDU/SNMP adapter init: %s", e)
|
|
143
|
+
|
|
144
|
+
def collect(
|
|
145
|
+
self,
|
|
146
|
+
interval_start: datetime,
|
|
147
|
+
interval_end: datetime,
|
|
148
|
+
) -> List[Measurement]:
|
|
149
|
+
"""Recolhe medições de todos os adapters activos."""
|
|
150
|
+
all_measurements: List[Measurement] = []
|
|
151
|
+
for adapter in self._adapters:
|
|
152
|
+
try:
|
|
153
|
+
ms = adapter.collect(interval_start, interval_end)
|
|
154
|
+
all_measurements.extend(ms)
|
|
155
|
+
logger.debug("Adapter %s: %d measurements", adapter.adapter_name, len(ms))
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logger.error("Adapter %s.collect() error: %s", adapter.adapter_name, e)
|
|
158
|
+
return all_measurements
|
|
159
|
+
|
|
160
|
+
def active_adapters(self) -> list[str]:
|
|
161
|
+
return [a.adapter_name for a in self._adapters]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Serial Alice — Adapter Registry v3
|
|
3
|
+
====================================
|
|
4
|
+
Registry de todos os adapters de medição multi-componente.
|
|
5
|
+
|
|
6
|
+
Uso:
|
|
7
|
+
from agent.adapters import collect_all_measurements, AdapterRegistry
|
|
8
|
+
|
|
9
|
+
registry = AdapterRegistry()
|
|
10
|
+
measurements = registry.collect(started_at, ended_at)
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from typing import List
|
|
17
|
+
|
|
18
|
+
from .base import Measurement, EnergyAdapter
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger("sa.adapters")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AdapterRegistry:
|
|
24
|
+
"""
|
|
25
|
+
Registo e orquestrador de todos os adapters activos.
|
|
26
|
+
|
|
27
|
+
Inicializa os adapters disponíveis e recolhe medições de todos em paralelo.
|
|
28
|
+
Adapters indisponíveis são ignorados silenciosamente.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
enable_gpu: bool = True,
|
|
34
|
+
enable_cpu: bool = True,
|
|
35
|
+
enable_ram: bool = True,
|
|
36
|
+
enable_disk: bool = True,
|
|
37
|
+
enable_network: bool = True,
|
|
38
|
+
enable_machine: bool = True,
|
|
39
|
+
):
|
|
40
|
+
self._adapters: list[EnergyAdapter] = []
|
|
41
|
+
self._init_adapters(enable_gpu, enable_cpu, enable_ram,
|
|
42
|
+
enable_disk, enable_network, enable_machine)
|
|
43
|
+
|
|
44
|
+
def _init_adapters(self, gpu, cpu, ram, disk, net, machine):
|
|
45
|
+
if gpu:
|
|
46
|
+
try:
|
|
47
|
+
from .gpu import NvmlGpuAdapter, DcgmGpuAdapter
|
|
48
|
+
nvml = NvmlGpuAdapter()
|
|
49
|
+
if nvml.is_available():
|
|
50
|
+
self._adapters.append(nvml)
|
|
51
|
+
logger.info("Adapter activo: nvml_gpu")
|
|
52
|
+
else:
|
|
53
|
+
dcgm = DcgmGpuAdapter()
|
|
54
|
+
if dcgm.is_available():
|
|
55
|
+
self._adapters.append(dcgm)
|
|
56
|
+
logger.info("Adapter activo: dcgm_gpu")
|
|
57
|
+
else:
|
|
58
|
+
# Tenta Windows RAPL para iGPU Intel
|
|
59
|
+
try:
|
|
60
|
+
from .windows_rapl import WindowsRaplAdapter
|
|
61
|
+
win_rapl = WindowsRaplAdapter()
|
|
62
|
+
if win_rapl.is_available():
|
|
63
|
+
self._adapters.append(win_rapl)
|
|
64
|
+
logger.info("Adapter activo: windows_rapl (Intel iGPU + CPU via LHM)")
|
|
65
|
+
else:
|
|
66
|
+
logger.debug("GPU adapters indisponíveis (sem NVML, DCGM, ou LHM)")
|
|
67
|
+
except Exception as we:
|
|
68
|
+
logger.debug("WindowsRaplAdapter init: %s", we)
|
|
69
|
+
logger.debug("GPU adapters indisponíveis")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.warning("GPU adapter init: %s", e)
|
|
72
|
+
|
|
73
|
+
if cpu:
|
|
74
|
+
try:
|
|
75
|
+
from .cpu import get_best_cpu_adapter
|
|
76
|
+
a = get_best_cpu_adapter()
|
|
77
|
+
self._adapters.append(a)
|
|
78
|
+
logger.info("Adapter activo: %s", a.adapter_name)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.warning("CPU adapter init: %s", e)
|
|
81
|
+
|
|
82
|
+
if ram:
|
|
83
|
+
try:
|
|
84
|
+
from .system import RamAdapter
|
|
85
|
+
a = RamAdapter()
|
|
86
|
+
if a.is_available():
|
|
87
|
+
self._adapters.append(a)
|
|
88
|
+
logger.info("Adapter activo: ram_model")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.warning("RAM adapter init: %s", e)
|
|
91
|
+
|
|
92
|
+
if disk:
|
|
93
|
+
try:
|
|
94
|
+
from .system import DiskAdapter
|
|
95
|
+
a = DiskAdapter()
|
|
96
|
+
if a.is_available():
|
|
97
|
+
self._adapters.append(a)
|
|
98
|
+
logger.info("Adapter activo: disk_model")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.warning("Disk adapter init: %s", e)
|
|
101
|
+
|
|
102
|
+
if net:
|
|
103
|
+
try:
|
|
104
|
+
from .system import NetworkAdapter
|
|
105
|
+
a = NetworkAdapter()
|
|
106
|
+
if a.is_available():
|
|
107
|
+
self._adapters.append(a)
|
|
108
|
+
logger.info("Adapter activo: network_model")
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.warning("Network adapter init: %s", e)
|
|
111
|
+
|
|
112
|
+
if machine:
|
|
113
|
+
try:
|
|
114
|
+
from .machine import get_best_machine_adapter
|
|
115
|
+
a = get_best_machine_adapter()
|
|
116
|
+
if a:
|
|
117
|
+
self._adapters.append(a)
|
|
118
|
+
logger.info("Adapter activo: %s", a.adapter_name)
|
|
119
|
+
else:
|
|
120
|
+
logger.info("Machine adapter: nenhum disponível (sem BMC/PDU configurado)")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.warning("Machine adapter init: %s", e)
|
|
123
|
+
|
|
124
|
+
def collect(
|
|
125
|
+
self,
|
|
126
|
+
interval_start: datetime,
|
|
127
|
+
interval_end: datetime,
|
|
128
|
+
) -> List[Measurement]:
|
|
129
|
+
"""Recolhe medições de todos os adapters activos."""
|
|
130
|
+
all_measurements: List[Measurement] = []
|
|
131
|
+
for adapter in self._adapters:
|
|
132
|
+
try:
|
|
133
|
+
ms = adapter.collect(interval_start, interval_end)
|
|
134
|
+
all_measurements.extend(ms)
|
|
135
|
+
logger.debug("Adapter %s: %d measurements", adapter.adapter_name, len(ms))
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error("Adapter %s.collect() error: %s", adapter.adapter_name, e)
|
|
138
|
+
return all_measurements
|
|
139
|
+
|
|
140
|
+
def active_adapters(self) -> list[str]:
|
|
141
|
+
return [a.adapter_name for a in self._adapters]
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Serial Alice — EnergyAdapter base interface e schema Measurement
|
|
3
|
+
===============================================================
|
|
4
|
+
Arquitectura plugin-based para medição multi-componente.
|
|
5
|
+
|
|
6
|
+
Todos os adapters implementam EnergyAdapter.collect() e retornam
|
|
7
|
+
List[Measurement] com schema canónico definido aqui.
|
|
8
|
+
|
|
9
|
+
Unidade interna: Joules (J).
|
|
10
|
+
Todos os adapters convertem para J internamente.
|
|
11
|
+
Helpers wh_to_j() e j_to_wh() estão disponíveis.
|
|
12
|
+
|
|
13
|
+
Regras:
|
|
14
|
+
- timestamps em UTC, ISO 8601
|
|
15
|
+
- granularidade ≤ 5s (recomendado: 1s)
|
|
16
|
+
- provenance explícito: "measured" | "estimated"
|
|
17
|
+
- confidence: 0.0–1.0 numérico
|
|
18
|
+
- source identificado com versão de adapter
|
|
19
|
+
|
|
20
|
+
Versão do schema: measurement-v1
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import hashlib
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import time
|
|
28
|
+
from dataclasses import dataclass, field, asdict
|
|
29
|
+
from datetime import datetime, timezone
|
|
30
|
+
from enum import Enum
|
|
31
|
+
from typing import List, Optional
|
|
32
|
+
|
|
33
|
+
SCHEMA_VERSION = "measurement-v1"
|
|
34
|
+
|
|
35
|
+
# ── Enumerações canónicas ─────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
class Component(str, Enum):
|
|
38
|
+
GPU = "gpu"
|
|
39
|
+
CPU = "cpu"
|
|
40
|
+
RAM = "ram"
|
|
41
|
+
DISK = "disk"
|
|
42
|
+
NETWORK = "network"
|
|
43
|
+
BOARD = "board" # motherboard / chipset
|
|
44
|
+
PSU = "psu" # power supply losses
|
|
45
|
+
FANS = "fans"
|
|
46
|
+
TOTAL = "total" # máquina inteira (BMC/PDU)
|
|
47
|
+
|
|
48
|
+
class Scope(str, Enum):
|
|
49
|
+
DEVICE = "device" # leitura de um dispositivo específico
|
|
50
|
+
HOST = "host" # leitura de toda a máquina
|
|
51
|
+
RACK = "rack" # leitura do rack (PDU)
|
|
52
|
+
|
|
53
|
+
class MetricType(str, Enum):
|
|
54
|
+
POWER = "power" # W instantâneo
|
|
55
|
+
ENERGY = "energy" # J ou Wh acumulado
|
|
56
|
+
UTILIZATION = "utilization" # %
|
|
57
|
+
|
|
58
|
+
class Unit(str, Enum):
|
|
59
|
+
WATTS = "W"
|
|
60
|
+
JOULES = "J"
|
|
61
|
+
WH = "Wh"
|
|
62
|
+
PERCENT = "%"
|
|
63
|
+
BYTES = "B"
|
|
64
|
+
BYTES_SEC = "B/s"
|
|
65
|
+
|
|
66
|
+
class Provenance(str, Enum):
|
|
67
|
+
MEASURED = "measured" # leitura de sensor real
|
|
68
|
+
ESTIMATED = "estimated" # modelo/heurística
|
|
69
|
+
|
|
70
|
+
class MeasurementSource(str, Enum):
|
|
71
|
+
NVML = "nvml" # NVIDIA Management Library
|
|
72
|
+
DCGM = "dcgm" # NVIDIA DCGM exporter
|
|
73
|
+
RAPL = "rapl" # Intel RAPL (Running Average Power Limit)
|
|
74
|
+
AMD_SMI = "amd_smi" # AMD ROCm SMI
|
|
75
|
+
REDFISH = "redfish" # Redfish / IPMI BMC (REST interface)
|
|
76
|
+
IPMI = "ipmi" # IPMI/DCMI via ipmitool (binary protocol)
|
|
77
|
+
SNMP = "snmp" # PDU via SNMP
|
|
78
|
+
PDU_REST = "pdu_rest" # PDU via REST
|
|
79
|
+
PSUTIL = "psutil" # psutil (estimativa CPU)
|
|
80
|
+
MODEL = "model" # modelo estático (TDP, bandwidth)
|
|
81
|
+
MANUAL = "manual" # declarado pelo operador
|
|
82
|
+
SIMULATION= "simulation"# modo dev/teste
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ── Schema canónico ───────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class Measurement:
|
|
89
|
+
"""
|
|
90
|
+
Leitura de energia canónica — única estrutura que atravessa todo o sistema.
|
|
91
|
+
|
|
92
|
+
Unidade interna: Joules (J) para energy, Watts (W) para power.
|
|
93
|
+
Adapters que lêem noutras unidades convertem antes de retornar.
|
|
94
|
+
|
|
95
|
+
Fields obrigatórios: timestamp, host_id, component, scope,
|
|
96
|
+
metric_type, value, unit, provenance, source
|
|
97
|
+
Fields opcionais: device_id, confidence, adapter_version,
|
|
98
|
+
context (metadados extra do adapter)
|
|
99
|
+
"""
|
|
100
|
+
# Identificação
|
|
101
|
+
timestamp: str # ISO 8601 UTC do momento da leitura
|
|
102
|
+
host_id: str # identificador da máquina (hash ou hostname)
|
|
103
|
+
component: Component # gpu | cpu | ram | disk | network | board | psu | total
|
|
104
|
+
scope: Scope # device | host | rack
|
|
105
|
+
metric_type: MetricType # power | energy | utilization
|
|
106
|
+
value: float # valor numérico
|
|
107
|
+
unit: Unit # W | J | Wh | % | B | B/s
|
|
108
|
+
provenance: Provenance # measured | estimated
|
|
109
|
+
source: MeasurementSource # nvml | rapl | redfish | snmp | ...
|
|
110
|
+
|
|
111
|
+
# Identificação de dispositivo
|
|
112
|
+
device_id: str | None = None # UUID, PCI slot, ou índice
|
|
113
|
+
device_model: str | None = None # modelo do hardware (ex: "A100-SXM4-80GB")
|
|
114
|
+
|
|
115
|
+
# Qualidade
|
|
116
|
+
confidence: float = 1.0 # 0.0–1.0 (1.0 = máxima confiança)
|
|
117
|
+
uncertainty_pct: float = 5.0 # incerteza estimada em %
|
|
118
|
+
|
|
119
|
+
# Versionamento do adapter
|
|
120
|
+
adapter_version: str = "1.0"
|
|
121
|
+
|
|
122
|
+
# Metadados extra (específicos do adapter)
|
|
123
|
+
context: dict = field(default_factory=dict)
|
|
124
|
+
|
|
125
|
+
# Schema version para auditabilidade
|
|
126
|
+
schema_version: str = SCHEMA_VERSION
|
|
127
|
+
|
|
128
|
+
def to_dict(self) -> dict:
|
|
129
|
+
d = asdict(self)
|
|
130
|
+
# Converter enums para strings para serialização
|
|
131
|
+
d["component"] = self.component.value if isinstance(self.component, Component) else self.component
|
|
132
|
+
d["scope"] = self.scope.value if isinstance(self.scope, Scope) else self.scope
|
|
133
|
+
d["metric_type"] = self.metric_type.value if isinstance(self.metric_type, MetricType) else self.metric_type
|
|
134
|
+
d["unit"] = self.unit.value if isinstance(self.unit, Unit) else self.unit
|
|
135
|
+
d["provenance"] = self.provenance.value if isinstance(self.provenance, Provenance) else self.provenance
|
|
136
|
+
d["source"] = self.source.value if isinstance(self.source, MeasurementSource) else self.source
|
|
137
|
+
return d
|
|
138
|
+
|
|
139
|
+
def to_joules(self) -> float:
|
|
140
|
+
"""Converte o valor para Joules independentemente da unidade."""
|
|
141
|
+
if self.unit == Unit.JOULES:
|
|
142
|
+
return self.value
|
|
143
|
+
if self.unit == Unit.WH:
|
|
144
|
+
return self.value * 3600.0
|
|
145
|
+
if self.unit == Unit.WATTS:
|
|
146
|
+
# Para leituras de potência, precisa do intervalo de tempo
|
|
147
|
+
# Retorna watts — caller deve multiplicar pelo dt
|
|
148
|
+
return self.value
|
|
149
|
+
return self.value
|
|
150
|
+
|
|
151
|
+
def canonical_hash(self) -> str:
|
|
152
|
+
"""Hash SHA-256 do conteúdo canónico para deduplicação."""
|
|
153
|
+
key = json.dumps({
|
|
154
|
+
"timestamp": self.timestamp,
|
|
155
|
+
"host_id": self.host_id,
|
|
156
|
+
"device_id": self.device_id,
|
|
157
|
+
"component": self.component.value if isinstance(self.component, Component) else self.component,
|
|
158
|
+
"source": self.source.value if isinstance(self.source, MeasurementSource) else self.source,
|
|
159
|
+
"value": self.value,
|
|
160
|
+
}, sort_keys=True)
|
|
161
|
+
return hashlib.sha256(key.encode()).hexdigest()[:16]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ── Interface do Adapter ──────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
class EnergyAdapter:
|
|
167
|
+
"""
|
|
168
|
+
Interface base para todos os adapters de medição energética.
|
|
169
|
+
|
|
170
|
+
Cada adapter representa uma fonte de dados específica (NVML, RAPL,
|
|
171
|
+
Redfish, SNMP/PDU, etc.).
|
|
172
|
+
|
|
173
|
+
Implementação mínima:
|
|
174
|
+
- is_available() → bool
|
|
175
|
+
- adapter_name / adapter_version (class attributes)
|
|
176
|
+
- collect(interval_start, interval_end) → List[Measurement]
|
|
177
|
+
|
|
178
|
+
Boas práticas:
|
|
179
|
+
- Nunca lançar excepção em collect() — retornar lista vazia em caso de falha
|
|
180
|
+
- Registar erros com logger.warning() ou logger.error()
|
|
181
|
+
- Marcar leituras estimadas com provenance=Provenance.ESTIMATED
|
|
182
|
+
- Incluir metadata relevante no context de cada Measurement
|
|
183
|
+
"""
|
|
184
|
+
adapter_name: str = "base"
|
|
185
|
+
adapter_version: str = "0.0"
|
|
186
|
+
|
|
187
|
+
def __init__(self):
|
|
188
|
+
import logging
|
|
189
|
+
self._log = logging.getLogger(f"sa.adapter.{self.adapter_name}")
|
|
190
|
+
self._host_id = self._get_host_id()
|
|
191
|
+
|
|
192
|
+
def _get_host_id(self) -> str:
|
|
193
|
+
"""Identificador único da máquina (hash do hostname + MAC)."""
|
|
194
|
+
import socket
|
|
195
|
+
hostname = socket.gethostname()
|
|
196
|
+
return hashlib.sha256(hostname.encode()).hexdigest()[:16]
|
|
197
|
+
|
|
198
|
+
def is_available(self) -> bool:
|
|
199
|
+
"""Retorna True se o adapter pode operar neste ambiente."""
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
def collect(
|
|
203
|
+
self,
|
|
204
|
+
interval_start: datetime,
|
|
205
|
+
interval_end: datetime,
|
|
206
|
+
) -> List[Measurement]:
|
|
207
|
+
"""
|
|
208
|
+
Recolhe medições para o intervalo temporal especificado.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
interval_start: início do intervalo (UTC)
|
|
212
|
+
interval_end: fim do intervalo (UTC)
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Lista de Measurement. Pode ser vazia se adapter indisponível.
|
|
216
|
+
"""
|
|
217
|
+
raise NotImplementedError
|
|
218
|
+
|
|
219
|
+
def _ts(self, dt: datetime | None = None) -> str:
|
|
220
|
+
"""ISO 8601 UTC timestamp."""
|
|
221
|
+
return (dt or datetime.now(timezone.utc)).isoformat()
|
|
222
|
+
|
|
223
|
+
def _duration(self, start: datetime, end: datetime) -> float:
|
|
224
|
+
return (end - start).total_seconds()
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# ── Helpers de conversão ──────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
def wh_to_j(wh: float) -> float:
|
|
230
|
+
return wh * 3600.0
|
|
231
|
+
|
|
232
|
+
def j_to_wh(j: float) -> float:
|
|
233
|
+
return j / 3600.0
|
|
234
|
+
|
|
235
|
+
def trapezoid_integrate_wh(power_samples: list[float], duration_s: float) -> float:
|
|
236
|
+
"""Integração trapezoidal de amostras de potência (W) → energia (Wh)."""
|
|
237
|
+
if len(power_samples) < 2:
|
|
238
|
+
return power_samples[0] * duration_s / 3600.0 if power_samples else 0.0
|
|
239
|
+
n = len(power_samples)
|
|
240
|
+
dt = duration_s / (n - 1)
|
|
241
|
+
ws = 0.5 * (power_samples[0] + power_samples[-1])
|
|
242
|
+
ws += sum(power_samples[1:-1])
|
|
243
|
+
ws *= dt
|
|
244
|
+
return ws / 3600.0
|