Skip to main content

rusty_stack/installers/common/
telemetry.rs

1use super::{log_warn, SealedToken};
2use crate::state::GPUInfo;
3use crate::telemetry::OptInGate;
4use serde::{Deserialize, Serialize};
5use std::time::Duration;
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub enum BuildReportStatus {
9    Success,
10    Failure,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct BuildReport {
15    pub gpu_arch: String,
16    pub gpu_count: usize,
17    pub gpu_name: String,
18    pub rocm_version: String,
19    pub os: String,
20    pub os_distro: String,
21    pub build_duration_seconds: u64,
22    pub git_commit: String,
23    pub build_status: BuildReportStatus,
24    pub install_path: String,
25    pub cmake_flags: Vec<String>,
26    pub verification_path: String,
27    pub rdna3_validation_passed: bool,
28    pub wmma_available: bool,
29    pub shared_memory_ok: bool,
30    pub tokens_per_second_wmma: Option<f32>,
31    pub tokens_per_second_fallback: Option<f32>,
32    pub binary_version: String,
33    pub was_prebuilt: bool,
34}
35
36impl BuildReport {
37    pub fn from_hardware(
38        gpu: &GPUInfo,
39        os: impl Into<String>,
40        os_distro: impl Into<String>,
41        build_duration: Duration,
42        git_commit: impl Into<String>,
43        install_path: impl Into<String>,
44        cmake_flags: Vec<String>,
45        verification_path: impl Into<String>,
46        binary_version: impl Into<String>,
47        was_prebuilt: bool,
48    ) -> Self {
49        Self {
50            gpu_arch: gpu.architecture.clone(),
51            gpu_count: gpu.gpu_count,
52            gpu_name: gpu.model.clone(),
53            rocm_version: gpu.rocm_version.clone(),
54            os: os.into(),
55            os_distro: os_distro.into(),
56            build_duration_seconds: build_duration.as_secs(),
57            git_commit: git_commit.into(),
58            build_status: BuildReportStatus::Success,
59            install_path: install_path.into(),
60            cmake_flags,
61            verification_path: verification_path.into(),
62            rdna3_validation_passed: false,
63            wmma_available: false,
64            shared_memory_ok: false,
65            tokens_per_second_wmma: None,
66            tokens_per_second_fallback: None,
67            binary_version: binary_version.into(),
68            was_prebuilt,
69        }
70    }
71}
72
73pub fn submit_build_report(report: BuildReport) {
74    // Check opt-in gate before attempting any network call (VAL-TELEM-003/004)
75    if let Ok(gate) = OptInGate::new() {
76        if !gate.is_enabled() {
77            log_warn("telemetry opt-in disabled, skipping build report submission");
78            return;
79        }
80    }
81
82    let mut token = SealedToken::from_env();
83    let payload = build_report_payload(&report);
84
85    let response = ureq::post(submission_endpoint())
86        .header("Authorization", format!("Bearer {}", token.as_str()))
87        .header("Accept", "application/vnd.github+json")
88        .header("X-GitHub-Api-Version", "2022-11-28")
89        .header("User-Agent", "rusty-stack")
90        .send_json(payload);
91
92    if let Err(err) = response {
93        log_warn(&format!("telemetry dispatch failed: {}", err));
94    }
95
96    token.purge();
97}
98
99pub fn build_report_from_state(
100    gpu: &GPUInfo,
101    os: impl Into<String>,
102    os_distro: impl Into<String>,
103    build_duration: Duration,
104    git_commit: impl Into<String>,
105    install_path: impl Into<String>,
106    cmake_flags: Vec<String>,
107    verification_path: impl Into<String>,
108    binary_version: impl Into<String>,
109    was_prebuilt: bool,
110) -> BuildReport {
111    BuildReport::from_hardware(
112        gpu,
113        os,
114        os_distro,
115        build_duration,
116        git_commit,
117        install_path,
118        cmake_flags,
119        verification_path,
120        binary_version,
121        was_prebuilt,
122    )
123}
124
125pub fn build_report_payload(report: &BuildReport) -> serde_json::Value {
126    serde_json::json!({
127        "event_type": "build-validation-report",
128        "client_payload": report,
129    })
130}
131
132pub fn submission_endpoint() -> &'static str {
133    "https://api.github.com/repos/scooter-lacroix/llama.cpp-turboquant-hip/dispatches"
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use std::time::Duration;
140
141    #[test]
142    fn build_report_serializes_all_fields() {
143        let gpu = GPUInfo {
144            architecture: "gfx1100".into(),
145            gpu_count: 2,
146            model: "AMD Radeon RX 7900 XTX".into(),
147            rocm_version: "7.2.1".into(),
148            ..Default::default()
149        };
150        let report = BuildReport::from_hardware(
151            &gpu,
152            "linux",
153            "ubuntu",
154            Duration::from_secs(42),
155            "abc123",
156            "/tmp/install",
157            vec!["-DGGML_HIP=ON".into()],
158            "/tmp/verify",
159            "v0.3.2",
160            true,
161        );
162
163        let json = serde_json::to_value(report).unwrap();
164        for key in [
165            "gpu_arch",
166            "gpu_count",
167            "gpu_name",
168            "rocm_version",
169            "os",
170            "os_distro",
171            "build_duration_seconds",
172            "git_commit",
173            "build_status",
174            "install_path",
175            "cmake_flags",
176            "verification_path",
177            "rdna3_validation_passed",
178            "wmma_available",
179            "shared_memory_ok",
180            "tokens_per_second_wmma",
181            "tokens_per_second_fallback",
182            "binary_version",
183            "was_prebuilt",
184        ] {
185            assert!(json.get(key).is_some(), "missing key {key}");
186        }
187    }
188
189    #[test]
190    fn submission_payload_matches_repository_dispatch_format() {
191        let gpu = GPUInfo {
192            architecture: "gfx1100".into(),
193            gpu_count: 1,
194            model: "AMD Radeon RX 7900 XTX".into(),
195            rocm_version: "7.2.1".into(),
196            ..Default::default()
197        };
198        let report = BuildReport::from_hardware(
199            &gpu,
200            "linux",
201            "ubuntu",
202            Duration::from_secs(12),
203            "commit123",
204            "/opt/mlstack",
205            vec!["-DGGML_HIP=ON".into(), "-DGPU_TARGETS=gfx1100".into()],
206            "/tmp/verify",
207            "v0.3.2",
208            false,
209        );
210
211        let payload = build_report_payload(&report);
212        assert_eq!(
213            payload["event_type"],
214            serde_json::Value::String("build-validation-report".into())
215        );
216        assert_eq!(payload["client_payload"]["gpu_arch"], "gfx1100");
217        assert_eq!(payload["client_payload"]["build_duration_seconds"], 12);
218        assert!(payload["client_payload"]["cmake_flags"].is_array());
219    }
220}