Skip to content

Commit 421bf7e

Browse files
authored
fix(coderd): use insights for DAUs, simplify metricscache (#12775)
Fixes #12134 Fixes coder/customers#384 Refs #12122
1 parent 5d82a78 commit 421bf7e

File tree

7 files changed

+86
-653
lines changed

7 files changed

+86
-653
lines changed

coderd/coderd.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,8 @@ func New(options *Options) *API {
366366
options.Database,
367367
options.Logger.Named("metrics_cache"),
368368
metricscache.Intervals{
369-
TemplateDAUs: options.MetricsCacheRefreshInterval,
370-
DeploymentStats: options.AgentStatsRefreshInterval,
369+
TemplateBuildTimes: options.MetricsCacheRefreshInterval,
370+
DeploymentStats: options.AgentStatsRefreshInterval,
371371
},
372372
)
373373

coderd/insights.go

+41-7
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,19 @@ const insightsTimeLayout = time.RFC3339
3232
// @Success 200 {object} codersdk.DAUsResponse
3333
// @Router /insights/daus [get]
3434
func (api *API) deploymentDAUs(rw http.ResponseWriter, r *http.Request) {
35-
ctx := r.Context()
3635
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceDeploymentValues) {
3736
httpapi.Forbidden(rw)
3837
return
3938
}
4039

41-
vals := r.URL.Query()
40+
api.returnDAUsInternal(rw, r, nil)
41+
}
42+
43+
func (api *API) returnDAUsInternal(rw http.ResponseWriter, r *http.Request, templateIDs []uuid.UUID) {
44+
ctx := r.Context()
45+
4246
p := httpapi.NewQueryParamParser()
47+
vals := r.URL.Query()
4348
tzOffset := p.Int(vals, 0, "tz_offset")
4449
p.ErrorExcessParams(vals)
4550
if len(p.Errors) > 0 {
@@ -50,12 +55,41 @@ func (api *API) deploymentDAUs(rw http.ResponseWriter, r *http.Request) {
5055
return
5156
}
5257

53-
_, resp, _ := api.metricsCache.DeploymentDAUs(tzOffset)
54-
if resp == nil || resp.Entries == nil {
55-
httpapi.Write(ctx, rw, http.StatusOK, &codersdk.DAUsResponse{
56-
Entries: []codersdk.DAUEntry{},
58+
loc := time.FixedZone("", tzOffset*3600)
59+
// If the time is 14:01 or 14:31, we still want to include all the
60+
// data between 14:00 and 15:00. Our rollups buckets are 30 minutes
61+
// so this works nicely. It works just as well for 23:59 as well.
62+
nextHourInLoc := time.Now().In(loc).Truncate(time.Hour).Add(time.Hour)
63+
// Always return 60 days of data (2 months).
64+
sixtyDaysAgo := nextHourInLoc.In(loc).Truncate(24*time.Hour).AddDate(0, 0, -60)
65+
66+
rows, err := api.Database.GetTemplateInsightsByInterval(ctx, database.GetTemplateInsightsByIntervalParams{
67+
StartTime: sixtyDaysAgo,
68+
EndTime: nextHourInLoc,
69+
IntervalDays: 1,
70+
TemplateIDs: templateIDs,
71+
})
72+
if err != nil {
73+
if httpapi.Is404Error(err) {
74+
httpapi.ResourceNotFound(rw)
75+
return
76+
}
77+
78+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
79+
Message: "Internal error fetching DAUs.",
80+
Detail: err.Error(),
81+
})
82+
}
83+
84+
resp := codersdk.DAUsResponse{
85+
TZHourOffset: tzOffset,
86+
Entries: make([]codersdk.DAUEntry, 0, len(rows)),
87+
}
88+
for _, row := range rows {
89+
resp.Entries = append(resp.Entries, codersdk.DAUEntry{
90+
Date: row.StartTime.Format(time.DateOnly),
91+
Amount: int(row.ActiveUsers),
5792
})
58-
return
5993
}
6094
httpapi.Write(ctx, rw, http.StatusOK, resp)
6195
}

coderd/insights_test.go

+34-40
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,25 @@ import (
3939
)
4040

4141
func TestDeploymentInsights(t *testing.T) {
42-
t.Skipf("This test is flaky: https://github.com/coder/coder/issues/12509")
43-
4442
t.Parallel()
4543

4644
clientTz, err := time.LoadLocation("America/Chicago")
4745
require.NoError(t, err)
4846

49-
db, ps := dbtestutil.NewDB(t)
47+
db, ps := dbtestutil.NewDB(t, dbtestutil.WithDumpOnFailure())
5048
logger := slogtest.Make(t, nil)
49+
rollupEvents := make(chan dbrollup.Event)
5150
client := coderdtest.New(t, &coderdtest.Options{
5251
Database: db,
5352
Pubsub: ps,
5453
Logger: &logger,
5554
IncludeProvisionerDaemon: true,
56-
AgentStatsRefreshInterval: time.Millisecond * 50,
55+
AgentStatsRefreshInterval: time.Millisecond * 100,
5756
DatabaseRolluper: dbrollup.New(
58-
logger.Named("dbrollup"),
57+
logger.Named("dbrollup").Leveled(slog.LevelDebug),
5958
db,
6059
dbrollup.WithInterval(time.Millisecond*100),
60+
dbrollup.WithEventChannel(rollupEvents),
6161
),
6262
})
6363

@@ -75,57 +75,51 @@ func TestDeploymentInsights(t *testing.T) {
7575
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
7676
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
7777

78-
_ = agenttest.New(t, client.URL, authToken)
79-
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
80-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
81-
defer cancel()
78+
ctx := testutil.Context(t, testutil.WaitLong)
8279

83-
daus, err := client.DeploymentDAUs(context.Background(), codersdk.TimezoneOffsetHour(clientTz))
80+
// Pre-check, no permission issues.
81+
daus, err := client.DeploymentDAUs(ctx, codersdk.TimezoneOffsetHour(clientTz))
8482
require.NoError(t, err)
8583

86-
res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{})
87-
require.NoError(t, err)
88-
assert.NotZero(t, res.Workspaces[0].LastUsedAt)
84+
_ = agenttest.New(t, client.URL, authToken)
85+
resources := coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()
8986

9087
conn, err := workspacesdk.New(client).
9188
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
92-
Logger: slogtest.Make(t, nil).Named("tailnet"),
89+
Logger: slogtest.Make(t, nil).Named("dialagent"),
9390
})
9491
require.NoError(t, err)
95-
defer func() {
96-
_ = conn.Close()
97-
}()
92+
defer conn.Close()
9893

9994
sshConn, err := conn.SSHClient(ctx)
10095
require.NoError(t, err)
101-
_ = sshConn.Close()
96+
defer sshConn.Close()
10297

103-
wantDAUs := &codersdk.DAUsResponse{
104-
TZHourOffset: codersdk.TimezoneOffsetHour(clientTz),
105-
Entries: []codersdk.DAUEntry{
106-
{
107-
Date: time.Now().In(clientTz).Format("2006-01-02"),
108-
Amount: 1,
109-
},
110-
},
111-
}
112-
require.Eventuallyf(t, func() bool {
113-
daus, err = client.DeploymentDAUs(ctx, codersdk.TimezoneOffsetHour(clientTz))
114-
require.NoError(t, err)
115-
return len(daus.Entries) > 0
116-
},
117-
testutil.WaitShort, testutil.IntervalFast,
118-
"deployment daus never loaded",
119-
)
120-
gotDAUs, err := client.DeploymentDAUs(ctx, codersdk.TimezoneOffsetHour(clientTz))
98+
sess, err := sshConn.NewSession()
12199
require.NoError(t, err)
122-
require.Equal(t, gotDAUs, wantDAUs)
100+
defer sess.Close()
123101

124-
template, err = client.Template(ctx, template.ID)
102+
r, w := io.Pipe()
103+
defer r.Close()
104+
defer w.Close()
105+
sess.Stdin = r
106+
sess.Stdout = io.Discard
107+
err = sess.Start("cat")
125108
require.NoError(t, err)
126109

127-
res, err = client.Workspaces(ctx, codersdk.WorkspaceFilter{})
128-
require.NoError(t, err)
110+
for {
111+
select {
112+
case <-ctx.Done():
113+
require.Fail(t, "timed out waiting for deployment daus to update", daus)
114+
case <-rollupEvents:
115+
}
116+
117+
daus, err = client.DeploymentDAUs(ctx, codersdk.TimezoneOffsetHour(clientTz))
118+
require.NoError(t, err)
119+
if len(daus.Entries) > 0 && daus.Entries[len(daus.Entries)-1].Amount > 0 {
120+
break
121+
}
122+
}
129123
}
130124

131125
func TestUserActivityInsights_SanityCheck(t *testing.T) {

coderd/metricscache/metrics_internal_test.go

-93
This file was deleted.

0 commit comments

Comments
 (0)