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)]
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 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}