mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add connection statistics for workspace agents (#6469)
* fix: don't make session counts cumulative This made for some weird tracking... we want the point-in-time number of counts! * Add databasefake query for getting agent stats * Add deployment stats endpoint * The query... works?!? * Fix aggregation query * Select from multiple tables instead * Fix continuous stats * Increase period of stat refreshes * Add workspace counts to deployment stats * fmt * Add a slight bit of responsiveness * Fix template version editor overflow * Add refresh button * Fix font family on button * Fix latest stat being reported * Revert agent conn stats * Fix linting error * Fix tests * Fix gen * Fix migrations * Block on sending stat updates * Add test fixtures * Fix response structure * make gen
This commit is contained in:
@@ -312,6 +312,53 @@ func (*fakeQuerier) DeleteOldWorkspaceAgentStats(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetDeploymentWorkspaceAgentStats(_ context.Context, createdAfter time.Time) (database.GetDeploymentWorkspaceAgentStatsRow, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
agentStatsCreatedAfter := make([]database.WorkspaceAgentStat, 0)
|
||||
for _, agentStat := range q.workspaceAgentStats {
|
||||
if agentStat.CreatedAt.After(createdAfter) {
|
||||
agentStatsCreatedAfter = append(agentStatsCreatedAfter, agentStat)
|
||||
}
|
||||
}
|
||||
|
||||
latestAgentStats := map[uuid.UUID]database.WorkspaceAgentStat{}
|
||||
for _, agentStat := range q.workspaceAgentStats {
|
||||
if agentStat.CreatedAt.After(createdAfter) {
|
||||
latestAgentStats[agentStat.AgentID] = agentStat
|
||||
}
|
||||
}
|
||||
|
||||
stat := database.GetDeploymentWorkspaceAgentStatsRow{}
|
||||
for _, agentStat := range latestAgentStats {
|
||||
stat.SessionCountVSCode += agentStat.SessionCountVSCode
|
||||
stat.SessionCountJetBrains += agentStat.SessionCountJetBrains
|
||||
stat.SessionCountReconnectingPTY += agentStat.SessionCountReconnectingPTY
|
||||
stat.SessionCountSSH += agentStat.SessionCountSSH
|
||||
}
|
||||
|
||||
latencies := make([]float64, 0)
|
||||
for _, agentStat := range agentStatsCreatedAfter {
|
||||
stat.WorkspaceRxBytes += agentStat.RxBytes
|
||||
stat.WorkspaceTxBytes += agentStat.TxBytes
|
||||
latencies = append(latencies, agentStat.ConnectionMedianLatencyMS)
|
||||
}
|
||||
|
||||
tryPercentile := func(fs []float64, p float64) float64 {
|
||||
if len(fs) == 0 {
|
||||
return -1
|
||||
}
|
||||
sort.Float64s(fs)
|
||||
return fs[int(float64(len(fs))*p/100)]
|
||||
}
|
||||
|
||||
stat.WorkspaceConnectionLatency50 = tryPercentile(latencies, 50)
|
||||
stat.WorkspaceConnectionLatency95 = tryPercentile(latencies, 95)
|
||||
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) InsertWorkspaceAgentStat(_ context.Context, p database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) {
|
||||
if err := validateDatabaseType(p); err != nil {
|
||||
return database.WorkspaceAgentStat{}, err
|
||||
@@ -1031,7 +1078,7 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
|
||||
}
|
||||
|
||||
if arg.Status != "" {
|
||||
build, err := q.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID)
|
||||
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get latest build: %w", err)
|
||||
}
|
||||
@@ -1120,7 +1167,7 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
|
||||
}
|
||||
|
||||
if arg.HasAgent != "" {
|
||||
build, err := q.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID)
|
||||
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get latest build: %w", err)
|
||||
}
|
||||
@@ -1426,10 +1473,14 @@ func (q *fakeQuerier) GetWorkspaceBuildByJobID(_ context.Context, jobID uuid.UUI
|
||||
return database.WorkspaceBuild{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetLatestWorkspaceBuildByWorkspaceID(_ context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
func (q *fakeQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
return q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspaceID)
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) getLatestWorkspaceBuildByWorkspaceIDNoLock(_ context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
var row database.WorkspaceBuild
|
||||
var buildNum int32 = -1
|
||||
for _, workspaceBuild := range q.workspaceBuilds {
|
||||
@@ -3609,6 +3660,50 @@ func (q *fakeQuerier) UpdateWorkspaceLastUsedAt(_ context.Context, arg database.
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetDeploymentWorkspaceStats(ctx context.Context) (database.GetDeploymentWorkspaceStatsRow, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
stat := database.GetDeploymentWorkspaceStatsRow{}
|
||||
for _, workspace := range q.workspaces {
|
||||
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
|
||||
if err != nil {
|
||||
return stat, err
|
||||
}
|
||||
job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID)
|
||||
if err != nil {
|
||||
return stat, err
|
||||
}
|
||||
if !job.StartedAt.Valid {
|
||||
stat.PendingWorkspaces++
|
||||
continue
|
||||
}
|
||||
if job.StartedAt.Valid &&
|
||||
!job.CanceledAt.Valid &&
|
||||
time.Since(job.UpdatedAt) <= 30*time.Second &&
|
||||
!job.CompletedAt.Valid {
|
||||
stat.BuildingWorkspaces++
|
||||
continue
|
||||
}
|
||||
if job.CompletedAt.Valid &&
|
||||
!job.CanceledAt.Valid &&
|
||||
!job.Error.Valid {
|
||||
if build.Transition == database.WorkspaceTransitionStart {
|
||||
stat.RunningWorkspaces++
|
||||
}
|
||||
if build.Transition == database.WorkspaceTransitionStop {
|
||||
stat.StoppedWorkspaces++
|
||||
}
|
||||
continue
|
||||
}
|
||||
if job.CanceledAt.Valid || job.Error.Valid {
|
||||
stat.FailedWorkspaces++
|
||||
continue
|
||||
}
|
||||
}
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateWorkspaceTTLToBeWithinTemplateMax(_ context.Context, arg database.UpdateWorkspaceTTLToBeWithinTemplateMaxParams) error {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user