mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add WatchAllWorkspaceBuilds endpoint for autostart scaletests (#22057)
This PR adds a `WatchAllWorkspaces` function with `watch-all-workspaces` endpoint, which can be used to listen on a single global pubsub channel for _all_ workspace build updates, and makes use of it in the autostart scaletest. This negates the need to use a workspace watch pubsub channel _per_ workspace, which has auth overhead associated with each call. This is especially relevant in situations such as the autostart scaletest, where we need to start/stop a set of workspaces before we can configure their autostart config. The overhead associated with all the watch requests skews the scaletest results and makes it harder to reason about the performance of the autostart feature itself. The autostart scaletest also no longer generates its own metrics nor does it wait for all the workspaces to actually start via autostart. We should update the scaletest dashboard after both PRs are merged to measure autostart performance via the new metrics. The new function/endpoint and its usage in the autostart scaletest are gated behind an experiment feature flag, this is something we should discuss whether we want to enable the endpoint in prod by default or not. If so, we can remove the experiment. --------- Signed-off-by: Callum Styan <callumstyan@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Callum Styan <callum@coder.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
Callum Styan
parent
b492c42624
commit
36665e17b2
+12
-8
@@ -4298,14 +4298,15 @@ type Experiment string
|
||||
|
||||
const (
|
||||
// Add new experiments here!
|
||||
ExperimentExample Experiment = "example" // This isn't used for anything.
|
||||
ExperimentAutoFillParameters Experiment = "auto-fill-parameters" // This should not be taken out of experiments until we have redesigned the feature.
|
||||
ExperimentNotifications Experiment = "notifications" // Sends notifications via SMTP and webhooks following certain events.
|
||||
ExperimentWorkspaceUsage Experiment = "workspace-usage" // Enables the new workspace usage tracking.
|
||||
ExperimentWebPush Experiment = "web-push" // Enables web push notifications through the browser.
|
||||
ExperimentOAuth2 Experiment = "oauth2" // Enables OAuth2 provider functionality.
|
||||
ExperimentAgents Experiment = "agents" // Enables agent-powered chat functionality.
|
||||
ExperimentMCPServerHTTP Experiment = "mcp-server-http" // Enables the MCP HTTP server functionality.
|
||||
ExperimentExample Experiment = "example" // This isn't used for anything.
|
||||
ExperimentAutoFillParameters Experiment = "auto-fill-parameters" // This should not be taken out of experiments until we have redesigned the feature.
|
||||
ExperimentNotifications Experiment = "notifications" // Sends notifications via SMTP and webhooks following certain events.
|
||||
ExperimentWorkspaceUsage Experiment = "workspace-usage" // Enables the new workspace usage tracking.
|
||||
ExperimentWebPush Experiment = "web-push" // Enables web push notifications through the browser.
|
||||
ExperimentOAuth2 Experiment = "oauth2" // Enables OAuth2 provider functionality.
|
||||
ExperimentAgents Experiment = "agents" // Enables agent-powered chat functionality.
|
||||
ExperimentMCPServerHTTP Experiment = "mcp-server-http" // Enables the MCP HTTP server functionality.
|
||||
ExperimentWorkspaceBuildUpdates Experiment = "workspace-build-updates" // Enables publishing workspace build updates to the all builds pubsub channel.
|
||||
)
|
||||
|
||||
func (e Experiment) DisplayName() string {
|
||||
@@ -4326,6 +4327,8 @@ func (e Experiment) DisplayName() string {
|
||||
return "Agents"
|
||||
case ExperimentMCPServerHTTP:
|
||||
return "MCP HTTP Server Functionality"
|
||||
case ExperimentWorkspaceBuildUpdates:
|
||||
return "Workspace Build Updates Channel"
|
||||
default:
|
||||
// Split on hyphen and convert to title case
|
||||
// e.g. "web-push" -> "Web Push", "mcp-server-http" -> "Mcp Server Http"
|
||||
@@ -4344,6 +4347,7 @@ var ExperimentsKnown = Experiments{
|
||||
ExperimentOAuth2,
|
||||
ExperimentAgents,
|
||||
ExperimentMCPServerHTTP,
|
||||
ExperimentWorkspaceBuildUpdates,
|
||||
}
|
||||
|
||||
// ExperimentsSafe should include all experiments that are safe for
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -13,6 +14,8 @@ import (
|
||||
|
||||
"cdr.dev/slog/v3"
|
||||
"github.com/coder/coder/v2/coderd/tracing"
|
||||
"github.com/coder/coder/v2/codersdk/wsjson"
|
||||
"github.com/coder/websocket"
|
||||
)
|
||||
|
||||
type AutomaticUpdates string
|
||||
@@ -788,6 +791,62 @@ func (c *Client) WorkspaceExternalAgentCredentials(ctx context.Context, workspac
|
||||
return credentials, json.NewDecoder(res.Body).Decode(&credentials)
|
||||
}
|
||||
|
||||
// WorkspaceBuildUpdate contains information about a workspace build state change.
|
||||
// This is published via the /watch-all-workspacebuilds SSE endpoint when the
|
||||
// workspace-build-updates experiment is enabled.
|
||||
type WorkspaceBuildUpdate struct {
|
||||
WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"`
|
||||
WorkspaceName string `json:"workspace_name"`
|
||||
BuildID uuid.UUID `json:"build_id" format:"uuid"`
|
||||
// Transition is the workspace transition type: "start", "stop", or "delete".
|
||||
Transition string `json:"transition"`
|
||||
// JobStatus is the provisioner job status: "pending", "running",
|
||||
// "succeeded", "canceling", "canceled", or "failed".
|
||||
JobStatus string `json:"job_status"`
|
||||
BuildNumber int32 `json:"build_number"`
|
||||
}
|
||||
|
||||
// WatchAllWorkspaceBuilds watches for workspace build updates across all workspaces.
|
||||
// This requires the workspace-build-updates experiment to be enabled.
|
||||
// The returned decoder should be closed by calling Close() when done to properly
|
||||
// clean up the WebSocket connection.
|
||||
func (c *Client) WatchAllWorkspaceBuilds(ctx context.Context) (*wsjson.Decoder[WorkspaceBuildUpdate], error) {
|
||||
ctx, span := tracing.StartSpan(ctx)
|
||||
defer span.End()
|
||||
|
||||
serverURL, err := c.URL.Parse("/api/experimental/watch-all-workspacebuilds")
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse url: %w", err)
|
||||
}
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("create cookie jar: %w", err)
|
||||
}
|
||||
jar.SetCookies(serverURL, []*http.Cookie{{
|
||||
Name: SessionTokenCookie,
|
||||
Value: c.SessionToken(),
|
||||
}})
|
||||
httpClient := &http.Client{
|
||||
Jar: jar,
|
||||
Transport: c.HTTPClient.Transport,
|
||||
}
|
||||
|
||||
conn, res, err := websocket.Dial(ctx, serverURL.String(), &websocket.DialOptions{
|
||||
HTTPClient: httpClient,
|
||||
CompressionMode: websocket.CompressionDisabled,
|
||||
})
|
||||
if err != nil {
|
||||
if res == nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, ReadBodyAsError(res)
|
||||
}
|
||||
|
||||
d := wsjson.NewDecoder[WorkspaceBuildUpdate](conn, websocket.MessageText, c.logger)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// WorkspaceAvailableUsers returns users available for workspace creation.
|
||||
// This is used to populate the owner dropdown when creating workspaces for
|
||||
// other users.
|
||||
|
||||
Reference in New Issue
Block a user