Skip to content

Commit

Permalink
Send anonymized telemetry to scarf.sh
Browse files Browse the repository at this point in the history
  • Loading branch information
jackkleeman committed Jan 10, 2025
1 parent 558f2dd commit f7f2db9
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/service-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.

use crate::http::HttpClient;
pub use crate::http::HttpClient;
use crate::lambda::LambdaClient;

pub use crate::http::HttpError;
Expand Down
7 changes: 7 additions & 0 deletions crates/types/src/config/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ pub struct CommonOptions {
#[serde(with = "serde_with::As::<serde_with::DisplayFromStr>")]
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
pub initialization_timeout: humantime::Duration,

/// # Disable telemetry
///
/// Restate uses Scarf to collect anonymous usage data to help us understand how the software is being used.
/// You can set this flag to true to disable this collection. It can also be set with the environment variable DO_NOT_TRACK=true.
pub disable_telemetry: bool,
}

impl CommonOptions {
Expand Down Expand Up @@ -382,6 +388,7 @@ impl Default for CommonOptions {
Some(Duration::from_secs(5)),
),
initialization_timeout: Duration::from_secs(5 * 60).into(),
disable_telemetry: false,
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions crates/types/src/config_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ impl ConfigLoader {
Env::raw()
.only(&["MEMORY_LIMIT"])
.map(|_| "rocksdb-total-memory-limit".into()),
)
.merge(
Env::raw()
.only(&["DO_NOT_TRACK"])
.map(|_| "disable-telemetry".into()),
);

if let Some(no_proxy) = Env::var("NO_PROXY") {
Expand Down
4 changes: 4 additions & 0 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ restate-errors = { workspace = true }
restate-fs-util = { workspace = true }
restate-node = { workspace = true }
restate-rocksdb = { workspace = true }
restate-service-client = { workspace = true }
restate-tracing-instrumentation = { workspace = true, features = ["rt-tokio"] }
restate-types = { workspace = true, features = ["clap"] }
restate-worker = { workspace = true }
Expand All @@ -47,6 +48,8 @@ derive_builder = { workspace = true }
enumset = { workspace = true }
futures-util = { workspace = true }
humantime = { workspace = true }
http = { workspace = true }
http-body-util = { workspace = true }
pin-project = { workspace = true }
rlimit = { workspace = true }
rocksdb = { workspace = true }
Expand All @@ -60,6 +63,7 @@ toml = { version = "0.8.12" }
tracing = { workspace = true }
tracing-panic = { version = "0.1.2" }
regex = "1.10.4"
ulid = { workspace = true }
url = { version = "2.5.4", features = [] }
workspace-hack = { version = "0.1", path = "../workspace-hack" }

Expand Down
5 changes: 5 additions & 0 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use restate_types::config::{node_dir, Configuration};
use restate_types::config_loader::ConfigLoaderBuilder;

mod signal;
mod telemetry;

use restate_node::Node;
#[cfg(not(target_env = "msvc"))]
Expand Down Expand Up @@ -212,6 +213,10 @@ fn main() {
.expect("Error when trying to wipe the configured storage path");
}

// Initialize telemetry
let telemetry = telemetry::Telemetry::create(&Configuration::pinned().common);
telemetry.start();

let node = Node::create(Configuration::updateable()).await;
if let Err(err) = node {
handle_error(err);
Expand Down
124 changes: 124 additions & 0 deletions server/src/telemetry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright (c) 2023 - 2025 Restate Software, Inc., Restate GmbH.
// All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.

use std::{str::FromStr, time::Duration};

use http::{uri::PathAndQuery, HeaderMap, HeaderValue, Uri};
use restate_core::{cancellation_watcher, TaskCenter, TaskKind};
use restate_service_client::HttpClient;
use restate_types::config::CommonOptions;
use tokio::time::Instant;
use tracing::debug;

use crate::build_info::{
RESTATE_SERVER_DEBUG, RESTATE_SERVER_TARGET_TRIPLE, RESTATE_SERVER_VERSION,
};

static TELEMETRY_URI: &str = "https://restate.gateway.scarf.sh/restate-server/";
const TELEMETRY_PERIOD: Duration = Duration::from_secs(3600 * 24);

pub enum Telemetry {
Enabled(TelemetryEnabled),
Disabled,
}

pub struct TelemetryEnabled {
client: HttpClient,
user_agent: String,
start_time: Instant,
}

impl Telemetry {
pub fn create(options: &CommonOptions) -> Self {
if options.disable_telemetry {
Self::Disabled
} else {
let client = HttpClient::from_options(&options.service_client.http);
let session_id = ulid::Ulid::new().to_string();
let user_agent = format!("restate-server {session_id}");

Self::Enabled(TelemetryEnabled {
client,
user_agent,
start_time: Instant::now(),
})
}
}

pub fn start(self) {
match self {
Telemetry::Disabled => {}
Telemetry::Enabled(enabled) => {
if let Err(err) =
TaskCenter::spawn_child(TaskKind::RoleRunner, "telemetry-service", async {
let cancelled = cancellation_watcher();

tokio::select! {
result = enabled.run() => {
result
}
_ = cancelled =>{
}
}

Ok(())
})
{
debug!(error = %err, "Failed to start telemetry service");
}
}
}
}
}

impl TelemetryEnabled {
async fn run(self) {
let mut interval = tokio::time::interval(TELEMETRY_PERIOD);
loop {
interval.tick().await; // first tick completes immediately
self.send_telemetry().await;
}
}

async fn send_telemetry(&self) {
let uptime_hours =
(Instant::now().duration_since(self.start_time).as_secs() / 3600).to_string();

let uri = Uri::from_str(&format!(
"{TELEMETRY_URI}?target={RESTATE_SERVER_TARGET_TRIPLE}&version={RESTATE_SERVER_VERSION}&debug={RESTATE_SERVER_DEBUG}&uptime={uptime_hours}"
)).expect("uri must parse");

debug!(%uri, user_agent = %self.user_agent, "Sending telemetry data");

match self
.client
.request(
uri,
None,
http::Method::GET,
http_body_util::Empty::new(),
PathAndQuery::from_static("/"),
HeaderMap::from_iter([(
http::header::USER_AGENT,
HeaderValue::from_str(&self.user_agent)
.expect("user agent must be a valid header value"),
)]),
)
.await
{
Ok(resp) => {
debug!(status = %resp.status(), "Sent telemetry data")
}
Err(err) => {
debug!(error = %err, "Failed to send telemetry data")
}
}
}
}

0 comments on commit f7f2db9

Please sign in to comment.