perf(coderd): build the workspace build fan-out maps once per batch (#28074)

`convertWorkspaceBuild` rebuilt seven maps from the caller's global
slices on every call and rescanned the provisioner daemon rows to filter
by job ID. `convertWorkspaceBuilds` calls it once per build with
identical slices, so map construction cost `O(builds x rows)` where
`O(rows)` suffices — quadratic in the number of workspaces on `GET
/api/v2/workspaces`.

The maps move into a `workspaceBuildIndex` built once per batch, keyed
exactly as before and now including daemons by job ID.
`convertWorkspaceBuild` takes the index instead of eight slices. Its
parent already hoisted `workspaceByID`, `jobByID`, and
`templateVersionByID` out of the same loop; this makes the rest
consistent.

Agents are sorted while the index is built, so a resource read by
several builds is sorted once rather than once per build. Same
comparator over the same rows, so the order is unchanged;
`TestConvertWorkspaceBuildsAgentOrder` covers it.

`BenchmarkConvertWorkspaceBuilds`, 5 resources x 2 agents x 4 apps per
build:

| builds | ns/op | B/op | allocs/op |
| --- | --- | --- | --- |
| 1 | 48.3k -> 48.1k | 121k -> 125k | 342 -> 359 |
| 25 | 12.7M -> 1.34M | 38.0MB -> 3.2MB | 69,621 -> 8,347 |
| 100 | 186M -> 5.67M | 596MB -> 13.0MB | 1,024,941 -> 33,080 |

Single-build conversion is a wash (one extra struct allocation); the
quadratic term is gone.

Addresses the map-allocation half of PLAT-386 / #27205. Bounding the
page size is separate (#28040) and does not remove this cost: at 100
workspaces per page it is still 100 passes over every resource, agent,
app, script, log source, status, and daemon row in the page.

---

Created with Coder Agents on behalf of @jscottmiller.
This commit is contained in:
J. Scott Miller
2026-08-18 11:33:01 -05:00
committed by GitHub
parent 27e3d0fb00
commit 62f4afbb60
3 changed files with 415 additions and 97 deletions
+115 -89
View File
@@ -1,6 +1,7 @@
package coderd
import (
"cmp"
"context"
"database/sql"
"encoding/json"
@@ -81,15 +82,17 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) {
workspaceBuild,
workspace,
data.jobs[0],
data.resources,
data.metadata,
data.agents,
data.apps,
data.appStatuses,
data.scripts,
data.logSources,
newWorkspaceBuildIndex(
data.resources,
data.metadata,
data.agents,
data.apps,
data.appStatuses,
data.scripts,
data.logSources,
nil,
),
data.templateVersions[0],
nil,
)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
@@ -291,15 +294,17 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ
workspaceBuild,
workspace,
data.jobs[0],
data.resources,
data.metadata,
data.agents,
data.apps,
data.appStatuses,
data.scripts,
data.logSources,
newWorkspaceBuildIndex(
data.resources,
data.metadata,
data.agents,
data.apps,
data.appStatuses,
data.scripts,
data.logSources,
data.provisionerDaemons,
),
data.templateVersions[0],
data.provisionerDaemons,
)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
@@ -616,15 +621,8 @@ func (api *API) postWorkspaceBuildsInternal(
*workspaceBuild,
workspace,
queuePos,
[]database.WorkspaceResource{},
[]database.WorkspaceResourceMetadatum{},
[]database.WorkspaceAgent{},
[]database.WorkspaceApp{},
[]database.WorkspaceAppStatus{},
[]database.GetWorkspaceAgentScriptsByAgentIDsRow{},
[]database.WorkspaceAgentLogSource{},
newWorkspaceBuildIndex(nil, nil, nil, nil, nil, nil, nil, provisionerDaemons),
database.TemplateVersion{},
provisionerDaemons,
)
if err != nil {
return codersdk.WorkspaceBuild{}, httperror.NewResponseError(
@@ -1245,6 +1243,78 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaceBuilds []datab
}, nil
}
// workspaceBuildIndex groups build rows by the ID they are looked up with.
// Conversion reorders rows within a bucket in place, so an index must be read
// by a single goroutine.
type workspaceBuildIndex struct {
resourcesByJobID map[uuid.UUID][]database.WorkspaceResource
metadataByResourceID map[uuid.UUID][]database.WorkspaceResourceMetadatum
agentsByResourceID map[uuid.UUID][]database.WorkspaceAgent
appsByAgentID map[uuid.UUID][]database.WorkspaceApp
statusesByAgentID map[uuid.UUID][]database.WorkspaceAppStatus
scriptsByAgentID map[uuid.UUID][]database.GetWorkspaceAgentScriptsByAgentIDsRow
logSourcesByAgentID map[uuid.UUID][]database.WorkspaceAgentLogSource
daemonsByJobID map[uuid.UUID][]database.ProvisionerDaemon
}
// newWorkspaceBuildIndex makes one pass over each slice, copying rows into
// per-ID buckets. The input slices are neither modified nor aliased.
func newWorkspaceBuildIndex(
workspaceResources []database.WorkspaceResource,
resourceMetadata []database.WorkspaceResourceMetadatum,
resourceAgents []database.WorkspaceAgent,
agentApps []database.WorkspaceApp,
agentAppStatuses []database.WorkspaceAppStatus,
agentScripts []database.GetWorkspaceAgentScriptsByAgentIDsRow,
agentLogSources []database.WorkspaceAgentLogSource,
provisionerDaemons []database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow,
) *workspaceBuildIndex {
index := &workspaceBuildIndex{
resourcesByJobID: make(map[uuid.UUID][]database.WorkspaceResource),
metadataByResourceID: make(map[uuid.UUID][]database.WorkspaceResourceMetadatum),
agentsByResourceID: make(map[uuid.UUID][]database.WorkspaceAgent),
appsByAgentID: make(map[uuid.UUID][]database.WorkspaceApp),
statusesByAgentID: make(map[uuid.UUID][]database.WorkspaceAppStatus),
scriptsByAgentID: make(map[uuid.UUID][]database.GetWorkspaceAgentScriptsByAgentIDsRow),
logSourcesByAgentID: make(map[uuid.UUID][]database.WorkspaceAgentLogSource),
daemonsByJobID: make(map[uuid.UUID][]database.ProvisionerDaemon),
}
for _, resource := range workspaceResources {
index.resourcesByJobID[resource.JobID] = append(index.resourcesByJobID[resource.JobID], resource)
}
for _, metadata := range resourceMetadata {
index.metadataByResourceID[metadata.WorkspaceResourceID] = append(index.metadataByResourceID[metadata.WorkspaceResourceID], metadata)
}
for _, agent := range resourceAgents {
index.agentsByResourceID[agent.ResourceID] = append(index.agentsByResourceID[agent.ResourceID], agent)
}
for _, app := range agentApps {
index.appsByAgentID[app.AgentID] = append(index.appsByAgentID[app.AgentID], app)
}
for _, status := range agentAppStatuses {
index.statusesByAgentID[status.AgentID] = append(index.statusesByAgentID[status.AgentID], status)
}
for _, script := range agentScripts {
index.scriptsByAgentID[script.WorkspaceAgentID] = append(index.scriptsByAgentID[script.WorkspaceAgentID], script)
}
for _, logSource := range agentLogSources {
index.logSourcesByAgentID[logSource.WorkspaceAgentID] = append(index.logSourcesByAgentID[logSource.WorkspaceAgentID], logSource)
}
for _, daemon := range provisionerDaemons {
index.daemonsByJobID[daemon.JobID] = append(index.daemonsByJobID[daemon.JobID], daemon.ProvisionerDaemon)
}
// Agents within a resource are ordered by display order, then name.
for _, agents := range index.agentsByResourceID {
slices.SortFunc(agents, func(a, b database.WorkspaceAgent) int {
return cmp.Or(
cmp.Compare(a.DisplayOrder, b.DisplayOrder),
cmp.Compare(a.Name, b.Name),
)
})
}
return index
}
func (api *API) convertWorkspaceBuilds(
workspaceBuilds []database.WorkspaceBuild,
workspaces []database.Workspace,
@@ -1272,6 +1342,17 @@ func (api *API) convertWorkspaceBuilds(
templateVersionByID[templateVersion.ID] = templateVersion
}
index := newWorkspaceBuildIndex(
workspaceResources,
resourceMetadata,
resourceAgents,
agentApps,
agentAppStatuses,
agentScripts,
agentLogSources,
provisionerDaemons,
)
// Should never be nil for API consistency
apiBuilds := []codersdk.WorkspaceBuild{}
for _, build := range workspaceBuilds {
@@ -1292,15 +1373,8 @@ func (api *API) convertWorkspaceBuilds(
build,
workspace,
job,
workspaceResources,
resourceMetadata,
resourceAgents,
agentApps,
agentAppStatuses,
agentScripts,
agentLogSources,
index,
templateVersion,
provisionerDaemons,
)
if err != nil {
return nil, xerrors.Errorf("converting workspace build: %w", err)
@@ -1316,64 +1390,16 @@ func (api *API) convertWorkspaceBuild(
build database.WorkspaceBuild,
workspace database.Workspace,
job database.GetProvisionerJobsByIDsWithQueuePositionRow,
workspaceResources []database.WorkspaceResource,
resourceMetadata []database.WorkspaceResourceMetadatum,
resourceAgents []database.WorkspaceAgent,
agentApps []database.WorkspaceApp,
agentAppStatuses []database.WorkspaceAppStatus,
agentScripts []database.GetWorkspaceAgentScriptsByAgentIDsRow,
agentLogSources []database.WorkspaceAgentLogSource,
index *workspaceBuildIndex,
templateVersion database.TemplateVersion,
provisionerDaemons []database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow,
) (codersdk.WorkspaceBuild, error) {
resourcesByJobID := map[uuid.UUID][]database.WorkspaceResource{}
for _, resource := range workspaceResources {
resourcesByJobID[resource.JobID] = append(resourcesByJobID[resource.JobID], resource)
}
metadataByResourceID := map[uuid.UUID][]database.WorkspaceResourceMetadatum{}
for _, metadata := range resourceMetadata {
metadataByResourceID[metadata.WorkspaceResourceID] = append(metadataByResourceID[metadata.WorkspaceResourceID], metadata)
}
agentsByResourceID := map[uuid.UUID][]database.WorkspaceAgent{}
for _, agent := range resourceAgents {
agentsByResourceID[agent.ResourceID] = append(agentsByResourceID[agent.ResourceID], agent)
}
appsByAgentID := map[uuid.UUID][]database.WorkspaceApp{}
for _, app := range agentApps {
appsByAgentID[app.AgentID] = append(appsByAgentID[app.AgentID], app)
}
scriptsByAgentID := map[uuid.UUID][]database.GetWorkspaceAgentScriptsByAgentIDsRow{}
for _, script := range agentScripts {
scriptsByAgentID[script.WorkspaceAgentID] = append(scriptsByAgentID[script.WorkspaceAgentID], script)
}
logSourcesByAgentID := map[uuid.UUID][]database.WorkspaceAgentLogSource{}
for _, logSource := range agentLogSources {
logSourcesByAgentID[logSource.WorkspaceAgentID] = append(logSourcesByAgentID[logSource.WorkspaceAgentID], logSource)
}
provisionerDaemonsForThisWorkspaceBuild := []database.ProvisionerDaemon{}
for _, provisionerDaemon := range provisionerDaemons {
if provisionerDaemon.JobID != job.ProvisionerJob.ID {
continue
}
provisionerDaemonsForThisWorkspaceBuild = append(provisionerDaemonsForThisWorkspaceBuild, provisionerDaemon.ProvisionerDaemon)
}
matchedProvisioners := db2sdk.MatchedProvisioners(provisionerDaemonsForThisWorkspaceBuild, job.ProvisionerJob.CreatedAt, provisionerdserver.StaleInterval)
statusesByAgentID := map[uuid.UUID][]database.WorkspaceAppStatus{}
for _, status := range agentAppStatuses {
statusesByAgentID[status.AgentID] = append(statusesByAgentID[status.AgentID], status)
}
matchedProvisioners := db2sdk.MatchedProvisioners(index.daemonsByJobID[job.ProvisionerJob.ID], job.ProvisionerJob.CreatedAt, provisionerdserver.StaleInterval)
resources := resourcesByJobID[job.ProvisionerJob.ID]
resources := index.resourcesByJobID[job.ProvisionerJob.ID]
apiResources := make([]codersdk.WorkspaceResource, 0)
resourceAgentsMinOrder := map[uuid.UUID]int32{} // map[resource.ID]minOrder
for _, resource := range resources {
agents := agentsByResourceID[resource.ID]
sort.Slice(agents, func(i, j int) bool {
if agents[i].DisplayOrder != agents[j].DisplayOrder {
return agents[i].DisplayOrder < agents[j].DisplayOrder
}
return agents[i].Name < agents[j].Name
})
agents := index.agentsByResourceID[resource.ID]
apiAgents := make([]codersdk.WorkspaceAgent, 0)
resourceAgentsMinOrder[resource.ID] = math.MaxInt32
@@ -1381,10 +1407,10 @@ func (api *API) convertWorkspaceBuild(
for _, agent := range agents {
resourceAgentsMinOrder[resource.ID] = min(resourceAgentsMinOrder[resource.ID], agent.DisplayOrder)
apps := appsByAgentID[agent.ID]
scripts := scriptsByAgentID[agent.ID]
statuses := statusesByAgentID[agent.ID]
logSources := logSourcesByAgentID[agent.ID]
apps := index.appsByAgentID[agent.ID]
scripts := index.scriptsByAgentID[agent.ID]
statuses := index.statusesByAgentID[agent.ID]
logSources := index.logSourcesByAgentID[agent.ID]
apiAgent, err := db2sdk.WorkspaceAgent(
api.DERPMap(), *api.TailnetCoordinator.Load(), agent, db2sdk.Apps(apps, statuses, agent, workspace.OwnerUsername, workspace.WorkspaceTable()), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout,
api.DeploymentValues.AgentFallbackTroubleshootingURL.String(),
@@ -1394,7 +1420,7 @@ func (api *API) convertWorkspaceBuild(
}
apiAgents = append(apiAgents, apiAgent)
}
metadata := append(make([]database.WorkspaceResourceMetadatum, 0), metadataByResourceID[resource.ID]...)
metadata := append(make([]database.WorkspaceResourceMetadatum, 0), index.metadataByResourceID[resource.ID]...)
apiResources = append(apiResources, convertWorkspaceResource(resource, apiAgents, metadata))
}
sort.Slice(apiResources, func(i, j int) bool {
+299
View File
@@ -0,0 +1,299 @@
package coderd
import (
"database/sql"
"fmt"
"strings"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"cdr.dev/slog/v3"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/tailnet"
)
// buildFixture holds one batch of rows shaped like the output of
// workspaceBuildsData: a set of builds plus the flat resource, agent, app,
// script, log source, status, and daemon rows for all of them.
type buildFixture struct {
builds []database.WorkspaceBuild
workspaces []database.Workspace
jobs []database.GetProvisionerJobsByIDsWithQueuePositionRow
templateVersions []database.TemplateVersion
resources []database.WorkspaceResource
metadata []database.WorkspaceResourceMetadatum
agents []database.WorkspaceAgent
apps []database.WorkspaceApp
statuses []database.WorkspaceAppStatus
scripts []database.GetWorkspaceAgentScriptsByAgentIDsRow
logSources []database.WorkspaceAgentLogSource
daemons []database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow
}
func newBuildFixture(builds, resourcesPerBuild int, agentsPerResource int32, appsPerAgent int) buildFixture {
now := time.Date(2026, 8, 11, 0, 0, 0, 0, time.UTC)
f := buildFixture{}
for b := range builds {
workspaceID := uuid.New()
jobID := uuid.New()
templateVersionID := uuid.New()
f.workspaces = append(f.workspaces, database.Workspace{
ID: workspaceID,
OwnerID: uuid.New(),
OwnerUsername: fmt.Sprintf("owner-%d", b),
Name: fmt.Sprintf("workspace-%d", b),
})
f.jobs = append(f.jobs, database.GetProvisionerJobsByIDsWithQueuePositionRow{
ProvisionerJob: database.ProvisionerJob{
ID: jobID,
CreatedAt: now,
StartedAt: sql.NullTime{Time: now, Valid: true},
},
})
f.templateVersions = append(f.templateVersions, database.TemplateVersion{
ID: templateVersionID,
Name: fmt.Sprintf("version-%d", b),
})
f.builds = append(f.builds, database.WorkspaceBuild{
ID: uuid.New(),
WorkspaceID: workspaceID,
JobID: jobID,
TemplateVersionID: templateVersionID,
BuildNumber: 1,
Transition: database.WorkspaceTransitionStart,
})
f.daemons = append(f.daemons, database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{
JobID: jobID,
ProvisionerDaemon: database.ProvisionerDaemon{
ID: uuid.New(),
Name: fmt.Sprintf("daemon-%d", b),
LastSeenAt: sql.NullTime{Time: now, Valid: true},
Provisioners: []database.ProvisionerType{database.ProvisionerTypeEcho},
},
})
for r := range resourcesPerBuild {
resourceID := uuid.New()
f.resources = append(f.resources, database.WorkspaceResource{
ID: resourceID,
JobID: jobID,
Transition: database.WorkspaceTransitionStart,
Type: "example",
Name: fmt.Sprintf("resource-%d-%d", b, r),
})
f.metadata = append(f.metadata, database.WorkspaceResourceMetadatum{
WorkspaceResourceID: resourceID,
Key: "key",
Value: sql.NullString{String: "value", Valid: true},
})
for a := range agentsPerResource {
agentID := uuid.New()
f.agents = append(f.agents, database.WorkspaceAgent{
ID: agentID,
ResourceID: resourceID,
Name: fmt.Sprintf("agent-%d-%d-%d", b, r, a),
DisplayOrder: agentsPerResource - a,
APIKeyScope: database.AgentKeyScopeEnumAll,
})
f.scripts = append(f.scripts, database.GetWorkspaceAgentScriptsByAgentIDsRow{
WorkspaceAgentID: agentID,
Script: "echo hello",
})
f.logSources = append(f.logSources, database.WorkspaceAgentLogSource{
WorkspaceAgentID: agentID,
DisplayName: "startup",
})
for p := range appsPerAgent {
appID := uuid.New()
f.apps = append(f.apps, database.WorkspaceApp{
ID: appID,
AgentID: agentID,
Slug: fmt.Sprintf("app-%d-%d-%d-%d", b, r, a, p),
DisplayName: "App",
Health: database.WorkspaceAppHealthHealthy,
SharingLevel: database.AppSharingLevelOwner,
OpenIn: database.WorkspaceAppOpenInSlimWindow,
})
f.statuses = append(f.statuses, database.WorkspaceAppStatus{
ID: uuid.New(),
AgentID: agentID,
AppID: appID,
State: database.WorkspaceAppStatusStateComplete,
})
}
}
}
}
return f
}
func newConvertAPI() *API {
api := &API{
Options: &Options{
AgentInactiveDisconnectTimeout: time.Minute,
DeploymentValues: &codersdk.DeploymentValues{},
},
}
coordinator := tailnet.NewCoordinator(slog.Logger{})
api.TailnetCoordinator.Store(&coordinator)
return api
}
func (f buildFixture) convert(api *API) ([]codersdk.WorkspaceBuild, error) {
return api.convertWorkspaceBuilds(
f.builds,
f.workspaces,
f.jobs,
f.resources,
f.metadata,
f.agents,
f.apps,
f.statuses,
f.scripts,
f.logSources,
f.templateVersions,
f.daemons,
)
}
// TestConvertWorkspaceBuildsAgentOrder covers the ordering that the shared
// index is responsible for: agents come back sorted by display order. The
// fixture assigns display order in reverse of agent name, so the expected
// result is names in descending order.
func TestConvertWorkspaceBuildsAgentOrder(t *testing.T) {
t.Parallel()
api := newConvertAPI()
fixture := newBuildFixture(3, 2, 3, 1)
builds, err := fixture.convert(api)
require.NoError(t, err)
require.Len(t, builds, 3)
for _, build := range builds {
require.NotEmpty(t, build.Resources)
for _, resource := range build.Resources {
require.Len(t, resource.Agents, 3)
for i := 1; i < len(resource.Agents); i++ {
require.Greater(t, resource.Agents[i-1].Name, resource.Agents[i].Name)
}
}
}
}
// TestConvertWorkspaceBuildsAgentNameTiebreak covers the second half of the
// agent comparison: agents sharing a display order are ordered by name.
func TestConvertWorkspaceBuildsAgentNameTiebreak(t *testing.T) {
t.Parallel()
api := newConvertAPI()
fixture := newBuildFixture(1, 1, 3, 1)
for i := range fixture.agents {
fixture.agents[i].DisplayOrder = 0
}
builds, err := fixture.convert(api)
require.NoError(t, err)
require.Len(t, builds, 1)
require.Len(t, builds[0].Resources, 1)
agents := builds[0].Resources[0].Agents
require.Len(t, agents, 3)
for i := 1; i < len(agents); i++ {
require.Less(t, agents[i-1].Name, agents[i].Name)
}
}
// TestConvertWorkspaceBuildsRowsPerBuild asserts that a build receives only the
// rows keyed to its own job and agents. The fixture encodes the build index in
// app slugs, so apps read from another build are detectable by slug.
func TestConvertWorkspaceBuildsRowsPerBuild(t *testing.T) {
t.Parallel()
const (
buildCount = 3
resourcesPerJob = 2
agentsPerRes = 2
appsPerAgent = 2
)
api := newConvertAPI()
fixture := newBuildFixture(buildCount, resourcesPerJob, agentsPerRes, appsPerAgent)
builds, err := fixture.convert(api)
require.NoError(t, err)
require.Len(t, builds, buildCount)
for b, build := range builds {
require.Len(t, build.Resources, resourcesPerJob)
for _, resource := range build.Resources {
require.Equal(t, fixture.jobs[b].ProvisionerJob.ID, resource.JobID)
require.Len(t, resource.Metadata, 1)
require.Len(t, resource.Agents, agentsPerRes)
for _, agent := range resource.Agents {
require.Len(t, agent.Scripts, 1)
require.Len(t, agent.LogSources, 1)
require.Len(t, agent.Apps, appsPerAgent)
for _, app := range agent.Apps {
require.True(t, strings.HasPrefix(app.Slug, fmt.Sprintf("app-%d-", b)), app.Slug)
require.Len(t, app.Statuses, 1)
}
}
}
}
}
// TestConvertWorkspaceBuildsMatchedProvisioners asserts that eligible daemons
// are matched to the build whose job they were fetched for. The last build in
// the batch has no daemon rows, so it reports zero counts while the others
// report one.
func TestConvertWorkspaceBuildsMatchedProvisioners(t *testing.T) {
t.Parallel()
api := newConvertAPI()
fixture := newBuildFixture(3, 1, 1, 1)
fixture.daemons = fixture.daemons[:len(fixture.daemons)-1]
builds, err := fixture.convert(api)
require.NoError(t, err)
require.Len(t, builds, 3)
for _, build := range builds[:2] {
require.NotNil(t, build.MatchedProvisioners)
require.Equal(t, 1, build.MatchedProvisioners.Count)
require.Equal(t, 1, build.MatchedProvisioners.Available)
require.True(t, build.MatchedProvisioners.MostRecentlySeen.Valid)
}
last := builds[2]
require.NotNil(t, last.MatchedProvisioners)
require.Equal(t, 0, last.MatchedProvisioners.Count)
require.Equal(t, 0, last.MatchedProvisioners.Available)
require.False(t, last.MatchedProvisioners.MostRecentlySeen.Valid)
}
func BenchmarkConvertWorkspaceBuilds(b *testing.B) {
for _, builds := range []int{1, 25, 100} {
b.Run(fmt.Sprintf("Builds%d", builds), func(b *testing.B) {
api := newConvertAPI()
fixture := newBuildFixture(builds, 5, 2, 4)
b.ReportAllocs()
for b.Loop() {
if _, err := fixture.convert(api); err != nil {
b.Fatal(err)
}
}
})
}
}
+1 -8
View File
@@ -834,15 +834,8 @@ func createWorkspace(
ProvisionerJob: *provisionerJob,
QueuePosition: 0,
},
[]database.WorkspaceResource{},
[]database.WorkspaceResourceMetadatum{},
[]database.WorkspaceAgent{},
[]database.WorkspaceApp{},
[]database.WorkspaceAppStatus{},
[]database.GetWorkspaceAgentScriptsByAgentIDsRow{},
[]database.WorkspaceAgentLogSource{},
newWorkspaceBuildIndex(nil, nil, nil, nil, nil, nil, nil, provisionerDaemons),
database.TemplateVersion{},
provisionerDaemons,
)
if err != nil {
return codersdk.Workspace{}, httperror.NewResponseError(http.StatusInternalServerError, codersdk.Response{