Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit353f5de

Browse files
authored
fix(coderd): fix logic for reporting prebuilt workspace duration metric (#19641)
## DescriptionWhen creating a prebuilt workspace, both `flags.IsPrebuild` and`flags.IsFirstBuild` are true. Previously, the logic rejected cases withmultiple flags, so `coderd_workspace_creation_duration_seconds` wasn’tupdated for prebuilt creations. This is the only valid scenario wheretwo flags can be true.## Changes* Fix logic to update `coderd_workspace_creation_duration_seconds`metric for prebuilt workspaces.* Add prebuild helper functions to coderdenttest (other prebuild testscan reuse this).* Update workspace's provisionerdmetric tests to include this metric.Follow-up:#19503Related to:#19528
1 parent02ecf32 commit353f5de

File tree

3 files changed

+185
-96
lines changed

3 files changed

+185
-96
lines changed

‎coderd/provisionerdserver/metrics.go‎

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -100,25 +100,14 @@ func (m *Metrics) Register(reg prometheus.Registerer) error {
100100
returnreg.Register(m.workspaceClaimTimings)
101101
}
102102

103-
func (fWorkspaceTimingFlags)count()int {
104-
count:=0
105-
iff.IsPrebuild {
106-
count++
107-
}
108-
iff.IsClaim {
109-
count++
110-
}
111-
iff.IsFirstBuild {
112-
count++
113-
}
114-
returncount
115-
}
116-
117-
// getWorkspaceTimingType returns the type of the workspace build:
118-
// - isPrebuild: if the workspace build corresponds to the creation of a prebuilt workspace
119-
// - isClaim: if the workspace build corresponds to the claim of a prebuilt workspace
120-
// - isWorkspaceFirstBuild: if the workspace build corresponds to the creation of a regular workspace
121-
// (not created from the prebuild pool)
103+
// getWorkspaceTimingType classifies a workspace build:
104+
// - PrebuildCreation: creation of a prebuilt workspace
105+
// - PrebuildClaim: claim of an existing prebuilt workspace
106+
// - WorkspaceCreation: first build of a regular (non-prebuilt) workspace
107+
//
108+
// Note: order matters. Creating a prebuilt workspace is also a first build
109+
// (IsPrebuild && IsFirstBuild). We check IsPrebuild before IsFirstBuild so
110+
// prebuilds take precedence. This is the only case where two flags can be true.
122111
funcgetWorkspaceTimingType(flagsWorkspaceTimingFlags)WorkspaceTimingType {
123112
switch {
124113
caseflags.IsPrebuild:
@@ -149,14 +138,6 @@ func (m *Metrics) UpdateWorkspaceTimingsMetrics(
149138
"isClaim",flags.IsClaim,
150139
"isWorkspaceFirstBuild",flags.IsFirstBuild)
151140

152-
ifflags.count()>1 {
153-
m.logger.Warn(ctx,"invalid workspace timing flags",
154-
"isPrebuild",flags.IsPrebuild,
155-
"isClaim",flags.IsClaim,
156-
"isWorkspaceFirstBuild",flags.IsFirstBuild)
157-
return
158-
}
159-
160141
workspaceTimingType:=getWorkspaceTimingType(flags)
161142
switchworkspaceTimingType {
162143
caseWorkspaceCreation:

‎enterprise/coderd/coderdenttest/coderdenttest.go‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/ed25519"
66
"crypto/rand"
77
"crypto/tls"
8+
"database/sql"
89
"io"
910
"net/http"
1011
"os/exec"
@@ -23,10 +24,13 @@ import (
2324
"github.com/coder/coder/v2/coderd/coderdtest"
2425
"github.com/coder/coder/v2/coderd/database"
2526
"github.com/coder/coder/v2/coderd/database/pubsub"
27+
"github.com/coder/coder/v2/coderd/prebuilds"
28+
"github.com/coder/coder/v2/coderd/util/ptr"
2629
"github.com/coder/coder/v2/codersdk"
2730
"github.com/coder/coder/v2/codersdk/drpcsdk"
2831
"github.com/coder/coder/v2/enterprise/coderd"
2932
"github.com/coder/coder/v2/enterprise/coderd/license"
33+
entprebuilds"github.com/coder/coder/v2/enterprise/coderd/prebuilds"
3034
"github.com/coder/coder/v2/enterprise/dbcrypt"
3135
"github.com/coder/coder/v2/provisioner/echo"
3236
"github.com/coder/coder/v2/provisioner/terraform"
@@ -446,3 +450,98 @@ func newExternalProvisionerDaemon(t testing.TB, client *codersdk.Client, org uui
446450

447451
returncloser
448452
}
453+
454+
funcGetRunningPrebuilds(
455+
ctx context.Context,
456+
t*testing.T,
457+
db database.Store,
458+
desiredPrebuildsint,
459+
) []database.GetRunningPrebuiltWorkspacesRow {
460+
t.Helper()
461+
462+
varrunningPrebuilds []database.GetRunningPrebuiltWorkspacesRow
463+
testutil.Eventually(ctx,t,func(context.Context)bool {
464+
prebuiltWorkspaces,err:=db.GetRunningPrebuiltWorkspaces(ctx)
465+
assert.NoError(t,err,"failed to get running prebuilds")
466+
467+
for_,prebuild:=rangeprebuiltWorkspaces {
468+
runningPrebuilds=append(runningPrebuilds,prebuild)
469+
470+
agents,err:=db.GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx,prebuild.ID)
471+
assert.NoError(t,err,"failed to get agents")
472+
473+
// Manually mark all agents as ready since tests don't have real agent processes
474+
// that would normally report their lifecycle state. Prebuilt workspaces are only
475+
// eligible for claiming when their agents reach the "ready" state.
476+
for_,agent:=rangeagents {
477+
err=db.UpdateWorkspaceAgentLifecycleStateByID(ctx, database.UpdateWorkspaceAgentLifecycleStateByIDParams{
478+
ID:agent.ID,
479+
LifecycleState:database.WorkspaceAgentLifecycleStateReady,
480+
StartedAt: sql.NullTime{Time:time.Now().Add(time.Hour),Valid:true},
481+
ReadyAt: sql.NullTime{Time:time.Now().Add(-1*time.Hour),Valid:true},
482+
})
483+
assert.NoError(t,err,"failed to update agent")
484+
}
485+
}
486+
487+
t.Logf("found %d running prebuilds so far, want %d",len(runningPrebuilds),desiredPrebuilds)
488+
returnlen(runningPrebuilds)==desiredPrebuilds
489+
},testutil.IntervalSlow,"found %d running prebuilds, expected %d",len(runningPrebuilds),desiredPrebuilds)
490+
491+
returnrunningPrebuilds
492+
}
493+
494+
funcMustRunReconciliationLoopForPreset(
495+
ctx context.Context,
496+
t*testing.T,
497+
db database.Store,
498+
reconciler*entprebuilds.StoreReconciler,
499+
preset codersdk.Preset,
500+
) []*prebuilds.ReconciliationActions {
501+
t.Helper()
502+
503+
state,err:=reconciler.SnapshotState(ctx,db)
504+
require.NoError(t,err)
505+
ps,err:=state.FilterByPreset(preset.ID)
506+
require.NoError(t,err)
507+
require.NotNil(t,ps)
508+
actions,err:=reconciler.CalculateActions(ctx,*ps)
509+
require.NoError(t,err)
510+
require.NotNil(t,actions)
511+
require.NoError(t,reconciler.ReconcilePreset(ctx,*ps))
512+
513+
returnactions
514+
}
515+
516+
funcMustClaimPrebuild(
517+
ctx context.Context,
518+
t*testing.T,
519+
client*codersdk.Client,
520+
userClient*codersdk.Client,
521+
usernamestring,
522+
version codersdk.TemplateVersion,
523+
presetID uuid.UUID,
524+
autostartSchedule...string,
525+
) codersdk.Workspace {
526+
t.Helper()
527+
528+
varstartSchedulestring
529+
iflen(autostartSchedule)>0 {
530+
startSchedule=autostartSchedule[0]
531+
}
532+
533+
workspaceName:=strings.ReplaceAll(testutil.GetRandomName(t),"_","-")
534+
userWorkspace,err:=userClient.CreateUserWorkspace(ctx,username, codersdk.CreateWorkspaceRequest{
535+
TemplateVersionID:version.ID,
536+
Name:workspaceName,
537+
TemplateVersionPresetID:presetID,
538+
AutostartSchedule:ptr.Ref(startSchedule),
539+
})
540+
require.NoError(t,err)
541+
build:=coderdtest.AwaitWorkspaceBuildJobCompleted(t,userClient,userWorkspace.LatestBuild.ID)
542+
require.Equal(t,build.Job.Status,codersdk.ProvisionerJobSucceeded)
543+
workspace:=coderdtest.MustWorkspace(t,client,userWorkspace.ID)
544+
require.Equal(t,codersdk.WorkspaceTransitionStart,workspace.LatestBuild.Transition)
545+
546+
returnworkspace
547+
}

‎enterprise/coderd/workspaces_test.go‎

Lines changed: 78 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2879,113 +2879,122 @@ func TestWorkspaceProvisionerdServerMetrics(t *testing.T) {
28792879
t.Parallel()
28802880

28812881
// Setup
2882-
log:=testutil.Logger(t)
2882+
clock:=quartz.NewMock(t)
2883+
ctx:=testutil.Context(t,testutil.WaitSuperLong)
2884+
db,pb:=dbtestutil.NewDB(t,dbtestutil.WithDumpOnFailure())
2885+
logger:=testutil.Logger(t)
28832886
reg:=prometheus.NewRegistry()
2884-
provisionerdserverMetrics:=provisionerdserver.NewMetrics(log)
2887+
provisionerdserverMetrics:=provisionerdserver.NewMetrics(logger)
28852888
err:=provisionerdserverMetrics.Register(reg)
28862889
require.NoError(t,err)
2887-
client,db,owner:=coderdenttest.NewWithDatabase(t,&coderdenttest.Options{
2890+
client,_,api,owner:=coderdenttest.NewWithAPI(t,&coderdenttest.Options{
28882891
Options:&coderdtest.Options{
2892+
Database:db,
2893+
Pubsub:pb,
28892894
IncludeProvisionerDaemon:true,
2895+
Clock:clock,
28902896
ProvisionerdServerMetrics:provisionerdserverMetrics,
28912897
},
2892-
LicenseOptions:&coderdenttest.LicenseOptions{
2893-
Features: license.Features{
2894-
codersdk.FeatureWorkspacePrebuilds:1,
2895-
},
2896-
},
28972898
})
28982899

2899-
// Given: a template and a template version with a preset without prebuild instances
2900-
presetNoPrebuildID:=uuid.New()
2901-
versionNoPrebuild:=coderdtest.CreateTemplateVersion(t,client,owner.OrganizationID,nil)
2902-
_=coderdtest.AwaitTemplateVersionJobCompleted(t,client,versionNoPrebuild.ID)
2903-
templateNoPrebuild:=coderdtest.CreateTemplate(t,client,owner.OrganizationID,versionNoPrebuild.ID)
2904-
presetNoPrebuild:=dbgen.Preset(t,db, database.InsertPresetParams{
2905-
ID:presetNoPrebuildID,
2906-
TemplateVersionID:versionNoPrebuild.ID,
2907-
})
2900+
// Setup Prebuild reconciler
2901+
cache:=files.New(prometheus.NewRegistry(),&coderdtest.FakeAuthorizer{})
2902+
reconciler:=prebuilds.NewStoreReconciler(
2903+
db,pb,cache,
2904+
codersdk.PrebuildsConfig{},
2905+
logger,
2906+
clock,
2907+
prometheus.NewRegistry(),
2908+
notifications.NewNoopEnqueuer(),
2909+
api.AGPL.BuildUsageChecker,
2910+
)
2911+
varclaimer agplprebuilds.Claimer=prebuilds.NewEnterpriseClaimer(db)
2912+
api.AGPL.PrebuildsClaimer.Store(&claimer)
2913+
2914+
organizationName,err:=client.Organization(ctx,owner.OrganizationID)
2915+
require.NoError(t,err)
2916+
userClient,user:=coderdtest.CreateAnotherUser(t,client,owner.OrganizationID,rbac.RoleMember())
29082917

2909-
// Given: a template and a template version with a preset with a prebuild instance
2910-
presetPrebuildID:=uuid.New()
2911-
versionPrebuild:=coderdtest.CreateTemplateVersion(t,client,owner.OrganizationID,nil)
2912-
_=coderdtest.AwaitTemplateVersionJobCompleted(t,client,versionPrebuild.ID)
2918+
// Setup template and template version with a preset with 1 prebuild instance
2919+
versionPrebuild:=coderdtest.CreateTemplateVersion(t,client,owner.OrganizationID,templateWithAgentAndPresetsWithPrebuilds(1))
2920+
coderdtest.AwaitTemplateVersionJobCompleted(t,client,versionPrebuild.ID)
29132921
templatePrebuild:=coderdtest.CreateTemplate(t,client,owner.OrganizationID,versionPrebuild.ID)
2914-
presetPrebuild:=dbgen.Preset(t,db, database.InsertPresetParams{
2915-
ID:presetPrebuildID,
2916-
TemplateVersionID:versionPrebuild.ID,
2917-
DesiredInstances: sql.NullInt32{Int32:1,Valid:true},
2918-
})
2919-
// Given: a prebuild workspace
2920-
wb:=dbfake.WorkspaceBuild(t,db, database.WorkspaceTable{
2921-
OwnerID:database.PrebuildsSystemUserID,
2922-
TemplateID:templatePrebuild.ID,
2923-
}).Seed(database.WorkspaceBuild{
2924-
TemplateVersionID:versionPrebuild.ID,
2925-
TemplateVersionPresetID: uuid.NullUUID{
2926-
UUID:presetPrebuildID,
2927-
Valid:true,
2928-
},
2929-
}).WithAgent(func(agent []*proto.Agent) []*proto.Agent {
2930-
returnagent
2931-
}).Do()
2922+
presetsPrebuild,err:=client.TemplateVersionPresets(ctx,versionPrebuild.ID)
2923+
require.NoError(t,err)
2924+
require.Len(t,presetsPrebuild,1)
29322925

2933-
// Mark the prebuilt workspace's agent as ready so the prebuild can be claimed
2934-
// nolint:gocritic
2935-
ctx:=dbauthz.AsSystemRestricted(testutil.Context(t,testutil.WaitLong))
2936-
agent,err:=db.GetWorkspaceAgentAndLatestBuildByAuthToken(ctx,uuid.MustParse(wb.AgentToken))
2926+
// Setup template and template version with a preset without prebuild instances
2927+
versionNoPrebuild:=coderdtest.CreateTemplateVersion(t,client,owner.OrganizationID,templateWithAgentAndPresetsWithPrebuilds(0))
2928+
coderdtest.AwaitTemplateVersionJobCompleted(t,client,versionNoPrebuild.ID)
2929+
templateNoPrebuild:=coderdtest.CreateTemplate(t,client,owner.OrganizationID,versionNoPrebuild.ID)
2930+
presetsNoPrebuild,err:=client.TemplateVersionPresets(ctx,versionNoPrebuild.ID)
29372931
require.NoError(t,err)
2938-
err=db.UpdateWorkspaceAgentLifecycleStateByID(ctx, database.UpdateWorkspaceAgentLifecycleStateByIDParams{
2939-
ID:agent.WorkspaceAgent.ID,
2940-
LifecycleState:database.WorkspaceAgentLifecycleStateReady,
2932+
require.Len(t,presetsNoPrebuild,1)
2933+
2934+
// Given: no histogram value for prebuilt workspaces creation
2935+
prebuildCreationMetric:=promhelp.MetricValue(t,reg,"coderd_workspace_creation_duration_seconds", prometheus.Labels{
2936+
"organization_name":organizationName.Name,
2937+
"template_name":templatePrebuild.Name,
2938+
"preset_name":presetsPrebuild[0].Name,
2939+
"type":"prebuild",
29412940
})
2942-
require.NoError(t,err)
2941+
require.Nil(t,prebuildCreationMetric)
29432942

2944-
organizationName,err:=client.Organization(ctx,owner.OrganizationID)
2945-
require.NoError(t,err)
2946-
user,err:=client.User(ctx,"testUser")
2947-
require.NoError(t,err)
2943+
// Given: reconciliation loop runs and starts prebuilt workspace
2944+
coderdenttest.MustRunReconciliationLoopForPreset(ctx,t,db,reconciler,presetsPrebuild[0])
2945+
runningPrebuilds:=coderdenttest.GetRunningPrebuilds(ctx,t,db,1)
2946+
require.Len(t,runningPrebuilds,1)
2947+
2948+
// Then: the histogram value for prebuilt workspace creation should be updated
2949+
prebuildCreationHistogram:=promhelp.HistogramValue(t,reg,"coderd_workspace_creation_duration_seconds", prometheus.Labels{
2950+
"organization_name":organizationName.Name,
2951+
"template_name":templatePrebuild.Name,
2952+
"preset_name":presetsPrebuild[0].Name,
2953+
"type":"prebuild",
2954+
})
2955+
require.NotNil(t,prebuildCreationHistogram)
2956+
require.Equal(t,uint64(1),prebuildCreationHistogram.GetSampleCount())
2957+
2958+
// Given: a running prebuilt workspace, ready to be claimed
2959+
prebuild:=coderdtest.MustWorkspace(t,client,runningPrebuilds[0].ID)
2960+
require.Equal(t,codersdk.WorkspaceTransitionStart,prebuild.LatestBuild.Transition)
2961+
require.Nil(t,prebuild.DormantAt)
2962+
require.Nil(t,prebuild.DeletingAt)
29482963

29492964
// Given: no histogram value for prebuilt workspaces claim
2950-
prebuiltWorkspaceHistogramMetric:=promhelp.MetricValue(t,reg,"coderd_prebuilt_workspace_claim_duration_seconds", prometheus.Labels{
2965+
prebuildClaimMetric:=promhelp.MetricValue(t,reg,"coderd_prebuilt_workspace_claim_duration_seconds", prometheus.Labels{
29512966
"organization_name":organizationName.Name,
29522967
"template_name":templatePrebuild.Name,
2953-
"preset_name":presetPrebuild.Name,
2968+
"preset_name":presetsPrebuild[0].Name,
29542969
})
2955-
require.Nil(t,prebuiltWorkspaceHistogramMetric)
2970+
require.Nil(t,prebuildClaimMetric)
29562971

29572972
// Given: the prebuilt workspace is claimed by a user
2958-
claimedWorkspace,err:=client.CreateUserWorkspace(ctx,user.ID.String(), codersdk.CreateWorkspaceRequest{
2959-
TemplateVersionID:versionPrebuild.ID,
2960-
TemplateVersionPresetID:presetPrebuildID,
2961-
Name:coderdtest.RandomUsername(t),
2962-
})
2963-
require.NoError(t,err)
2964-
coderdtest.AwaitWorkspaceBuildJobCompleted(t,client,claimedWorkspace.LatestBuild.ID)
2965-
require.Equal(t,wb.Workspace.ID,claimedWorkspace.ID)
2973+
workspace:=coderdenttest.MustClaimPrebuild(ctx,t,client,userClient,user.Username,versionPrebuild,presetsPrebuild[0].ID)
2974+
require.Equal(t,prebuild.ID,workspace.ID)
29662975

29672976
// Then: the histogram value for prebuilt workspace claim should be updated
2968-
prebuiltWorkspaceHistogram:=promhelp.HistogramValue(t,reg,"coderd_prebuilt_workspace_claim_duration_seconds", prometheus.Labels{
2977+
prebuildClaimHistogram:=promhelp.HistogramValue(t,reg,"coderd_prebuilt_workspace_claim_duration_seconds", prometheus.Labels{
29692978
"organization_name":organizationName.Name,
29702979
"template_name":templatePrebuild.Name,
2971-
"preset_name":presetPrebuild.Name,
2980+
"preset_name":presetsPrebuild[0].Name,
29722981
})
2973-
require.NotNil(t,prebuiltWorkspaceHistogram)
2974-
require.Equal(t,uint64(1),prebuiltWorkspaceHistogram.GetSampleCount())
2982+
require.NotNil(t,prebuildClaimHistogram)
2983+
require.Equal(t,uint64(1),prebuildClaimHistogram.GetSampleCount())
29752984

29762985
// Given: no histogram value for regular workspaces creation
29772986
regularWorkspaceHistogramMetric:=promhelp.MetricValue(t,reg,"coderd_workspace_creation_duration_seconds", prometheus.Labels{
29782987
"organization_name":organizationName.Name,
29792988
"template_name":templateNoPrebuild.Name,
2980-
"preset_name":presetNoPrebuild.Name,
2989+
"preset_name":presetsNoPrebuild[0].Name,
29812990
"type":"regular",
29822991
})
29832992
require.Nil(t,regularWorkspaceHistogramMetric)
29842993

29852994
// Given: a user creates a regular workspace (without prebuild pool)
29862995
regularWorkspace,err:=client.CreateUserWorkspace(ctx,user.ID.String(), codersdk.CreateWorkspaceRequest{
29872996
TemplateVersionID:versionNoPrebuild.ID,
2988-
TemplateVersionPresetID:presetNoPrebuildID,
2997+
TemplateVersionPresetID:presetsNoPrebuild[0].ID,
29892998
Name:coderdtest.RandomUsername(t),
29902999
})
29913000
require.NoError(t,err)
@@ -2995,7 +3004,7 @@ func TestWorkspaceProvisionerdServerMetrics(t *testing.T) {
29953004
regularWorkspaceHistogram:=promhelp.HistogramValue(t,reg,"coderd_workspace_creation_duration_seconds", prometheus.Labels{
29963005
"organization_name":organizationName.Name,
29973006
"template_name":templateNoPrebuild.Name,
2998-
"preset_name":presetNoPrebuild.Name,
3007+
"preset_name":presetsNoPrebuild[0].Name,
29993008
"type":"regular",
30003009
})
30013010
require.NotNil(t,regularWorkspaceHistogram)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp