Track active projects in metrics
An active project is defined as a project where there has been at least a buffer edit, a join request/response, or a follow update in the last minute.
This commit is contained in:
parent
6d93a41f40
commit
3a1d0dd692
5 changed files with 97 additions and 22 deletions
|
@ -61,8 +61,10 @@ pub use store::{Store, Worktree};
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref METRIC_CONNECTIONS: IntGauge =
|
static ref METRIC_CONNECTIONS: IntGauge =
|
||||||
register_int_gauge!("connections", "number of connections").unwrap();
|
register_int_gauge!("connections", "number of connections").unwrap();
|
||||||
static ref METRIC_PROJECTS: IntGauge =
|
static ref METRIC_REGISTERED_PROJECTS: IntGauge =
|
||||||
register_int_gauge!("projects", "number of open projects").unwrap();
|
register_int_gauge!("registered_projects", "number of registered projects").unwrap();
|
||||||
|
static ref METRIC_ACTIVE_PROJECTS: IntGauge =
|
||||||
|
register_int_gauge!("active_projects", "number of active projects").unwrap();
|
||||||
static ref METRIC_SHARED_PROJECTS: IntGauge = register_int_gauge!(
|
static ref METRIC_SHARED_PROJECTS: IntGauge = register_int_gauge!(
|
||||||
"shared_projects",
|
"shared_projects",
|
||||||
"number of open projects with one or more guests"
|
"number of open projects with one or more guests"
|
||||||
|
@ -159,6 +161,7 @@ impl Server {
|
||||||
.add_message_handler(Server::leave_project)
|
.add_message_handler(Server::leave_project)
|
||||||
.add_message_handler(Server::respond_to_join_project_request)
|
.add_message_handler(Server::respond_to_join_project_request)
|
||||||
.add_message_handler(Server::update_project)
|
.add_message_handler(Server::update_project)
|
||||||
|
.add_message_handler(Server::register_project_activity)
|
||||||
.add_request_handler(Server::update_worktree)
|
.add_request_handler(Server::update_worktree)
|
||||||
.add_message_handler(Server::start_language_server)
|
.add_message_handler(Server::start_language_server)
|
||||||
.add_message_handler(Server::update_language_server)
|
.add_message_handler(Server::update_language_server)
|
||||||
|
@ -844,6 +847,16 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn register_project_activity(
|
||||||
|
self: Arc<Server>,
|
||||||
|
request: TypedEnvelope<proto::RegisterProjectActivity>,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.store_mut()
|
||||||
|
.await
|
||||||
|
.register_project_activity(request.payload.project_id, request.sender_id)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn update_worktree(
|
async fn update_worktree(
|
||||||
self: Arc<Server>,
|
self: Arc<Server>,
|
||||||
request: TypedEnvelope<proto::UpdateWorktree>,
|
request: TypedEnvelope<proto::UpdateWorktree>,
|
||||||
|
@ -1001,10 +1014,12 @@ impl Server {
|
||||||
request: TypedEnvelope<proto::UpdateBuffer>,
|
request: TypedEnvelope<proto::UpdateBuffer>,
|
||||||
response: Response<proto::UpdateBuffer>,
|
response: Response<proto::UpdateBuffer>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let receiver_ids = self
|
let receiver_ids = {
|
||||||
.store()
|
let mut store = self.store_mut().await;
|
||||||
.await
|
store.register_project_activity(request.payload.project_id, request.sender_id)?;
|
||||||
.project_connection_ids(request.payload.project_id, request.sender_id)?;
|
store.project_connection_ids(request.payload.project_id, request.sender_id)?
|
||||||
|
};
|
||||||
|
|
||||||
broadcast(request.sender_id, receiver_ids, |connection_id| {
|
broadcast(request.sender_id, receiver_ids, |connection_id| {
|
||||||
self.peer
|
self.peer
|
||||||
.forward_send(request.sender_id, connection_id, request.payload.clone())
|
.forward_send(request.sender_id, connection_id, request.payload.clone())
|
||||||
|
@ -1065,14 +1080,18 @@ impl Server {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let leader_id = ConnectionId(request.payload.leader_id);
|
let leader_id = ConnectionId(request.payload.leader_id);
|
||||||
let follower_id = request.sender_id;
|
let follower_id = request.sender_id;
|
||||||
if !self
|
|
||||||
.store()
|
|
||||||
.await
|
|
||||||
.project_connection_ids(request.payload.project_id, follower_id)?
|
|
||||||
.contains(&leader_id)
|
|
||||||
{
|
{
|
||||||
Err(anyhow!("no such peer"))?;
|
let mut store = self.store_mut().await;
|
||||||
|
if store
|
||||||
|
.project_connection_ids(request.payload.project_id, follower_id)?
|
||||||
|
.contains(&leader_id)
|
||||||
|
{
|
||||||
|
Err(anyhow!("no such peer"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.register_project_activity(request.payload.project_id, follower_id)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response_payload = self
|
let mut response_payload = self
|
||||||
.peer
|
.peer
|
||||||
.forward_request(request.sender_id, leader_id, request.payload)
|
.forward_request(request.sender_id, leader_id, request.payload)
|
||||||
|
@ -1086,14 +1105,14 @@ impl Server {
|
||||||
|
|
||||||
async fn unfollow(self: Arc<Self>, request: TypedEnvelope<proto::Unfollow>) -> Result<()> {
|
async fn unfollow(self: Arc<Self>, request: TypedEnvelope<proto::Unfollow>) -> Result<()> {
|
||||||
let leader_id = ConnectionId(request.payload.leader_id);
|
let leader_id = ConnectionId(request.payload.leader_id);
|
||||||
if !self
|
let mut store = self.store_mut().await;
|
||||||
.store()
|
if !store
|
||||||
.await
|
|
||||||
.project_connection_ids(request.payload.project_id, request.sender_id)?
|
.project_connection_ids(request.payload.project_id, request.sender_id)?
|
||||||
.contains(&leader_id)
|
.contains(&leader_id)
|
||||||
{
|
{
|
||||||
Err(anyhow!("no such peer"))?;
|
Err(anyhow!("no such peer"))?;
|
||||||
}
|
}
|
||||||
|
store.register_project_activity(request.payload.project_id, request.sender_id)?;
|
||||||
self.peer
|
self.peer
|
||||||
.forward_send(request.sender_id, leader_id, request.payload)?;
|
.forward_send(request.sender_id, leader_id, request.payload)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1103,10 +1122,10 @@ impl Server {
|
||||||
self: Arc<Self>,
|
self: Arc<Self>,
|
||||||
request: TypedEnvelope<proto::UpdateFollowers>,
|
request: TypedEnvelope<proto::UpdateFollowers>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let connection_ids = self
|
let mut store = self.store_mut().await;
|
||||||
.store()
|
store.register_project_activity(request.payload.project_id, request.sender_id)?;
|
||||||
.await
|
let connection_ids =
|
||||||
.project_connection_ids(request.payload.project_id, request.sender_id)?;
|
store.project_connection_ids(request.payload.project_id, request.sender_id)?;
|
||||||
let leader_id = request
|
let leader_id = request
|
||||||
.payload
|
.payload
|
||||||
.variant
|
.variant
|
||||||
|
@ -1574,12 +1593,14 @@ impl<'a> Drop for StoreWriteGuard<'a> {
|
||||||
let metrics = self.metrics();
|
let metrics = self.metrics();
|
||||||
|
|
||||||
METRIC_CONNECTIONS.set(metrics.connections as _);
|
METRIC_CONNECTIONS.set(metrics.connections as _);
|
||||||
METRIC_PROJECTS.set(metrics.registered_projects as _);
|
METRIC_REGISTERED_PROJECTS.set(metrics.registered_projects as _);
|
||||||
|
METRIC_ACTIVE_PROJECTS.set(metrics.active_projects as _);
|
||||||
METRIC_SHARED_PROJECTS.set(metrics.shared_projects as _);
|
METRIC_SHARED_PROJECTS.set(metrics.shared_projects as _);
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
connections = metrics.connections,
|
connections = metrics.connections,
|
||||||
registered_projects = metrics.registered_projects,
|
registered_projects = metrics.registered_projects,
|
||||||
|
active_projects = metrics.active_projects,
|
||||||
shared_projects = metrics.shared_projects,
|
shared_projects = metrics.shared_projects,
|
||||||
"metrics"
|
"metrics"
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,6 +9,7 @@ use std::{
|
||||||
mem,
|
mem,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str,
|
str,
|
||||||
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
@ -41,6 +42,8 @@ pub struct Project {
|
||||||
pub active_replica_ids: HashSet<ReplicaId>,
|
pub active_replica_ids: HashSet<ReplicaId>,
|
||||||
pub worktrees: BTreeMap<u64, Worktree>,
|
pub worktrees: BTreeMap<u64, Worktree>,
|
||||||
pub language_servers: Vec<proto::LanguageServer>,
|
pub language_servers: Vec<proto::LanguageServer>,
|
||||||
|
#[serde(skip)]
|
||||||
|
last_activity: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize)]
|
#[derive(Default, Serialize)]
|
||||||
|
@ -84,6 +87,7 @@ pub struct LeftProject {
|
||||||
pub struct Metrics {
|
pub struct Metrics {
|
||||||
pub connections: usize,
|
pub connections: usize,
|
||||||
pub registered_projects: usize,
|
pub registered_projects: usize,
|
||||||
|
pub active_projects: usize,
|
||||||
pub shared_projects: usize,
|
pub shared_projects: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,13 +95,17 @@ impl Store {
|
||||||
pub fn metrics(&self) -> Metrics {
|
pub fn metrics(&self) -> Metrics {
|
||||||
let connections = self.connections.values().filter(|c| !c.admin).count();
|
let connections = self.connections.values().filter(|c| !c.admin).count();
|
||||||
let mut registered_projects = 0;
|
let mut registered_projects = 0;
|
||||||
|
let mut active_projects = 0;
|
||||||
let mut shared_projects = 0;
|
let mut shared_projects = 0;
|
||||||
for project in self.projects.values() {
|
for project in self.projects.values() {
|
||||||
if let Some(connection) = self.connections.get(&project.host_connection_id) {
|
if let Some(connection) = self.connections.get(&project.host_connection_id) {
|
||||||
if !connection.admin {
|
if !connection.admin {
|
||||||
registered_projects += 1;
|
registered_projects += 1;
|
||||||
if !project.guests.is_empty() {
|
if project.is_active() {
|
||||||
shared_projects += 1;
|
active_projects += 1;
|
||||||
|
if !project.guests.is_empty() {
|
||||||
|
shared_projects += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +114,7 @@ impl Store {
|
||||||
Metrics {
|
Metrics {
|
||||||
connections,
|
connections,
|
||||||
registered_projects,
|
registered_projects,
|
||||||
|
active_projects,
|
||||||
shared_projects,
|
shared_projects,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,6 +327,7 @@ impl Store {
|
||||||
active_replica_ids: Default::default(),
|
active_replica_ids: Default::default(),
|
||||||
worktrees: Default::default(),
|
worktrees: Default::default(),
|
||||||
language_servers: Default::default(),
|
language_servers: Default::default(),
|
||||||
|
last_activity: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if let Some(connection) = self.connections.get_mut(&host_connection_id) {
|
if let Some(connection) = self.connections.get_mut(&host_connection_id) {
|
||||||
|
@ -338,6 +348,7 @@ impl Store {
|
||||||
.get_mut(&project_id)
|
.get_mut(&project_id)
|
||||||
.ok_or_else(|| anyhow!("no such project"))?;
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
if project.host_connection_id == connection_id {
|
if project.host_connection_id == connection_id {
|
||||||
|
project.last_activity = Some(Instant::now());
|
||||||
let mut old_worktrees = mem::take(&mut project.worktrees);
|
let mut old_worktrees = mem::take(&mut project.worktrees);
|
||||||
for worktree in worktrees {
|
for worktree in worktrees {
|
||||||
if let Some(old_worktree) = old_worktrees.remove(&worktree.id) {
|
if let Some(old_worktree) = old_worktrees.remove(&worktree.id) {
|
||||||
|
@ -460,6 +471,7 @@ impl Store {
|
||||||
.get_mut(&project_id)
|
.get_mut(&project_id)
|
||||||
.ok_or_else(|| anyhow!("no such project"))?;
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
connection.requested_projects.insert(project_id);
|
connection.requested_projects.insert(project_id);
|
||||||
|
project.last_activity = Some(Instant::now());
|
||||||
project
|
project
|
||||||
.join_requests
|
.join_requests
|
||||||
.entry(requester_id)
|
.entry(requester_id)
|
||||||
|
@ -484,6 +496,8 @@ impl Store {
|
||||||
let requester_connection = self.connections.get_mut(&receipt.sender_id)?;
|
let requester_connection = self.connections.get_mut(&receipt.sender_id)?;
|
||||||
requester_connection.requested_projects.remove(&project_id);
|
requester_connection.requested_projects.remove(&project_id);
|
||||||
}
|
}
|
||||||
|
project.last_activity = Some(Instant::now());
|
||||||
|
|
||||||
Some(receipts)
|
Some(receipts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,6 +529,7 @@ impl Store {
|
||||||
receipts_with_replica_ids.push((receipt, replica_id));
|
receipts_with_replica_ids.push((receipt, replica_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project.last_activity = Some(Instant::now());
|
||||||
Some((receipts_with_replica_ids, project))
|
Some((receipts_with_replica_ids, project))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,6 +580,8 @@ impl Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project.last_activity = Some(Instant::now());
|
||||||
|
|
||||||
Ok(LeftProject {
|
Ok(LeftProject {
|
||||||
host_connection_id: project.host_connection_id,
|
host_connection_id: project.host_connection_id,
|
||||||
host_user_id: project.host_user_id,
|
host_user_id: project.host_user_id,
|
||||||
|
@ -653,6 +670,25 @@ impl Store {
|
||||||
.ok_or_else(|| anyhow!("no such project"))
|
.ok_or_else(|| anyhow!("no such project"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn register_project_activity(
|
||||||
|
&mut self,
|
||||||
|
project_id: u64,
|
||||||
|
connection_id: ConnectionId,
|
||||||
|
) -> Result<()> {
|
||||||
|
let project = self
|
||||||
|
.projects
|
||||||
|
.get_mut(&project_id)
|
||||||
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
|
if project.host_connection_id == connection_id
|
||||||
|
|| project.guests.contains_key(&connection_id)
|
||||||
|
{
|
||||||
|
project.last_activity = Some(Instant::now());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("no such project"))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_project(&self, project_id: u64, connection_id: ConnectionId) -> Result<&Project> {
|
pub fn read_project(&self, project_id: u64, connection_id: ConnectionId) -> Result<&Project> {
|
||||||
let project = self
|
let project = self
|
||||||
.projects
|
.projects
|
||||||
|
@ -758,6 +794,13 @@ impl Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
|
fn is_active(&self) -> bool {
|
||||||
|
const ACTIVE_PROJECT_TIMEOUT: Duration = Duration::from_secs(60);
|
||||||
|
self.last_activity.map_or(false, |last_activity| {
|
||||||
|
last_activity.elapsed() < ACTIVE_PROJECT_TIMEOUT
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn guest_connection_ids(&self) -> Vec<ConnectionId> {
|
pub fn guest_connection_ids(&self) -> Vec<ConnectionId> {
|
||||||
self.guests.keys().copied().collect()
|
self.guests.keys().copied().collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1780,6 +1780,10 @@ impl Project {
|
||||||
operations: vec![language::proto::serialize_operation(&operation)],
|
operations: vec![language::proto::serialize_operation(&operation)],
|
||||||
});
|
});
|
||||||
cx.background().spawn(request).detach_and_log_err(cx);
|
cx.background().spawn(request).detach_and_log_err(cx);
|
||||||
|
} else if let Some(project_id) = self.remote_id() {
|
||||||
|
let _ = self
|
||||||
|
.client
|
||||||
|
.send(proto::RegisterProjectActivity { project_id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BufferEvent::Edited { .. } => {
|
BufferEvent::Edited { .. } => {
|
||||||
|
|
|
@ -36,6 +36,7 @@ message Envelope {
|
||||||
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 29;
|
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 29;
|
||||||
|
|
||||||
UpdateProject update_project = 30;
|
UpdateProject update_project = 30;
|
||||||
|
RegisterProjectActivity register_project_activity = 31;
|
||||||
UpdateWorktree update_worktree = 32;
|
UpdateWorktree update_worktree = 32;
|
||||||
|
|
||||||
CreateProjectEntry create_project_entry = 33;
|
CreateProjectEntry create_project_entry = 33;
|
||||||
|
@ -135,6 +136,10 @@ message UpdateProject {
|
||||||
repeated WorktreeMetadata worktrees = 2;
|
repeated WorktreeMetadata worktrees = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message RegisterProjectActivity {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message RequestJoinProject {
|
message RequestJoinProject {
|
||||||
uint64 requester_id = 1;
|
uint64 requester_id = 1;
|
||||||
uint64 project_id = 2;
|
uint64 project_id = 2;
|
||||||
|
|
|
@ -134,6 +134,7 @@ messages!(
|
||||||
(Ping, Foreground),
|
(Ping, Foreground),
|
||||||
(ProjectUnshared, Foreground),
|
(ProjectUnshared, Foreground),
|
||||||
(RegisterProject, Foreground),
|
(RegisterProject, Foreground),
|
||||||
|
(RegisterProjectActivity, Foreground),
|
||||||
(ReloadBuffers, Foreground),
|
(ReloadBuffers, Foreground),
|
||||||
(ReloadBuffersResponse, Foreground),
|
(ReloadBuffersResponse, Foreground),
|
||||||
(RemoveProjectCollaborator, Foreground),
|
(RemoveProjectCollaborator, Foreground),
|
||||||
|
@ -236,6 +237,7 @@ entity_messages!(
|
||||||
PerformRename,
|
PerformRename,
|
||||||
PrepareRename,
|
PrepareRename,
|
||||||
ProjectUnshared,
|
ProjectUnshared,
|
||||||
|
RegisterProjectActivity,
|
||||||
ReloadBuffers,
|
ReloadBuffers,
|
||||||
RemoveProjectCollaborator,
|
RemoveProjectCollaborator,
|
||||||
RenameProjectEntry,
|
RenameProjectEntry,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue