Add system_id (#18040)
This PR adds `system_id` to telemetry, which is contained within a new `global` database (accessible by any release channel of Zed on a single system). This will help us get a more accurate understanding of user count, instead of relying on `installationd_id`, which is different per release channel. This doesn't solve the problem of a user with multiple machines, but it gets us closer. Release Notes: - N/A
This commit is contained in:
parent
5e6d1814e5
commit
ca4980df02
8 changed files with 184 additions and 62 deletions
|
@ -37,9 +37,10 @@ pub struct Telemetry {
|
||||||
|
|
||||||
struct TelemetryState {
|
struct TelemetryState {
|
||||||
settings: TelemetrySettings,
|
settings: TelemetrySettings,
|
||||||
metrics_id: Option<Arc<str>>, // Per logged-in user
|
system_id: Option<Arc<str>>, // Per system
|
||||||
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
||||||
session_id: Option<String>, // Per app launch
|
session_id: Option<String>, // Per app launch
|
||||||
|
metrics_id: Option<Arc<str>>, // Per logged-in user
|
||||||
release_channel: Option<&'static str>,
|
release_channel: Option<&'static str>,
|
||||||
architecture: &'static str,
|
architecture: &'static str,
|
||||||
events_queue: Vec<EventWrapper>,
|
events_queue: Vec<EventWrapper>,
|
||||||
|
@ -191,9 +192,10 @@ impl Telemetry {
|
||||||
settings: *TelemetrySettings::get_global(cx),
|
settings: *TelemetrySettings::get_global(cx),
|
||||||
architecture: env::consts::ARCH,
|
architecture: env::consts::ARCH,
|
||||||
release_channel,
|
release_channel,
|
||||||
|
system_id: None,
|
||||||
installation_id: None,
|
installation_id: None,
|
||||||
metrics_id: None,
|
|
||||||
session_id: None,
|
session_id: None,
|
||||||
|
metrics_id: None,
|
||||||
events_queue: Vec::new(),
|
events_queue: Vec::new(),
|
||||||
flush_events_task: None,
|
flush_events_task: None,
|
||||||
log_file: None,
|
log_file: None,
|
||||||
|
@ -283,11 +285,13 @@ impl Telemetry {
|
||||||
|
|
||||||
pub fn start(
|
pub fn start(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
session_id: String,
|
session_id: String,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
|
state.system_id = system_id.map(|id| id.into());
|
||||||
state.installation_id = installation_id.map(|id| id.into());
|
state.installation_id = installation_id.map(|id| id.into());
|
||||||
state.session_id = Some(session_id);
|
state.session_id = Some(session_id);
|
||||||
state.app_version = release_channel::AppVersion::global(cx).to_string();
|
state.app_version = release_channel::AppVersion::global(cx).to_string();
|
||||||
|
@ -637,9 +641,10 @@ impl Telemetry {
|
||||||
let state = this.state.lock();
|
let state = this.state.lock();
|
||||||
|
|
||||||
let request_body = EventRequestBody {
|
let request_body = EventRequestBody {
|
||||||
|
system_id: state.system_id.as_deref().map(Into::into),
|
||||||
installation_id: state.installation_id.as_deref().map(Into::into),
|
installation_id: state.installation_id.as_deref().map(Into::into),
|
||||||
metrics_id: state.metrics_id.as_deref().map(Into::into),
|
|
||||||
session_id: state.session_id.clone(),
|
session_id: state.session_id.clone(),
|
||||||
|
metrics_id: state.metrics_id.as_deref().map(Into::into),
|
||||||
is_staff: state.is_staff,
|
is_staff: state.is_staff,
|
||||||
app_version: state.app_version.clone(),
|
app_version: state.app_version.clone(),
|
||||||
os_name: state.os_name.clone(),
|
os_name: state.os_name.clone(),
|
||||||
|
@ -711,6 +716,7 @@ mod tests {
|
||||||
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
||||||
));
|
));
|
||||||
let http = FakeHttpClient::with_200_response();
|
let http = FakeHttpClient::with_200_response();
|
||||||
|
let system_id = Some("system_id".to_string());
|
||||||
let installation_id = Some("installation_id".to_string());
|
let installation_id = Some("installation_id".to_string());
|
||||||
let session_id = "session_id".to_string();
|
let session_id = "session_id".to_string();
|
||||||
|
|
||||||
|
@ -718,7 +724,7 @@ mod tests {
|
||||||
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
||||||
|
|
||||||
telemetry.state.lock().max_queue_size = 4;
|
telemetry.state.lock().max_queue_size = 4;
|
||||||
telemetry.start(installation_id, session_id, cx);
|
telemetry.start(system_id, installation_id, session_id, cx);
|
||||||
|
|
||||||
assert!(is_empty_state(&telemetry));
|
assert!(is_empty_state(&telemetry));
|
||||||
|
|
||||||
|
@ -796,13 +802,14 @@ mod tests {
|
||||||
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
|
||||||
));
|
));
|
||||||
let http = FakeHttpClient::with_200_response();
|
let http = FakeHttpClient::with_200_response();
|
||||||
|
let system_id = Some("system_id".to_string());
|
||||||
let installation_id = Some("installation_id".to_string());
|
let installation_id = Some("installation_id".to_string());
|
||||||
let session_id = "session_id".to_string();
|
let session_id = "session_id".to_string();
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
let telemetry = Telemetry::new(clock.clone(), http, cx);
|
||||||
telemetry.state.lock().max_queue_size = 4;
|
telemetry.state.lock().max_queue_size = 4;
|
||||||
telemetry.start(installation_id, session_id, cx);
|
telemetry.start(system_id, installation_id, session_id, cx);
|
||||||
|
|
||||||
assert!(is_empty_state(&telemetry));
|
assert!(is_empty_state(&telemetry));
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,8 @@ pub async fn post_crash(
|
||||||
installation_id = %installation_id,
|
installation_id = %installation_id,
|
||||||
description = %description,
|
description = %description,
|
||||||
backtrace = %summary,
|
backtrace = %summary,
|
||||||
"crash report");
|
"crash report"
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
|
if let Some(slack_panics_webhook) = app.config.slack_panics_webhook.clone() {
|
||||||
let payload = slack::WebhookBody::new(|w| {
|
let payload = slack::WebhookBody::new(|w| {
|
||||||
|
@ -627,7 +628,9 @@ where
|
||||||
|
|
||||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||||
pub struct EditorEventRow {
|
pub struct EditorEventRow {
|
||||||
|
system_id: String,
|
||||||
installation_id: String,
|
installation_id: String,
|
||||||
|
session_id: Option<String>,
|
||||||
metrics_id: String,
|
metrics_id: String,
|
||||||
operation: String,
|
operation: String,
|
||||||
app_version: String,
|
app_version: String,
|
||||||
|
@ -647,7 +650,6 @@ pub struct EditorEventRow {
|
||||||
historical_event: bool,
|
historical_event: bool,
|
||||||
architecture: String,
|
architecture: String,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
session_id: Option<String>,
|
|
||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
@ -677,9 +679,10 @@ impl EditorEventRow {
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
architecture: body.architecture.clone(),
|
architecture: body.architecture.clone(),
|
||||||
|
system_id: body.system_id.clone().unwrap_or_default(),
|
||||||
installation_id: body.installation_id.clone().unwrap_or_default(),
|
installation_id: body.installation_id.clone().unwrap_or_default(),
|
||||||
metrics_id: body.metrics_id.clone().unwrap_or_default(),
|
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
|
metrics_id: body.metrics_id.clone().unwrap_or_default(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
time: time.timestamp_millis(),
|
time: time.timestamp_millis(),
|
||||||
operation: event.operation,
|
operation: event.operation,
|
||||||
|
@ -699,6 +702,7 @@ impl EditorEventRow {
|
||||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||||
pub struct InlineCompletionEventRow {
|
pub struct InlineCompletionEventRow {
|
||||||
installation_id: String,
|
installation_id: String,
|
||||||
|
session_id: Option<String>,
|
||||||
provider: String,
|
provider: String,
|
||||||
suggestion_accepted: bool,
|
suggestion_accepted: bool,
|
||||||
app_version: String,
|
app_version: String,
|
||||||
|
@ -713,7 +717,6 @@ pub struct InlineCompletionEventRow {
|
||||||
city: String,
|
city: String,
|
||||||
time: i64,
|
time: i64,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
session_id: Option<String>,
|
|
||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
patch: Option<i32>,
|
patch: Option<i32>,
|
||||||
|
@ -879,7 +882,9 @@ impl AssistantEventRow {
|
||||||
|
|
||||||
#[derive(Debug, clickhouse::Row, Serialize)]
|
#[derive(Debug, clickhouse::Row, Serialize)]
|
||||||
pub struct CpuEventRow {
|
pub struct CpuEventRow {
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
|
session_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
usage_as_percentage: f32,
|
usage_as_percentage: f32,
|
||||||
core_count: u32,
|
core_count: u32,
|
||||||
|
@ -888,7 +893,6 @@ pub struct CpuEventRow {
|
||||||
os_name: String,
|
os_name: String,
|
||||||
os_version: String,
|
os_version: String,
|
||||||
time: i64,
|
time: i64,
|
||||||
session_id: Option<String>,
|
|
||||||
// pub normalized_cpu_usage: f64, MATERIALIZED
|
// pub normalized_cpu_usage: f64, MATERIALIZED
|
||||||
major: Option<i32>,
|
major: Option<i32>,
|
||||||
minor: Option<i32>,
|
minor: Option<i32>,
|
||||||
|
@ -917,6 +921,7 @@ impl CpuEventRow {
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
|
@ -940,6 +945,7 @@ pub struct MemoryEventRow {
|
||||||
os_version: String,
|
os_version: String,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
|
@ -971,6 +977,7 @@ impl MemoryEventRow {
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
|
@ -994,6 +1001,7 @@ pub struct AppEventRow {
|
||||||
os_version: String,
|
os_version: String,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
|
@ -1024,6 +1032,7 @@ impl AppEventRow {
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
|
@ -1046,6 +1055,7 @@ pub struct SettingEventRow {
|
||||||
os_version: String,
|
os_version: String,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
|
@ -1076,6 +1086,7 @@ impl SettingEventRow {
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
|
@ -1099,6 +1110,7 @@ pub struct ExtensionEventRow {
|
||||||
os_version: String,
|
os_version: String,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
session_id: Option<String>,
|
session_id: Option<String>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
|
@ -1134,6 +1146,7 @@ impl ExtensionEventRow {
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
|
@ -1224,6 +1237,7 @@ pub struct EditEventRow {
|
||||||
os_version: String,
|
os_version: String,
|
||||||
|
|
||||||
// ClientEventBase
|
// ClientEventBase
|
||||||
|
system_id: Option<String>,
|
||||||
installation_id: Option<String>,
|
installation_id: Option<String>,
|
||||||
// Note: This column name has a typo in the ClickHouse table.
|
// Note: This column name has a typo in the ClickHouse table.
|
||||||
#[serde(rename = "sesssion_id")]
|
#[serde(rename = "sesssion_id")]
|
||||||
|
@ -1261,6 +1275,7 @@ impl EditEventRow {
|
||||||
release_channel: body.release_channel.clone().unwrap_or_default(),
|
release_channel: body.release_channel.clone().unwrap_or_default(),
|
||||||
os_name: body.os_name.clone(),
|
os_name: body.os_name.clone(),
|
||||||
os_version: body.os_version.clone().unwrap_or_default(),
|
os_version: body.os_version.clone().unwrap_or_default(),
|
||||||
|
system_id: body.system_id.clone(),
|
||||||
installation_id: body.installation_id.clone(),
|
installation_id: body.installation_id.clone(),
|
||||||
session_id: body.session_id.clone(),
|
session_id: body.session_id.clone(),
|
||||||
is_staff: body.is_staff,
|
is_staff: body.is_staff,
|
||||||
|
|
|
@ -11,16 +11,14 @@ pub use smol;
|
||||||
pub use sqlez;
|
pub use sqlez;
|
||||||
pub use sqlez_macros;
|
pub use sqlez_macros;
|
||||||
|
|
||||||
use release_channel::ReleaseChannel;
|
|
||||||
pub use release_channel::RELEASE_CHANNEL;
|
pub use release_channel::RELEASE_CHANNEL;
|
||||||
use sqlez::domain::Migrator;
|
use sqlez::domain::Migrator;
|
||||||
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||||
use sqlez_macros::sql;
|
use sqlez_macros::sql;
|
||||||
use std::env;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::{atomic::Ordering, LazyLock};
|
||||||
use std::sync::LazyLock;
|
use std::{env, sync::atomic::AtomicBool};
|
||||||
use util::{maybe, ResultExt};
|
use util::{maybe, ResultExt};
|
||||||
|
|
||||||
const CONNECTION_INITIALIZE_QUERY: &str = sql!(
|
const CONNECTION_INITIALIZE_QUERY: &str = sql!(
|
||||||
|
@ -47,16 +45,12 @@ pub static ALL_FILE_DB_FAILED: LazyLock<AtomicBool> = LazyLock::new(|| AtomicBoo
|
||||||
/// This will retry a couple times if there are failures. If opening fails once, the db directory
|
/// This will retry a couple times if there are failures. If opening fails once, the db directory
|
||||||
/// is moved to a backup folder and a new one is created. If that fails, a shared in memory db is created.
|
/// is moved to a backup folder and a new one is created. If that fails, a shared in memory db is created.
|
||||||
/// In either case, static variables are set so that the user can be notified.
|
/// In either case, static variables are set so that the user can be notified.
|
||||||
pub async fn open_db<M: Migrator + 'static>(
|
pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, scope: &str) -> ThreadSafeConnection<M> {
|
||||||
db_dir: &Path,
|
|
||||||
release_channel: &ReleaseChannel,
|
|
||||||
) -> ThreadSafeConnection<M> {
|
|
||||||
if *ZED_STATELESS {
|
if *ZED_STATELESS {
|
||||||
return open_fallback_db().await;
|
return open_fallback_db().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let release_channel_name = release_channel.dev_name();
|
let main_db_dir = db_dir.join(format!("0-{}", scope));
|
||||||
let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
|
|
||||||
|
|
||||||
let connection = maybe!(async {
|
let connection = maybe!(async {
|
||||||
smol::fs::create_dir_all(&main_db_dir)
|
smol::fs::create_dir_all(&main_db_dir)
|
||||||
|
@ -118,7 +112,7 @@ pub async fn open_test_db<M: Migrator>(db_name: &str) -> ThreadSafeConnection<M>
|
||||||
/// Implements a basic DB wrapper for a given domain
|
/// Implements a basic DB wrapper for a given domain
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! define_connection {
|
macro_rules! define_connection {
|
||||||
(pub static ref $id:ident: $t:ident<()> = $migrations:expr;) => {
|
(pub static ref $id:ident: $t:ident<()> = $migrations:expr; $($global:ident)?) => {
|
||||||
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>);
|
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>);
|
||||||
|
|
||||||
impl ::std::ops::Deref for $t {
|
impl ::std::ops::Deref for $t {
|
||||||
|
@ -139,18 +133,23 @@ macro_rules! define_connection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub static $id: LazyLock<$t> = LazyLock::new(|| {
|
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||||
$t($crate::smol::block_on($crate::open_test_db(stringify!($id))))
|
$t($crate::smol::block_on($crate::open_test_db(stringify!($id))))
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(not(any(test, feature = "test-support")))]
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
pub static $id: LazyLock<$t> = LazyLock::new(|| {
|
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||||
$t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
|
let db_dir = $crate::database_dir();
|
||||||
|
let scope = if false $(|| stringify!($global) == "global")? {
|
||||||
|
"global"
|
||||||
|
} else {
|
||||||
|
$crate::RELEASE_CHANNEL.dev_name()
|
||||||
|
};
|
||||||
|
$t($crate::smol::block_on($crate::open_db(db_dir, scope)))
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => {
|
(pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr; $($global:ident)?) => {
|
||||||
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>);
|
pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>);
|
||||||
|
|
||||||
impl ::std::ops::Deref for $t {
|
impl ::std::ops::Deref for $t {
|
||||||
|
@ -178,7 +177,13 @@ macro_rules! define_connection {
|
||||||
|
|
||||||
#[cfg(not(any(test, feature = "test-support")))]
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
pub static $id: std::sync::LazyLock<$t> = std::sync::LazyLock::new(|| {
|
||||||
$t($crate::smol::block_on($crate::open_db($crate::database_dir(), &$crate::RELEASE_CHANNEL)))
|
let db_dir = $crate::database_dir();
|
||||||
|
let scope = if false $(|| stringify!($global) == "global")? {
|
||||||
|
"global"
|
||||||
|
} else {
|
||||||
|
$crate::RELEASE_CHANNEL.dev_name()
|
||||||
|
};
|
||||||
|
$t($crate::smol::block_on($crate::open_db(db_dir, scope)))
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -225,7 +230,11 @@ mod tests {
|
||||||
.prefix("DbTests")
|
.prefix("DbTests")
|
||||||
.tempdir()
|
.tempdir()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let _bad_db = open_db::<BadDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
let _bad_db = open_db::<BadDB>(
|
||||||
|
tempdir.path(),
|
||||||
|
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that DB exists but corrupted (causing recreate)
|
/// Test that DB exists but corrupted (causing recreate)
|
||||||
|
@ -262,13 +271,19 @@ mod tests {
|
||||||
.tempdir()
|
.tempdir()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
{
|
{
|
||||||
let corrupt_db =
|
let corrupt_db = open_db::<CorruptedDB>(
|
||||||
open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
tempdir.path(),
|
||||||
|
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
assert!(corrupt_db.persistent());
|
assert!(corrupt_db.persistent());
|
||||||
}
|
}
|
||||||
|
|
||||||
let good_db =
|
let good_db = open_db::<GoodDB>(
|
||||||
open_db::<GoodDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
tempdir.path(),
|
||||||
|
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
assert!(
|
assert!(
|
||||||
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -311,8 +326,11 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
{
|
{
|
||||||
// Setup the bad database
|
// Setup the bad database
|
||||||
let corrupt_db =
|
let corrupt_db = open_db::<CorruptedDB>(
|
||||||
open_db::<CorruptedDB>(tempdir.path(), &release_channel::ReleaseChannel::Dev).await;
|
tempdir.path(),
|
||||||
|
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
assert!(corrupt_db.persistent());
|
assert!(corrupt_db.persistent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,7 +341,7 @@ mod tests {
|
||||||
let guard = thread::spawn(move || {
|
let guard = thread::spawn(move || {
|
||||||
let good_db = smol::block_on(open_db::<GoodDB>(
|
let good_db = smol::block_on(open_db::<GoodDB>(
|
||||||
tmp_path.as_path(),
|
tmp_path.as_path(),
|
||||||
&release_channel::ReleaseChannel::Dev,
|
&release_channel::ReleaseChannel::Dev.dev_name(),
|
||||||
));
|
));
|
||||||
assert!(
|
assert!(
|
||||||
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
|
||||||
|
|
|
@ -60,3 +60,33 @@ mod tests {
|
||||||
assert_eq!(db.read_kvp("key-1").unwrap(), None);
|
assert_eq!(db.read_kvp("key-1").unwrap(), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
define_connection!(pub static ref GLOBAL_KEY_VALUE_STORE: GlobalKeyValueStore<()> =
|
||||||
|
&[sql!(
|
||||||
|
CREATE TABLE IF NOT EXISTS kv_store(
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
) STRICT;
|
||||||
|
)];
|
||||||
|
global
|
||||||
|
);
|
||||||
|
|
||||||
|
impl GlobalKeyValueStore {
|
||||||
|
query! {
|
||||||
|
pub fn read_kvp(key: &str) -> Result<Option<String>> {
|
||||||
|
SELECT value FROM kv_store WHERE key = (?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query! {
|
||||||
|
pub async fn write_kvp(key: String, value: String) -> Result<()> {
|
||||||
|
INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query! {
|
||||||
|
pub async fn delete_kvp(key: String) -> Result<()> {
|
||||||
|
DELETE FROM kv_store WHERE key = (?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -44,8 +44,8 @@ const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
|
||||||
struct FeedbackRequestBody<'a> {
|
struct FeedbackRequestBody<'a> {
|
||||||
feedback_text: &'a str,
|
feedback_text: &'a str,
|
||||||
email: Option<String>,
|
email: Option<String>,
|
||||||
metrics_id: Option<Arc<str>>,
|
|
||||||
installation_id: Option<Arc<str>>,
|
installation_id: Option<Arc<str>>,
|
||||||
|
metrics_id: Option<Arc<str>>,
|
||||||
system_specs: SystemSpecs,
|
system_specs: SystemSpecs,
|
||||||
is_staff: bool,
|
is_staff: bool,
|
||||||
}
|
}
|
||||||
|
@ -296,16 +296,16 @@ impl FeedbackModal {
|
||||||
}
|
}
|
||||||
|
|
||||||
let telemetry = zed_client.telemetry();
|
let telemetry = zed_client.telemetry();
|
||||||
let metrics_id = telemetry.metrics_id();
|
|
||||||
let installation_id = telemetry.installation_id();
|
let installation_id = telemetry.installation_id();
|
||||||
|
let metrics_id = telemetry.metrics_id();
|
||||||
let is_staff = telemetry.is_staff();
|
let is_staff = telemetry.is_staff();
|
||||||
let http_client = zed_client.http_client();
|
let http_client = zed_client.http_client();
|
||||||
let feedback_endpoint = http_client.build_url("/api/feedback");
|
let feedback_endpoint = http_client.build_url("/api/feedback");
|
||||||
let request = FeedbackRequestBody {
|
let request = FeedbackRequestBody {
|
||||||
feedback_text,
|
feedback_text,
|
||||||
email,
|
email,
|
||||||
metrics_id,
|
|
||||||
installation_id,
|
installation_id,
|
||||||
|
metrics_id,
|
||||||
system_specs,
|
system_specs,
|
||||||
is_staff: is_staff.unwrap_or(false),
|
is_staff: is_staff.unwrap_or(false),
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,12 +5,14 @@ use std::{fmt::Display, sync::Arc, time::Duration};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct EventRequestBody {
|
pub struct EventRequestBody {
|
||||||
|
/// Identifier unique to each system Zed is installed on
|
||||||
|
pub system_id: Option<String>,
|
||||||
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
|
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
|
||||||
pub installation_id: Option<String>,
|
pub installation_id: Option<String>,
|
||||||
/// Identifier unique to each logged in Zed user (randomly generated on first sign in)
|
/// Identifier unique to each logged in Zed user (randomly generated on first sign in)
|
||||||
pub metrics_id: Option<String>,
|
|
||||||
/// Identifier unique to each Zed session (differs for each time you open Zed)
|
/// Identifier unique to each Zed session (differs for each time you open Zed)
|
||||||
pub session_id: Option<String>,
|
pub session_id: Option<String>,
|
||||||
|
pub metrics_id: Option<String>,
|
||||||
/// True for Zed staff, otherwise false
|
/// True for Zed staff, otherwise false
|
||||||
pub is_staff: Option<bool>,
|
pub is_staff: Option<bool>,
|
||||||
/// Zed version number
|
/// Zed version number
|
||||||
|
@ -34,6 +36,7 @@ pub struct EventWrapper {
|
||||||
pub signed_in: bool,
|
pub signed_in: bool,
|
||||||
/// Duration between this event's timestamp and the timestamp of the first event in the current batch
|
/// Duration between this event's timestamp and the timestamp of the first event in the current batch
|
||||||
pub milliseconds_since_first_event: i64,
|
pub milliseconds_since_first_event: i64,
|
||||||
|
/// The event itself
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub event: Event,
|
pub event: Event,
|
||||||
}
|
}
|
||||||
|
@ -245,8 +248,11 @@ pub struct Panic {
|
||||||
pub architecture: String,
|
pub architecture: String,
|
||||||
/// The time the panic occurred (UNIX millisecond timestamp)
|
/// The time the panic occurred (UNIX millisecond timestamp)
|
||||||
pub panicked_on: i64,
|
pub panicked_on: i64,
|
||||||
|
/// Identifier unique to each system Zed is installed on
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub system_id: Option<String>,
|
||||||
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
|
/// Identifier unique to each Zed installation (differs for stable, preview, dev)
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub installation_id: Option<String>,
|
pub installation_id: Option<String>,
|
||||||
/// Identifier unique to each Zed session (differs for each time you open Zed)
|
/// Identifier unique to each Zed session (differs for each time you open Zed)
|
||||||
pub session_id: String,
|
pub session_id: String,
|
||||||
|
|
|
@ -13,7 +13,7 @@ use clap::{command, Parser};
|
||||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||||
use client::{parse_zed_link, Client, DevServerToken, ProxySettings, UserStore};
|
use client::{parse_zed_link, Client, DevServerToken, ProxySettings, UserStore};
|
||||||
use collab_ui::channel_view::ChannelView;
|
use collab_ui::channel_view::ChannelView;
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use env_logger::Builder;
|
use env_logger::Builder;
|
||||||
use fs::{Fs, RealFs};
|
use fs::{Fs, RealFs};
|
||||||
|
@ -334,19 +334,17 @@ fn main() {
|
||||||
.with_assets(Assets)
|
.with_assets(Assets)
|
||||||
.with_http_client(IsahcHttpClient::new(None, None));
|
.with_http_client(IsahcHttpClient::new(None, None));
|
||||||
|
|
||||||
let (installation_id, existing_installation_id_found) = app
|
let system_id = app.background_executor().block(system_id()).ok();
|
||||||
.background_executor()
|
let installation_id = app.background_executor().block(installation_id()).ok();
|
||||||
.block(installation_id())
|
let session_id = Uuid::new_v4().to_string();
|
||||||
.ok()
|
|
||||||
.unzip();
|
|
||||||
|
|
||||||
let session = app.background_executor().block(Session::new());
|
let session = app.background_executor().block(Session::new());
|
||||||
|
|
||||||
let app_version = AppVersion::init(env!("CARGO_PKG_VERSION"));
|
let app_version = AppVersion::init(env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
reliability::init_panic_hook(
|
reliability::init_panic_hook(
|
||||||
installation_id.clone(),
|
|
||||||
app_version,
|
app_version,
|
||||||
session.id().to_owned(),
|
system_id.as_ref().map(|id| id.to_string()),
|
||||||
|
installation_id.as_ref().map(|id| id.to_string()),
|
||||||
|
session_id.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let (open_listener, mut open_rx) = OpenListener::new();
|
let (open_listener, mut open_rx) = OpenListener::new();
|
||||||
|
@ -491,14 +489,26 @@ fn main() {
|
||||||
client::init(&client, cx);
|
client::init(&client, cx);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
let telemetry = client.telemetry();
|
let telemetry = client.telemetry();
|
||||||
telemetry.start(installation_id.clone(), session.id().to_owned(), cx);
|
telemetry.start(
|
||||||
telemetry.report_app_event(
|
system_id.as_ref().map(|id| id.to_string()),
|
||||||
match existing_installation_id_found {
|
installation_id.as_ref().map(|id| id.to_string()),
|
||||||
Some(false) => "first open",
|
session_id,
|
||||||
_ => "open",
|
cx,
|
||||||
}
|
|
||||||
.to_string(),
|
|
||||||
);
|
);
|
||||||
|
if let (Some(system_id), Some(installation_id)) = (&system_id, &installation_id) {
|
||||||
|
match (&system_id, &installation_id) {
|
||||||
|
(IdType::New(_), IdType::New(_)) => {
|
||||||
|
telemetry.report_app_event("first open".to_string());
|
||||||
|
telemetry.report_app_event("first open for release channel".to_string());
|
||||||
|
}
|
||||||
|
(IdType::Existing(_), IdType::New(_)) => {
|
||||||
|
telemetry.report_app_event("first open for release channel".to_string());
|
||||||
|
}
|
||||||
|
(_, IdType::Existing(_)) => {
|
||||||
|
telemetry.report_app_event("open".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let app_session = cx.new_model(|cx| AppSession::new(session, cx));
|
let app_session = cx.new_model(|cx| AppSession::new(session, cx));
|
||||||
|
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
|
@ -514,7 +524,11 @@ fn main() {
|
||||||
AppState::set_global(Arc::downgrade(&app_state), cx);
|
AppState::set_global(Arc::downgrade(&app_state), cx);
|
||||||
|
|
||||||
auto_update::init(client.http_client(), cx);
|
auto_update::init(client.http_client(), cx);
|
||||||
reliability::init(client.http_client(), installation_id, cx);
|
reliability::init(
|
||||||
|
client.http_client(),
|
||||||
|
installation_id.clone().map(|id| id.to_string()),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
let prompt_builder = init_common(app_state.clone(), cx);
|
let prompt_builder = init_common(app_state.clone(), cx);
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
@ -755,7 +769,23 @@ async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn installation_id() -> Result<(String, bool)> {
|
async fn system_id() -> Result<IdType> {
|
||||||
|
let key_name = "system_id".to_string();
|
||||||
|
|
||||||
|
if let Ok(Some(system_id)) = GLOBAL_KEY_VALUE_STORE.read_kvp(&key_name) {
|
||||||
|
return Ok(IdType::Existing(system_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
let system_id = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
GLOBAL_KEY_VALUE_STORE
|
||||||
|
.write_kvp(key_name, system_id.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(IdType::New(system_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_id() -> Result<IdType> {
|
||||||
let legacy_key_name = "device_id".to_string();
|
let legacy_key_name = "device_id".to_string();
|
||||||
let key_name = "installation_id".to_string();
|
let key_name = "installation_id".to_string();
|
||||||
|
|
||||||
|
@ -765,11 +795,11 @@ async fn installation_id() -> Result<(String, bool)> {
|
||||||
.write_kvp(key_name, installation_id.clone())
|
.write_kvp(key_name, installation_id.clone())
|
||||||
.await?;
|
.await?;
|
||||||
KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
|
KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
|
||||||
return Ok((installation_id, true));
|
return Ok(IdType::Existing(installation_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
|
if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
|
||||||
return Ok((installation_id, true));
|
return Ok(IdType::Existing(installation_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
let installation_id = Uuid::new_v4().to_string();
|
let installation_id = Uuid::new_v4().to_string();
|
||||||
|
@ -778,7 +808,7 @@ async fn installation_id() -> Result<(String, bool)> {
|
||||||
.write_kvp(key_name, installation_id.clone())
|
.write_kvp(key_name, installation_id.clone())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok((installation_id, false))
|
Ok(IdType::New(installation_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn restore_or_create_workspace(
|
async fn restore_or_create_workspace(
|
||||||
|
@ -1087,6 +1117,20 @@ struct Args {
|
||||||
dev_server_token: Option<String>,
|
dev_server_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum IdType {
|
||||||
|
New(String),
|
||||||
|
Existing(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for IdType {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
IdType::New(id) | IdType::Existing(id) => id.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_url_arg(arg: &str, cx: &AppContext) -> Result<String> {
|
fn parse_url_arg(arg: &str, cx: &AppContext) -> Result<String> {
|
||||||
match std::fs::canonicalize(Path::new(&arg)) {
|
match std::fs::canonicalize(Path::new(&arg)) {
|
||||||
Ok(path) => Ok(format!(
|
Ok(path) => Ok(format!(
|
||||||
|
|
|
@ -28,8 +28,9 @@ use crate::stdout_is_a_pty;
|
||||||
static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
|
static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
pub fn init_panic_hook(
|
pub fn init_panic_hook(
|
||||||
installation_id: Option<String>,
|
|
||||||
app_version: SemanticVersion,
|
app_version: SemanticVersion,
|
||||||
|
system_id: Option<String>,
|
||||||
|
installation_id: Option<String>,
|
||||||
session_id: String,
|
session_id: String,
|
||||||
) {
|
) {
|
||||||
let is_pty = stdout_is_a_pty();
|
let is_pty = stdout_is_a_pty();
|
||||||
|
@ -102,6 +103,7 @@ pub fn init_panic_hook(
|
||||||
architecture: env::consts::ARCH.into(),
|
architecture: env::consts::ARCH.into(),
|
||||||
panicked_on: Utc::now().timestamp_millis(),
|
panicked_on: Utc::now().timestamp_millis(),
|
||||||
backtrace,
|
backtrace,
|
||||||
|
system_id: system_id.clone(),
|
||||||
installation_id: installation_id.clone(),
|
installation_id: installation_id.clone(),
|
||||||
session_id: session_id.clone(),
|
session_id: session_id.clone(),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue