Skip to content

Commit 0ba618d

Browse files
committed
Support delete & stop in build progress bar
1 parent de5ba47 commit 0ba618d

File tree

8 files changed

+91
-46
lines changed

8 files changed

+91
-46
lines changed

coderd/database/databasefake/databasefake.go

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -235,33 +235,50 @@ func (q *fakeQuerier) GetTemplateDAUs(_ context.Context, templateID uuid.UUID) (
235235
return rs, nil
236236
}
237237

238-
func (q *fakeQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg database.GetTemplateAverageBuildTimeParams) (float64, error) {
239-
var times []float64
238+
func (q *fakeQuerier) GetTemplateAverageBuildTime(ctx context.Context, arg database.GetTemplateAverageBuildTimeParams) (database.GetTemplateAverageBuildTimeRow, error) {
239+
var emptyRow database.GetTemplateAverageBuildTimeRow
240+
var (
241+
startTimes []float64
242+
stopTimes []float64
243+
deleteTimes []float64
244+
)
240245
for _, wb := range q.workspaceBuilds {
241-
if wb.Transition != database.WorkspaceTransitionStart {
242-
continue
243-
}
244246
version, err := q.GetTemplateVersionByID(ctx, wb.TemplateVersionID)
245247
if err != nil {
246-
return -1, err
248+
return emptyRow, err
247249
}
248250
if version.TemplateID != arg.TemplateID {
249251
continue
250252
}
251253

252254
job, err := q.GetProvisionerJobByID(ctx, wb.JobID)
253255
if err != nil {
254-
return -1, err
256+
return emptyRow, err
255257
}
256258
if job.CompletedAt.Valid {
257-
times = append(times, job.CompletedAt.Time.Sub(job.StartedAt.Time).Seconds())
259+
took := job.CompletedAt.Time.Sub(job.StartedAt.Time).Seconds()
260+
if wb.Transition == database.WorkspaceTransitionStart {
261+
startTimes = append(startTimes, took)
262+
} else if wb.Transition == database.WorkspaceTransitionStop {
263+
stopTimes = append(stopTimes, took)
264+
} else if wb.Transition == database.WorkspaceTransitionDelete {
265+
deleteTimes = append(deleteTimes, took)
266+
}
258267
}
259268
}
260-
sort.Float64s(times)
261-
if len(times) == 0 {
262-
return -1, nil
269+
270+
tryMedian := func(fs []float64) float64 {
271+
if len(fs) == 0 {
272+
return -1
273+
}
274+
sort.Float64s(fs)
275+
return fs[len(startTimes)/2]
263276
}
264-
return times[len(times)/2], nil
277+
var row database.GetTemplateAverageBuildTimeRow
278+
row.DeleteMedian = tryMedian(deleteTimes)
279+
row.StopMedian = tryMedian(stopTimes)
280+
row.StartMedian = tryMedian(startTimes)
281+
return row, nil
265282
}
266283

267284
func (q *fakeQuerier) ParameterValue(_ context.Context, id uuid.UUID) (database.ParameterValue, error) {

coderd/database/querier.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

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

coderd/database/queries/templates.sql

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ RETURNING
109109
-- name: GetTemplateAverageBuildTime :one
110110
WITH build_times AS (
111111
SELECT
112-
EXTRACT(EPOCH FROM (pj.completed_at - pj.started_at))::FLOAT AS exec_time_sec
112+
EXTRACT(EPOCH FROM (pj.completed_at - pj.started_at))::FLOAT AS exec_time_sec,
113+
workspace_builds.transition
113114
FROM
114115
workspace_builds
115116
JOIN template_versions ON
@@ -118,14 +119,18 @@ JOIN provisioner_jobs pj ON
118119
workspace_builds.job_id = pj.id
119120
WHERE
120121
template_versions.template_id = @template_id AND
121-
(workspace_builds.transition = 'start') AND
122122
(pj.completed_at IS NOT NULL) AND (pj.started_at IS NOT NULL) AND
123123
(pj.started_at > @start_time) AND
124124
(pj.canceled_at IS NULL) AND
125125
((pj.error IS NULL) OR (pj.error = ''))
126126
ORDER BY
127127
workspace_builds.created_at DESC
128128
)
129-
SELECT coalesce((PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY exec_time_sec)), -1)::FLOAT
129+
SELECT
130+
-- Postgres offers no clear way to DRY this short of a function or other
131+
-- complexities.
132+
coalesce((PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY exec_time_sec) FILTER (WHERE transition = 'start')), -1)::FLOAT AS start_median,
133+
coalesce((PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY exec_time_sec) FILTER (WHERE transition = 'stop')), -1)::FLOAT AS stop_median,
134+
coalesce((PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY exec_time_sec) FILTER (WHERE transition = 'delete')), -1)::FLOAT AS delete_median
130135
FROM build_times
131136
;

