Skip to content

Commit 27f0413

Browse files
authored
feat: add flag to disable template insights (#20940)
Closes #20399 To summarize the original commit messages: - Do not log stats to the database. - Return errors on the insight endpoints. - Update the frontend to show those errors. - Also fixes an issue with getting the user status count via codersdk, since I added a test to ensure it was not disabled by this flag and it was sending the wrong payload.
1 parent b9f8295 commit 27f0413

File tree

19 files changed

+1590
-954
lines changed

19 files changed

+1590
-954
lines changed

cli/testdata/coder_server_--help.golden

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,16 @@ INTROSPECTION / PROMETHEUS OPTIONS:
277277
--prometheus-enable bool, $CODER_PROMETHEUS_ENABLE
278278
Serve prometheus metrics on the address defined by prometheus address.
279279

280+
INTROSPECTION / TEMPLATE INSIGHTS OPTIONS:
281+
--template-insights-enable bool, $CODER_TEMPLATE_INSIGHTS_ENABLE (default: true)
282+
Enable the collection and display of template insights along with the
283+
associated API endpoints. This will also enable aggregating these
284+
insights into daily active users, application usage, and transmission
285+
rates for overall deployment stats. When disabled, these values will
286+
be zero, which will also affect what the bottom deployment overview
287+
bar displays. Disabling will also prevent Prometheus collection of
288+
these values.
289+
280290
INTROSPECTION / TRACING OPTIONS:
281291
--trace-logs bool, $CODER_TRACE_LOGS
282292
Enables capturing of logs as events in traces. This is useful for

cli/testdata/server-config.yaml.golden

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ autobuildPollInterval: 1m0s
191191
# (default: 1m0s, type: duration)
192192
jobHangDetectorInterval: 1m0s
193193
introspection:
194+
templateInsights:
195+
# Enable the collection and display of template insights along with the associated
196+
# API endpoints. This will also enable aggregating these insights into daily
197+
# active users, application usage, and transmission rates for overall deployment
198+
# stats. When disabled, these values will be zero, which will also affect what the
199+
# bottom deployment overview bar displays. Disabling will also prevent Prometheus
200+
# collection of these values.
201+
# (default: true, type: bool)
202+
enable: true
194203
prometheus:
195204
# Serve prometheus metrics on the address defined by prometheus address.
196205
# (default: <unset>, type: bool)

coderd/agentapi/stats_test.go

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"github.com/coder/coder/v2/testutil"
2929
)
3030

31-
func TestUpdateStates(t *testing.T) {
31+
func TestUpdateStats(t *testing.T) {
3232
t.Parallel()
3333

3434
var (
@@ -542,6 +542,135 @@ func TestUpdateStates(t *testing.T) {
542542
}
543543
require.True(t, updateAgentMetricsFnCalled)
544544
})
545+
546+
t.Run("DropStats", func(t *testing.T) {
547+
t.Parallel()
548+
549+
var (
550+
now = dbtime.Now()
551+
dbM = dbmock.NewMockStore(gomock.NewController(t))
552+
ps = pubsub.NewInMemory()
553+
554+
templateScheduleStore = schedule.MockTemplateScheduleStore{
555+
GetFn: func(context.Context, database.Store, uuid.UUID) (schedule.TemplateScheduleOptions, error) {
556+
panic("should not be called")
557+
},
558+
SetFn: func(context.Context, database.Store, database.Template, schedule.TemplateScheduleOptions) (database.Template, error) {
559+
panic("not implemented")
560+
},
561+
}
562+
updateAgentMetricsFnCalled = false
563+
tickCh = make(chan time.Time)
564+
flushCh = make(chan int, 1)
565+
wut = workspacestats.NewTracker(dbM,
566+
workspacestats.TrackerWithTickFlush(tickCh, flushCh),
567+
)
568+
569+
req = &agentproto.UpdateStatsRequest{
570+
Stats: &agentproto.Stats{
571+
ConnectionsByProto: map[string]int64{
572+
"tcp": 1,
573+
"dean": 2,
574+
},
575+
ConnectionCount: 3,
576+
ConnectionMedianLatencyMs: 23,
577+
RxPackets: 120,
578+
RxBytes: 1000,
579+
TxPackets: 130,
580+
TxBytes: 2000,
581+
SessionCountVscode: 1,
582+
SessionCountJetbrains: 2,
583+
SessionCountReconnectingPty: 3,
584+
SessionCountSsh: 4,
585+
Metrics: []*agentproto.Stats_Metric{
586+
{
587+
Name: "awesome metric",
588+
Value: 42,
589+
},
590+
{
591+
Name: "uncool metric",
592+
Value: 0,
593+
},
594+
},
595+
},
596+
}
597+
)
598+
api := agentapi.StatsAPI{
599+
AgentFn: func(context.Context) (database.WorkspaceAgent, error) {
600+
return agent, nil
601+
},
602+
Workspace: &workspaceAsCacheFields,
603+
Database: dbM,
604+
StatsReporter: workspacestats.NewReporter(workspacestats.ReporterOptions{
605+
Database: dbM,
606+
Pubsub: ps,
607+
StatsBatcher: nil, // Should not be called.
608+
UsageTracker: wut,
609+
TemplateScheduleStore: templateScheduleStorePtr(templateScheduleStore),
610+
UpdateAgentMetricsFn: func(ctx context.Context, labels prometheusmetrics.AgentMetricLabels, metrics []*agentproto.Stats_Metric) {
611+
updateAgentMetricsFnCalled = true
612+
assert.Equal(t, prometheusmetrics.AgentMetricLabels{
613+
Username: user.Username,
614+
WorkspaceName: workspace.Name,
615+
AgentName: agent.Name,
616+
TemplateName: template.Name,
617+
}, labels)
618+
assert.Equal(t, req.Stats.Metrics, metrics)
619+
},
620+
DisableDatabaseInserts: true,
621+
}),
622+
AgentStatsRefreshInterval: 10 * time.Second,
623+
TimeNowFn: func() time.Time {
624+
return now
625+
},
626+
}
627+
defer wut.Close()
628+
629+
// We expect an activity bump because ConnectionCount > 0.
630+
dbM.EXPECT().ActivityBumpWorkspace(gomock.Any(), database.ActivityBumpWorkspaceParams{
631+
WorkspaceID: workspace.ID,
632+
NextAutostart: time.Time{}.UTC(),
633+
}).Return(nil)
634+
635+
// Workspace last used at gets bumped.
636+
dbM.EXPECT().BatchUpdateWorkspaceLastUsedAt(gomock.Any(), database.BatchUpdateWorkspaceLastUsedAtParams{
637+
IDs: []uuid.UUID{workspace.ID},
638+
LastUsedAt: now,
639+
}).Return(nil)
640+
641+
// Ensure that pubsub notifications are sent.
642+
notifyDescription := make(chan struct{})
643+
ps.SubscribeWithErr(wspubsub.WorkspaceEventChannel(workspace.OwnerID),
644+
wspubsub.HandleWorkspaceEvent(
645+
func(_ context.Context, e wspubsub.WorkspaceEvent, err error) {
646+
if err != nil {
647+
return
648+
}
649+
if e.Kind == wspubsub.WorkspaceEventKindStatsUpdate && e.WorkspaceID == workspace.ID {
650+
go func() {
651+
notifyDescription <- struct{}{}
652+
}()
653+
}
654+
}))
655+
656+
resp, err := api.UpdateStats(context.Background(), req)
657+
require.NoError(t, err)
658+
require.Equal(t, &agentproto.UpdateStatsResponse{
659+
ReportInterval: durationpb.New(10 * time.Second),
660+
}, resp)
661+
662+
tickCh <- now
663+
count := <-flushCh
664+
require.Equal(t, 1, count, "expected one flush with one id")
665+
666+
ctx := testutil.Context(t, testutil.WaitShort)
667+
select {
668+
case <-ctx.Done():
669+
t.Error("timed out while waiting for pubsub notification")
670+
case <-notifyDescription:
671+
}
672+
require.True(t, updateAgentMetricsFnCalled)
673+
})
545674
}
546675

