Skip to content

feat(coderd): return agent script timings #14923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add back the workspace timings endpoint
  • Loading branch information
BrunoQuaresma committed Oct 4, 2024
commit c7ae412a1d0e61cacf98aac3d9f53680afdff4d6
35 changes: 35 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,7 @@ func New(options *Options) *API {
r.Post("/", api.postWorkspaceAgentPortShare)
r.Delete("/", api.deleteWorkspaceAgentPortShare)
})
r.Get("/timings", api.workspaceTimings)
})
})
r.Route("/workspacebuilds/{workspacebuild}", func(r chi.Router) {
Expand Down
43 changes: 6 additions & 37 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2846,19 +2846,8 @@ func (s *MethodTestSuite) TestSystemFunctions() {
j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{
Type: database.ProvisionerJobTypeWorkspaceBuild,
})
_ = dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{JobID: j.ID, WorkspaceID: w.ID})
t := dbgen.ProvisionerJobTimings(s.T(), db, database.InsertProvisionerJobTimingsParams{
JobID: j.ID,
StartedAt: []time.Time{dbtime.Now(), dbtime.Now()},
EndedAt: []time.Time{dbtime.Now(), dbtime.Now()},
Stage: []database.ProvisionerJobTimingStage{
database.ProvisionerJobTimingStageInit,
database.ProvisionerJobTimingStagePlan,
},
Source: []string{"source1", "source2"},
Action: []string{"action1", "action2"},
Resource: []string{"resource1", "resource2"},
})
b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{JobID: j.ID, WorkspaceID: w.ID})
t := dbgen.ProvisionerJobTimings(s.T(), db, b, 2)
check.Args(j.ID).Asserts(w, policy.ActionRead).Returns(t)
}))
s.Run("GetWorkspaceAgentScriptTimingsByBuildID", s.Subtest(func(db database.Store, check *expects) {
Expand All @@ -2873,31 +2862,11 @@ func (s *MethodTestSuite) TestSystemFunctions() {
agent := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{
ResourceID: resource.ID,
})
scripts := dbgen.WorkspaceAgentScripts(s.T(), db, database.InsertWorkspaceAgentScriptsParams{
script := dbgen.WorkspaceAgentScript(s.T(), db, database.WorkspaceAgentScript{
WorkspaceAgentID: agent.ID,
CreatedAt: time.Now(),
LogSourceID: []uuid.UUID{
uuid.New(),
},
LogPath: []string{""},
Script: []string{""},
Cron: []string{""},
StartBlocksLogin: []bool{false},
RunOnStart: []bool{false},
RunOnStop: []bool{false},
TimeoutSeconds: []int32{0},
DisplayName: []string{""},
ID: []uuid.UUID{
uuid.New(),
},
})
timing := dbgen.WorkspaceAgentScriptTiming(s.T(), db, database.InsertWorkspaceAgentScriptTimingsParams{
StartedAt: dbtime.Now(),
EndedAt: dbtime.Now(),
Stage: database.WorkspaceAgentScriptTimingStageStart,
ScriptID: scripts[0].ID,
ExitCode: 0,
Status: database.WorkspaceAgentScriptTimingStatusOk,
timing := dbgen.WorkspaceAgentScriptTiming(s.T(), db, database.WorkspaceAgentScriptTiming{
ScriptID: script.ID,
})
rows := []database.GetWorkspaceAgentScriptTimingsByBuildIDRow{
{
Expand All @@ -2907,7 +2876,7 @@ func (s *MethodTestSuite) TestSystemFunctions() {
ScriptID: timing.ScriptID,
ExitCode: timing.ExitCode,
Status: timing.Status,
DisplayName: scripts[0].DisplayName,
DisplayName: script.DisplayName,
},
}
check.Args(build.ID).Asserts(rbac.ResourceSystem, policy.ActionRead).Returns(rows)
Expand Down
65 changes: 57 additions & 8 deletions coderd/database/dbgen/dbgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,45 @@ func WorkspaceAgent(t testing.TB, db database.Store, orig database.WorkspaceAgen
return agt
}

func WorkspaceAgentScripts(t testing.TB, db database.Store, orig database.InsertWorkspaceAgentScriptsParams) []database.WorkspaceAgentScript {
scripts, err := db.InsertWorkspaceAgentScripts(genCtx, orig)
func WorkspaceAgentScript(t testing.TB, db database.Store, orig database.WorkspaceAgentScript) database.WorkspaceAgentScript {
scripts, err := db.InsertWorkspaceAgentScripts(genCtx, database.InsertWorkspaceAgentScriptsParams{
WorkspaceAgentID: takeFirst(orig.WorkspaceAgentID, uuid.New()),
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
LogSourceID: []uuid.UUID{takeFirst(orig.LogSourceID, uuid.New())},
LogPath: []string{takeFirst(orig.LogPath, "")},
Script: []string{takeFirst(orig.Script, "")},
Cron: []string{takeFirst(orig.Cron, "")},
StartBlocksLogin: []bool{takeFirst(orig.StartBlocksLogin, false)},
RunOnStart: []bool{takeFirst(orig.RunOnStart, false)},
RunOnStop: []bool{takeFirst(orig.RunOnStop, false)},
TimeoutSeconds: []int32{takeFirst(orig.TimeoutSeconds, 0)},
DisplayName: []string{takeFirst(orig.DisplayName, "")},
ID: []uuid.UUID{takeFirst(orig.ID, uuid.New())},
})
require.NoError(t, err, "insert workspace agent script")
return scripts
require.NotEmpty(t, scripts, "insert workspace agent script returned no scripts")
return scripts[0]
}

func WorkspaceAgentScriptTiming(t testing.TB, db database.Store, orig database.InsertWorkspaceAgentScriptTimingsParams) database.WorkspaceAgentScriptTiming {
timing, err := db.InsertWorkspaceAgentScriptTimings(genCtx, orig)
func WorkspaceAgentScriptTimings(t testing.TB, db database.Store, script database.WorkspaceAgentScript, count int) []database.WorkspaceAgentScriptTiming {
timings := make([]database.WorkspaceAgentScriptTiming, count)
for i := range count {
timings[i] = WorkspaceAgentScriptTiming(t, db, database.WorkspaceAgentScriptTiming{
ScriptID: script.ID,
})
}
return timings
}

func WorkspaceAgentScriptTiming(t testing.TB, db database.Store, orig database.WorkspaceAgentScriptTiming) database.WorkspaceAgentScriptTiming {
timing, err := db.InsertWorkspaceAgentScriptTimings(genCtx, database.InsertWorkspaceAgentScriptTimingsParams{
StartedAt: takeFirst(orig.StartedAt, dbtime.Now()),
EndedAt: takeFirst(orig.EndedAt, dbtime.Now()),
Stage: takeFirst(orig.Stage, database.WorkspaceAgentScriptTimingStageStart),
ScriptID: takeFirst(orig.ScriptID, uuid.New()),
ExitCode: takeFirst(orig.ExitCode, 0),
Status: takeFirst(orig.Status, database.WorkspaceAgentScriptTimingStatusOk),
})
require.NoError(t, err, "insert workspace agent script")
return timing
}
Expand Down Expand Up @@ -947,12 +978,30 @@ func CryptoKey(t testing.TB, db database.Store, seed database.CryptoKey) databas
return key
}

func ProvisionerJobTimings(t testing.TB, db database.Store, seed database.InsertProvisionerJobTimingsParams) []database.ProvisionerJobTiming {
timings, err := db.InsertProvisionerJobTimings(genCtx, seed)
require.NoError(t, err, "insert provisioner job timings")
func ProvisionerJobTimings(t testing.TB, db database.Store, build database.WorkspaceBuild, count int) []database.ProvisionerJobTiming {
timings := make([]database.ProvisionerJobTiming, count)
for i := range count {
timings[i] = provisionerJobTiming(t, db, database.ProvisionerJobTiming{
JobID: build.JobID,
})
}
return timings
}

func provisionerJobTiming(t testing.TB, db database.Store, seed database.ProvisionerJobTiming) database.ProvisionerJobTiming {
timing, err := db.InsertProvisionerJobTimings(genCtx, database.InsertProvisionerJobTimingsParams{
JobID: takeFirst(seed.JobID, uuid.New()),
StartedAt: []time.Time{takeFirst(seed.StartedAt, dbtime.Now())},
EndedAt: []time.Time{takeFirst(seed.EndedAt, dbtime.Now())},
Stage: []database.ProvisionerJobTimingStage{takeFirst(seed.Stage, database.ProvisionerJobTimingStageInit)},
Source: []string{takeFirst(seed.Source, "source")},
Action: []string{takeFirst(seed.Action, "action")},
Resource: []string{takeFirst(seed.Resource, "resource")},
})
require.NoError(t, err, "insert provisioner job timing")
return timing[0]
}

func must[V any](v V, err error) V {
if err != nil {
panic(err)
Expand Down
85 changes: 45 additions & 40 deletions coderd/workspacebuilds.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,52 +661,16 @@ func (api *API) workspaceBuildTimings(rw http.ResponseWriter, r *http.Request) {
build = httpmw.WorkspaceBuildParam(r)
)

provisionerTimings, err := api.Database.GetProvisionerJobTimingsByJobID(ctx, build.JobID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching workspace timings.",
Detail: err.Error(),
})
return
}

agentScriptTimings, err := api.Database.GetWorkspaceAgentScriptTimingsByBuildID(ctx, build.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
timings, err := api.buildTimings(ctx, build)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Internal error fetching workspace agent script timings.",
Message: "Internal error fetching timings.",
Detail: err.Error(),
})
return
}

res := codersdk.WorkspaceBuildTimings{
ProvisionerTimings: make([]codersdk.ProvisionerTiming, 0, len(provisionerTimings)),
AgentScriptTimings: make([]codersdk.AgentScriptTiming, 0, len(agentScriptTimings)),
}

for _, t := range provisionerTimings {
res.ProvisionerTimings = append(res.ProvisionerTimings, codersdk.ProvisionerTiming{
JobID: t.JobID,
Stage: string(t.Stage),
Source: t.Source,
Action: t.Action,
Resource: t.Resource,
StartedAt: t.StartedAt,
EndedAt: t.EndedAt,
})
}
for _, t := range agentScriptTimings {
res.AgentScriptTimings = append(res.AgentScriptTimings, codersdk.AgentScriptTiming{
StartedAt: t.StartedAt,
EndedAt: t.EndedAt,
ExitCode: t.ExitCode,
Stage: string(t.Stage),
Status: string(t.Status),
DisplayName: t.DisplayName,
})
}

httpapi.Write(ctx, rw, http.StatusOK, res)
httpapi.Write(ctx, rw, http.StatusOK, timings)
}

type workspaceBuildsData struct {
Expand Down Expand Up @@ -1072,3 +1036,44 @@ func convertWorkspaceStatus(jobStatus codersdk.ProvisionerJobStatus, transition
// return error status since we should never get here
return codersdk.WorkspaceStatusFailed
}

func (api *API) buildTimings(ctx context.Context, build database.WorkspaceBuild) (codersdk.WorkspaceBuildTimings, error) {
provisionerTimings, err := api.Database.GetProvisionerJobTimingsByJobID(ctx, build.JobID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return codersdk.WorkspaceBuildTimings{}, xerrors.Errorf("fetching provisioner job timings: %w", err)
}

agentScriptTimings, err := api.Database.GetWorkspaceAgentScriptTimingsByBuildID(ctx, build.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return codersdk.WorkspaceBuildTimings{}, xerrors.Errorf("fetching workspace agent script timings: %w", err)
}

res := codersdk.WorkspaceBuildTimings{
ProvisionerTimings: make([]codersdk.ProvisionerTiming, 0, len(provisionerTimings)),
AgentScriptTimings: make([]codersdk.AgentScriptTiming, 0, len(agentScriptTimings)),
}

for _, t := range provisionerTimings {
res.ProvisionerTimings = append(res.ProvisionerTimings, codersdk.ProvisionerTiming{
JobID: t.JobID,
Stage: string(t.Stage),
Source: t.Source,
Action: t.Action,
Resource: t.Resource,
StartedAt: t.StartedAt,
EndedAt: t.EndedAt,
})
}
for _, t := range agentScriptTimings {
res.AgentScriptTimings = append(res.AgentScriptTimings, codersdk.AgentScriptTiming{
StartedAt: t.StartedAt,
EndedAt: t.EndedAt,
ExitCode: t.ExitCode,
Stage: string(t.Stage),
Status: string(t.Status),
DisplayName: t.DisplayName,
})
}

return res, nil
}
Loading
Loading