sampler/
config.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::Error;
6use fidl_fuchsia_diagnostics as fdiagnostics;
7use fuchsia_inspect::{Node, NumericProperty, UintProperty};
8use fuchsia_inspect_contrib::nodes::BoundedListNode;
9use sampler_component_config::Config as ComponentConfig;
10use sampler_config::runtime::ProjectConfig;
11use sampler_config::{MetricType, ProjectId};
12use std::cell::RefCell;
13use std::collections::HashMap;
14
15/// Container for all configurations needed to instantiate the Sampler infrastructure.
16/// Includes:
17///      - Project configurations.
18///      - Whether to configure the ArchiveReader for tests (e.g. longer timeouts)
19///      - Minimum sample rate.
20#[derive(Debug)]
21pub struct SamplerConfig {
22    pub project_configs: Vec<ProjectConfig>,
23    pub stats: SamplerStats,
24}
25
26#[derive(Debug)]
27pub struct ProjectStats {
28    _project_node: Node,
29    pub metrics_configured: UintProperty,
30    pub cobalt_logs_sent: UintProperty,
31    pub events: RefCell<BoundedListNode>,
32}
33
34#[derive(Default, Debug)]
35pub struct SamplerStats {
36    pub projects: HashMap<ProjectId, ProjectStats>,
37}
38
39impl SamplerConfig {
40    pub fn new(config: ComponentConfig, stats: &Node) -> Result<Self, Error> {
41        let ComponentConfig { minimum_sample_rate_sec, project_configs } = config;
42        let mut sampler_stats = SamplerStats::default();
43        let project_configs = project_configs
44            .into_iter()
45            .map(|config| {
46                let config: ProjectConfig = serde_json::from_str(&config)?;
47                if config.poll_rate_sec < minimum_sample_rate_sec {
48                    return Err(anyhow::anyhow!(
49                        "Project {} had illegal poll rate. Actual: {}s, Min: {}s",
50                        config.project_id,
51                        config.poll_rate_sec,
52                        minimum_sample_rate_sec
53                    ));
54                }
55                sampler_stats
56                    .projects
57                    .entry(config.project_id)
58                    .and_modify(|project| {
59                        project.metrics_configured.add(config.metrics.len() as u64);
60                    })
61                    .or_insert_with(|| {
62                        let project_node =
63                            stats.create_child(format!("project_{}", config.project_id));
64                        let metrics_configured = project_node
65                            .create_uint("metrics_configured", config.metrics.len() as u64);
66                        let cobalt_logs_sent = project_node.create_uint("cobalt_logs_sent", 0);
67                        let events = RefCell::new(BoundedListNode::new(
68                            project_node.create_child("events"),
69                            300,
70                        ));
71                        ProjectStats {
72                            _project_node: project_node,
73                            metrics_configured,
74                            cobalt_logs_sent,
75                            events,
76                        }
77                    });
78                Ok(config)
79            })
80            .collect::<Result<Vec<_>, Error>>()?;
81
82        Ok(Self { project_configs, stats: sampler_stats })
83    }
84
85    pub fn sample_data(&self) -> Vec<fdiagnostics::SampleDatum> {
86        let mut data = vec![];
87        for project in &self.project_configs {
88            for metric in &project.metrics {
89                let strategy = Some(match metric.metric_type {
90                    MetricType::Integer | MetricType::String => {
91                        fdiagnostics::SampleStrategy::Always
92                    }
93                    MetricType::IntHistogram | MetricType::Occurrence => {
94                        fdiagnostics::SampleStrategy::OnDiff
95                    }
96                });
97
98                for selector in &metric.selectors {
99                    data.push(fdiagnostics::SampleDatum {
100                        selector: Some(fdiagnostics::SelectorArgument::StructuredSelector(
101                            selector.clone(),
102                        )),
103                        interval_secs: Some(project.poll_rate_sec),
104                        strategy,
105                        ..Default::default()
106                    });
107                }
108            }
109        }
110
111        data
112    }
113}