Skip to main content

shared/
telemetry.rs

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}