Skip to content

Commit 96a7b46

Browse files
committed
Move workspaceagent endpoint
1 parent 0d4fc9d commit 96a7b46

File tree

7 files changed

+142
-152
lines changed

7 files changed

+142
-152
lines changed

coderd/coderd.go

+2-6
Original file line numberDiff line numberDiff line change
@@ -363,12 +363,6 @@ func New(options *Options) *API {
363363
})
364364
})
365365
})
366-
r.Route("/metrics", func(r chi.Router) {
367-
r.Group(func(r chi.Router) {
368-
r.Use(httpmw.ExtractWorkspaceAgent(options.Database))
369-
r.Get("/report-agent-stats", api.workspaceAgentReportStats)
370-
})
371-
})
372366
r.Route("/workspaceagents", func(r chi.Router) {
373367
r.Post("/azure-instance-identity", api.postWorkspaceAuthAzureInstanceIdentity)
374368
r.Post("/aws-instance-identity", api.postWorkspaceAuthAWSInstanceIdentity)
@@ -384,6 +378,8 @@ func New(options *Options) *API {
384378
r.Get("/iceservers", api.workspaceAgentICEServers)
385379

386380
r.Get("/coordinate", api.workspaceAgentCoordinate)
381+
382+
r.Get("/report-stats", api.workspaceAgentReportStats)
387383
})
388384
r.Route("/{workspaceagent}", func(r chi.Router) {
389385
r.Use(

coderd/coderdtest/authtest.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
196196
"GET:/api/v2/workspaceagents/me/turn": {NoAuthorize: true},
197197
"GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true},
198198
"POST:/api/v2/workspaceagents/me/version": {NoAuthorize: true},
199+
"GET:/api/v2/workspaceagents/me/report-stats": {NoAuthorize: true},
199200
"GET:/api/v2/workspaceagents/{workspaceagent}/iceservers": {NoAuthorize: true},
200-
"GET:/api/v2/metrics/report-agent-stats": {NoAuthorize: true},
201201

202202
// These endpoints have more assertions. This is good, add more endpoints to assert if you can!
203203
"GET:/api/v2/organizations/{organization}": {AssertObject: rbac.ResourceOrganization.InOrg(a.Admin.OrganizationID)},

coderd/metrics.go

-140
This file was deleted.

coderd/workspaceagents.go

+123
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net"
1010
"net/http"
1111
"net/netip"
12+
"reflect"
1213
"strconv"
1314
"strings"
1415
"time"
@@ -18,6 +19,7 @@ import (
1819
"golang.org/x/mod/semver"
1920
"golang.org/x/xerrors"
2021
"nhooyr.io/websocket"
22+
"nhooyr.io/websocket/wsjson"
2123
"tailscale.com/tailcfg"
2224

2325
"cdr.dev/slog"
@@ -739,6 +741,127 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator *tailnet.Coordi
739741

740742
return workspaceAgent, nil
741743
}
744+
func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Request) {
745+
api.websocketWaitMutex.Lock()
746+
api.websocketWaitGroup.Add(1)
747+
api.websocketWaitMutex.Unlock()
748+
defer api.websocketWaitGroup.Done()
749+
750+
workspaceAgent := httpmw.WorkspaceAgent(r)
751+
resource, err := api.Database.GetWorkspaceResourceByID(r.Context(), workspaceAgent.ResourceID)
752+
if err != nil {
753+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
754+
Message: "Failed to get workspace resource.",
755+
Detail: err.Error(),
756+
})
757+
return
758+
}
759+
760+
build, err := api.Database.GetWorkspaceBuildByJobID(r.Context(), resource.JobID)
761+
if err != nil {
762+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
763+
Message: "Failed to get build.",
764+
Detail: err.Error(),
765+
})
766+
return
767+
}
768+
769+
workspace, err := api.Database.GetWorkspaceByID(r.Context(), build.WorkspaceID)
770+
if err != nil {
771+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
772+
Message: "Failed to get workspace.",
773+
Detail: err.Error(),
774+
})
775+
return
776+
}
777+
778+
conn, err := websocket.Accept(rw, r, &websocket.AcceptOptions{
779+
CompressionMode: websocket.CompressionDisabled,
780+
})
781+
if err != nil {
782+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
783+
Message: "Failed to accept websocket.",
784+
Detail: err.Error(),
785+
})
786+
return
787+
}
788+
defer conn.Close(websocket.StatusAbnormalClosure, "")
789+
790+
// Allow overriding the stat interval for debugging and testing purposes.
791+
ctx := r.Context()
792+
timer := time.NewTicker(api.AgentStatsRefreshInterval)
793+
var lastReport codersdk.AgentStatsReportResponse
794+
for {
795+
err := wsjson.Write(ctx, conn, codersdk.AgentStatsReportRequest{})
796+
if err != nil {
797+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
798+
Message: "Failed to write report request.",
799+
Detail: err.Error(),
800+
})
801+
return
802+
}
803+
var rep codersdk.AgentStatsReportResponse
804+
805+
err = wsjson.Read(ctx, conn, &rep)
806+
if err != nil {
807+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
808+
Message: "Failed to read report response.",
809+
Detail: err.Error(),
810+
})
811+
return
812+
}
813+
814+
repJSON, err := json.Marshal(rep)
815+
if err != nil {
816+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
817+
Message: "Failed to marshal stat json.",
818+
Detail: err.Error(),
819+
})
820+
return
821+
}
822+
823+
// Avoid inserting duplicate rows to preserve DB space.
824+
var insert = !reflect.DeepEqual(lastReport, rep)
825+
826+
api.Logger.Debug(ctx, "read stats report",
827+
slog.F("interval", api.AgentStatsRefreshInterval),
828+
slog.F("agent", workspaceAgent.ID),
829+
slog.F("resource", resource.ID),
830+
slog.F("workspace", workspace.ID),
831+
slog.F("insert", insert),
832+
slog.F("payload", rep),
833+
)
834+
835+
if insert {
836+
lastReport = rep
837+
838+
_, err = api.Database.InsertAgentStat(ctx, database.InsertAgentStatParams{
839+
ID: uuid.New(),
840+
CreatedAt: time.Now(),
841+
AgentID: workspaceAgent.ID,
842+
WorkspaceID: build.WorkspaceID,
843+
UserID: workspace.OwnerID,
844+
TemplateID: workspace.TemplateID,
845+
Payload: json.RawMessage(repJSON),
846+
})
847+
if err != nil {
848+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
849+
Message: "Failed to insert agent stat.",
850+
Detail: err.Error(),
851+
})
852+
return
853+
}
854+
}
855+
856+
select {
857+
case <-timer.C:
858+
continue
859+
case <-ctx.Done():
860+
conn.Close(websocket.StatusNormalClosure, "")
861+
return
862+
}
863+
}
864+
}
742865

