use serde::Serialize;
use std::env;
#[derive(Debug, Clone, Serialize)]
pub struct Telemetry {
pub sdk_name: &'static str,
pub sdk_version: &'static str,
pub os_name: String,
pub os_version: String,
pub platform: &'static str,
pub device_type: &'static str,
pub architecture: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub cpu_cores: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_gb: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timezone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub app_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub app_build: Option<String>,
}
impl Telemetry {
pub fn collect(app_version: Option<String>, app_build: Option<String>) -> Self {
Self {
sdk_name: crate::SDK_NAME,
sdk_version: crate::VERSION,
os_name: os_name(),
os_version: os_version(),
platform: platform(),
device_type: device_type(),
architecture: architecture(),
cpu_cores: num_cpus(),
memory_gb: memory_gb(),
locale: locale(),
language: language(),
timezone: timezone(),
app_version,
app_build,
}
}
}
fn os_name() -> String {
#[cfg(target_os = "macos")]
return "macOS".to_string();
#[cfg(target_os = "windows")]
return "Windows".to_string();
#[cfg(target_os = "linux")]
return "Linux".to_string();
#[cfg(target_os = "ios")]
return "iOS".to_string();
#[cfg(target_os = "android")]
return "Android".to_string();
#[cfg(not(any(
target_os = "macos",
target_os = "windows",
target_os = "linux",
target_os = "ios",
target_os = "android"
)))]
return env::consts::OS.to_string();
}
fn os_version() -> String {
#[cfg(target_os = "macos")]
{
if let Ok(output) = std::process::Command::new("sw_vers")
.arg("-productVersion")
.output()
{
if output.status.success() {
if let Ok(version) = String::from_utf8(output.stdout) {
return version.trim().to_string();
}
}
}
}
#[cfg(target_os = "windows")]
{
if let Ok(output) = std::process::Command::new("cmd")
.args(["/C", "ver"])
.output()
{
if output.status.success() {
if let Ok(version) = String::from_utf8(output.stdout) {
if let Some(start) = version.find('[') {
if let Some(end) = version.find(']') {
return version[start + 1..end]
.replace("Version ", "")
.trim()
.to_string();
}
}
}
}
}
}
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/etc/os-release") {
for line in content.lines() {
if line.starts_with("VERSION_ID=") {
return line
.trim_start_matches("VERSION_ID=")
.trim_matches('"')
.to_string();
}
}
}
if let Ok(output) = std::process::Command::new("uname").arg("-r").output() {
if output.status.success() {
if let Ok(version) = String::from_utf8(output.stdout) {
return version.trim().to_string();
}
}
}
}
"unknown".to_string()
}
fn platform() -> &'static str {
"native"
}
fn device_type() -> &'static str {
#[cfg(target_os = "macos")]
return "desktop";
#[cfg(target_os = "windows")]
return "desktop";
#[cfg(target_os = "linux")]
return "desktop";
#[cfg(target_os = "ios")]
{
return "phone";
}
#[cfg(target_os = "android")]
{
return "phone";
}
#[cfg(target_os = "tvos")]
return "tv";
#[cfg(target_os = "watchos")]
return "watch";
#[cfg(not(any(
target_os = "macos",
target_os = "windows",
target_os = "linux",
target_os = "ios",
target_os = "android",
target_os = "tvos",
target_os = "watchos"
)))]
return "desktop";
}
fn architecture() -> &'static str {
env::consts::ARCH
}
fn num_cpus() -> Option<usize> {
std::thread::available_parallelism().ok().map(|p| p.get())
}
fn memory_gb() -> Option<u64> {
#[cfg(target_os = "macos")]
{
if let Ok(output) = std::process::Command::new("sysctl")
.args(["-n", "hw.memsize"])
.output()
{
if output.status.success() {
if let Ok(mem_str) = String::from_utf8(output.stdout) {
if let Ok(bytes) = mem_str.trim().parse::<u64>() {
return Some(bytes / (1024 * 1024 * 1024)); }
}
}
}
}
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/meminfo") {
for line in content.lines() {
if line.starts_with("MemTotal:") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
if let Ok(kb) = parts[1].parse::<u64>() {
return Some(kb / (1024 * 1024)); }
}
}
}
}
}
#[cfg(target_os = "windows")]
{
if let Ok(output) = std::process::Command::new("wmic")
.args(["ComputerSystem", "get", "TotalPhysicalMemory"])
.output()
{
if output.status.success() {
if let Ok(mem_str) = String::from_utf8(output.stdout) {
for line in mem_str.lines().skip(1) {
if let Ok(bytes) = line.trim().parse::<u64>() {
return Some(bytes / (1024 * 1024 * 1024));
}
}
}
}
}
}
None
}
fn locale() -> Option<String> {
env::var("LANG").ok().or_else(|| env::var("LC_ALL").ok())
}
fn language() -> Option<String> {
locale().and_then(|l| {
l.split('_')
.next()
.map(|s| s.split('.').next().unwrap_or(s).to_string())
})
}
fn timezone() -> Option<String> {
env::var("TZ").ok()
}
#[allow(dead_code)]
pub fn generate_device_id() -> String {
crate::device::generate_device_id()
}