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}
59
60impl TelemetryData {
61    pub async fn collect(state: &crate::State) -> Result<Self, anyhow::Error> {
62        let settings = state.settings.get().await?;
63        let uuid = settings.telemetry_uuid.unwrap_or_else(uuid::Uuid::new_v4);
64
65        if settings.telemetry_uuid.is_none() {
66            drop(settings);
67            let mut new_settings = state.settings.get_mut().await?;
68            new_settings.telemetry_uuid = Some(uuid);
69            new_settings.save().await?;
70        } else {
71            drop(settings);
72        }
73
74        let mut node_results = Vec::new();
75        let mut node_page = 1;
76        loop {
77            let nodes = crate::models::node::Node::all_with_pagination(
78                &state.database,
79                node_page,
80                50,
81                None,
82            )
83            .await?;
84            if nodes.data.is_empty() {
85                break;
86            }
87
88            for node in nodes.data {
89                let overview = match node
90                    .api_client(&state.database)
91                    .await?
92                    .get_system_overview()
93                    .await
94                {
95                    Ok(overview) => overview,
96                    Err(_) => continue,
97                };
98
99                node_results.push(TelemetryDataNode {
100                    version: overview.version,
101                    container_type: overview.container_type,
102                    memory: overview.memory,
103                    servers: overview.servers,
104                    architecture: overview.architecture,
105                    kernel_version: overview.kernel_version,
106                });
107            }
108
109            node_page += 1;
110        }
111
112        let user_languages = sqlx::query!(
113            "SELECT users.language, COUNT(*) as count
114            FROM users
115            GROUP BY users.language"
116        )
117        .fetch_all(state.database.read())
118        .await?;
119        let backup_disks = sqlx::query!(
120            r#"SELECT server_backups.disk as "disk: crate::models::server_backup::BackupDisk", COUNT(*) as count
121            FROM server_backups
122            WHERE server_backups.completed IS NOT NULL AND server_backups.deleted IS NULL
123            GROUP BY server_backups.disk"#
124        )
125        .fetch_all(state.database.read())
126        .await?;
127        let servers = sqlx::query!(
128            "SELECT COUNT(*) as count
129            FROM servers"
130        )
131        .fetch_one(state.database.read())
132        .await?;
133
134        Ok(Self {
135            uuid,
136            panel: TelemetryDataPanel {
137                version: state.version.to_compact_string(),
138                container_type: state.container_type,
139                database_version: state.database.version().await?,
140                cache_version: state.cache.version().await?,
141                architecture: std::env::consts::ARCH,
142                kernel_version: sysinfo::System::kernel_long_version().into(),
143            },
144            resources: TelemetryDataResources {
145                users: TelemetryDataResourcesUsers {
146                    total: user_languages
147                        .iter()
148                        .map(|r| r.count.unwrap_or(0) as u64)
149                        .sum(),
150                    languages: user_languages
151                        .into_iter()
152                        .map(|r| (r.language.into(), r.count.unwrap_or(0) as u64))
153                        .collect(),
154                },
155                backups: TelemetryDataResourcesBackups {
156                    total: backup_disks
157                        .iter()
158                        .map(|r| r.count.unwrap_or(0) as u64)
159                        .sum(),
160                    disks: backup_disks
161                        .into_iter()
162                        .map(|r| (r.disk, r.count.unwrap_or(0) as u64))
163                        .collect(),
164                },
165                servers: TelemetryDataResourcesServers {
166                    total: servers.count.unwrap_or(0) as u64,
167                },
168            },
169            extensions: state.extensions.extensions().await.clone(),
170            nodes: node_results,
171        })
172    }
173}