stratum_apps/monitoring/
miner_telemetry.rs1use serde::{Deserialize, Serialize};
4use utoipa::ToSchema;
5
6use asic_rs::{
7 core::data::{collector::DataField, hashrate::HashRateUnit, miner::MinerData},
8 MinerFactory,
9};
10use std::net::IpAddr;
11use tracing::debug;
12
13#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
15pub struct MinerTelemetry {
16 pub make: Option<String>,
18 pub model: Option<String>,
20 pub firmware_version: Option<String>,
22 pub reported_hashrate_hs: Option<f64>,
24 pub power_consumption_w: Option<f64>,
26 pub efficiency_j_per_th: Option<f64>,
28 pub average_temperature_c: Option<f64>,
30 pub uptime_secs: Option<u64>,
32 pub is_mining: Option<bool>,
34}
35
36impl From<MinerData> for MinerTelemetry {
37 fn from(data: MinerData) -> Self {
38 Self {
39 make: Some(data.device_info.make),
40 model: Some(data.device_info.model),
41 firmware_version: data.firmware_version,
42 reported_hashrate_hs: data
43 .hashrate
44 .map(|hashrate| hashrate.as_unit(HashRateUnit::Hash).value),
45 power_consumption_w: data.wattage.map(|power| power.as_watts()),
46 efficiency_j_per_th: data.efficiency,
47 average_temperature_c: data
48 .average_temperature
49 .map(|temperature| temperature.as_celsius()),
50 uptime_secs: data.uptime.map(|uptime| uptime.as_secs()),
51 is_mining: Some(data.is_mining),
52 }
53 }
54}
55
56const MINER_TELEMETRY_EXCLUDED_FIELDS: &[DataField] = &[
57 DataField::Mac,
58 DataField::SerialNumber,
59 DataField::Hostname,
60 DataField::ApiVersion,
61 DataField::ControlBoardVersion,
62 DataField::Chips,
63 DataField::ExpectedHashrate,
64 DataField::Fans,
65 DataField::PsuFans,
66 DataField::FluidTemperature,
67 DataField::TuningTarget,
68 DataField::LightFlashing,
69 DataField::Messages,
70 DataField::Pools,
71];
72
73pub struct MinerTelemetryCollector {
74 factory: MinerFactory,
75}
76
77impl MinerTelemetryCollector {
78 pub fn new() -> Self {
79 Self {
80 factory: MinerFactory::new(),
81 }
82 }
83
84 pub async fn fetch(&self, ip: IpAddr) -> Option<MinerTelemetry> {
85 let miner = match self.factory.get_miner(ip).await {
86 Ok(Some(miner)) => miner,
87 Ok(None) => {
88 debug!("No miner management interface found at {ip}");
89 return None;
90 }
91 Err(error) => {
92 debug!("Failed to get miner management interface at {ip}: {error}");
93 return None;
94 }
95 };
96
97 Some(MinerTelemetry::from(
98 miner
99 .get_data_filtered(MINER_TELEMETRY_EXCLUDED_FIELDS.to_vec())
100 .await,
101 ))
102 }
103}
104
105impl Default for MinerTelemetryCollector {
106 fn default() -> Self {
107 Self::new()
108 }
109}