report to new stats server (with status) (#1912)

* docstrings for things that I was curious about

* report to new stats server, with status

* Arc<Self> needs a clone

* fix stats url in docs

* fix stats url handling
This commit is contained in:
David Laban 2024-09-11 09:15:15 +01:00 committed by GitHub
parent a88335d05b
commit c8dec953cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 71 additions and 11 deletions

View file

@ -250,8 +250,8 @@ pub struct Args {
/// Disable statistics collection on popular crates. /// Disable statistics collection on popular crates.
/// ///
/// Strategy quick-install (can be disabled via --disable-strategies) collects /// Strategy quick-install (can be disabled via --disable-strategies) collects
/// statistics of popular crates by default, by sending the crate, version and /// statistics of popular crates by default, by sending the crate, version, target
/// target to https://warehouse-clerk-tmp.vercel.app/api/crate /// and status to https://cargo-quickinstall-stats-server.fly.dev/record-install
#[clap(help_heading = "Options", long, env = "BINSTALL_DISABLE_TELEMETRY")] #[clap(help_heading = "Options", long, env = "BINSTALL_DISABLE_TELEMETRY")]
pub(crate) disable_telemetry: bool, pub(crate) disable_telemetry: bool,
@ -675,7 +675,7 @@ mod test {
.unwrap() .unwrap()
.to_string(); .to_string();
assert!( assert!(
long_help.ends_with(binstalk::QUICK_INSTALL_STATS_URL), long_help.ends_with(binstalk::QUICKINSTALL_STATS_URL),
"{}", "{}",
long_help long_help
); );

View file

@ -24,6 +24,9 @@ use crate::FetchError;
static WARN_RATE_LIMIT_ONCE: Once = Once::new(); static WARN_RATE_LIMIT_ONCE: Once = Once::new();
static WARN_UNAUTHORIZED_ONCE: Once = Once::new(); static WARN_UNAUTHORIZED_ONCE: Once = Once::new();
/// Return Ok(Some(api_artifact_url)) if exists, or Ok(None) if it doesn't.
///
/// Caches info on all artifacts matching (repo, tag).
pub(super) async fn get_gh_release_artifact_url( pub(super) async fn get_gh_release_artifact_url(
gh_api_client: GhApiClient, gh_api_client: GhApiClient,
artifact: GhReleaseArtifact, artifact: GhReleaseArtifact,
@ -54,6 +57,10 @@ pub(super) async fn get_gh_release_artifact_url(
} }
} }
/// Check if the URL exists by querying the GitHub API.
///
/// Caches info on all artifacts matching (repo, tag).
///
/// This function returns a future where its size should be at most size of /// This function returns a future where its size should be at most size of
/// 2-4 pointers. /// 2-4 pointers.
pub(super) async fn does_url_exist( pub(super) async fn does_url_exist(

View file

@ -1,7 +1,7 @@
use std::{ use std::{
borrow::Cow, borrow::Cow,
path::Path, path::Path,
sync::{Arc, OnceLock}, sync::{Arc, Mutex, OnceLock},
}; };
use binstalk_downloader::remote::Method; use binstalk_downloader::remote::Method;
@ -16,7 +16,8 @@ use crate::{
}; };
const BASE_URL: &str = "https://github.com/cargo-bins/cargo-quickinstall/releases/download"; const BASE_URL: &str = "https://github.com/cargo-bins/cargo-quickinstall/releases/download";
pub const QUICK_INSTALL_STATS_URL: &str = "https://warehouse-clerk-tmp.vercel.app/api/crate"; pub const QUICKINSTALL_STATS_URL: &str =
"https://cargo-quickinstall-stats-server.fly.dev/record-install";
const QUICKINSTALL_SIGN_KEY: Cow<'static, str> = const QUICKINSTALL_SIGN_KEY: Cow<'static, str> =
Cow::Borrowed("RWTdnnab2pAka9OdwgCMYyOE66M/BlQoFWaJ/JjwcPV+f3n24IRTj97t"); Cow::Borrowed("RWTdnnab2pAka9OdwgCMYyOE66M/BlQoFWaJ/JjwcPV+f3n24IRTj97t");
@ -58,15 +59,39 @@ pub struct QuickInstall {
gh_api_client: GhApiClient, gh_api_client: GhApiClient,
is_supported_v: OnceCell<bool>, is_supported_v: OnceCell<bool>,
data: Arc<Data>,
package: String, package: String,
package_url: Url, package_url: Url,
signature_url: Url, signature_url: Url,
stats_url: Url,
signature_policy: SignaturePolicy, signature_policy: SignaturePolicy,
target_data: Arc<TargetDataErased>, target_data: Arc<TargetDataErased>,
signature_verifier: OnceLock<SignatureVerifier>, signature_verifier: OnceLock<SignatureVerifier>,
status: Mutex<Status>,
}
#[derive(Debug, Clone, Copy)]
enum Status {
Start,
NotFound,
Found,
AttemptingInstall,
InvalidSignature,
InstalledFromTarball,
}
impl Status {
fn as_str(&self) -> &'static str {
match self {
Status::Start => "start",
Status::NotFound => "not-found",
Status::Found => "found",
Status::AttemptingInstall => "attempting-install",
Status::InvalidSignature => "invalid-signature",
Status::InstalledFromTarball => "installed-from-tarball",
}
}
} }
impl QuickInstall { impl QuickInstall {
@ -116,6 +141,14 @@ impl QuickInstall {
} }
}) })
} }
fn get_status(&self) -> Status {
*self.status.lock().unwrap()
}
fn set_status(&self, status: Status) {
*self.status.lock().unwrap() = status;
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -137,6 +170,7 @@ impl super::Fetcher for QuickInstall {
Arc::new(Self { Arc::new(Self {
client, client,
data,
gh_api_client, gh_api_client,
is_supported_v: OnceCell::new(), is_supported_v: OnceCell::new(),
@ -144,14 +178,13 @@ impl super::Fetcher for QuickInstall {
.expect("package_url is pre-generated and should never be invalid url"), .expect("package_url is pre-generated and should never be invalid url"),
signature_url: Url::parse(&format!("{url}.sig")) signature_url: Url::parse(&format!("{url}.sig"))
.expect("signature_url is pre-generated and should never be invalid url"), .expect("signature_url is pre-generated and should never be invalid url"),
stats_url: Url::parse(&format!("{QUICK_INSTALL_STATS_URL}/{package}.tar.gz",))
.expect("stats_url is pre-generated and should never be invalid url"),
package, package,
signature_policy, signature_policy,
target_data, target_data,
signature_verifier: OnceLock::new(), signature_verifier: OnceLock::new(),
status: Mutex::new(Status::Start),
}) })
} }
@ -171,6 +204,7 @@ impl super::Fetcher for QuickInstall {
.await?; .await?;
if !is_found { if !is_found {
self.set_status(Status::NotFound);
return Ok(false); return Ok(false);
} }
@ -182,6 +216,7 @@ impl super::Fetcher for QuickInstall {
panic!("<QuickInstall as Fetcher>::find is run twice"); panic!("<QuickInstall as Fetcher>::find is run twice");
} }
self.set_status(Status::Found);
Ok(true) Ok(true)
}) })
} }
@ -209,6 +244,7 @@ by rust officially."#,
} }
async fn fetch_and_extract(&self, dst: &Path) -> Result<ExtractedFiles, FetchError> { async fn fetch_and_extract(&self, dst: &Path) -> Result<ExtractedFiles, FetchError> {
self.set_status(Status::AttemptingInstall);
let Some(verifier) = self.signature_verifier.get() else { let Some(verifier) = self.signature_verifier.get() else {
panic!("<QuickInstall as Fetcher>::find has not been called yet!") panic!("<QuickInstall as Fetcher>::find has not been called yet!")
}; };
@ -227,8 +263,10 @@ by rust officially."#,
if let Some(info) = verifier.info() { if let Some(info) = verifier.info() {
info!("Verified signature for package '{}': {info}", self.package); info!("Verified signature for package '{}': {info}", self.package);
} }
self.set_status(Status::InstalledFromTarball);
Ok(files) Ok(files)
} else { } else {
self.set_status(Status::InvalidSignature);
Err(FetchError::InvalidSignature) Err(FetchError::InvalidSignature)
} }
} }
@ -280,10 +318,20 @@ impl QuickInstall {
return Ok(()); return Ok(());
} }
let url = self.stats_url.clone(); let mut url = Url::parse(QUICKINSTALL_STATS_URL)
.expect("stats_url is pre-generated and should never be invalid url");
url.query_pairs_mut()
.append_pair("crate", &self.data.name)
.append_pair("version", &self.data.version)
.append_pair("target", &self.target_data.target)
.append_pair(
"agent",
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")),
)
.append_pair("status", self.get_status().as_str());
debug!("Sending installation report to quickinstall ({url})"); debug!("Sending installation report to quickinstall ({url})");
self.client.request(Method::HEAD, url).send(true).await?; self.client.request(Method::POST, url).send(true).await?;
Ok(()) Ok(())
} }

