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