feat: add provisioning timings to understand slow build times (#14274)

This commit is contained in:
Danny Kopping
2024-08-21 14:18:58 +02:00
committed by GitHub
parent 9c8c6a952d
commit 6960d194ae
42 changed files with 2653 additions and 701 deletions
+8
View File
@@ -2793,6 +2793,14 @@ func (q *querier) InsertProvisionerJobLogs(ctx context.Context, arg database.Ins
return q.db.InsertProvisionerJobLogs(ctx, arg)
}
// TODO: We need to create a ProvisionerJob resource type
func (q *querier) InsertProvisionerJobTimings(ctx context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) {
// if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil {
// return nil, err
// }
return q.db.InsertProvisionerJobTimings(ctx, arg)
}
func (q *querier) InsertProvisionerKey(ctx context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) {
return insert(q.log, q.auth, rbac.ResourceProvisionerKeys.InOrg(arg.OrganizationID).WithID(arg.ID), q.db.InsertProvisionerKey)(ctx, arg)
}
+7
View File
@@ -2470,6 +2470,13 @@ func (s *MethodTestSuite) TestSystemFunctions() {
JobID: j.ID,
}).Asserts( /*rbac.ResourceSystem, policy.ActionCreate*/ )
}))
s.Run("InsertProvisionerJobTimings", s.Subtest(func(db database.Store, check *expects) {
// TODO: we need to create a ProvisionerJob resource
j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{})
check.Args(database.InsertProvisionerJobTimingsParams{
JobID: j.ID,
}).Asserts( /*rbac.ResourceSystem, policy.ActionCreate*/ )
}))
s.Run("UpsertProvisionerDaemon", s.Subtest(func(db database.Store, check *expects) {
org := dbgen.Organization(s.T(), db, database.Organization{})
pd := rbac.ResourceProvisionerDaemon.InOrg(org.ID)
+9
View File
@@ -6652,6 +6652,15 @@ func (q *FakeQuerier) InsertProvisionerJobLogs(_ context.Context, arg database.I
return logs, nil
}
func (*FakeQuerier) InsertProvisionerJobTimings(_ context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) {
err := validateDatabaseType(arg)
if err != nil {
return nil, err
}
return nil, nil
}
func (q *FakeQuerier) InsertProvisionerKey(_ context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) {
err := validateDatabaseType(arg)
if err != nil {
+7
View File
@@ -1712,6 +1712,13 @@ func (m metricsStore) InsertProvisionerJobLogs(ctx context.Context, arg database
return logs, err
}
func (m metricsStore) InsertProvisionerJobTimings(ctx context.Context, arg database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) {
start := time.Now()
r0, r1 := m.s.InsertProvisionerJobTimings(ctx, arg)
m.queryLatencies.WithLabelValues("InsertProvisionerJobTimings").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) InsertProvisionerKey(ctx context.Context, arg database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) {
start := time.Now()
r0, r1 := m.s.InsertProvisionerKey(ctx, arg)
+15
View File
@@ -3604,6 +3604,21 @@ func (mr *MockStoreMockRecorder) InsertProvisionerJobLogs(arg0, arg1 any) *gomoc
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertProvisionerJobLogs", reflect.TypeOf((*MockStore)(nil).InsertProvisionerJobLogs), arg0, arg1)
}
// InsertProvisionerJobTimings mocks base method.
func (m *MockStore) InsertProvisionerJobTimings(arg0 context.Context, arg1 database.InsertProvisionerJobTimingsParams) ([]database.ProvisionerJobTiming, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertProvisionerJobTimings", arg0, arg1)
ret0, _ := ret[0].([]database.ProvisionerJobTiming)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertProvisionerJobTimings indicates an expected call of InsertProvisionerJobTimings.
func (mr *MockStoreMockRecorder) InsertProvisionerJobTimings(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertProvisionerJobTimings", reflect.TypeOf((*MockStore)(nil).InsertProvisionerJobTimings), arg0, arg1)
}
// InsertProvisionerKey mocks base method.
func (m *MockStore) InsertProvisionerKey(arg0 context.Context, arg1 database.InsertProvisionerKeyParams) (database.ProvisionerKey, error) {
m.ctrl.T.Helper()
+4
View File
@@ -9,6 +9,10 @@ func Now() time.Time {
// Time returns a time compatible with Postgres. Postgres only stores dates with
// microsecond precision.
// FIXME(dannyk): refactor all calls to Time() to expect the input time to be modified to UTC; there are currently a
//
// few calls whose behavior would change subtly.
// See https://github.com/coder/coder/pull/14274#discussion_r1718427461
func Time(t time.Time) time.Time {
return t.Round(time.Microsecond)
}
+89
View File
@@ -136,6 +136,13 @@ CREATE TYPE provisioner_job_status AS ENUM (
COMMENT ON TYPE provisioner_job_status IS 'Computed status of a provisioner job. Jobs could be stuck in a hung state, these states do not guarantee any transition to another state.';
CREATE TYPE provisioner_job_timing_stage AS ENUM (
'init',
'plan',
'graph',
'apply'
);
CREATE TYPE provisioner_job_type AS ENUM (
'template_version_import',
'workspace_build',
@@ -849,6 +856,33 @@ CREATE SEQUENCE provisioner_job_logs_id_seq
ALTER SEQUENCE provisioner_job_logs_id_seq OWNED BY provisioner_job_logs.id;
CREATE VIEW provisioner_job_stats AS
SELECT
NULL::uuid AS job_id,
NULL::provisioner_job_status AS job_status,
NULL::uuid AS workspace_id,
NULL::uuid AS worker_id,
NULL::text AS error,
NULL::text AS error_code,
NULL::timestamp with time zone AS updated_at,
NULL::double precision AS queued_secs,
NULL::double precision AS completion_secs,
NULL::double precision AS canceled_secs,
NULL::double precision AS init_secs,
NULL::double precision AS plan_secs,
NULL::double precision AS graph_secs,
NULL::double precision AS apply_secs;
CREATE TABLE provisioner_job_timings (
job_id uuid NOT NULL,
started_at timestamp with time zone NOT NULL,
ended_at timestamp with time zone NOT NULL,
stage provisioner_job_timing_stage NOT NULL,
source text NOT NULL,
action text NOT NULL,
resource text NOT NULL
);
CREATE TABLE provisioner_jobs (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -1915,6 +1949,58 @@ CREATE INDEX workspace_resources_job_id_idx ON workspace_resources USING btree (
CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false);
CREATE OR REPLACE VIEW provisioner_job_stats AS
SELECT pj.id AS job_id,
pj.job_status,
wb.workspace_id,
pj.worker_id,
pj.error,
pj.error_code,
pj.updated_at,
GREATEST(date_part('epoch'::text, (pj.started_at - pj.created_at)), (0)::double precision) AS queued_secs,
GREATEST(date_part('epoch'::text, (pj.completed_at - pj.started_at)), (0)::double precision) AS completion_secs,
GREATEST(date_part('epoch'::text, (pj.canceled_at - pj.started_at)), (0)::double precision) AS canceled_secs,
GREATEST(date_part('epoch'::text, (max(
CASE
WHEN (pjt.stage = 'init'::provisioner_job_timing_stage) THEN pjt.ended_at
ELSE NULL::timestamp with time zone
END) - min(
CASE
WHEN (pjt.stage = 'init'::provisioner_job_timing_stage) THEN pjt.started_at
ELSE NULL::timestamp with time zone
END))), (0)::double precision) AS init_secs,
GREATEST(date_part('epoch'::text, (max(
CASE
WHEN (pjt.stage = 'plan'::provisioner_job_timing_stage) THEN pjt.ended_at
ELSE NULL::timestamp with time zone
END) - min(
CASE
WHEN (pjt.stage = 'plan'::provisioner_job_timing_stage) THEN pjt.started_at
ELSE NULL::timestamp with time zone
END))), (0)::double precision) AS plan_secs,
GREATEST(date_part('epoch'::text, (max(
CASE
WHEN (pjt.stage = 'graph'::provisioner_job_timing_stage) THEN pjt.ended_at
ELSE NULL::timestamp with time zone
END) - min(
CASE
WHEN (pjt.stage = 'graph'::provisioner_job_timing_stage) THEN pjt.started_at
ELSE NULL::timestamp with time zone
END))), (0)::double precision) AS graph_secs,
GREATEST(date_part('epoch'::text, (max(
CASE
WHEN (pjt.stage = 'apply'::provisioner_job_timing_stage) THEN pjt.ended_at
ELSE NULL::timestamp with time zone
END) - min(
CASE
WHEN (pjt.stage = 'apply'::provisioner_job_timing_stage) THEN pjt.started_at
ELSE NULL::timestamp with time zone
END))), (0)::double precision) AS apply_secs
FROM ((provisioner_jobs pj
JOIN workspace_builds wb ON ((wb.job_id = pj.id)))
LEFT JOIN provisioner_job_timings pjt ON ((pjt.job_id = pj.id)))
GROUP BY pj.id, wb.workspace_id;
CREATE TRIGGER inhibit_enqueue_if_disabled BEFORE INSERT ON notification_messages FOR EACH ROW EXECUTE FUNCTION inhibit_enqueue_if_disabled();
CREATE TRIGGER remove_organization_member_custom_role BEFORE DELETE ON custom_roles FOR EACH ROW EXECUTE FUNCTION remove_organization_member_role();
@@ -2012,6 +2098,9 @@ ALTER TABLE ONLY provisioner_daemons
ALTER TABLE ONLY provisioner_job_logs
ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ALTER TABLE ONLY provisioner_job_timings
ADD CONSTRAINT provisioner_job_timings_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ALTER TABLE ONLY provisioner_jobs
ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
@@ -29,6 +29,7 @@ const (
ForeignKeyParameterSchemasJobID ForeignKeyConstraint = "parameter_schemas_job_id_fkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ForeignKeyProvisionerDaemonsOrganizationID ForeignKeyConstraint = "provisioner_daemons_organization_id_fkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ForeignKeyProvisionerJobLogsJobID ForeignKeyConstraint = "provisioner_job_logs_job_id_fkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ForeignKeyProvisionerJobTimingsJobID ForeignKeyConstraint = "provisioner_job_timings_job_id_fkey" // ALTER TABLE ONLY provisioner_job_timings ADD CONSTRAINT provisioner_job_timings_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ForeignKeyProvisionerJobsOrganizationID ForeignKeyConstraint = "provisioner_jobs_organization_id_fkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ForeignKeyProvisionerKeysOrganizationID ForeignKeyConstraint = "provisioner_keys_organization_id_fkey" // ALTER TABLE ONLY provisioner_keys ADD CONSTRAINT provisioner_keys_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
ForeignKeyTailnetAgentsCoordinatorID ForeignKeyConstraint = "tailnet_agents_coordinator_id_fkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_coordinator_id_fkey FOREIGN KEY (coordinator_id) REFERENCES tailnet_coordinators(id) ON DELETE CASCADE;
@@ -0,0 +1,5 @@
DROP VIEW IF EXISTS provisioner_job_stats;
DROP TYPE IF EXISTS provisioner_job_timing_stage CASCADE;
DROP TABLE IF EXISTS provisioner_job_timings;
@@ -0,0 +1,45 @@
CREATE TYPE provisioner_job_timing_stage AS ENUM (
'init',
'plan',
'graph',
'apply'
);
CREATE TABLE provisioner_job_timings
(
job_id uuid NOT NULL REFERENCES provisioner_jobs (id) ON DELETE CASCADE,
started_at timestamp with time zone not null,
ended_at timestamp with time zone not null,
stage provisioner_job_timing_stage not null,
source text not null,
action text not null,
resource text not null
);
CREATE VIEW provisioner_job_stats AS
SELECT pj.id AS job_id,
pj.job_status,
wb.workspace_id,
pj.worker_id,
pj.error,
pj.error_code,
pj.updated_at,
GREATEST(EXTRACT(EPOCH FROM (pj.started_at - pj.created_at)), 0) AS queued_secs,
GREATEST(EXTRACT(EPOCH FROM (pj.completed_at - pj.started_at)), 0) AS completion_secs,
GREATEST(EXTRACT(EPOCH FROM (pj.canceled_at - pj.started_at)), 0) AS canceled_secs,
GREATEST(EXTRACT(EPOCH FROM (
MAX(CASE WHEN pjt.stage = 'init'::provisioner_job_timing_stage THEN pjt.ended_at END) -
MIN(CASE WHEN pjt.stage = 'init'::provisioner_job_timing_stage THEN pjt.started_at END))), 0) AS init_secs,
GREATEST(EXTRACT(EPOCH FROM (
MAX(CASE WHEN pjt.stage = 'plan'::provisioner_job_timing_stage THEN pjt.ended_at END) -
MIN(CASE WHEN pjt.stage = 'plan'::provisioner_job_timing_stage THEN pjt.started_at END))), 0) AS plan_secs,
GREATEST(EXTRACT(EPOCH FROM (
MAX(CASE WHEN pjt.stage = 'graph'::provisioner_job_timing_stage THEN pjt.ended_at END) -
MIN(CASE WHEN pjt.stage = 'graph'::provisioner_job_timing_stage THEN pjt.started_at END))), 0) AS graph_secs,
GREATEST(EXTRACT(EPOCH FROM (
MAX(CASE WHEN pjt.stage = 'apply'::provisioner_job_timing_stage THEN pjt.ended_at END) -
MIN(CASE WHEN pjt.stage = 'apply'::provisioner_job_timing_stage THEN pjt.started_at END))), 0) AS apply_secs
FROM provisioner_jobs pj
JOIN workspace_builds wb ON wb.job_id = pj.id
LEFT JOIN provisioner_job_timings pjt ON pjt.job_id = pj.id
GROUP BY pj.id, wb.workspace_id;
@@ -0,0 +1,13 @@
INSERT INTO provisioner_job_timings (job_id, started_at, ended_at, stage, source, action, resource)
VALUES
-- Job 1 - init stage
('424a58cb-61d6-4627-9907-613c396c4a38', NOW() - INTERVAL '1 hour 55 minutes', NOW() - INTERVAL '1 hour 50 minutes', 'init', 'source1', 'action1', 'resource1'),
-- Job 1 - plan stage
('424a58cb-61d6-4627-9907-613c396c4a38', NOW() - INTERVAL '1 hour 50 minutes', NOW() - INTERVAL '1 hour 40 minutes', 'plan', 'source2', 'action2', 'resource2'),
-- Job 1 - graph stage
('424a58cb-61d6-4627-9907-613c396c4a38', NOW() - INTERVAL '1 hour 40 minutes', NOW() - INTERVAL '1 hour 30 minutes', 'graph', 'source3', 'action3', 'resource3'),
-- Job 1 - apply stage
('424a58cb-61d6-4627-9907-613c396c4a38', NOW() - INTERVAL '1 hour 30 minutes', NOW() - INTERVAL '1 hour 20 minutes', 'apply', 'source4', 'action4', 'resource4');
+91
View File
@@ -1216,6 +1216,70 @@ func AllProvisionerJobStatusValues() []ProvisionerJobStatus {
}
}
type ProvisionerJobTimingStage string
const (
ProvisionerJobTimingStageInit ProvisionerJobTimingStage = "init"
ProvisionerJobTimingStagePlan ProvisionerJobTimingStage = "plan"
ProvisionerJobTimingStageGraph ProvisionerJobTimingStage = "graph"
ProvisionerJobTimingStageApply ProvisionerJobTimingStage = "apply"
)
func (e *ProvisionerJobTimingStage) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = ProvisionerJobTimingStage(s)
case string:
*e = ProvisionerJobTimingStage(s)
default:
return fmt.Errorf("unsupported scan type for ProvisionerJobTimingStage: %T", src)
}
return nil
}
type NullProvisionerJobTimingStage struct {
ProvisionerJobTimingStage ProvisionerJobTimingStage `json:"provisioner_job_timing_stage"`
Valid bool `json:"valid"` // Valid is true if ProvisionerJobTimingStage is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullProvisionerJobTimingStage) Scan(value interface{}) error {
if value == nil {
ns.ProvisionerJobTimingStage, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.ProvisionerJobTimingStage.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullProvisionerJobTimingStage) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.ProvisionerJobTimingStage), nil
}
func (e ProvisionerJobTimingStage) Valid() bool {
switch e {
case ProvisionerJobTimingStageInit,
ProvisionerJobTimingStagePlan,
ProvisionerJobTimingStageGraph,
ProvisionerJobTimingStageApply:
return true
}
return false
}
func AllProvisionerJobTimingStageValues() []ProvisionerJobTimingStage {
return []ProvisionerJobTimingStage{
ProvisionerJobTimingStageInit,
ProvisionerJobTimingStagePlan,
ProvisionerJobTimingStageGraph,
ProvisionerJobTimingStageApply,
}
}
type ProvisionerJobType string
const (
@@ -2282,6 +2346,33 @@ type ProvisionerJobLog struct {
ID int64 `db:"id" json:"id"`
}
type ProvisionerJobStat struct {
JobID uuid.UUID `db:"job_id" json:"job_id"`
JobStatus ProvisionerJobStatus `db:"job_status" json:"job_status"`
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"`
Error sql.NullString `db:"error" json:"error"`
ErrorCode sql.NullString `db:"error_code" json:"error_code"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
QueuedSecs float64 `db:"queued_secs" json:"queued_secs"`
CompletionSecs float64 `db:"completion_secs" json:"completion_secs"`
CanceledSecs float64 `db:"canceled_secs" json:"canceled_secs"`
InitSecs float64 `db:"init_secs" json:"init_secs"`
PlanSecs float64 `db:"plan_secs" json:"plan_secs"`
GraphSecs float64 `db:"graph_secs" json:"graph_secs"`
ApplySecs float64 `db:"apply_secs" json:"apply_secs"`
}
type ProvisionerJobTiming struct {
JobID uuid.UUID `db:"job_id" json:"job_id"`
StartedAt time.Time `db:"started_at" json:"started_at"`
EndedAt time.Time `db:"ended_at" json:"ended_at"`
Stage ProvisionerJobTimingStage `db:"stage" json:"stage"`
Source string `db:"source" json:"source"`
Action string `db:"action" json:"action"`
Resource string `db:"resource" json:"resource"`
}
type ProvisionerKey struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
+1
View File
@@ -357,6 +357,7 @@ type sqlcQuerier interface {
InsertOrganizationMember(ctx context.Context, arg InsertOrganizationMemberParams) (OrganizationMember, error)
InsertProvisionerJob(ctx context.Context, arg InsertProvisionerJobParams) (ProvisionerJob, error)
InsertProvisionerJobLogs(ctx context.Context, arg InsertProvisionerJobLogsParams) ([]ProvisionerJobLog, error)
InsertProvisionerJobTimings(ctx context.Context, arg InsertProvisionerJobTimingsParams) ([]ProvisionerJobTiming, error)
InsertProvisionerKey(ctx context.Context, arg InsertProvisionerKeyParams) (ProvisionerKey, error)
InsertReplica(ctx context.Context, arg InsertReplicaParams) (Replica, error)
InsertTemplate(ctx context.Context, arg InsertTemplateParams) error
+62
View File
@@ -5545,6 +5545,68 @@ func (q *sqlQuerier) InsertProvisionerJob(ctx context.Context, arg InsertProvisi
return i, err
}
const insertProvisionerJobTimings = `-- name: InsertProvisionerJobTimings :many
INSERT INTO provisioner_job_timings (job_id, started_at, ended_at, stage, source, action, resource)
SELECT
$1::uuid AS provisioner_job_id,
unnest($2::timestamptz[]),
unnest($3::timestamptz[]),
unnest($4::provisioner_job_timing_stage[]),
unnest($5::text[]),
unnest($6::text[]),
unnest($7::text[])
RETURNING job_id, started_at, ended_at, stage, source, action, resource
`
type InsertProvisionerJobTimingsParams struct {
JobID uuid.UUID `db:"job_id" json:"job_id"`
StartedAt []time.Time `db:"started_at" json:"started_at"`
EndedAt []time.Time `db:"ended_at" json:"ended_at"`
Stage []ProvisionerJobTimingStage `db:"stage" json:"stage"`
Source []string `db:"source" json:"source"`
Action []string `db:"action" json:"action"`
Resource []string `db:"resource" json:"resource"`
}
func (q *sqlQuerier) InsertProvisionerJobTimings(ctx context.Context, arg InsertProvisionerJobTimingsParams) ([]ProvisionerJobTiming, error) {
rows, err := q.db.QueryContext(ctx, insertProvisionerJobTimings,
arg.JobID,
pq.Array(arg.StartedAt),
pq.Array(arg.EndedAt),
pq.Array(arg.Stage),
pq.Array(arg.Source),
pq.Array(arg.Action),
pq.Array(arg.Resource),
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ProvisionerJobTiming
for rows.Next() {
var i ProvisionerJobTiming
if err := rows.Scan(
&i.JobID,
&i.StartedAt,
&i.EndedAt,
&i.Stage,
&i.Source,
&i.Action,
&i.Resource,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateProvisionerJobByID = `-- name: UpdateProvisionerJobByID :exec
UPDATE
provisioner_jobs
@@ -144,3 +144,15 @@ WHERE
updated_at < $1
AND started_at IS NOT NULL
AND completed_at IS NULL;
-- name: InsertProvisionerJobTimings :many
INSERT INTO provisioner_job_timings (job_id, started_at, ended_at, stage, source, action, resource)
SELECT
@job_id::uuid AS provisioner_job_id,
unnest(@started_at::timestamptz[]),
unnest(@ended_at::timestamptz[]),
unnest(@stage::provisioner_job_timing_stage[]),
unnest(@source::text[]),
unnest(@action::text[]),
unnest(@resource::text[])
RETURNING *;
+3
View File
@@ -73,6 +73,9 @@ sql:
- column: "notification_messages.payload"
go_type:
type: "[]byte"
- column: "provisioner_job_stats.*_secs"
go_type:
type: "float64"
rename:
group_member: GroupMemberTable
group_members_expanded: GroupMember