743866
// wsNetConn wraps net.Conn created by websocket.NetConn(). Cancel func
744867
// is called if a read or write error is encountered.

codersdk/metrics.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (c *Client) AgentReportStats(
2727
log slog.Logger,
2828
stats func() *agent.Stats,
2929
) (io.Closer, error) {
30-
serverURL, err := c.URL.Parse("/api/v2/metrics/report-agent-stats")
30+
serverURL, err := c.URL.Parse("/api/v2/workspaceagents/me/report-stats")
3131
if err != nil {
3232
return nil, xerrors.Errorf("parse url: %w", err)
3333
}

site/src/pages/TemplatePage/DAUChart.tsx

+14-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import useTheme from "@material-ui/styles/useTheme"
22

33
import { Theme } from "@material-ui/core/styles"
44
import {
5+
BarElement,
56
CategoryScale,
67
Chart as ChartJS,
78
ChartOptions,
@@ -21,7 +22,16 @@ import { FC } from "react"
2122
import { Line } from "react-chartjs-2"
2223
import * as TypesGen from "../../api/typesGenerated"
2324

24-
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend)
25+
ChartJS.register(
26+
CategoryScale,
27+
LinearScale,
28+
PointElement,
29+
BarElement,
30+
LineElement,
31+
Title,
32+
Tooltip,
33+
Legend,
34+
)
2535

2636
export interface DAUChartProps {
2737
templateDAUs: TypesGen.TemplateDAUsResponse
@@ -82,7 +92,9 @@ export const DAUChart: FC<DAUChartProps> = ({ templateDAUs: templateMetricsData
8292
<h3>{Language.chartTitle}</h3>
8393
<HelpTooltip size="small">
8494
<HelpTooltipTitle>How do we calculate DAUs?</HelpTooltipTitle>
85-
<HelpTooltipText>We use workspace connection traffic to compute DAUs.</HelpTooltipText>
95+
<HelpTooltipText>
96+
We use all workspace connection traffic to calculate DAUs.
97+
</HelpTooltipText>
8698
</HelpTooltip>
8799
</Stack>
88100
<Line

site/src/pages/TemplatePage/TemplatePageView.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,8 @@ export const TemplatePageView: FC<React.PropsWithChildren<TemplatePageViewProps>
103103
</Stack>
104104
</PageHeader>
105105

106-
{templateDAUs && <DAUChart templateDAUs={templateDAUs} />}
107-
108106
<Stack spacing={2.5}>
107+
{templateDAUs && <DAUChart templateDAUs={templateDAUs} />}
109108
<TemplateStats template={template} activeVersion={activeTemplateVersion} />
110109
<WorkspaceSection
111110
title={Language.resourcesTitle}

0 commit comments

Comments
 (0)