mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: track the first time html is served in telemetry (#16334)
Addresses https://github.com/coder/nexus/issues/175. ## Changes - Adds the `telemetry_items` database table. It's a key value store for telemetry events that don't fit any other database tables. - Adds a telemetry report when HTML is served for the first time in `site.go`.
This commit is contained in:
@@ -579,6 +579,17 @@ func (r *remoteReporter) createSnapshot() (*Snapshot, error) {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
eg.Go(func() error {
|
||||
items, err := r.options.Database.GetTelemetryItems(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get telemetry items: %w", err)
|
||||
}
|
||||
snapshot.TelemetryItems = make([]TelemetryItem, 0, len(items))
|
||||
for _, item := range items {
|
||||
snapshot.TelemetryItems = append(snapshot.TelemetryItems, ConvertTelemetryItem(item))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
err := eg.Wait()
|
||||
if err != nil {
|
||||
@@ -985,6 +996,15 @@ func ConvertOrganization(org database.Organization) Organization {
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertTelemetryItem(item database.TelemetryItem) TelemetryItem {
|
||||
return TelemetryItem{
|
||||
Key: item.Key,
|
||||
Value: item.Value,
|
||||
CreatedAt: item.CreatedAt,
|
||||
UpdatedAt: item.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// Snapshot represents a point-in-time anonymized database dump.
|
||||
// Data is aggregated by latest on the server-side, so partial data
|
||||
// can be sent without issue.
|
||||
@@ -1012,6 +1032,7 @@ type Snapshot struct {
|
||||
Workspaces []Workspace `json:"workspaces"`
|
||||
NetworkEvents []NetworkEvent `json:"network_events"`
|
||||
Organizations []Organization `json:"organizations"`
|
||||
TelemetryItems []TelemetryItem `json:"telemetry_items"`
|
||||
}
|
||||
|
||||
// Deployment contains information about the host running Coder.
|
||||
@@ -1536,6 +1557,25 @@ type Organization struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type telemetryItemKey string
|
||||
|
||||
// The comment below gets rid of the warning that the name "TelemetryItemKey" has
|
||||
// the "Telemetry" prefix, and that stutters when you use it outside the package
|
||||
// (telemetry.TelemetryItemKey...). "TelemetryItem" is the name of a database table,
|
||||
// so it makes sense to use the "Telemetry" prefix.
|
||||
//
|
||||
//revive:disable:exported
|
||||
const (
|
||||
TelemetryItemKeyHTMLFirstServedAt telemetryItemKey = "html_first_served_at"
|
||||
)
|
||||
|
||||
type TelemetryItem struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type noopReporter struct{}
|
||||
|
||||
func (*noopReporter) Report(_ *Snapshot) {}
|
||||
|
||||
@@ -75,6 +75,10 @@ func TestTelemetry(t *testing.T) {
|
||||
Health: database.WorkspaceAppHealthDisabled,
|
||||
OpenIn: database.WorkspaceAppOpenInSlimWindow,
|
||||
})
|
||||
_ = dbgen.TelemetryItem(t, db, database.TelemetryItem{
|
||||
Key: string(telemetry.TelemetryItemKeyHTMLFirstServedAt),
|
||||
Value: time.Now().Format(time.RFC3339),
|
||||
})
|
||||
group := dbgen.Group(t, db, database.Group{})
|
||||
_ = dbgen.GroupMember(t, db, database.GroupMemberTable{UserID: user.ID, GroupID: group.ID})
|
||||
wsagent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{})
|
||||
@@ -127,7 +131,7 @@ func TestTelemetry(t *testing.T) {
|
||||
require.Len(t, snapshot.WorkspaceProxies, 1)
|
||||
require.Len(t, snapshot.WorkspaceModules, 1)
|
||||
require.Len(t, snapshot.Organizations, 1)
|
||||
|
||||
require.Len(t, snapshot.TelemetryItems, 1)
|
||||
wsa := snapshot.WorkspaceAgents[0]
|
||||
require.Len(t, wsa.Subsystems, 2)
|
||||
require.Equal(t, string(database.WorkspaceAgentSubsystemEnvbox), wsa.Subsystems[0])
|
||||
@@ -316,6 +320,47 @@ func TestTelemetryInstallSource(t *testing.T) {
|
||||
require.Equal(t, "aws_marketplace", deployment.InstallSource)
|
||||
}
|
||||
|
||||
func TestTelemetryItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
key := testutil.GetRandomName(t)
|
||||
value := time.Now().Format(time.RFC3339)
|
||||
|
||||
err := db.InsertTelemetryItemIfNotExists(ctx, database.InsertTelemetryItemIfNotExistsParams{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
item, err := db.GetTelemetryItem(ctx, key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, item.Key, key)
|
||||
require.Equal(t, item.Value, value)
|
||||
|
||||
// Inserting a new value should not update the existing value
|
||||
err = db.InsertTelemetryItemIfNotExists(ctx, database.InsertTelemetryItemIfNotExistsParams{
|
||||
Key: key,
|
||||
Value: "new_value",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
item, err = db.GetTelemetryItem(ctx, key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, item.Value, value)
|
||||
|
||||
// Upserting a new value should update the existing value
|
||||
err = db.UpsertTelemetryItem(ctx, database.UpsertTelemetryItemParams{
|
||||
Key: key,
|
||||
Value: "new_value",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
item, err = db.GetTelemetryItem(ctx, key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, item.Value, "new_value")
|
||||
}
|
||||
|
||||
func collectSnapshot(t *testing.T, db database.Store, addOptionsFn func(opts telemetry.Options) telemetry.Options) (*telemetry.Deployment, *telemetry.Snapshot) {
|
||||
t.Helper()
|
||||
deployment := make(chan *telemetry.Deployment, 64)
|
||||
|
||||
Reference in New Issue
Block a user