|
|
@@ -7,11 +7,31 @@
|
|
|
|
|
|
use anyhow::{Context, Result, anyhow};
|
|
|
use serde_json::json;
|
|
|
+use std::sync::OnceLock;
|
|
|
use std::time::Duration;
|
|
|
use tracing::{debug, info, warn};
|
|
|
|
|
|
use crate::config::{Cluster, Config, HomeAssistantConfig};
|
|
|
|
|
|
+/// Process-wide shared HA client, built once. The fleet probe rebuilds HA
|
|
|
+/// state every 30 s across several clusters — a fresh `Client` per call
|
|
|
+/// re-parses the system CA bundle (rustls) and allocates ~1.7 MB of TLS
|
|
|
+/// setup each time, ~2 GB/h of churn in the long-running dashboard.
|
|
|
+static SHARED_CLIENT: OnceLock<Option<Client>> = OnceLock::new();
|
|
|
+
|
|
|
+/// The shared client for long-lived callers (the web dashboard's probe
|
|
|
+/// loop); CLI paths (one-shot `power`) may keep using `Client::new`.
|
|
|
+pub fn shared_client(config: &Config) -> Option<Client> {
|
|
|
+ SHARED_CLIENT
|
|
|
+ .get_or_init(|| {
|
|
|
+ config
|
|
|
+ .home_assistant
|
|
|
+ .as_ref()
|
|
|
+ .and_then(|ha| Client::new(ha).ok())
|
|
|
+ })
|
|
|
+ .clone()
|
|
|
+}
|
|
|
+
|
|
|
/// Thin async wrapper around the HA REST API.
|
|
|
///
|
|
|
/// `base_url` is the HA root URL (`http://homeassistant.local:8123`),
|
|
|
@@ -153,10 +173,7 @@ pub async fn extra_entities_states(config: &Config, cluster: &Cluster) -> Entity
|
|
|
if cluster.extra_entities.is_empty() {
|
|
|
return EntityStates::default();
|
|
|
}
|
|
|
- let ha_client: Option<Client> = config
|
|
|
- .home_assistant
|
|
|
- .as_ref()
|
|
|
- .and_then(|ha| Client::new(ha).ok());
|
|
|
+ let ha_client: Option<Client> = shared_client(config);
|
|
|
let has_ha = ha_client.is_some();
|
|
|
let mut handles = Vec::new();
|
|
|
for e in &cluster.extra_entities {
|