1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! Scaphandre power→energy math + state update.
//!
//! Holds the pieces specific to Scaphandre's per-process power gauge:
//! [`compute_energy_per_op_kwh`] turns `(microwatts, scrape_interval, ops)`
//! into kWh-per-op, and [`apply_scrape`] glues that together with the
//! config's `process_map` to publish a fresh [`ScaphandreState`].
//! The cross-scraper per-service op-delta tracker lives in
//! [`crate::score::ops_snapshot_diff`].
use HashMap;
use ScaphandreConfig;
use ProcessPower;
use ;
/// Convert a per-process power reading + observed op count into an
/// energy-per-op coefficient (kWh per op).
///
/// Formula:
/// ```text
/// energy_per_op_kwh =
/// (power_watts × scrape_interval_secs) / (ops × 3_600_000)
/// ```
///
/// The `3_600_000` constant converts joules (watt-seconds) to kWh
/// (1 kWh = 3.6 × 10⁶ J).
///
/// Returns `None` when `ops == 0` so the scraper can keep the
/// previous (still-valid) entry unchanged instead of producing a
/// division-by-zero or a coefficient that flaps every scrape. Keeping
/// stale-but-present is the user-validated decision against model-tag
/// flapping for idle services.
/// Apply a freshly-scraped batch of process-power readings to a
/// [`ScaphandreState`], updating each mapped service's energy-per-op
/// coefficient.
///
/// Takes the already-parsed `power_readings` (from
/// [`super::parser::parse_scaphandre_metrics`]), the per-service op
/// delta (from
/// [`crate::score::ops_snapshot_diff::OpsSnapshotDiff::delta_and_advance`]),
/// and the scraper config. Iterates the config's `process_map` to find each
/// mapped service, looks up its process's current power, and calls
/// [`compute_energy_per_op_kwh`] to derive the coefficient. If the
/// op delta is 0 for a service, the existing entry (if any) is left
/// unchanged, see the rationale in [`compute_energy_per_op_kwh`].
///
/// Uses the `ArcSwap` pattern: builds a new `HashMap` starting from
/// the current published one (so previous entries that don't get
/// updated this tick are inherited), mutates it locally, and
/// publishes the result atomically at the end via
/// `ScaphandreState::publish`.
// `clippy::implicit_hasher` would want `op_deltas` generic over the
// hasher (`<S: BuildHasher>`). We take it as a concrete `HashMap`
// because the only caller is the scraper loop, which gets the map
// from `MetricsState::snapshot_service_io_ops` (default hasher).
// A hasher type parameter here would leak into every test fixture
// for no practical benefit.