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.
Files changed (154) hide show
  1. serial_alice_agent-1.0.1/PKG-INFO +94 -0
  2. serial_alice_agent-1.0.1/README.md +54 -0
  3. serial_alice_agent-1.0.1/agent/__init__.py +0 -0
  4. serial_alice_agent-1.0.1/agent/adapters/__init__.py +161 -0
  5. serial_alice_agent-1.0.1/agent/adapters/adapters_init.py +141 -0
  6. serial_alice_agent-1.0.1/agent/adapters/base.py +244 -0
  7. serial_alice_agent-1.0.1/agent/adapters/cpu.py +280 -0
  8. serial_alice_agent-1.0.1/agent/adapters/gpu.py +307 -0
  9. serial_alice_agent-1.0.1/agent/adapters/ipmi_adapter.py +150 -0
  10. serial_alice_agent-1.0.1/agent/adapters/machine.py +416 -0
  11. serial_alice_agent-1.0.1/agent/adapters/pdu_snmp_adapter.py +159 -0
  12. serial_alice_agent-1.0.1/agent/adapters/signed_exporter.py +361 -0
  13. serial_alice_agent-1.0.1/agent/adapters/system.py +275 -0
  14. serial_alice_agent-1.0.1/agent/adapters/windows_rapl.py +421 -0
  15. serial_alice_agent-1.0.1/agent/agent.py +750 -0
  16. serial_alice_agent-1.0.1/agent/anomaly/__init__.py +26 -0
  17. serial_alice_agent-1.0.1/agent/anomaly/detector.py +222 -0
  18. serial_alice_agent-1.0.1/agent/anomaly/monitor.py +179 -0
  19. serial_alice_agent-1.0.1/agent/anomaly/power_source.py +166 -0
  20. serial_alice_agent-1.0.1/agent/attestation/__init__.py +0 -0
  21. serial_alice_agent-1.0.1/agent/attestation/nvidia_cc.py +407 -0
  22. serial_alice_agent-1.0.1/agent/attestation/payload_builder.py +646 -0
  23. serial_alice_agent-1.0.1/agent/attestation/tee_quote.py +147 -0
  24. serial_alice_agent-1.0.1/agent/attestation/tpm.py +377 -0
  25. serial_alice_agent-1.0.1/agent/attribution/__init__.py +0 -0
  26. serial_alice_agent-1.0.1/agent/attribution/workload.py +355 -0
  27. serial_alice_agent-1.0.1/agent/cli.py +511 -0
  28. serial_alice_agent-1.0.1/agent/client/__init__.py +0 -0
  29. serial_alice_agent-1.0.1/agent/client/serial_alice_api.py +284 -0
  30. serial_alice_agent-1.0.1/agent/config.py +332 -0
  31. serial_alice_agent-1.0.1/agent/energy/__init__.py +0 -0
  32. serial_alice_agent-1.0.1/agent/hardware/__init__.py +0 -0
  33. serial_alice_agent-1.0.1/agent/hardware/cpu_discovery.py +241 -0
  34. serial_alice_agent-1.0.1/agent/machine/__init__.py +0 -0
  35. serial_alice_agent-1.0.1/agent/machine/agent_identity.py +196 -0
  36. serial_alice_agent-1.0.1/agent/machine/fingerprint_v2.py +316 -0
  37. serial_alice_agent-1.0.1/agent/machine/release_signing.py +252 -0
  38. serial_alice_agent-1.0.1/agent/optimization/__init__.py +50 -0
  39. serial_alice_agent-1.0.1/agent/optimization/collector.py +328 -0
  40. serial_alice_agent-1.0.1/agent/optimization/metrics.py +184 -0
  41. serial_alice_agent-1.0.1/agent/optimization/models.py +268 -0
  42. serial_alice_agent-1.0.1/agent/optimization/phase1/__init__.py +43 -0
  43. serial_alice_agent-1.0.1/agent/optimization/phase1/classifier.py +279 -0
  44. serial_alice_agent-1.0.1/agent/optimization/phase1/cooldown.py +101 -0
  45. serial_alice_agent-1.0.1/agent/optimization/phase1/db_models.py +185 -0
  46. serial_alice_agent-1.0.1/agent/optimization/phase1/engine.py +386 -0
  47. serial_alice_agent-1.0.1/agent/optimization/phase1/explainability.py +248 -0
  48. serial_alice_agent-1.0.1/agent/optimization/phase1/guardrails.py +146 -0
  49. serial_alice_agent-1.0.1/agent/optimization/phase1/hardware_profile.py +207 -0
  50. serial_alice_agent-1.0.1/agent/optimization/phase1/hysteresis.py +87 -0
  51. serial_alice_agent-1.0.1/agent/optimization/phase1/models.py +315 -0
  52. serial_alice_agent-1.0.1/agent/optimization/phase1/policy.py +149 -0
  53. serial_alice_agent-1.0.1/agent/optimization/phase1/repository.py +272 -0
  54. serial_alice_agent-1.0.1/agent/optimization/phase2/__init__.py +41 -0
  55. serial_alice_agent-1.0.1/agent/optimization/phase2/anomaly_detector.py +325 -0
  56. serial_alice_agent-1.0.1/agent/optimization/phase2/canary.py +253 -0
  57. serial_alice_agent-1.0.1/agent/optimization/phase2/confidence_calibrator.py +177 -0
  58. serial_alice_agent-1.0.1/agent/optimization/phase2/engine.py +329 -0
  59. serial_alice_agent-1.0.1/agent/optimization/phase2/kpi.py +218 -0
  60. serial_alice_agent-1.0.1/agent/optimization/phase2/models.py +392 -0
  61. serial_alice_agent-1.0.1/agent/optimization/phase2/policy_version_manager.py +189 -0
  62. serial_alice_agent-1.0.1/agent/optimization/phase2/scorer.py +274 -0
  63. serial_alice_agent-1.0.1/agent/optimization/phase3/__init__.py +53 -0
  64. serial_alice_agent-1.0.1/agent/optimization/phase3/blast_radius.py +166 -0
  65. serial_alice_agent-1.0.1/agent/optimization/phase3/db_models.py +288 -0
  66. serial_alice_agent-1.0.1/agent/optimization/phase3/digital_twin.py +273 -0
  67. serial_alice_agent-1.0.1/agent/optimization/phase3/engine.py +232 -0
  68. serial_alice_agent-1.0.1/agent/optimization/phase3/explainability.py +344 -0
  69. serial_alice_agent-1.0.1/agent/optimization/phase3/hardware_benchmark.py +218 -0
  70. serial_alice_agent-1.0.1/agent/optimization/phase3/idle_node_detector.py +152 -0
  71. serial_alice_agent-1.0.1/agent/optimization/phase3/models.py +560 -0
  72. serial_alice_agent-1.0.1/agent/optimization/phase3/node_efficiency_scorer.py +198 -0
  73. serial_alice_agent-1.0.1/agent/optimization/phase3/rack_hotspot_detector.py +164 -0
  74. serial_alice_agent-1.0.1/agent/optimization/phase3/repository.py +357 -0
  75. serial_alice_agent-1.0.1/agent/optimization/phase3/scheduler_adapter.py +365 -0
  76. serial_alice_agent-1.0.1/agent/optimization/phase3/thermal_state_machine.py +223 -0
  77. serial_alice_agent-1.0.1/agent/optimization/phase3/twin_calibration.py +222 -0
  78. serial_alice_agent-1.0.1/agent/optimization/phase3/workload_consolidator.py +247 -0
  79. serial_alice_agent-1.0.1/agent/optimization/phase4/__init__.py +39 -0
  80. serial_alice_agent-1.0.1/agent/optimization/phase4/batch_shift_recommender.py +192 -0
  81. serial_alice_agent-1.0.1/agent/optimization/phase4/carbon_client.py +200 -0
  82. serial_alice_agent-1.0.1/agent/optimization/phase4/contextual_bandit.py +393 -0
  83. serial_alice_agent-1.0.1/agent/optimization/phase4/engine.py +317 -0
  84. serial_alice_agent-1.0.1/agent/optimization/phase4/governance.py +426 -0
  85. serial_alice_agent-1.0.1/agent/optimization/phase4/kill_switch.py +198 -0
  86. serial_alice_agent-1.0.1/agent/optimization/phase4/models.py +537 -0
  87. serial_alice_agent-1.0.1/agent/optimization/power_optimizer.py +221 -0
  88. serial_alice_agent-1.0.1/agent/optimization/safety.py +237 -0
  89. serial_alice_agent-1.0.1/agent/optimization/session_manager.py +397 -0
  90. serial_alice_agent-1.0.1/agent/rapl_signer/__init__.py +38 -0
  91. serial_alice_agent-1.0.1/agent/rapl_signer/cli.py +106 -0
  92. serial_alice_agent-1.0.1/agent/rapl_signer/proxy.py +115 -0
  93. serial_alice_agent-1.0.1/agent/rapl_signer/signer.py +132 -0
  94. serial_alice_agent-1.0.1/agent/reconciliation/__init__.py +0 -0
  95. serial_alice_agent-1.0.1/agent/reconciliation/engine.py +348 -0
  96. serial_alice_agent-1.0.1/agent/router_training/train_alice_router.py +362 -0
  97. serial_alice_agent-1.0.1/agent/runtime/__init__.py +0 -0
  98. serial_alice_agent-1.0.1/agent/runtime/job_detector.py +614 -0
  99. serial_alice_agent-1.0.1/agent/serial-alice-agent.service +46 -0
  100. serial_alice_agent-1.0.1/agent/session_runner.py +363 -0
  101. serial_alice_agent-1.0.1/agent/telemetry/__init__.py +0 -0
  102. serial_alice_agent-1.0.1/agent/telemetry/__main__.py +197 -0
  103. serial_alice_agent-1.0.1/agent/telemetry/adapters/__init__.py +0 -0
  104. serial_alice_agent-1.0.1/agent/telemetry/adapters/sources.py +551 -0
  105. serial_alice_agent-1.0.1/agent/telemetry/attribution/__init__.py +0 -0
  106. serial_alice_agent-1.0.1/agent/telemetry/attribution/engine.py +261 -0
  107. serial_alice_agent-1.0.1/agent/telemetry/buffering/__init__.py +0 -0
  108. serial_alice_agent-1.0.1/agent/telemetry/buffering/buffer.py +249 -0
  109. serial_alice_agent-1.0.1/agent/telemetry/capture/__init__.py +0 -0
  110. serial_alice_agent-1.0.1/agent/telemetry/capture/collector.py +175 -0
  111. serial_alice_agent-1.0.1/agent/telemetry/config/manager.py +217 -0
  112. serial_alice_agent-1.0.1/agent/telemetry/core/contract.py +206 -0
  113. serial_alice_agent-1.0.1/agent/telemetry/cpu_telemetry.py +275 -0
  114. serial_alice_agent-1.0.1/agent/telemetry/errors/taxonomy.py +189 -0
  115. serial_alice_agent-1.0.1/agent/telemetry/gpu_telemetry.py +341 -0
  116. serial_alice_agent-1.0.1/agent/telemetry/health/__init__.py +0 -0
  117. serial_alice_agent-1.0.1/agent/telemetry/health/monitor.py +239 -0
  118. serial_alice_agent-1.0.1/agent/telemetry/integrity/__init__.py +0 -0
  119. serial_alice_agent-1.0.1/agent/telemetry/integrity/identity.py +225 -0
  120. serial_alice_agent-1.0.1/agent/telemetry/models/event.py +366 -0
  121. serial_alice_agent-1.0.1/agent/telemetry/normalization/__init__.py +0 -0
  122. serial_alice_agent-1.0.1/agent/telemetry/normalization/normalizer.py +242 -0
  123. serial_alice_agent-1.0.1/agent/telemetry/pipeline.py +353 -0
  124. serial_alice_agent-1.0.1/agent/telemetry/pipeline_v2.py +381 -0
  125. serial_alice_agent-1.0.1/agent/telemetry/quality/__init__.py +0 -0
  126. serial_alice_agent-1.0.1/agent/telemetry/quality/engine.py +211 -0
  127. serial_alice_agent-1.0.1/agent/telemetry/quality/scoring.py +433 -0
  128. serial_alice_agent-1.0.1/agent/telemetry/reconciliation/__init__.py +0 -0
  129. serial_alice_agent-1.0.1/agent/telemetry/reconciliation/engine.py +392 -0
  130. serial_alice_agent-1.0.1/agent/telemetry/security/hardening.py +120 -0
  131. serial_alice_agent-1.0.1/agent/telemetry/signing/__init__.py +0 -0
  132. serial_alice_agent-1.0.1/agent/telemetry/signing/signer.py +187 -0
  133. serial_alice_agent-1.0.1/agent/telemetry/temporal/integrity.py +280 -0
  134. serial_alice_agent-1.0.1/agent/telemetry/tests_pipeline.py +520 -0
  135. serial_alice_agent-1.0.1/agent/telemetry/transport/__init__.py +0 -0
  136. serial_alice_agent-1.0.1/agent/telemetry/transport/http.py +201 -0
  137. serial_alice_agent-1.0.1/agent/tests/__init__.py +0 -0
  138. serial_alice_agent-1.0.1/agent/tests/test_agent.py +486 -0
  139. serial_alice_agent-1.0.1/agent/tests/test_anomaly.py +181 -0
  140. serial_alice_agent-1.0.1/agent/tests/test_machine_identity.py +332 -0
  141. serial_alice_agent-1.0.1/agent/tests/test_rapl_signer.py +277 -0
  142. serial_alice_agent-1.0.1/agent/tests/test_standalone_no_app.py +109 -0
  143. serial_alice_agent-1.0.1/agent/verification/__init__.py +1 -0
  144. serial_alice_agent-1.0.1/agent/verification/offline_verifier.py +545 -0
  145. serial_alice_agent-1.0.1/agent/verification/sth_checker.py +307 -0
  146. serial_alice_agent-1.0.1/agent/windows_service.py +159 -0
  147. serial_alice_agent-1.0.1/pyproject.toml +80 -0
  148. serial_alice_agent-1.0.1/serial_alice_agent.egg-info/PKG-INFO +94 -0
  149. serial_alice_agent-1.0.1/serial_alice_agent.egg-info/SOURCES.txt +152 -0
  150. serial_alice_agent-1.0.1/serial_alice_agent.egg-info/dependency_links.txt +1 -0
  151. serial_alice_agent-1.0.1/serial_alice_agent.egg-info/entry_points.txt +2 -0
  152. serial_alice_agent-1.0.1/serial_alice_agent.egg-info/requires.txt +20 -0
  153. serial_alice_agent-1.0.1/serial_alice_agent.egg-info/top_level.txt +1 -0
  154. 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