547676
func templateScheduleStorePtr(store schedule.TemplateScheduleStore) *atomic.Pointer[schedule.TemplateScheduleStore] {

coderd/apidoc/docs.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -768,14 +768,15 @@ func New(options *Options) *API {
768768
}
769769

770770
api.statsReporter = workspacestats.NewReporter(workspacestats.ReporterOptions{
771-
Database: options.Database,
772-
Logger: options.Logger.Named("workspacestats"),
773-
Pubsub: options.Pubsub,
774-
TemplateScheduleStore: options.TemplateScheduleStore,
775-
StatsBatcher: options.StatsBatcher,
776-
UsageTracker: options.WorkspaceUsageTracker,
777-
UpdateAgentMetricsFn: options.UpdateAgentMetrics,
778-
AppStatBatchSize: workspaceapps.DefaultStatsDBReporterBatchSize,
771+
Database: options.Database,
772+
Logger: options.Logger.Named("workspacestats"),
773+
Pubsub: options.Pubsub,
774+
TemplateScheduleStore: options.TemplateScheduleStore,
775+
StatsBatcher: options.StatsBatcher,
776+
UsageTracker: options.WorkspaceUsageTracker,
777+
UpdateAgentMetricsFn: options.UpdateAgentMetrics,
778+
AppStatBatchSize: workspaceapps.DefaultStatsDBReporterBatchSize,
779+
DisableDatabaseInserts: !options.DeploymentValues.TemplateInsights.Enable.Value(),
779780
})
780781
workspaceAppsLogger := options.Logger.Named("workspaceapps")
781782
if options.WorkspaceAppsStatsCollectorOptions.Logger == nil {
@@ -1528,11 +1529,28 @@ func New(options *Options) *API {
15281529
})
15291530
r.Route("/insights", func(r chi.Router) {
15301531
r.Use(apiKeyMiddleware)
1531-
r.Get("/daus", api.deploymentDAUs)
1532-
r.Get("/user-activity", api.insightsUserActivity)
1532+
r.Group(func(r chi.Router) {
1533+
r.Use(
1534+
func(next http.Handler) http.Handler {
1535+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
1536+
if !options.DeploymentValues.TemplateInsights.Enable.Value() {
1537+
httpapi.Write(context.Background(), rw, http.StatusNotFound, codersdk.Response{
1538+
Message: "Not Found.",
1539+
Detail: "Template insights are disabled.",
1540+
})
1541+
return
1542+
}
1543+
1544+
next.ServeHTTP(rw, r)
1545+
})
1546+
},
1547+
)
1548+
r.Get("/daus", api.deploymentDAUs)
1549+
r.Get("/user-activity", api.insightsUserActivity)
1550+
r.Get("/user-latency", api.insightsUserLatency)
1551+
r.Get("/templates", api.insightsTemplates)
1552+
})
15331553
r.Get("/user-status-counts", api.insightsUserStatusCounts)
1534-
r.Get("/user-latency", api.insightsUserLatency)
1535-
r.Get("/templates", api.insightsTemplates)
15361554
})
15371555
r.Route("/debug", func(r chi.Router) {
15381556
r.Use(

0 commit comments

Comments
 (0)