mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
* feat: Add anonymized telemetry to report product usage This adds a background service to report telemetry to a Coder server for usage data. There will be realtime event data sent in the future, but for now usage will report on a CRON. * Fix flake and requested changes * Add reporting options for setup * Add reporting for workspaces * Add resources as they are reported * Track API key usage * Ensure telemetry is tracked prior to exit
149 lines
4.4 KiB
Go
149 lines
4.4 KiB
Go
package telemetry_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/goleak"
|
|
|
|
"cdr.dev/slog"
|
|
"cdr.dev/slog/sloggers/slogtest"
|
|
"github.com/coder/coder/buildinfo"
|
|
"github.com/coder/coder/coderd/database"
|
|
"github.com/coder/coder/coderd/database/databasefake"
|
|
"github.com/coder/coder/coderd/telemetry"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
goleak.VerifyTestMain(m)
|
|
}
|
|
|
|
func TestTelemetry(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("Snapshot", func(t *testing.T) {
|
|
t.Parallel()
|
|
db := databasefake.New()
|
|
ctx := context.Background()
|
|
_, err := db.InsertAPIKey(ctx, database.InsertAPIKeyParams{
|
|
ID: uuid.NewString(),
|
|
LastUsed: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertParameterSchema(ctx, database.InsertParameterSchemaParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertTemplate(ctx, database.InsertTemplateParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertUser(ctx, database.InsertUserParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertWorkspace(ctx, database.InsertWorkspaceParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertWorkspaceApp(ctx, database.InsertWorkspaceAppParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertWorkspaceAgent(ctx, database.InsertWorkspaceAgentParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = db.InsertWorkspaceResource(ctx, database.InsertWorkspaceResourceParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
snapshot := collectSnapshot(t, db)
|
|
require.Len(t, snapshot.ParameterSchemas, 1)
|
|
require.Len(t, snapshot.ProvisionerJobs, 1)
|
|
require.Len(t, snapshot.Templates, 1)
|
|
require.Len(t, snapshot.TemplateVersions, 1)
|
|
require.Len(t, snapshot.Users, 1)
|
|
require.Len(t, snapshot.Workspaces, 1)
|
|
require.Len(t, snapshot.WorkspaceApps, 1)
|
|
require.Len(t, snapshot.WorkspaceAgents, 1)
|
|
require.Len(t, snapshot.WorkspaceBuilds, 1)
|
|
require.Len(t, snapshot.WorkspaceResources, 1)
|
|
})
|
|
t.Run("HashedEmail", func(t *testing.T) {
|
|
t.Parallel()
|
|
db := databasefake.New()
|
|
_, err := db.InsertUser(context.Background(), database.InsertUserParams{
|
|
ID: uuid.New(),
|
|
Email: "kyle@coder.com",
|
|
CreatedAt: database.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
snapshot := collectSnapshot(t, db)
|
|
require.Len(t, snapshot.Users, 1)
|
|
require.Equal(t, snapshot.Users[0].EmailHashed, "bb44bf07cf9a2db0554bba63a03d822c927deae77df101874496df5a6a3e896d@coder.com")
|
|
})
|
|
}
|
|
|
|
func collectSnapshot(t *testing.T, db database.Store) *telemetry.Snapshot {
|
|
t.Helper()
|
|
deployment := make(chan struct{}, 64)
|
|
snapshot := make(chan *telemetry.Snapshot, 64)
|
|
r := chi.NewRouter()
|
|
r.Post("/deployment", func(w http.ResponseWriter, r *http.Request) {
|
|
require.Equal(t, buildinfo.Version(), r.Header.Get(telemetry.VersionHeader))
|
|
w.WriteHeader(http.StatusAccepted)
|
|
deployment <- struct{}{}
|
|
})
|
|
r.Post("/snapshot", func(w http.ResponseWriter, r *http.Request) {
|
|
require.Equal(t, buildinfo.Version(), r.Header.Get(telemetry.VersionHeader))
|
|
w.WriteHeader(http.StatusAccepted)
|
|
ss := &telemetry.Snapshot{}
|
|
err := json.NewDecoder(r.Body).Decode(ss)
|
|
require.NoError(t, err)
|
|
snapshot <- ss
|
|
})
|
|
server := httptest.NewServer(r)
|
|
t.Cleanup(server.Close)
|
|
serverURL, err := url.Parse(server.URL)
|
|
require.NoError(t, err)
|
|
reporter, err := telemetry.New(telemetry.Options{
|
|
Database: db,
|
|
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
|
URL: serverURL,
|
|
DeploymentID: uuid.NewString(),
|
|
})
|
|
require.NoError(t, err)
|
|
t.Cleanup(reporter.Close)
|
|
<-deployment
|
|
return <-snapshot
|
|
}
|