coderd/metricscache/metricscache.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type Cache struct {
2929

3030
templateDAUResponses atomic.Pointer[map[uuid.UUID]codersdk.TemplateDAUsResponse]
3131
templateUniqueUsers atomic.Pointer[map[uuid.UUID]int]
32-
templateAverageBuildTime atomic.Pointer[map[uuid.UUID]time.Duration]
32+
templateAverageBuildTime atomic.Pointer[map[uuid.UUID]database.GetTemplateAverageBuildTimeRow]
3333

3434
done chan struct{}
3535
cancel func()
@@ -130,9 +130,9 @@ func (c *Cache) refresh(ctx context.Context) error {
130130
}
131131

132132
var (
133-
templateDAUs = make(map[uuid.UUID]codersdk.TemplateDAUsResponse, len(templates))
134-
templateUniqueUsers = make(map[uuid.UUID]int)
135-
templateAverageBuildTimeSec = make(map[uuid.UUID]time.Duration)
133+
templateDAUs = make(map[uuid.UUID]codersdk.TemplateDAUsResponse, len(templates))
134+
templateUniqueUsers = make(map[uuid.UUID]int)
135+
templateAverageBuildTimes = make(map[uuid.UUID]database.GetTemplateAverageBuildTimeRow)
136136
)
137137
for _, template := range templates {
138138
rows, err := c.database.GetTemplateDAUs(ctx, template.ID)
@@ -141,6 +141,7 @@ func (c *Cache) refresh(ctx context.Context) error {
141141
}
142142
templateDAUs[template.ID] = convertDAUResponse(rows)
143143
templateUniqueUsers[template.ID] = countUniqueUsers(rows)
144+
144145
templateAvgBuildTime, err := c.database.GetTemplateAverageBuildTime(ctx, database.GetTemplateAverageBuildTimeParams{
145146
TemplateID: uuid.NullUUID{
146147
UUID: template.ID,
@@ -151,14 +152,15 @@ func (c *Cache) refresh(ctx context.Context) error {
151152
Valid: true,
152153
},
153154
})
155+
154156
if err != nil {
155157
return err
156158
}
157-
templateAverageBuildTimeSec[template.ID] = time.Duration(float64(time.Second) * templateAvgBuildTime)
159+
templateAverageBuildTimes[template.ID] = templateAvgBuildTime
158160
}
159161
c.templateDAUResponses.Store(&templateDAUs)
160162
c.templateUniqueUsers.Store(&templateUniqueUsers)
161-
c.templateAverageBuildTime.Store(&templateAverageBuildTimeSec)
163+
c.templateAverageBuildTime.Store(&templateAverageBuildTimes)
162164

163165
return nil
164166
}
@@ -239,17 +241,22 @@ func (c *Cache) TemplateUniqueUsers(id uuid.UUID) (int, bool) {
239241
return resp, true
240242
}
241243

242-
func (c *Cache) TemplateAverageBuildTime(id uuid.UUID) (time.Duration, bool) {
244+
func (c *Cache) TemplateAverageBuildTime(id uuid.UUID) (*codersdk.TemplateBuildTimeStats, bool) {
243245
m := c.templateAverageBuildTime.Load()
244246
if m == nil {
245247
// Data loading.
246-
return -1, false
248+
return nil, false
247249
}
248250

249251
resp, ok := (*m)[id]
250-
if !ok || resp <= 0 {
252+
if !ok || resp.StartMedian <= 0 || resp.StopMedian <= 0 {
251253
// No data or not enough builds.
252-
return -1, false
254+
return nil, false
253255
}
254-
return resp, true
256+
257+
return &codersdk.TemplateBuildTimeStats{
258+
StartMillis: int64(resp.StartMedian * 1000),
259+
StopMillis: int64(resp.StopMedian * 1000),
260+
DeleteMillis: int64(resp.DeleteMedian * 1000),
261+
}, true
255262
}

coderd/templates.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ func (api *API) convertTemplate(
792792
ActiveVersionID: template.ActiveVersionID,
793793
WorkspaceOwnerCount: workspaceOwnerCount,
794794
ActiveUserCount: activeCount,
795-
AverageBuildTimeMillis: averageBuildTimeMillis,
795+
BuildTimeStats: averageBuildTimeMillis,
796796
Description: template.Description,
797797
Icon: template.Icon,
798798
MaxTTLMillis: time.Duration(template.MaxTtl).Milliseconds(),

coderd/templates_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ func TestTemplateMetrics(t *testing.T) {
594594
})
595595
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
596596
require.Equal(t, -1, template.ActiveUserCount)
597-
require.EqualValues(t, -1, template.AverageBuildTimeMillis)
597+
require.EqualValues(t, -1, template.BuildTimeStats)
598598

599599
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
600600
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
@@ -661,7 +661,7 @@ func TestTemplateMetrics(t *testing.T) {
661661
template, err = client.Template(ctx, template.ID)
662662
require.NoError(t, err)
663663
require.Equal(t, 1, template.ActiveUserCount)
664-
require.Greater(t, template.AverageBuildTimeMillis, int64(1))
664+
require.Greater(t, template.BuildTimeStats, int64(1))
665665

666666
workspaces, err = client.Workspaces(ctx, codersdk.WorkspaceFilter{})
667667
require.NoError(t, err)

codersdk/templates.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,20 @@ type Template struct {
2323
ActiveVersionID uuid.UUID `json:"active_version_id"`
2424
WorkspaceOwnerCount uint32 `json:"workspace_owner_count"`
2525
// ActiveUserCount is set to -1 when loading.
26-
ActiveUserCount int `json:"active_user_count"`
27-
// AverageBuildTimeMillis is set to -1 when there aren't enough recent builds.
28-
AverageBuildTimeMillis int64 `json:"average_build_time_ms"`
29-
Description string `json:"description"`
30-
Icon string `json:"icon"`
31-
MaxTTLMillis int64 `json:"max_ttl_ms"`
32-
MinAutostartIntervalMillis int64 `json:"min_autostart_interval_ms"`
33-
CreatedByID uuid.UUID `json:"created_by_id"`
34-
CreatedByName string `json:"created_by_name"`
26+
ActiveUserCount int `json:"active_user_count"`
27+
BuildTimeStats TemplateBuildTimeStats `json:"build_time_stats"`
28+
Description string `json:"description"`
29+
Icon string `json:"icon"`
30+
MaxTTLMillis int64 `json:"max_ttl_ms"`
31+
MinAutostartIntervalMillis int64 `json:"min_autostart_interval_ms"`
32+
CreatedByID uuid.UUID `json:"created_by_id"`
33+
CreatedByName string `json:"created_by_name"`
34+
}
35+
36+
type TemplateBuildTimeStats struct {
37+
StartMillis int64 `json:"start_ms"`
38+
StopMillis int64 `json:"stop_ms"`
39+
DeleteMillis int64 `json:"delete_ms"`
3540
}
3641

3742
type UpdateActiveTemplateVersion struct {

0 commit comments

Comments
 (0)