Skip to content

Commit b639e0d

Browse files
authored
new: Add toolchain usage telemetry. (#2045)
* Update url. * Add method. * Change to an instance. * Switch to singleton. * Remove checks. * Change to post. * switch to map. * Add checks.
1 parent 992d293 commit b639e0d

11 files changed

Lines changed: 162 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#### ⚙️ Internal
1717

18+
- Added telemetry for toolchain usage.
1819
- Updated Rust to v1.88.0.
1920

2021
## 1.38.3

crates/action-pipeline/src/action_pipeline.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::subscribers::console_subscriber::ConsoleSubscriber;
77
use crate::subscribers::notifications_subscriber::NotificationsSubscriber;
88
use crate::subscribers::remote_subscriber::RemoteSubscriber;
99
use crate::subscribers::reports_subscriber::ReportsSubscriber;
10+
use crate::subscribers::telemetry_subscriber::TelemetrySubscriber;
1011
use crate::subscribers::webhooks_subscriber::WebhooksSubscriber;
1112
use miette::IntoDiagnostic;
1213
use moon_action::{Action, ActionNode, ActionPipelineStatus};
@@ -389,6 +390,8 @@ impl ActionPipeline {
389390
.await;
390391
}
391392

393+
debug!("Subscribing remote services");
394+
392395
self.emitter.subscribe(RemoteSubscriber).await;
393396

394397
debug!("Subscribing run reports and estimates");
@@ -454,6 +457,16 @@ impl ActionPipeline {
454457
))
455458
.await;
456459
}
460+
461+
if self.app_context.workspace_config.telemetry {
462+
debug!("Subscribing telemetry");
463+
464+
self.emitter
465+
.subscribe(TelemetrySubscriber::new(Arc::clone(
466+
&self.app_context.toolchain_config,
467+
)))
468+
.await;
469+
}
457470
}
458471
}
459472

crates/action-pipeline/src/subscribers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ pub mod console_subscriber;
33
pub mod notifications_subscriber;
44
pub mod remote_subscriber;
55
pub mod reports_subscriber;
6+
pub mod telemetry_subscriber;
67
pub mod webhooks_subscriber;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use crate::event_emitter::{Event, Subscriber};
2+
use async_trait::async_trait;
3+
use moon_api::Launchpad;
4+
use moon_common::is_ci;
5+
use moon_config::ToolchainConfig;
6+
use std::collections::BTreeMap;
7+
use std::sync::Arc;
8+
9+
pub struct TelemetrySubscriber {
10+
toolchain_config: Arc<ToolchainConfig>,
11+
}
12+
13+
impl TelemetrySubscriber {
14+
pub fn new(toolchain_config: Arc<ToolchainConfig>) -> Self {
15+
Self { toolchain_config }
16+
}
17+
}
18+
19+
#[async_trait]
20+
impl Subscriber for TelemetrySubscriber {
21+
async fn on_emit<'data>(&mut self, event: &Event<'data>) -> miette::Result<()> {
22+
// Only capture toolchain usage in CI
23+
if !is_ci() {
24+
return Ok(());
25+
}
26+
27+
if matches!(event, Event::PipelineStarted { .. }) {
28+
let mut toolchains = BTreeMap::default();
29+
30+
for (id, plugin) in &self.toolchain_config.plugins {
31+
if let Some(locator) = &plugin.plugin {
32+
toolchains.insert(id.to_string(), locator.to_string());
33+
}
34+
}
35+
36+
for platform in self.toolchain_config.get_enabled_platforms() {
37+
toolchains.insert(platform.to_string().to_lowercase(), "legacy".to_owned());
38+
}
39+
40+
if !toolchains.is_empty() {
41+
let _ = Launchpad::instance()
42+
.track_toolchain_usage(toolchains)
43+
.await;
44+
}
45+
}
46+
47+
Ok(())
48+
}
49+
}

