Skip to content

Commit 8dbf511

Browse files
committed
Minor Kyle updates
1 parent b5c1662 commit 8dbf511

File tree

7 files changed

+170
-173
lines changed

7 files changed

+170
-173
lines changed

coderd/coderd.go

-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package coderd
22

33
import (
4-
"context"
54
"crypto/x509"
65
"io"
76
"net/http"
@@ -116,8 +115,6 @@ func New(options *Options) *API {
116115
options.Logger.Named("metrics_cache"),
117116
)
118117

119-
metricsCache.Start(context.Background())
120-
121118
r := chi.NewRouter()
122119
api := &API{
123120
Options: options,

coderd/metrics.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func (api *API) daus(rw http.ResponseWriter, r *http.Request) {
2525
httpapi.Forbidden(rw)
2626
return
2727
}
28-
resp := api.metricsCache.GetDAUs()
28+
resp := api.metricsCache.DAUs()
2929
if resp.Entries == nil {
3030
resp.Entries = []codersdk.DAUEntry{}
3131
}

coderd/metrics_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func TestWorkspaceReportStats(t *testing.T) {
8080
daus, err := client.GetDAUsFromAgentStats(context.Background())
8181
require.NoError(t, err)
8282

83-
require.Equal(t, &codersdk.GetDAUsResponse{
83+
require.Equal(t, &codersdk.DAUsResponse{
8484
Entries: []codersdk.DAUEntry{},
8585
}, daus, "no DAUs when stats are empty")
8686

@@ -106,7 +106,7 @@ func TestWorkspaceReportStats(t *testing.T) {
106106
daus, err = client.GetDAUsFromAgentStats(context.Background())
107107
require.NoError(t, err)
108108

109-
require.Equal(t, &codersdk.GetDAUsResponse{
109+
require.Equal(t, &codersdk.DAUsResponse{
110110
Entries: []codersdk.DAUEntry{
111111
{
112112

coderd/metricscache/cache.go

-162
This file was deleted.

coderd/metricscache/metricscache.go

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package metricscache
2+
3+
import (
4+
"context"
5+
"os"
6+
"strconv"
7+
"sync/atomic"
8+
"time"
9+
10+
"golang.org/x/xerrors"
11+
12+
"cdr.dev/slog"
13+
"github.com/coder/coder/coderd/database"
14+
"github.com/coder/coder/codersdk"
15+
"github.com/coder/retry"
16+
)
17+
18+
type Cache struct {
19+
database database.Store
20+
log slog.Logger
21+
22+
dausResponse atomic.Pointer[codersdk.DAUsResponse]
23+
24+
doneCh chan struct{}
25+
cancel func()
26+
}
27+
28+
func New(db database.Store, log slog.Logger) *Cache {
29+
ctx, cancel := context.WithCancel(context.Background())
30+
31+
c := &Cache{
32+
database: db,
33+
log: log,
34+
doneCh: make(chan struct{}),
35+
cancel: cancel,
36+
}
37+
go c.run(ctx)
38+
return c
39+
}
40+
41+
const CacheRefreshIntervalEnv = "CODER_METRICS_CACHE_INTERVAL_MS"
42+
43+
func fillEmptyDAUDays(rows []database.GetDAUsFromAgentStatsRow) []database.GetDAUsFromAgentStatsRow {
44+
var newRows []database.GetDAUsFromAgentStatsRow
45+
46+
for i, row := range rows {
47+
if i == 0 {
48+
newRows = append(newRows, row)
49+
continue
50+
}
51+
52+
last := rows[i-1]
53+
54+
const day = time.Hour * 24
55+
diff := row.Date.Sub(last.Date)
56+
for diff > day {
57+
if diff <= day {
58+
break
59+
}
60+
last.Date = last.Date.Add(day)
61+
last.Daus = 0
62+
newRows = append(newRows, last)
63+
diff -= day
64+
}
65+
66+
newRows = append(newRows, row)
67+
continue
68+
}
69+
70+
return newRows
71+
}
72+
73+
func (c *Cache) refresh(ctx context.Context) error {
74+
err := c.database.DeleteOldAgentStats(ctx)
75+
if err != nil {
76+
return xerrors.Errorf("delete old stats: %w", err)
77+
}
78+
79+
daus, err := c.database.GetDAUsFromAgentStats(ctx)
80+
if err != nil {
81+
return err
82+
}
83+
84+
var resp codersdk.DAUsResponse
85+
for _, ent := range fillEmptyDAUDays(daus) {
86+
resp.Entries = append(resp.Entries, codersdk.DAUEntry{
87+
Date: ent.Date,
88+
DAUs: int(ent.Daus),
89+
})
90+
}
91+
92+
c.dausResponse.Store(&resp)
93+
return nil
94+
}
95+
96+
func (c *Cache) run(ctx context.Context) {
97+
defer close(c.doneCh)
98+
99+
interval := time.Hour
100+
101+
intervalEnv, ok := os.LookupEnv(CacheRefreshIntervalEnv)
102+
if ok {
103+
intervalMs, err := strconv.Atoi(intervalEnv)
104+
if err != nil {
105+
c.log.Error(
106+
ctx,
107+
"could not parse interval from env",
108+
slog.F("interval", intervalEnv),
109+
)
110+
} else {
111+
interval = time.Duration(intervalMs) * time.Millisecond
112+
}
113+
}
114+
115+
ticker := time.NewTicker(interval)
116+
defer ticker.Stop()
117+
118+
for {
119+
for r := retry.New(time.Millisecond*100, time.Minute); r.Wait(ctx); {
120+
start := time.Now()
121+
err := c.refresh(ctx)
122+
if err != nil {
123+
c.log.Error(ctx, "refresh", slog.Error(err))
124+
continue
125+
}
126+
c.log.Debug(
127+
ctx,
128+
"metrics refreshed",
129+
slog.F("took", time.Since(start)),
130+
slog.F("interval", interval),
131+
)
132+
break
133+
}
134+
135+
select {
136+
case <-ticker.C:
137+
case <-c.doneCh:
138+
return
139+
case <-ctx.Done():
140+
return
141+
}
142+
}
143+
}
144+
145+
func (c *Cache) Close() error {
146+
c.cancel()
147+
<-c.doneCh
148+
return nil
149+
}
150+
151+
// DAUs returns the DAUs or nil if they aren't ready yet.
152+
func (c *Cache) DAUs() codersdk.DAUsResponse {
153+
r := c.dausResponse.Load()
154+
if r == nil {
155+
return codersdk.DAUsResponse{}
156+
}
157+
return *r
158+
}

0 commit comments

Comments
 (0)