Skip to content
Open
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
10 changes: 10 additions & 0 deletions cli/testdata/coder_server_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,16 @@ INTROSPECTION / PROMETHEUS OPTIONS:
--prometheus-enable bool, $CODER_PROMETHEUS_ENABLE
Serve prometheus metrics on the address defined by prometheus address.

INTROSPECTION / TEMPLATE INSIGHTS OPTIONS:
--template-insights-enable bool, $CODER_TEMPLATE_INSIGHTS_ENABLE (default: true)
Enable the collection and display of template insights along with the
associated API endpoints. This will also enable aggregating these
insights into daily active users, application usage, and transmission
rates for overall deployment stats. When disabled, these values will
be zero, which will also affect what the bottom deployment overview
bar displays. Disabling will also prevent Prometheus collection of
Comment on lines +278 to +279
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
be zero, which will also affect what the bottom deployment overview
bar displays. Disabling will also prevent Prometheus collection of
be zero. Disabling this will also disable traffic and connection insights in the deployment stats shown to admins in the bottom bar of the Coder UI.
Disabling will also prevent Prometheus collection of

Ignore indentation.

these values.

INTROSPECTION / TRACING OPTIONS:
--trace-logs bool, $CODER_TRACE_LOGS
Enables capturing of logs as events in traces. This is useful for
Expand Down
9 changes: 9 additions & 0 deletions cli/testdata/server-config.yaml.golden
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ autobuildPollInterval: 1m0s
# (default: 1m0s, type: duration)
jobHangDetectorInterval: 1m0s
introspection:
templateInsights:
# Enable the collection and display of template insights along with the associated
# API endpoints. This will also enable aggregating these insights into daily
# active users, application usage, and transmission rates for overall deployment
# stats. When disabled, these values will be zero, which will also affect what the
# bottom deployment overview bar displays. Disabling will also prevent Prometheus
# collection of these values.
# (default: true, type: bool)
enable: true
prometheus:
# Serve prometheus metrics on the address defined by prometheus address.
# (default: <unset>, type: bool)
Expand Down
131 changes: 130 additions & 1 deletion coderd/agentapi/stats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"github.com/coder/coder/v2/testutil"
)