crates/api/src/launchpad.rs

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
use miette::IntoDiagnostic;
22
use moon_cache::{CacheEngine, cache_item};
3-
use moon_common::{consts::CONFIG_DIRNAME, is_test_env};
3+
use moon_common::{consts::CONFIG_DIRNAME, is_ci, is_test_env};
44
use moon_env::MoonEnvironment;
55
use moon_env_var::GlobalEnvBag;
66
use moon_time::now_millis;
77
use semver::Version;
88
use serde::{Deserialize, Serialize};
99
use starbase_utils::{fs, json};
10-
use std::env::{self, consts};
10+
use std::collections::BTreeMap;
11+
use std::env::consts;
1112
use std::path::Path;
13+
use std::sync::{Arc, OnceLock};
1214
use std::time::Duration;
1315
use tracing::{debug, instrument};
1416
use uuid::Uuid;
1517

18+
static INSTANCE: OnceLock<Arc<Launchpad>> = OnceLock::new();
19+
1620
const ALERT_PAUSE_DURATION: Duration = Duration::from_secs(43200); // 12 hours
1721

1822
#[derive(Debug, Default, Deserialize, Serialize)]
@@ -30,6 +34,11 @@ cache_item!(
3034
}
3135
);
3236

37+
#[derive(Serialize)]
38+
pub struct ToolchainUsage {
39+
pub toolchains: BTreeMap<String, String>,
40+
}
41+
3342
fn load_or_create_anonymous_uid(id_path: &Path) -> miette::Result<String> {
3443
if id_path.exists() {
3544
return Ok(fs::read_file(id_path)?);
@@ -60,13 +69,43 @@ pub struct VersionCheck {
6069
pub update_available: bool,
6170
}
6271

63-
pub struct Launchpad;
72+
pub struct Launchpad {
73+
#[allow(dead_code)]
74+
moon_env: Arc<MoonEnvironment>,
75+
moon_version: String,
76+
user_id: String,
77+
repo_id: Option<String>,
78+
}
6479

6580
impl Launchpad {
81+
pub fn register(moon_env: Arc<MoonEnvironment>) -> miette::Result<()> {
82+
let user_id = load_or_create_anonymous_uid(&moon_env.id_file)?;
83+
84+
let repo_id = fs::find_upwards(CONFIG_DIRNAME, &moon_env.working_dir)
85+
.map(|dir| create_anonymous_rid(dir.parent().unwrap()));
86+
87+
let moon_version = GlobalEnvBag::instance()
88+
.get("MOON_VERSION")
89+
.unwrap_or_default();
90+
91+
let _ = INSTANCE.set(Arc::new(Self {
92+
moon_env,
93+
moon_version,
94+
user_id,
95+
repo_id,
96+
}));
97+
98+
Ok(())
99+
}
100+
101+
pub fn instance() -> Arc<Launchpad> {
102+
Arc::clone(INSTANCE.get().unwrap())
103+
}
104+
66105
#[instrument(skip_all)]
67106
pub async fn check_version(
107+
&self,
68108
cache_engine: &CacheEngine,
69-
moon_env: &MoonEnvironment,
70109
bypass_cache: bool,
71110
manifest_url: &str,
72111
) -> miette::Result<Option<VersionCheck>> {
@@ -81,7 +120,7 @@ impl Launchpad {
81120
}
82121
}
83122

84-
if let Some(result) = Self::check_version_without_cache(moon_env, manifest_url).await? {
123+
if let Some(result) = self.check_version_without_cache(manifest_url).await? {
85124
state.data.last_check_time = Some(now);
86125
state.data.local_version = Some(result.local_version.clone());
87126
state.data.remote_version = Some(result.remote_version.clone());
@@ -94,54 +133,33 @@ impl Launchpad {
94133
}
95134

96135
pub async fn check_version_without_cache(
97-
moon_env: &MoonEnvironment,
136+
&self,
98137
manifest_url: &str,
99138
) -> miette::Result<Option<VersionCheck>> {
100139
if is_test_env() || proto_core::is_offline() {
101140
return Ok(None);
102141
}
103142

104-
let version = GlobalEnvBag::instance()
105-
.get("MOON_VERSION")
106-
.unwrap_or_default();
143+
let version = &self.moon_version;
107144

108145
debug!(
109146
current_version = &version,
110147
manifest_url = manifest_url,
111148
"Checking for a new version of moon"
112149
);
113150

114-
let mut client = reqwest::Client::new()
115-
.get(manifest_url)
116-
.header("X-Moon-OS", consts::OS.to_owned())
117-
.header("X-Moon-Arch", consts::ARCH.to_owned())
118-
.header("X-Moon-Version", &version)
119-
.header("X-Moon-CI", ci_env::is_ci().to_string())
151+
let request = self
152+
.create_request(manifest_url)?
120153
.header(
121154
"X-Moon-CI-Provider",
122155
format!("{:?}", ci_env::detect_provider()),
123156
)
124-
.header("X-Moon-CD", cd_env::is_cd().to_string())
125157
.header(
126158
"X-Moon-CD-Provider",
127159
format!("{:?}", cd_env::detect_provider()),
128-
)
129-
.header(
130-
"X-Moon-UID",
131-
load_or_create_anonymous_uid(&moon_env.id_file)?,
132-
);
133-
134-
if let Some(moon_dir) = fs::find_upwards(
135-
CONFIG_DIRNAME,
136-
env::current_dir().expect("Invalid working directory!"),
137-
) {
138-
client = client.header(
139-
"X-Moon-RID",
140-
create_anonymous_rid(moon_dir.parent().unwrap()),
141160
);
142-
}
143161

144-
let Ok(response) = client.send().await else {
162+
let Ok(response) = request.send().await else {
145163
return Ok(None);
146164
};
147165

@@ -150,7 +168,7 @@ impl Launchpad {
150168
};
151169

152170
let data: CurrentVersion = json::parse(text)?;
153-
let local_version = Version::parse(&version).into_diagnostic()?;
171+
let local_version = Version::parse(version).into_diagnostic()?;
154172
let remote_version = Version::parse(&data.current_version).into_diagnostic()?;
155173
let update_available = remote_version > local_version;
156174

@@ -168,4 +186,38 @@ impl Launchpad {
168186
update_available,
169187
}))
170188
}
189+
190+
pub async fn track_toolchain_usage(
191+
&self,
192+
toolchains: BTreeMap<String, String>,
193+
) -> miette::Result<()> {
194+
if !is_ci() || is_test_env() || proto_core::is_offline() {
195+
return Ok(());
196+
}
197+
198+
let request = self
199+
.create_request("https://launch.moonrepo.app/moon/toolchain_usage")?
200+
.json(&ToolchainUsage { toolchains });
201+
202+
let _response = request.send().await.into_diagnostic()?;
203+
204+
Ok(())
205+
}
206+
207+
fn create_request(&self, url: &str) -> miette::Result<reqwest::RequestBuilder> {
208+
let mut client = reqwest::Client::new()
209+
.post(url)
210+
.header("X-Moon-OS", consts::OS.to_owned())
211+
.header("X-Moon-Arch", consts::ARCH.to_owned())
212+
.header("X-Moon-Version", self.moon_version.clone())
213+
.header("X-Moon-CI", ci_env::is_ci().to_string())
214+
.header("X-Moon-CD", cd_env::is_cd().to_string())
215+
.header("X-Moon-UID", self.user_id.clone());
216+
217+
if let Some(repo_id) = &self.repo_id {
218+
client = client.header("X-Moon-RID", repo_id.to_owned());
219+
}
220+
221+
Ok(client)
222+
}
171223
}

crates/app/src/commands/upgrade.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,9 @@ pub async fn upgrade(session: MoonSession) -> AppResult {
3232
return Err(AppError::UpgradeRequiresInternet.into());
3333
}
3434

35-
let remote_version = match Launchpad::check_version_without_cache(
36-
&session.moon_env,
37-
&session.toolchain_config.moon.manifest_url,
38-
)
39-
.await
35+
let remote_version = match Launchpad::instance()
36+
.check_version_without_cache(&session.toolchain_config.moon.manifest_url)
37+
.await
4038
{
4139
Ok(Some(result)) if result.update_available => result.remote_version,
4240
Ok(_) => {

crates/app/src/session.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::components::*;
44
use crate::systems::*;
55
use async_trait::async_trait;
66
use moon_action_graph::{ActionGraphBuilder, ActionGraphBuilderOptions};
7+
use moon_api::Launchpad;
78
use moon_app_context::AppContext;
89
use moon_cache::CacheEngine;
910
use moon_common::is_formatted_output;
@@ -317,10 +318,11 @@ impl AppSession for MoonSession {
317318
self.tasks_config = tasks_config;
318319
}
319320

320-
// Load components
321+
// Load singleton components
321322

322323
startup::register_feature_flags(&self.workspace_config)?;
323324

325+
Launchpad::register(self.moon_env.clone())?;
324326
ProcessRegistry::register(self.workspace_config.pipeline.kill_process_threshold);
325327

326328
Ok(None)
@@ -376,7 +378,6 @@ impl AppSession for MoonSession {
376378

377379
execute::check_for_new_version(
378380
&self.console,
379-
&self.moon_env,
380381
&cache_engine,
381382
&self.toolchain_config.moon.manifest_url,
382383
)

crates/app/src/systems/execute.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@ use moon_api::Launchpad;
22
use moon_cache::CacheEngine;
33
use moon_common::{color, is_formatted_output, is_test_env};
44
use moon_console::{Checkpoint, Console};
5-
use moon_env::MoonEnvironment;
65
use starbase::AppResult;
76
use tracing::{debug, instrument};
87

98
#[instrument(skip_all)]
109
pub async fn check_for_new_version(
1110
console: &Console,
12-
moon_env: &MoonEnvironment,
1311
cache_engine: &CacheEngine,
1412
manifest_url: &str,
1513
) -> AppResult {
1614
if is_test_env() || is_formatted_output() {
1715
return Ok(None);
1816
}
1917

20-
match Launchpad::check_version(cache_engine, moon_env, false, manifest_url).await {
18+
match Launchpad::instance()
19+
.check_version(cache_engine, false, manifest_url)
20+
.await
21+
{
2122
Ok(Some(result)) => {
2223
if !result.update_available {
2324
return Ok(None);

crates/config/src/toolchain/moon_config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ config_struct!(
66
#[derive(Config)]
77
pub struct MoonConfig {
88
/// A secure URL to lookup the latest version.
9-
#[setting(validate = validate::url_secure, default = "https://launch.moonrepo.app/versions/cli/current")]
9+
#[setting(validate = validate::url_secure, default = "https://launch.moonrepo.app/moon/check_version")]
1010
pub manifest_url: String,
1111

1212
/// A secure URL for downloading the moon binary.

packages/types/src/toolchain-config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export interface MoonConfig {
111111
/**
112112
* A secure URL to lookup the latest version.
113113
*
114-
* @default 'https://launch.moonrepo.app/versions/cli/current'
114+
* @default 'https://launch.moonrepo.app/moon/check_version'
115115
*/
116116
manifestUrl?: string;
117117
}
@@ -481,7 +481,7 @@ export interface PartialMoonConfig {
481481
/**
482482
* A secure URL to lookup the latest version.
483483
*
484-
* @default 'https://launch.moonrepo.app/versions/cli/current'
484+
* @default 'https://launch.moonrepo.app/moon/check_version'
485485
*/
486486
manifestUrl?: string | null;
487487
}

0 commit comments

Comments
 (0)