From eb335b5708c9870bb15833e3e07e7bc5c0c4709c Mon Sep 17 00:00:00 2001 From: tknickman Date: Tue, 2 Jan 2024 18:47:52 -0500 Subject: [PATCH] feat(telemetry): add run and repo events --- Cargo.lock | 1 + crates/turborepo-ci/src/lib.rs | 3 +- crates/turborepo-env/src/lib.rs | 9 +++ crates/turborepo-ffi/src/lib.rs | 2 +- crates/turborepo-lib/src/run/mod.rs | 30 ++++++++- .../turborepo-lib/src/task_graph/visitor.rs | 1 + crates/turborepo-lib/src/task_hash.rs | 11 +++- crates/turborepo-scm/Cargo.toml | 1 + crates/turborepo-scm/src/package_deps.rs | 62 ++++++++++++------ .../turborepo-telemetry/src/events/generic.rs | 65 +++++++++++++++++++ crates/turborepo-telemetry/src/events/repo.rs | 28 +++++--- crates/turborepo-telemetry/src/events/task.rs | 35 ++++++++++ 12 files changed, 213 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c6386c30991a..cbe0e96989b00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11320,6 +11320,7 @@ dependencies = [ "thiserror", "tracing", "turbopath", + "turborepo-telemetry", "wax", "which", ] diff --git a/crates/turborepo-ci/src/lib.rs b/crates/turborepo-ci/src/lib.rs index ef8aeb24b7b5f..5d0034914863a 100644 --- a/crates/turborepo-ci/src/lib.rs +++ b/crates/turborepo-ci/src/lib.rs @@ -83,8 +83,7 @@ impl Vendor { None } - #[allow(dead_code)] - fn get_name() -> Option<&'static str> { + pub fn get_name() -> Option<&'static str> { Self::infer().map(|v| v.name) } diff --git a/crates/turborepo-env/src/lib.rs b/crates/turborepo-env/src/lib.rs index 80c2055c5bc60..4866781e35aba 100644 --- a/crates/turborepo-env/src/lib.rs +++ b/crates/turborepo-env/src/lib.rs @@ -21,6 +21,15 @@ pub enum ResolvedEnvMode { Strict, } +impl std::fmt::Display for ResolvedEnvMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ResolvedEnvMode::Loose => write!(f, "loose"), + ResolvedEnvMode::Strict => write!(f, "strict"), + } + } +} + #[derive(Clone, Debug, Error)] pub enum Error { #[error("Failed to parse regex: {0}")] diff --git a/crates/turborepo-ffi/src/lib.rs b/crates/turborepo-ffi/src/lib.rs index d20d0f3d4d936..6fd575ba0d85f 100644 --- a/crates/turborepo-ffi/src/lib.rs +++ b/crates/turborepo-ffi/src/lib.rs @@ -250,7 +250,7 @@ pub extern "C" fn get_package_file_hashes(buffer: Buffer) -> Buffer { }; let inputs = req.inputs.as_slice(); let hasher = turborepo_scm::SCM::new(&turbo_root); - let response = match hasher.get_package_file_hashes(&turbo_root, &package_path, inputs) { + let response = match hasher.get_package_file_hashes(&turbo_root, &package_path, inputs, None) { Ok(hashes) => { let mut to_return = HashMap::new(); for (filename, hash) in hashes { diff --git a/crates/turborepo-lib/src/run/mod.rs b/crates/turborepo-lib/src/run/mod.rs index 7f6a7c43a53f9..daf8ab4483bbc 100644 --- a/crates/turborepo-lib/src/run/mod.rs +++ b/crates/turborepo-lib/src/run/mod.rs @@ -22,7 +22,7 @@ use itertools::Itertools; use rayon::iter::ParallelBridge; use tracing::debug; use turborepo_analytics::{start_analytics, AnalyticsHandle, AnalyticsSender}; -use turborepo_api_client::{APIAuth, APIClient}; +use turborepo_api_client::{APIAuth, APIClient, Client}; use turborepo_cache::{AsyncCache, RemoteCacheOpts}; use turborepo_ci::Vendor; use turborepo_env::EnvironmentVariableMap; @@ -32,6 +32,10 @@ use turborepo_repository::{ package_json::PackageJson, }; use turborepo_scm::SCM; +use turborepo_telemetry::events::{ + generic::{DaemonInitStatus, GenericEventBuilder}, + repo::{RepoEventBuilder, RepoType}, +}; use turborepo_ui::{cprint, cprintln, ColorSelector, BOLD_GREY, GREY}; use self::task_id::TaskName; @@ -170,6 +174,8 @@ impl<'a> Run<'a> { let package_json_path = self.base.repo_root.join_component("package.json"); let root_package_json = PackageJson::load(&package_json_path)?; let mut opts = self.opts()?; + let run_telemetry = GenericEventBuilder::new(); + let repo_telemetry = RepoEventBuilder::new(&self.base.repo_root.to_string()); let config = self.base.config()?; @@ -184,16 +190,28 @@ impl<'a> Run<'a> { // value opts.cache_opts.skip_remote = !enabled; } - + run_telemetry.track_is_linked(is_linked); + // we only track the remote cache if we're linked because this defaults to + // Vercel + if is_linked { + run_telemetry.track_remote_cache(api_client.base_url()); + } let _is_structured_output = opts.run_opts.graph.is_some() || matches!(opts.run_opts.dry_run, Some(DryRunMode::Json)); let is_single_package = opts.run_opts.single_package; + repo_telemetry.track_type(if is_single_package { + RepoType::SinglePackage + } else { + RepoType::Monorepo + }); let is_ci_or_not_tty = turborepo_ci::is_ci() || !std::io::stdout().is_terminal(); + run_telemetry.track_ci(turborepo_ci::Vendor::get_name()); let daemon = match (is_ci_or_not_tty, opts.run_opts.daemon) { (true, None) => { + run_telemetry.track_daemon_init(DaemonInitStatus::Skipped); debug!("skipping turbod since we appear to be in a non-interactive context"); None } @@ -207,16 +225,19 @@ impl<'a> Run<'a> { match connector.connect().await { Ok(client) => { + run_telemetry.track_daemon_init(DaemonInitStatus::Started); debug!("running in daemon mode"); Some(client) } Err(e) => { + run_telemetry.track_daemon_init(DaemonInitStatus::Failed); debug!("failed to connect to daemon {e}"); None } } } (_, Some(false)) => { + run_telemetry.track_daemon_init(DaemonInitStatus::Disabled); debug!("skipping turbod since --no-daemon was passed"); None } @@ -236,6 +257,10 @@ impl<'a> Run<'a> { .build() .await?; + repo_telemetry.track_package_manager(pkg_dep_graph.package_manager().to_string()); + repo_telemetry.track_size(pkg_dep_graph.len()); + run_telemetry.track_run_type(opts.run_opts.dry_run.is_some()); + let root_turbo_json = TurboJson::load(&self.base.repo_root, &root_package_json, is_single_package)?; @@ -366,6 +391,7 @@ impl<'a> Run<'a> { workspaces, engine.task_definitions(), &self.base.repo_root, + &run_telemetry, )?; if opts.run_opts.parallel { diff --git a/crates/turborepo-lib/src/task_graph/visitor.rs b/crates/turborepo-lib/src/task_graph/visitor.rs index 49db6e1e92a31..50bb6bc335343 100644 --- a/crates/turborepo-lib/src/task_graph/visitor.rs +++ b/crates/turborepo-lib/src/task_graph/visitor.rs @@ -190,6 +190,7 @@ impl<'a> Visitor<'a> { EnvMode::Strict => ResolvedEnvMode::Strict, EnvMode::Loose => ResolvedEnvMode::Loose, }; + package_task_event.track_env_mode(&task_env_mode.to_string()); let dependency_set = engine.dependencies(&info).ok_or(Error::MissingDefinition)?; diff --git a/crates/turborepo-lib/src/task_hash.rs b/crates/turborepo-lib/src/task_hash.rs index 75e3c49cd8ca7..348314fb81fcc 100644 --- a/crates/turborepo-lib/src/task_hash.rs +++ b/crates/turborepo-lib/src/task_hash.rs @@ -12,7 +12,9 @@ use turborepo_cache::CacheHitMetadata; use turborepo_env::{BySource, DetailedMap, EnvironmentVariableMap, ResolvedEnvMode}; use turborepo_repository::package_graph::{WorkspaceInfo, WorkspaceName}; use turborepo_scm::SCM; -use turborepo_telemetry::events::task::PackageTaskEventBuilder; +use turborepo_telemetry::events::{ + generic::GenericEventBuilder, task::PackageTaskEventBuilder, EventBuilder, +}; use crate::{ engine::TaskNode, @@ -71,6 +73,7 @@ impl PackageInputsHashes { workspaces: HashMap<&WorkspaceName, &WorkspaceInfo>, task_definitions: &HashMap, TaskDefinition>, repo_root: &AbsoluteSystemPath, + telemetry: &GenericEventBuilder, ) -> Result { tracing::trace!(scm_manual=%scm.is_manual(), "scm running in {} mode", if scm.is_manual() { "manual" } else { "git" }); @@ -91,7 +94,11 @@ impl PackageInputsHashes { Ok(def) => def, Err(err) => return Some(Err(err)), }; + let package_task_event = + PackageTaskEventBuilder::new(task_id.package(), task_id.task()) + .with_parent(telemetry); + package_task_event.track_scm_mode(if scm.is_manual() { "manual" } else { "git" }); let workspace_name = task_id.to_workspace_name(); let pkg = match workspaces @@ -107,10 +114,12 @@ impl PackageInputsHashes { .parent() .unwrap_or_else(|| AnchoredSystemPath::new("").unwrap()); + let scm_telemetry = package_task_event.child(); let mut hash_object = match scm.get_package_file_hashes( repo_root, package_path, &task_definition.inputs, + Some(scm_telemetry), ) { Ok(hash_object) => hash_object, Err(err) => return Some(Err(err.into())), diff --git a/crates/turborepo-scm/Cargo.toml b/crates/turborepo-scm/Cargo.toml index 783d5b9c0a763..4a32d76753aab 100644 --- a/crates/turborepo-scm/Cargo.toml +++ b/crates/turborepo-scm/Cargo.toml @@ -21,6 +21,7 @@ sha1 = "0.10.5" thiserror = { workspace = true } tracing = { workspace = true } turbopath = { workspace = true } +turborepo-telemetry = { path = "../turborepo-telemetry" } wax = { workspace = true } which = { workspace = true } diff --git a/crates/turborepo-scm/src/package_deps.rs b/crates/turborepo-scm/src/package_deps.rs index 47db24fd2bf07..73763c7f01d15 100644 --- a/crates/turborepo-scm/src/package_deps.rs +++ b/crates/turborepo-scm/src/package_deps.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use itertools::{Either, Itertools}; use tracing::debug; use turbopath::{AbsoluteSystemPath, AnchoredSystemPath, PathError, RelativeUnixPathBuf}; +use turborepo_telemetry::events::task::{FileHashMethod, PackageTaskEventBuilder}; use crate::{hash_object::hash_objects, Error, Git, SCM}; @@ -28,26 +29,44 @@ impl SCM { turbo_root: &AbsoluteSystemPath, package_path: &AnchoredSystemPath, inputs: &[S], + telemetry: Option, ) -> Result { match self { - SCM::Manual => crate::manual::get_package_file_hashes_from_processing_gitignore( - turbo_root, - package_path, - inputs, - ), - SCM::Git(git) => git - .get_package_file_hashes(turbo_root, package_path, inputs) - .or_else(|e| { - debug!( - "failed to use git to hash files: {}. Falling back to manual", - e - ); - crate::manual::get_package_file_hashes_from_processing_gitignore( - turbo_root, - package_path, - inputs, - ) - }), + SCM::Manual => { + if let Some(telemetry) = telemetry { + telemetry.track_file_hash_method(FileHashMethod::Manual); + } + crate::manual::get_package_file_hashes_from_processing_gitignore( + turbo_root, + package_path, + inputs, + ) + } + SCM::Git(git) => { + let result = git.get_package_file_hashes(turbo_root, package_path, inputs); + match result { + Ok(hashes) => { + if let Some(telemetry) = telemetry { + telemetry.track_file_hash_method(FileHashMethod::Git); + } + Ok(hashes) + } + Err(err) => { + debug!( + "failed to use git to hash files: {}. Falling back to manual", + err + ); + if let Some(telemetry) = telemetry { + telemetry.track_file_hash_method(FileHashMethod::Manual); + } + crate::manual::get_package_file_hashes_from_processing_gitignore( + turbo_root, + package_path, + inputs, + ) + } + } + } } } @@ -272,7 +291,12 @@ mod tests { repo_root.join_component(".git").remove_dir_all().unwrap(); let pkg_path = repo_root.anchor(&my_pkg_dir).unwrap(); let hashes = git - .get_package_file_hashes::<&str>(&repo_root, &pkg_path, &[]) + .get_package_file_hashes::<&str>( + &repo_root, + &pkg_path, + &[], + Some(PackageTaskEventBuilder::new("my-pkg", "test")), + ) .unwrap(); let mut expected = GitHashes::new(); expected.insert( diff --git a/crates/turborepo-telemetry/src/events/generic.rs b/crates/turborepo-telemetry/src/events/generic.rs index 08c47c96b68d2..708427284d090 100644 --- a/crates/turborepo-telemetry/src/events/generic.rs +++ b/crates/turborepo-telemetry/src/events/generic.rs @@ -5,6 +5,17 @@ use uuid::Uuid; use super::{Event, EventBuilder, EventType, Identifiable}; use crate::{config::TelemetryConfig, telem}; +pub enum DaemonInitStatus { + // skipped due to context (running in CI etc) + Skipped, + /// daemon was started + Started, + /// daemon failed to start + Failed, + /// daemon was manually disabled by user + Disabled, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GenericEventBuilder { id: String, @@ -119,4 +130,58 @@ impl GenericEventBuilder { }); self } + + // run data + + pub fn track_is_linked(&self, is_linked: bool) -> &Self { + self.track(Event { + key: "is_linked".to_string(), + value: if is_linked { "true" } else { "false" }.to_string(), + is_sensitive: EventType::NonSensitive, + }); + self + } + + pub fn track_remote_cache(&self, cache_url: &str) -> &Self { + self.track(Event { + key: "remote_cache_url".to_string(), + value: cache_url.to_string(), + is_sensitive: EventType::NonSensitive, + }); + self + } + + pub fn track_ci(&self, ci: Option<&'static str>) -> &Self { + if let Some(ci) = ci { + self.track(Event { + key: "ci".to_string(), + value: ci.to_string(), + is_sensitive: EventType::NonSensitive, + }); + } + self + } + + pub fn track_run_type(&self, is_dry: bool) -> &Self { + self.track(Event { + key: "run_type".to_string(), + value: if is_dry { "dry" } else { "full" }.to_string(), + is_sensitive: EventType::NonSensitive, + }); + self + } + + pub fn track_daemon_init(&self, status: DaemonInitStatus) -> &Self { + self.track(Event { + key: "daemon_status".to_string(), + value: match status { + DaemonInitStatus::Skipped => "skipped".to_string(), + DaemonInitStatus::Started => "started".to_string(), + DaemonInitStatus::Failed => "failed".to_string(), + DaemonInitStatus::Disabled => "disabled".to_string(), + }, + is_sensitive: EventType::NonSensitive, + }); + self + } } diff --git a/crates/turborepo-telemetry/src/events/repo.rs b/crates/turborepo-telemetry/src/events/repo.rs index 770e328f5f683..76ad6577c2f5e 100644 --- a/crates/turborepo-telemetry/src/events/repo.rs +++ b/crates/turborepo-telemetry/src/events/repo.rs @@ -5,6 +5,11 @@ use uuid::Uuid; use super::{Event, EventBuilder, EventType, Identifiable}; use crate::{config::TelemetryConfig, telem}; +pub enum RepoType { + SinglePackage, + Monorepo, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RepoEventBuilder { id: String, @@ -46,36 +51,39 @@ impl EventBuilder for RepoEventBuilder { // events impl RepoEventBuilder { - pub fn new(repo: &str) -> Self { + pub fn new(repo_identifier: &str) -> Self { Self { id: Uuid::new_v4().to_string(), - repo: TelemetryConfig::one_way_hash(repo), + repo: TelemetryConfig::one_way_hash(repo_identifier), parent_id: None, } } - pub fn track_package_manager_name(&self, name: &str) -> &Self { + pub fn track_package_manager(&self, name: String) -> &Self { self.track(Event { - key: "package_manager_name".to_string(), + key: "package_manager".to_string(), value: name.to_string(), is_sensitive: EventType::NonSensitive, }); self } - pub fn track_package_manager_version(&self, version: &str) -> &Self { + pub fn track_type(&self, repo_type: RepoType) -> &Self { self.track(Event { - key: "package_manager_version".to_string(), - value: version.to_string(), + key: "repo_type".to_string(), + value: match repo_type { + RepoType::SinglePackage => "single_package".to_string(), + RepoType::Monorepo => "monorepo".to_string(), + }, is_sensitive: EventType::NonSensitive, }); self } - pub fn track_is_monorepo(&self, is_monorepo: bool) -> &Self { + pub fn track_size(&self, size: usize) -> &Self { self.track(Event { - key: "is_monorepo".to_string(), - value: is_monorepo.to_string(), + key: "workspace_count".to_string(), + value: size.to_string(), is_sensitive: EventType::NonSensitive, }); self diff --git a/crates/turborepo-telemetry/src/events/task.rs b/crates/turborepo-telemetry/src/events/task.rs index 88e68af9afaca..dc15fa74923fb 100644 --- a/crates/turborepo-telemetry/src/events/task.rs +++ b/crates/turborepo-telemetry/src/events/task.rs @@ -17,6 +17,11 @@ const ALLOWLIST: [&str; 8] = [ "check", ]; +pub enum FileHashMethod { + Git, + Manual, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PackageTaskEventBuilder { id: String, @@ -100,4 +105,34 @@ impl PackageTaskEventBuilder { }); self } + + pub fn track_env_mode(&self, mode: &str) -> &Self { + self.track(Event { + key: "env_mode".to_string(), + value: mode.to_string(), + is_sensitive: EventType::NonSensitive, + }); + self + } + + pub fn track_file_hash_method(&self, method: FileHashMethod) -> &Self { + self.track(Event { + key: "file_hash_method".to_string(), + value: match method { + FileHashMethod::Git => "git".to_string(), + FileHashMethod::Manual => "manual".to_string(), + }, + is_sensitive: EventType::NonSensitive, + }); + self + } + + pub fn track_scm_mode(&self, method: &str) -> &Self { + self.track(Event { + key: "scm_mode".to_string(), + value: method.to_string(), + is_sensitive: EventType::NonSensitive, + }); + self + } }