Skip to content

feat: expose user seat limits as Prometheus metrics #10169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
WIP
  • Loading branch information
mtojek committed Oct 10, 2023
commit 404896564e91e6cc8425d2af04668ee1eb4bae49
6 changes: 6 additions & 0 deletions cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,12 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
return xerrors.Errorf("register agents prometheus metric: %w", err)
}
defer closeAgentsFunc()

closeLicenseMetricsFunc, err := coderAPI.LicenseMetrics.Collect(ctx)
if err != nil {
return xerrors.Errorf("register license metric: %w", err)
}
defer closeLicenseMetricsFunc()
}

client := codersdk.New(localURL)
Expand Down
12 changes: 12 additions & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,16 @@ func New(options *Options) *API {
if err != nil {
panic(xerrors.Errorf("get deployment ID: %w", err))
}

licenseMetrics, err := prometheusmetrics.NewLicenseMetrics(&prometheusmetrics.LicenseMetricsOptions{
Database: options.Database,
Logger: options.Logger,
Registry: options.PrometheusRegistry,
})
if err != nil {
panic(xerrors.Errorf("unable to initialize license metrics: %w", err))
}

api := &API{
ctx: ctx,
cancel: cancel,
Expand Down Expand Up @@ -377,6 +387,8 @@ func New(options *Options) *API {
options.Logger.Named("acquirer"),
options.Database,
options.Pubsub),

LicenseMetrics: licenseMetrics,
}
if options.UpdateCheckOptions != nil {
api.updateChecker = updatecheck.New(
Expand Down
32 changes: 16 additions & 16 deletions coderd/prometheusmetrics/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import (
"sync/atomic"
"time"

"cdr.dev/slog"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/xerrors"

"cdr.dev/slog"

"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/codersdk"
)

Expand Down Expand Up @@ -51,14 +50,13 @@ func NewLicenseMetrics(opts *LicenseMetricsOptions) (*LicenseMetrics, error) {
}

func (lm *LicenseMetrics) Collect(ctx context.Context) (func(), error) {

licenseLimitGauge := prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "coderd",
Subsystem: "license",
Name: "user_limit",
Help: `The user seats limit based on the current license. "Zero" means unlimited or a disabled feature.`,
})
err := registerer.Register(licenseLimitGauge)
err := lm.registry.Register(licenseLimitGauge)
if err != nil {
return nil, err
}
Expand All @@ -69,14 +67,25 @@ func (lm *LicenseMetrics) Collect(ctx context.Context) (func(), error) {
Name: "active_users",
Help: "The number of active users.",
})
err = registerer.Register(activeUsersGauge)
err = lm.registry.Register(activeUsersGauge)
if err != nil {
return nil, err
}

userLimitGauge := prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "coderd",
Subsystem: "license",
Name: "user_limit",
Help: "The user seats limit based on the active Coder license.",
})
err = lm.registry.Register(activeUsersGauge)
if err != nil {
return nil, err
}

ctx, cancelFunc := context.WithCancel(ctx)
done := make(chan struct{})
ticker := time.NewTicker(duration)
ticker := time.NewTicker(lm.interval)
go func() {
defer close(done)
defer ticker.Stop()
Expand All @@ -87,15 +96,6 @@ func (lm *LicenseMetrics) Collect(ctx context.Context) (func(), error) {
case <-ticker.C:
}

apiKeys, err := db.GetAPIKeysLastUsedAfter(ctx, dbtime.Now().Add(-1*time.Hour))
if err != nil {
continue
}
distinctUsers := map[uuid.UUID]struct{}{}
for _, apiKey := range apiKeys {
distinctUsers[apiKey.UserID] = struct{}{}
}
gauge.Set(float64(len(distinctUsers)))
}
}()
return func() {
Expand Down
58 changes: 0 additions & 58 deletions coderd/prometheusmetrics/prometheusmetrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,61 +502,3 @@ func AgentStats(ctx context.Context, logger slog.Logger, registerer prometheus.R
<-done
}, nil
}

// License function tracks the number of occupied license seats, based on the entitlements.
func License(ctx context.Context, registerer prometheus.Registerer, db database.Store, entitlementsFetcher func() codersdk.Entitlements, duration time.Duration) (func(), error) {
if duration == 0 {
duration = 1 * time.Minute
}

licenseLimitGauge := prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "coderd",
Subsystem: "license",
Name: "user_limit",
Help: `The user seats limit based on the current license. "Zero" means unlimited or a disabled feature.`,
})
err := registerer.Register(licenseLimitGauge)
if err != nil {
return nil, err
}

activeUsersGauge := prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "coderd",
Subsystem: "license",
Name: "active_users",
Help: "The number of active users.",
})
err = registerer.Register(activeUsersGauge)
if err != nil {
return nil, err
}

ctx, cancelFunc := context.WithCancel(ctx)
done := make(chan struct{})
ticker := time.NewTicker(duration)
go func() {
defer close(done)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
}

apiKeys, err := db.GetAPIKeysLastUsedAfter(ctx, dbtime.Now().Add(-1*time.Hour))
if err != nil {
continue
}
distinctUsers := map[uuid.UUID]struct{}{}
for _, apiKey := range apiKeys {
distinctUsers[apiKey.UserID] = struct{}{}
}
gauge.Set(float64(len(distinctUsers)))
}
}()
return func() {
cancelFunc()
<-done
}, nil
}
1 change: 1 addition & 0 deletions enterprise/coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
defer api.entitlementsMu.Unlock()
api.entitlements = entitlements
api.AGPL.SiteHandler.Entitlements.Store(&entitlements)
api.AGPL.LicenseMetrics.Entitlements.Store(&entitlements)

return nil
}
Expand Down