Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions coderd/apidoc/docs.go

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

32 changes: 32 additions & 0 deletions coderd/apidoc/swagger.json

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

4 changes: 2 additions & 2 deletions coderd/database/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19083,9 +19083,9 @@ func TestUpsertTemplateUsageStatsStoresReportedAppNames(t *testing.T) {

func sessionFamilyCounts(t *testing.T, data json.RawMessage) map[codersdk.AppFamilyName]int64 {
t.Helper()
counts, err := codersdk.SessionCountsByFamilyJSON(data)
counts, err := codersdk.DecodeSessionCounts(data)
require.NoError(t, err)
return counts
return codersdk.SumByFamily(counts)
}

func TestUpdateUserEmail(t *testing.T) {
Expand Down
32 changes: 25 additions & 7 deletions coderd/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)

Expand Down Expand Up @@ -52,13 +55,28 @@ func TestDeploymentValues(t *testing.T) {

func TestDeploymentStats(t *testing.T) {
t.Parallel()
t.Log("This test is time-sensitive. It may fail if the deployment is not ready in time.")
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
client := coderdtest.New(t, &coderdtest.Options{})
ctx := testutil.Context(t, testutil.WaitLong)
db, _ := dbtestutil.NewDB(t)
dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{
ConnectionMedianLatencyMS: 10,
SessionCounts: dbgen.SessionCounts(t, map[string]int64{"vscode": 1, "cursor": 2, "future_ide": 3}),
})
client := coderdtest.New(t, &coderdtest.Options{Database: db})
_ = coderdtest.CreateFirstUser(t, client)
assert.True(t, testutil.Eventually(ctx, t, func(tctx context.Context) bool {
_, err := client.DeploymentStats(tctx)
var stats codersdk.DeploymentStats
require.True(t, testutil.Eventually(ctx, t, func(tctx context.Context) bool {
var err error
stats, err = client.DeploymentStats(tctx)
return err == nil
}, testutil.IntervalMedium), "failed to get deployment stats in time")
// Recognized names carry metadata, unknown names only a count, and the
// legacy family totals fold both known names into the VS Code family.
require.Equal(t, codersdk.SessionCountDeploymentStats{
SessionCounts: map[string]int64{"vscode": 1, "cursor": 2, "future_ide": 3},
Apps: map[string]codersdk.SessionCountApp{
"vscode": {DisplayName: "VS Code", Icon: "/icon/code.svg"},
"cursor": {DisplayName: "Cursor", Icon: "/icon/cursor.svg"},
},
VSCode: 3,
}, stats.SessionCount)
}
16 changes: 14 additions & 2 deletions coderd/metricscache/metricscache.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,17 @@ func (c *Cache) refreshDeploymentStats(ctx context.Context) error {

// The query sums sessions per app name, so a session reported under a name
// this version does not know about is counted here rather than dropped.
sessionCounts, err := codersdk.SessionCountsByFamilyJSON(agentStats.SessionCounts)
appCounts, err := codersdk.DecodeSessionCounts(agentStats.SessionCounts)
if err != nil {
return xerrors.Errorf("group deployment session counts by app family: %w", err)
return xerrors.Errorf("decode deployment session counts: %w", err)
}

sessionCounts := codersdk.SumByFamily(appCounts)
apps := make(map[string]codersdk.SessionCountApp, len(appCounts))
for name := range appCounts {
if app, ok := codersdk.SessionCountAppMetadata(name); ok {
apps[name] = app
}
}

workspaceStats, err := c.database.GetDeploymentWorkspaceStats(ctx)
Expand All @@ -179,6 +187,8 @@ func (c *Cache) refreshDeploymentStats(ctx context.Context) error {
TxBytes: agentStats.WorkspaceTxBytes,
},
SessionCount: codersdk.SessionCountDeploymentStats{
SessionCounts: appCounts,
Apps: apps,
VSCode: sessionCounts[codersdk.AppFamilyVSCode],
SSH: sessionCounts[codersdk.AppFamilySSH],
JetBrains: sessionCounts[codersdk.AppFamilyJetBrains],
Expand Down Expand Up @@ -282,6 +292,8 @@ func (c *Cache) TemplateWorkspaceOwners(id uuid.UUID) (int, bool) {
return resp, true
}

// DeploymentStats returns the latest published snapshot. The maps it contains
// are shared with the cache and must not be mutated.
func (c *Cache) DeploymentStats() (codersdk.DeploymentStats, bool) {
deploymentStats := c.deploymentStatsResponse.Load()
if deploymentStats == nil {
Expand Down
90 changes: 55 additions & 35 deletions coderd/metricscache/metricscache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/metricscache"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
Expand Down Expand Up @@ -293,39 +294,58 @@ func TestCache_BuildTime(t *testing.T) {

func TestCache_DeploymentStats(t *testing.T) {
t.Parallel()

var (
ctx = testutil.Context(t, testutil.WaitShort)
log = testutil.Logger(t)
clock = quartz.NewMock(t)
)

tickerTrap := clock.Trap().TickerFunc("metricscache")
defer tickerTrap.Close()

cache, db := newMetricsCache(t, log, clock, metricscache.Intervals{
DeploymentStats: time.Minute,
}, false)

dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{
CreatedAt: clock.Now(),
RxBytes: 1,
TxBytes: 1,
ConnectionCount: 1,
ConnectionMedianLatencyMS: 10,
// Names from the same family, one of them an alias, so the fixed
// deployment stats fields cover the app name folding.
SessionCounts: dbgen.SessionCounts(t, map[string]int64{"vscode": 1, "cursor": 2, "zed": 3}),
})

// Wait for both ticker functions to be created (template build times and deployment stats)
tickerTrap.MustWait(ctx).MustRelease(ctx)
tickerTrap.MustWait(ctx).MustRelease(ctx)

clock.Advance(time.Minute).MustWait(ctx)

stat, ok := cache.DeploymentStats()
require.True(t, ok, "cache should be populated after refresh")
require.Equal(t, int64(3), stat.SessionCount.VSCode)
require.Equal(t, int64(3), stat.SessionCount.SSH)
for _, usage := range []bool{false, true} {
name := "Stats"
if usage {
name = "Usage"
}
t.Run(name, func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
clock := quartz.NewMock(t)
now := dbtime.Now().Truncate(time.Minute)
clock.Set(now)
tickerTrap := clock.Trap().TickerFunc("metricscache")
defer tickerTrap.Close()
cache, db := newMetricsCache(t, testutil.Logger(t), clock, metricscache.Intervals{DeploymentStats: time.Minute}, usage)
counts := map[string]int64{"vscode": 1, "cursor": 2, "zed": 3, "future_ide": 4, "jetbrains": 5, "reconnecting_pty": 6}
agentStat := dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{
CreatedAt: now.Add(-2 * time.Minute), Usage: usage, RxBytes: 1, TxBytes: 1, ConnectionCount: 1,
ConnectionMedianLatencyMS: 10, SessionCounts: dbgen.SessionCounts(t, counts),
})
tickerTrap.MustWait(ctx).MustRelease(ctx)
tickerTrap.MustWait(ctx).MustRelease(ctx)
clock.Advance(time.Minute).MustWait(ctx)
stat, ok := cache.DeploymentStats()
require.True(t, ok)
// Legacy family totals fold vscode+cursor and zed+ssh; unknown
// names are counted but carry no metadata.
require.Equal(t, codersdk.SessionCountDeploymentStats{
SessionCounts: counts,
Apps: map[string]codersdk.SessionCountApp{
"vscode": {DisplayName: "VS Code", Icon: "/icon/code.svg"},
"cursor": {DisplayName: "Cursor", Icon: "/icon/cursor.svg"},
"zed": {DisplayName: "Zed", Icon: "/icon/zed.svg"},
"jetbrains": {DisplayName: "JetBrains", Icon: "/icon/jetbrains.svg"},
"reconnecting_pty": {DisplayName: "Web Terminal"},
},
VSCode: 3, SSH: 3, JetBrains: 5, ReconnectingPTY: 6,
}, stat.SessionCount)

// A later report with no sessions clears every count and app.
dbgen.WorkspaceAgentStat(t, db, database.WorkspaceAgentStat{
AgentID: agentStat.AgentID, UserID: agentStat.UserID, WorkspaceID: agentStat.WorkspaceID, TemplateID: agentStat.TemplateID,
CreatedAt: now.Add(-time.Minute), Usage: usage, ConnectionMedianLatencyMS: 10,
SessionCounts: dbgen.SessionCounts(t, map[string]int64{}),
})
clock.Advance(time.Minute).MustWait(ctx)
empty, ok := cache.DeploymentStats()
require.True(t, ok)
require.Equal(t, codersdk.SessionCountDeploymentStats{
SessionCounts: map[string]int64{},
Apps: map[string]codersdk.SessionCountApp{},
}, empty.SessionCount)
require.Equal(t, counts, stat.SessionCount.SessionCounts, "refresh must not mutate a published snapshot")
})
}
}
Loading
Loading