diff --git a/cmd/ghalistener/app/app.go b/cmd/ghalistener/app/app.go index 529b5ba30d..02807f6a9b 100644 --- a/cmd/ghalistener/app/app.go +++ b/cmd/ghalistener/app/app.go @@ -69,8 +69,8 @@ func New(config config.Config) (*App, error) { Repository: ghConfig.Repository, ServerAddr: config.MetricsAddr, ServerEndpoint: config.MetricsEndpoint, + Metrics: config.Metrics, Logger: app.logger.WithName("metrics exporter"), - Metrics: *config.Metrics, }) } diff --git a/cmd/ghalistener/metrics/metrics.go b/cmd/ghalistener/metrics/metrics.go index 5dad8b97f0..57d4e2e301 100644 --- a/cmd/ghalistener/metrics/metrics.go +++ b/cmd/ghalistener/metrics/metrics.go @@ -152,13 +152,148 @@ type ExporterConfig struct { ServerAddr string ServerEndpoint string Logger logr.Logger - Metrics v1alpha1.MetricsConfig + Metrics *v1alpha1.MetricsConfig +} + +var defaultMetrics = v1alpha1.MetricsConfig{ + Counters: map[string]*v1alpha1.CounterMetric{ + MetricStartedJobsTotal: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyJobName, + labelKeyEventName, + }, + }, + MetricCompletedJobsTotal: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyJobName, + labelKeyEventName, + labelKeyJobResult, + }, + }, + }, + Gauges: map[string]*v1alpha1.GaugeMetric{ + MetricAssignedJobs: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyRunnerScaleSetName, + labelKeyRunnerScaleSetNamespace, + }, + }, + MetricRunningJobs: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyRunnerScaleSetName, + labelKeyRunnerScaleSetNamespace, + }, + }, + MetricRegisteredRunners: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyRunnerScaleSetName, + labelKeyRunnerScaleSetNamespace, + }, + }, + MetricBusyRunners: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyRunnerScaleSetName, + labelKeyRunnerScaleSetNamespace, + }, + }, + MetricMinRunners: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyRunnerScaleSetName, + labelKeyRunnerScaleSetNamespace, + }, + }, + MetricMaxRunners: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyRunnerScaleSetName, + labelKeyRunnerScaleSetNamespace, + }, + }, + MetricDesiredRunners: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyRunnerScaleSetName, + labelKeyRunnerScaleSetNamespace, + }, + }, + MetricIdleRunners: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyRunnerScaleSetName, + labelKeyRunnerScaleSetNamespace, + }, + }, + }, + Histograms: map[string]*v1alpha1.HistogramMetric{ + MetricJobStartupDurationSeconds: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyJobName, + labelKeyEventName, + }, + Buckets: defaultRuntimeBuckets, + }, + MetricJobExecutionDurationSeconds: { + Labels: []string{ + labelKeyEnterprise, + labelKeyOrganization, + labelKeyRepository, + labelKeyJobName, + labelKeyEventName, + labelKeyJobResult, + }, + Buckets: defaultRuntimeBuckets, + }, + }, +} + +func (e *ExporterConfig) defaults() { + if e.ServerAddr == "" { + e.ServerAddr = ":8080" + } + if e.ServerEndpoint == "" { + e.ServerEndpoint = "/metrics" + } + if e.Metrics == nil { + defaultMetrics := defaultMetrics + e.Metrics = &defaultMetrics + } } func NewExporter(config ExporterConfig) ServerExporter { + config.defaults() reg := prometheus.NewRegistry() - metrics := installMetrics(config.Metrics, reg, config.Logger) + metrics := installMetrics(*config.Metrics, reg, config.Logger) mux := http.NewServeMux() mux.Handle( diff --git a/cmd/ghalistener/metrics/metrics_test.go b/cmd/ghalistener/metrics/metrics_test.go index e808bfc2b8..850560fbc1 100644 --- a/cmd/ghalistener/metrics/metrics_test.go +++ b/cmd/ghalistener/metrics/metrics_test.go @@ -7,6 +7,7 @@ import ( "github.com/go-logr/logr" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestInstallMetrics(t *testing.T) { @@ -86,3 +87,179 @@ func TestInstallMetrics(t *testing.T) { assert.Equal(t, duration.config.Labels, metricsConfig.Histograms[MetricJobStartupDurationSeconds].Labels) assert.Equal(t, duration.config.Buckets, defaultRuntimeBuckets) } + +func TestNewExporter(t *testing.T) { + t.Run("with defaults metrics applied", func(t *testing.T) { + config := ExporterConfig{ + ScaleSetName: "test-scale-set", + ScaleSetNamespace: "test-namespace", + Enterprise: "", + Organization: "org", + Repository: "repo", + ServerAddr: ":6060", + ServerEndpoint: "/metrics", + Logger: logr.Discard(), + Metrics: nil, // when metrics is nil, all default metrics should be registered + } + + exporter, ok := NewExporter(config).(*exporter) + require.True(t, ok, "expected exporter to be of type *exporter") + require.NotNil(t, exporter) + + reg := prometheus.NewRegistry() + wantMetrics := installMetrics(defaultMetrics, reg, config.Logger) + + assert.Equal(t, len(wantMetrics.counters), len(exporter.counters)) + for k, v := range wantMetrics.counters { + assert.Contains(t, exporter.counters, k) + assert.Equal(t, v.config, exporter.counters[k].config) + } + + assert.Equal(t, len(wantMetrics.gauges), len(exporter.gauges)) + for k, v := range wantMetrics.gauges { + assert.Contains(t, exporter.gauges, k) + assert.Equal(t, v.config, exporter.gauges[k].config) + } + + assert.Equal(t, len(wantMetrics.histograms), len(exporter.histograms)) + for k, v := range wantMetrics.histograms { + assert.Contains(t, exporter.histograms, k) + assert.Equal(t, v.config, exporter.histograms[k].config) + } + + require.NotNil(t, exporter.srv) + assert.Equal(t, config.ServerAddr, exporter.srv.Addr) + }) + + t.Run("with default server URL", func(t *testing.T) { + config := ExporterConfig{ + ScaleSetName: "test-scale-set", + ScaleSetNamespace: "test-namespace", + Enterprise: "", + Organization: "org", + Repository: "repo", + ServerAddr: "", // empty ServerAddr should default to ":8080" + ServerEndpoint: "", + Logger: logr.Discard(), + Metrics: nil, // when metrics is nil, all default metrics should be registered + } + + exporter, ok := NewExporter(config).(*exporter) + require.True(t, ok, "expected exporter to be of type *exporter") + require.NotNil(t, exporter) + + reg := prometheus.NewRegistry() + wantMetrics := installMetrics(defaultMetrics, reg, config.Logger) + + assert.Equal(t, len(wantMetrics.counters), len(exporter.counters)) + for k, v := range wantMetrics.counters { + assert.Contains(t, exporter.counters, k) + assert.Equal(t, v.config, exporter.counters[k].config) + } + + assert.Equal(t, len(wantMetrics.gauges), len(exporter.gauges)) + for k, v := range wantMetrics.gauges { + assert.Contains(t, exporter.gauges, k) + assert.Equal(t, v.config, exporter.gauges[k].config) + } + + assert.Equal(t, len(wantMetrics.histograms), len(exporter.histograms)) + for k, v := range wantMetrics.histograms { + assert.Contains(t, exporter.histograms, k) + assert.Equal(t, v.config, exporter.histograms[k].config) + } + + require.NotNil(t, exporter.srv) + assert.Equal(t, exporter.srv.Addr, ":8080") + }) + + t.Run("with metrics configured", func(t *testing.T) { + metricsConfig := v1alpha1.MetricsConfig{ + Counters: map[string]*v1alpha1.CounterMetric{ + MetricStartedJobsTotal: { + Labels: []string{labelKeyRepository}, + }, + }, + Gauges: map[string]*v1alpha1.GaugeMetric{ + MetricAssignedJobs: { + Labels: []string{labelKeyRepository}, + }, + }, + Histograms: map[string]*v1alpha1.HistogramMetric{ + MetricJobExecutionDurationSeconds: { + Labels: []string{labelKeyRepository}, + Buckets: []float64{0.1, 1}, + }, + }, + } + + config := ExporterConfig{ + ScaleSetName: "test-scale-set", + ScaleSetNamespace: "test-namespace", + Enterprise: "", + Organization: "org", + Repository: "repo", + ServerAddr: ":6060", + ServerEndpoint: "/metrics", + Logger: logr.Discard(), + Metrics: &metricsConfig, + } + + exporter, ok := NewExporter(config).(*exporter) + require.True(t, ok, "expected exporter to be of type *exporter") + require.NotNil(t, exporter) + + reg := prometheus.NewRegistry() + wantMetrics := installMetrics(metricsConfig, reg, config.Logger) + + assert.Equal(t, len(wantMetrics.counters), len(exporter.counters)) + for k, v := range wantMetrics.counters { + assert.Contains(t, exporter.counters, k) + assert.Equal(t, v.config, exporter.counters[k].config) + } + + assert.Equal(t, len(wantMetrics.gauges), len(exporter.gauges)) + for k, v := range wantMetrics.gauges { + assert.Contains(t, exporter.gauges, k) + assert.Equal(t, v.config, exporter.gauges[k].config) + } + + assert.Equal(t, len(wantMetrics.histograms), len(exporter.histograms)) + for k, v := range wantMetrics.histograms { + assert.Contains(t, exporter.histograms, k) + assert.Equal(t, v.config, exporter.histograms[k].config) + } + + require.NotNil(t, exporter.srv) + assert.Equal(t, config.ServerAddr, exporter.srv.Addr) + }) +} + +func TestExporterConfigDefaults(t *testing.T) { + config := ExporterConfig{ + ScaleSetName: "test-scale-set", + ScaleSetNamespace: "test-namespace", + Enterprise: "", + Organization: "org", + Repository: "repo", + ServerAddr: "", + ServerEndpoint: "", + Logger: logr.Discard(), + Metrics: nil, // when metrics is nil, all default metrics should be registered + } + + config.defaults() + want := ExporterConfig{ + ScaleSetName: "test-scale-set", + ScaleSetNamespace: "test-namespace", + Enterprise: "", + Organization: "org", + Repository: "repo", + ServerAddr: ":8080", // default server address + ServerEndpoint: "/metrics", // default server endpoint + Logger: logr.Discard(), + Metrics: &defaultMetrics, // when metrics is nil, all default metrics should be registered + } + + assert.Equal(t, want, config) +}