1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
// This file is linked to in a number of places, do not move/rename it without updating all the links!
//! All analytics events collected by the Rerun viewer are defined in this file.
//!
//! Analytics can be completely disabled with `rerun analytics disable`,
//! or by compiling rerun without the `analytics` feature flag.
//!
//! All collected analytics data is anonymized, stripping all personal identifiable information
//! as well as information about user data.
//! Read more about our analytics policy at <https://github.com/rerun-io/rerun/tree/main/crates/utils/re_analytics>.
/// Records a crash caused by a panic.
///
/// Used in `re_crash_handler`.
pub struct CrashPanic {
pub build_info: BuildInfo,
pub callstack: String,
pub message: Option<String>,
pub file_line: Option<String>,
}
/// Holds information about the user's environment.
///
/// Used in `re_viewer`.
pub struct Identify {
/// Info on how the `re_viewer` crate was built.
pub build_info: re_build_info::BuildInfo,
// If we happen to know the Python or Rust version used on the _host machine_, i.e. the
// machine running the viewer, then override the versions from `build_info`.
//
// The Python/Rust versions appearing in user profiles always apply to the host
// environment, _not_ the environment in which the data logging is taking place!
pub rust_version: Option<String>,
pub llvm_version: Option<String>,
pub python_version: Option<String>,
/// Opt-in meta-data you can set via `rerun analytics`.
///
/// For instance, Rerun employees are encouraged to set `rerun analytics email`.
/// For real users, this is usually empty.
pub opt_in_metadata: HashMap<String, Property>,
}
/// Sent when the viewer is first started.
///
/// Used in `re_viewer`.
pub struct ViewerStarted {
/// The URL on which the web viewer is running.
///
/// This will be used to populate `hashed_root_domain` property for all urls.
/// This will also populate `rerun_url` property if the url root domain is `rerun.io`.
pub url: Option<String>,
/// The environment in which the viewer is running.
pub app_env: &'static str,
}
/// Sent when a new recording is opened.
///
/// Used in `re_viewer`.
pub struct OpenRecording {
/// The URL on which the web viewer is running.
///
/// This will be used to populate `hashed_root_domain` property for all urls.
/// This will also populate `rerun_url` property if the url root domain is `rerun.io`.
pub url: Option<String>,
/// The environment in which the viewer is running.
pub app_env: &'static str,
pub store_info: Option<StoreInfo>,
/// How data is being loaded into the viewer.
pub data_source: Option<&'static str>,
}
/// Basic information about a recording's chunk store.
pub struct StoreInfo {
/// Name of the application.
///
/// In case the recording does not come from an official example, the id is hashed.
pub application_id: Id,
/// Name of the recording.
///
/// In case the recording does not come from an official example, the id is hashed.
pub recording_id: Id,
/// Where data is being logged.
pub store_source: String,
/// The Rerun version that was used to encode the RRD data.
pub store_version: String,
// Various versions of the host environment.
pub rust_version: Option<String>,
pub llvm_version: Option<String>,
pub python_version: Option<String>,
// Whether or not the data is coming from one of the Rerun example applications.
pub is_official_example: bool,
pub app_id_starts_with_rerun_example: bool,
}
#[derive(Clone)]
pub enum Id {
/// When running an example application we record the full id.
Official(String),
/// For user applications we hash the id.
Hashed(Property),
}
/// Sent when a Wasm file is served.
///
/// Used in `re_web_viewer_server`.
pub struct ServeWasm;
impl Event for ServeWasm {
const NAME: &'static str = "serve_wasm";
}
impl Properties for ServeWasm {
// No properties.
}
// ----------------------------------------------------------------------------
use std::collections::HashMap;
use re_build_info::BuildInfo;
use url::Url;
use crate::{AnalyticsEvent, Event, EventKind, Properties, Property};
impl From<Id> for Property {
fn from(val: Id) -> Self {
match val {
Id::Official(id) => Self::String(id),
Id::Hashed(id) => id,
}
}
}
impl Event for Identify {
const NAME: &'static str = "$identify";
const KIND: EventKind = EventKind::Update;
}
impl Properties for Identify {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
build_info,
rust_version,
llvm_version,
python_version,
opt_in_metadata,
} = self;
build_info.serialize(event);
event.insert_opt("rust_version", rust_version);
event.insert_opt("llvm_version", llvm_version);
event.insert_opt("python_version", python_version);
for (name, value) in opt_in_metadata {
event.insert(name, value);
}
}
}
impl Event for ViewerStarted {
const NAME: &'static str = "viewer_started";
}
const RERUN_DOMAINS: [&str; 1] = ["rerun.io"];
/// Given a URL, extract the root domain.
fn extract_root_domain(url: &str) -> Option<String> {
let parsed = Url::parse(url).ok()?;
let domain = parsed.domain()?;
let parts = domain.split('.').collect::<Vec<_>>();
if parts.len() >= 2 {
Some(parts[parts.len() - 2..].join("."))
} else {
None
}
}
fn add_sanitized_url_properties(event: &mut AnalyticsEvent, url: Option<String>) {
let Some(root_domain) = url.as_ref().and_then(|url| extract_root_domain(url)) else {
return;
};
if RERUN_DOMAINS.contains(&root_domain.as_str()) {
event.insert_opt("rerun_url", url);
}
let hashed = Property::from(root_domain).hashed();
event.insert("hashed_root_domain", hashed);
}
impl Properties for ViewerStarted {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self { url, app_env } = self;
event.insert("app_env", app_env);
add_sanitized_url_properties(event, url);
}
}
impl Event for OpenRecording {
const NAME: &'static str = "open_recording";
}
impl Properties for OpenRecording {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
url,
app_env,
store_info,
data_source,
} = self;
add_sanitized_url_properties(event, url);
event.insert("app_env", app_env);
if let Some(store_info) = store_info {
let StoreInfo {
application_id,
recording_id,
store_source,
store_version,
rust_version,
llvm_version,
python_version,
is_official_example,
app_id_starts_with_rerun_example,
} = store_info;
event.insert("application_id", application_id);
event.insert("recording_id", recording_id);
event.insert("store_source", store_source);
event.insert("store_version", store_version);
event.insert_opt("rust_version", rust_version);
event.insert_opt("llvm_version", llvm_version);
event.insert_opt("python_version", python_version);
event.insert("is_official_example", is_official_example);
event.insert(
"app_id_starts_with_rerun_example",
app_id_starts_with_rerun_example,
);
}
if let Some(data_source) = data_source {
event.insert("data_source", data_source);
}
}
}
impl Event for CrashPanic {
const NAME: &'static str = "crash-panic";
}
impl Properties for CrashPanic {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
build_info,
callstack,
message,
file_line,
} = self;
build_info.serialize(event);
event.insert("callstack", callstack);
event.insert_opt("message", message);
event.insert_opt("file_line", file_line);
}
}
pub struct CrashSignal {
pub build_info: BuildInfo,
pub signal: String,
pub callstack: String,
}
impl Event for CrashSignal {
const NAME: &'static str = "crash-signal";
}
impl Properties for CrashSignal {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
build_info,
signal,
callstack,
} = self;
build_info.serialize(event);
event.insert("signal", signal.clone());
event.insert("callstack", callstack.clone());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_root_domain() {
// Valid urls
assert_eq!(
extract_root_domain("https://rerun.io"),
Some("rerun.io".to_owned())
);
assert_eq!(
extract_root_domain("https://ReRun.io"),
Some("rerun.io".to_owned())
);
assert_eq!(
extract_root_domain("http://app.rerun.io"),
Some("rerun.io".to_owned())
);
assert_eq!(
extract_root_domain("https://www.rerun.io/viewer?url=https://app.rerun.io/version/0.15.1/examples/detect_and_track_objects.rrd"),
Some("rerun.io".to_owned())
);
// Local domains
assert_eq!(
extract_root_domain("http://localhost:9090/?url=ws://localhost:9877"),
None
);
assert_eq!(
extract_root_domain("http://127.0.0.1:9090/?url=ws://localhost:9877"),
None
);
// Invalid urls
assert_eq!(extract_root_domain("rerun.io"), None);
assert_eq!(extract_root_domain("https:/rerun"), None);
assert_eq!(extract_root_domain("https://rerun"), None);
}
}