1use compact_str::ToCompactString;
2use serde::Serialize;
3use std::collections::{BTreeMap, HashMap};
4use utoipa::ToSchema;
5
6nestify::nest! {
7 #[derive(ToSchema, Serialize)] pub struct TelemetryData {
8 pub uuid: uuid::Uuid,
9
10 #[schema(inline)]
11 pub panel: #[derive(ToSchema, Serialize)] pub struct TelemetryDataPanel {
12 pub version: compact_str::CompactString,
13 pub container_type: crate::AppContainerType,
14
15 pub database_version: compact_str::CompactString,
16 pub cache_version: compact_str::CompactString,
17
18 pub architecture: &'static str,
19 pub kernel_version: compact_str::CompactString,
20 },
21
22 #[schema(inline)]
23 pub resources: #[derive(ToSchema, Serialize)] pub struct TelemetryDataResources {
24 #[schema(inline)]
25 pub users: #[derive(ToSchema, Serialize)] pub struct TelemetryDataResourcesUsers {
26 pub total: u64,
27 pub languages: BTreeMap<compact_str::CompactString, u64>,
28 },
29
30 #[schema(inline)]
31 pub backups: #[derive(ToSchema, Serialize)] pub struct TelemetryDataResourcesBackups {
32 pub total: u64,
33 pub disks: HashMap<crate::models::server_backup::BackupDisk, u64>,
34 },
35
36 #[schema(inline)]
37 pub servers: #[derive(ToSchema, Serialize)] pub struct TelemetryDataResourcesServers {
38 pub total: u64,
39 },
40 },
41
42 pub extensions: Vec<crate::extensions::ConstructedExtension>,
43
44 #[schema(inline)]
45 pub nodes: Vec<#[derive(ToSchema, Serialize)] pub struct TelemetryDataNode {
46 pub version: compact_str::CompactString,
47 pub container_type: wings_api::AppContainerType,
48
49 #[schema(inline)]
50 pub memory: wings_api::system_overview::get::Response200Memory,
51 #[schema(inline)]
52 pub servers: wings_api::system_overview::get::Response200Servers,
53
54 pub architecture: compact_str::CompactString,
55 pub kernel_version: compact_str::CompactString,
56 }>,
57
58 #[schema(inline)]
59 pub database_agent_hosts: Vec<#[derive(ToSchema, Serialize)] pub struct TelemetryDataDatabaseAgentHost {
60 pub version: compact_str::CompactString,
61 pub container_type: db_agent_api::AppContainerType,
62
63 #[schema(inline)]
64 pub memory: db_agent_api::system_overview::get::Response200Memory,
65 #[schema(inline)]
66 pub instances: db_agent_api::system_overview::get::Response200Instances,
67
68 pub architecture: compact_str::CompactString,
69 pub kernel_version: compact_str::CompactString,
70 }>,
71 }
72}
73
74impl TelemetryData {
75 pub async fn collect(state: &crate::State) -> Result<Self, anyhow::Error> {
76 let settings = state.settings.get().await?;
77 let uuid = settings.telemetry_uuid.unwrap_or_else(uuid::Uuid::new_v4);
78
79 if settings.telemetry_uuid.is_none() {
80 drop(settings);
81 let mut new_settings = state.settings.get_mut().await?;
82 new_settings.telemetry_uuid = Some(uuid);
83 new_settings.save().await?;
84 } else {
85 drop(settings);
86 }
87
88 let mut node_results = Vec::new();
89 let mut node_page = 1;
90 loop {
91 let nodes = crate::models::node::Node::all_with_pagination(
92 &state.database,
93 node_page,
94 50,
95 None,
96 )
97 .await?;
98 if nodes.data.is_empty() {
99 break;
100 }
101
102 for node in nodes.data {
103 let overview = match node
104 .api_client(&state.database)
105 .await?
106 .get_system_overview()
107 .await
108 {
109 Ok(overview) => overview,
110 Err(_) => continue,
111 };
112
113 node_results.push(TelemetryDataNode {
114 version: overview.version,
115 container_type: overview.container_type,
116 memory: overview.memory,
117 servers: overview.servers,
118 architecture: overview.architecture,
119 kernel_version: overview.kernel_version,
120 });
121 }
122
123 node_page += 1;
124 }
125
126 let mut database_agent_host_results = Vec::new();
127 let mut database_agent_host_page = 1;
128 loop {
129 let database_agent_hosts =
130 crate::models::database_agent_host::DatabaseAgentHost::all_with_pagination(
131 &state.database,
132 database_agent_host_page,
133 50,
134 None,
135 )
136 .await?;
137 if database_agent_hosts.data.is_empty() {
138 break;
139 }
140
141 for database_agent_host in database_agent_hosts.data {
142 let overview = match database_agent_host
143 .api_client(&state.database)
144 .await?
145 .get_system_overview()
146 .await
147 {
148 Ok(overview) => overview,
149 Err(_) => continue,
150 };
151
152 database_agent_host_results.push(TelemetryDataDatabaseAgentHost {
153 version: overview.version,
154 container_type: overview.container_type,
155 memory: overview.memory,
156 instances: overview.instances,
157 architecture: overview.architecture,
158 kernel_version: overview.kernel_version,
159 });
160 }
161
162 database_agent_host_page += 1;
163 }
164
165 let user_languages = sqlx::query!(
166 "SELECT users.language, COUNT(*) as count
167 FROM users
168 GROUP BY users.language"
169 )
170 .fetch_all(state.database.read())
171 .await?;
172 let backup_disks = sqlx::query!(
173 r#"SELECT server_backups.disk as "disk: crate::models::server_backup::BackupDisk", COUNT(*) as count
174 FROM server_backups
175 WHERE server_backups.completed IS NOT NULL AND server_backups.deleted IS NULL
176 GROUP BY server_backups.disk"#
177 )
178 .fetch_all(state.database.read())
179 .await?;
180 let servers = sqlx::query!(
181 "SELECT COUNT(*) as count
182 FROM servers"
183 )
184 .fetch_one(state.database.read())
185 .await?;
186
187 Ok(Self {
188 uuid,
189 panel: TelemetryDataPanel {
190 version: state.version.to_compact_string(),
191 container_type: state.container_type,
192 database_version: state.database.version().await?,
193 cache_version: state.cache.version().await?,
194 architecture: std::env::consts::ARCH,
195 kernel_version: sysinfo::System::kernel_long_version().into(),
196 },
197 resources: TelemetryDataResources {
198 users: TelemetryDataResourcesUsers {
199 total: user_languages
200 .iter()
201 .map(|r| r.count.unwrap_or(0) as u64)
202 .sum(),
203 languages: user_languages
204 .into_iter()
205 .map(|r| (r.language.into(), r.count.unwrap_or(0) as u64))
206 .collect(),
207 },
208 backups: TelemetryDataResourcesBackups {
209 total: backup_disks
210 .iter()
211 .map(|r| r.count.unwrap_or(0) as u64)
212 .sum(),
213 disks: backup_disks
214 .into_iter()
215 .map(|r| (r.disk, r.count.unwrap_or(0) as u64))
216 .collect(),
217 },
218 servers: TelemetryDataResourcesServers {
219 total: servers.count.unwrap_or(0) as u64,
220 },
221 },
222 extensions: state.extensions.extensions().await.clone(),
223 nodes: node_results,
224 database_agent_hosts: database_agent_host_results,
225 })
226 }
227}