View file

@ -248,6 +248,8 @@ pub struct GhReleaseArtifactUrl(Url);
impl GhApiClient { impl GhApiClient {
/// Return `Ok(Some(api_artifact_url))` if exists. /// Return `Ok(Some(api_artifact_url))` if exists.
/// ///
/// Caches info on all artifacts matching (repo, tag).
///
/// The returned future is guaranteed to be pointer size. /// The returned future is guaranteed to be pointer size.
#[instrument(skip(self), ret(level = Level::DEBUG))] #[instrument(skip(self), ret(level = Level::DEBUG))]
pub async fn has_release_artifact( pub async fn has_release_artifact(

View file

@ -10,4 +10,4 @@ pub use binstalk_registry as registry;
pub use binstalk_types as manifests; pub use binstalk_types as manifests;
pub use detect_targets::{get_desired_targets, DesiredTargets, TARGET}; pub use detect_targets::{get_desired_targets, DesiredTargets, TARGET};
pub use fetchers::QUICK_INSTALL_STATS_URL; pub use fetchers::QUICKINSTALL_STATS_URL;

View file

@ -203,6 +203,7 @@ async fn resolve_inner(
{ {
Ok(bin_files) => { Ok(bin_files) => {
if !bin_files.is_empty() { if !bin_files.is_empty() {
fetcher.clone().report_to_upstream();
return Ok(Resolution::Fetch(Box::new(ResolutionFetch { return Ok(Resolution::Fetch(Box::new(ResolutionFetch {
fetcher: fetcher.clone(), fetcher: fetcher.clone(),
new_version: package_info.version, new_version: package_info.version,
@ -250,6 +251,8 @@ async fn resolve_inner(
} }
} }
// At this point, we don't know whether fallback to cargo install is allowed, or whether it will
// succeed, but things start to get convoluted when try to include that data, so this will do.
if !opts.disable_telemetry { if !opts.disable_telemetry {
for fetcher in handles { for fetcher in handles {
fetcher.report_to_upstream(); fetcher.report_to_upstream();