func TestUpdateStates(t *testing.T) {
func TestUpdateStats(t *testing.T) {
t.Parallel()

var (
Expand Down Expand Up @@ -542,6 +542,135 @@ func TestUpdateStates(t *testing.T) {
}
require.True(t, updateAgentMetricsFnCalled)
})

t.Run("DropStats", func(t *testing.T) {
t.Parallel()

var (
now = dbtime.Now()
dbM = dbmock.NewMockStore(gomock.NewController(t))
ps = pubsub.NewInMemory()

templateScheduleStore = schedule.MockTemplateScheduleStore{
GetFn: func(context.Context, database.Store, uuid.UUID) (schedule.TemplateScheduleOptions, error) {
panic("should not be called")
},
SetFn: func(context.Context, database.Store, database.Template, schedule.TemplateScheduleOptions) (database.Template, error) {
panic("not implemented")
},
}
updateAgentMetricsFnCalled = false
tickCh = make(chan time.Time)
flushCh = make(chan int, 1)
wut = workspacestats.NewTracker(dbM,
workspacestats.TrackerWithTickFlush(tickCh, flushCh),
)

req = &agentproto.UpdateStatsRequest{
Stats: &agentproto.Stats{
ConnectionsByProto: map[string]int64{
"tcp": 1,
"dean": 2,
},
ConnectionCount: 3,
ConnectionMedianLatencyMs: 23,
RxPackets: 120,
RxBytes: 1000,
TxPackets: 130,
TxBytes: 2000,
SessionCountVscode: 1,
SessionCountJetbrains: 2,
SessionCountReconnectingPty: 3,
SessionCountSsh: 4,
Metrics: []*agentproto.Stats_Metric{
{
Name: "awesome metric",
Value: 42,
},
{
Name: "uncool metric",
Value: 0,
},
},
},
}
)
api := agentapi.StatsAPI{
AgentFn: func(context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Workspace: &workspaceAsCacheFields,
Database: dbM,
StatsReporter: workspacestats.NewReporter(workspacestats.ReporterOptions{
Database: dbM,
Pubsub: ps,
StatsBatcher: nil, // Should not be called.
UsageTracker: wut,
TemplateScheduleStore: templateScheduleStorePtr(templateScheduleStore),
UpdateAgentMetricsFn: func(ctx context.Context, labels prometheusmetrics.AgentMetricLabels, metrics []*agentproto.Stats_Metric) {
updateAgentMetricsFnCalled = true
assert.Equal(t, prometheusmetrics.AgentMetricLabels{
Username: user.Username,
WorkspaceName: workspace.Name,
AgentName: agent.Name,
TemplateName: template.Name,
}, labels)
assert.Equal(t, req.Stats.Metrics, metrics)
},
DisableDatabaseInserts: true,
}),
AgentStatsRefreshInterval: 10 * time.Second,
TimeNowFn: func() time.Time {
return now
},
}
defer wut.Close()

// We expect an activity bump because ConnectionCount > 0.
dbM.EXPECT().ActivityBumpWorkspace(gomock.Any(), database.ActivityBumpWorkspaceParams{
WorkspaceID: workspace.ID,
NextAutostart: time.Time{}.UTC(),
}).Return(nil)

// Workspace last used at gets bumped.
dbM.EXPECT().BatchUpdateWorkspaceLastUsedAt(gomock.Any(), database.BatchUpdateWorkspaceLastUsedAtParams{
IDs: []uuid.UUID{workspace.ID},
LastUsedAt: now,
}).Return(nil)

// Ensure that pubsub notifications are sent.
notifyDescription := make(chan struct{})
ps.SubscribeWithErr(wspubsub.WorkspaceEventChannel(workspace.OwnerID),
wspubsub.HandleWorkspaceEvent(
func(_ context.Context, e wspubsub.WorkspaceEvent, err error) {
if err != nil {
return
}
if e.Kind == wspubsub.WorkspaceEventKindStatsUpdate && e.WorkspaceID == workspace.ID {
go func() {
notifyDescription <- struct{}{}
}()
}
}))

resp, err := api.UpdateStats(context.Background(), req)
require.NoError(t, err)
require.Equal(t, &agentproto.UpdateStatsResponse{
ReportInterval: durationpb.New(10 * time.Second),
}, resp)

tickCh <- now
count := <-flushCh
require.Equal(t, 1, count, "expected one flush with one id")

ctx := testutil.Context(t, testutil.WaitShort)
select {
case <-ctx.Done():
t.Error("timed out while waiting for pubsub notification")
case <-notifyDescription:
}
require.True(t, updateAgentMetricsFnCalled)
})
}

func templateScheduleStorePtr(store schedule.TemplateScheduleStore) *atomic.Pointer[schedule.TemplateScheduleStore] {
Expand Down
11 changes: 11 additions & 0 deletions coderd/apidoc/docs.go

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

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

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

42 changes: 30 additions & 12 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,14 +768,15 @@ func New(options *Options) *API {
}

api.statsReporter = workspacestats.NewReporter(workspacestats.ReporterOptions{
Database: options.Database,
Logger: options.Logger.Named("workspacestats"),
Pubsub: options.Pubsub,
TemplateScheduleStore: options.TemplateScheduleStore,
StatsBatcher: options.StatsBatcher,
UsageTracker: options.WorkspaceUsageTracker,
UpdateAgentMetricsFn: options.UpdateAgentMetrics,
AppStatBatchSize: workspaceapps.DefaultStatsDBReporterBatchSize,
Database: options.Database,
Logger: options.Logger.Named("workspacestats"),
Pubsub: options.Pubsub,
TemplateScheduleStore: options.TemplateScheduleStore,
StatsBatcher: options.StatsBatcher,
UsageTracker: options.WorkspaceUsageTracker,
UpdateAgentMetricsFn: options.UpdateAgentMetrics,
AppStatBatchSize: workspaceapps.DefaultStatsDBReporterBatchSize,
DisableDatabaseInserts: !options.DeploymentValues.TemplateInsights.Enable.Value(),
})
workspaceAppsLogger := options.Logger.Named("workspaceapps")
if options.WorkspaceAppsStatsCollectorOptions.Logger == nil {
Expand Down Expand Up @@ -1528,11 +1529,28 @@ func New(options *Options) *API {
})
r.Route("/insights", func(r chi.Router) {
r.Use(apiKeyMiddleware)
r.Get("/daus", api.deploymentDAUs)
r.Get("/user-activity", api.insightsUserActivity)
r.Group(func(r chi.Router) {
r.Use(
func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if !options.DeploymentValues.TemplateInsights.Enable.Value() {
httpapi.Write(context.Background(), rw, http.StatusNotFound, codersdk.Response{
Message: "Not Found.",
Detail: "Template insights are disabled.",
})
return
}

next.ServeHTTP(rw, r)
})
},
)
r.Get("/daus", api.deploymentDAUs)
r.Get("/user-activity", api.insightsUserActivity)
r.Get("/user-latency", api.insightsUserLatency)
r.Get("/templates", api.insightsTemplates)
})
r.Get("/user-status-counts", api.insightsUserStatusCounts)
r.Get("/user-latency", api.insightsUserLatency)
r.Get("/templates", api.insightsTemplates)
})
r.Route("/debug", func(r chi.Router) {
r.Use(
Expand Down
Loading
Loading