From f93b6dcb8c9cdc15b452bbfaac1758496ec77173 Mon Sep 17 00:00:00 2001 From: air-hand <45233435+air-hand@users.noreply.github.com> Date: Wed, 23 Jul 2025 20:57:39 +0900 Subject: [PATCH 1/4] feat: restore `runner_name` label to listener metrics --- cmd/ghalistener/metrics/metrics.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/ghalistener/metrics/metrics.go b/cmd/ghalistener/metrics/metrics.go index 2aed6fb81d..933f639d49 100644 --- a/cmd/ghalistener/metrics/metrics.go +++ b/cmd/ghalistener/metrics/metrics.go @@ -24,6 +24,7 @@ const ( labelKeyJobWorkflowRef = "job_workflow_ref" labelKeyEventName = "event_name" labelKeyJobResult = "job_result" + labelKeyRunnerName = "runner_name" ) const ( @@ -88,11 +89,14 @@ func (e *exporter) jobLabels(jobBase *actions.JobMessageBase) prometheus.Labels func (e *exporter) completedJobLabels(msg *actions.JobCompleted) prometheus.Labels { l := e.jobLabels(&msg.JobMessageBase) l[labelKeyJobResult] = msg.Result + l[labelKeyRunnerName] = msg.RunnerName return l } func (e *exporter) startedJobLabels(msg *actions.JobStarted) prometheus.Labels { - return e.jobLabels(&msg.JobMessageBase) + l := e.jobLabels(&msg.JobMessageBase) + l[labelKeyRunnerName] = msg.RunnerName + return l } //go:generate mockery --name Publisher --output ./mocks --outpkg mocks --case underscore From 4480de779bc54b3e44d02b2128b3bc5dce86e98c Mon Sep 17 00:00:00 2001 From: air-hand <45233435+air-hand@users.noreply.github.com> Date: Wed, 23 Jul 2025 20:58:55 +0900 Subject: [PATCH 2/4] feat: add runner_name metrics label in values.yaml as example --- charts/gha-runner-scale-set/values.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/charts/gha-runner-scale-set/values.yaml b/charts/gha-runner-scale-set/values.yaml index c602dd2280..b66c91ce11 100644 --- a/charts/gha-runner-scale-set/values.yaml +++ b/charts/gha-runner-scale-set/values.yaml @@ -154,7 +154,7 @@ githubConfigSecret: # counters: # gha_started_jobs_total: # labels: -# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref"] +# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "runner_name"] # gha_completed_jobs_total: # labels: # [ @@ -165,6 +165,7 @@ githubConfigSecret: # "event_name", # "job_result", # "job_workflow_ref", +# "runner_name", # ] # gauges: # gha_assigned_jobs: @@ -186,7 +187,7 @@ githubConfigSecret: # histograms: # gha_job_startup_duration_seconds: # labels: -# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref"] +# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "runner_name"] # buckets: # [ # 0.01, @@ -244,7 +245,8 @@ githubConfigSecret: # "job_name", # "event_name", # "job_result", -# "job_workflow_ref" +# "job_workflow_ref", +# "runner_name" # ] # buckets: # [ From e0a0eb256fe421ae83f5bf87db47f49fdad03cd4 Mon Sep 17 00:00:00 2001 From: air-hand <45233435+air-hand@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:00:49 +0900 Subject: [PATCH 3/4] feat: add unit tests for job label generation methods --- cmd/ghalistener/metrics/metrics_test.go | 91 +++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/cmd/ghalistener/metrics/metrics_test.go b/cmd/ghalistener/metrics/metrics_test.go index 850560fbc1..5863ea1296 100644 --- a/cmd/ghalistener/metrics/metrics_test.go +++ b/cmd/ghalistener/metrics/metrics_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" + "github.com/actions/actions-runner-controller/github/actions" "github.com/go-logr/logr" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" @@ -263,3 +264,93 @@ func TestExporterConfigDefaults(t *testing.T) { assert.Equal(t, want, config) } + +func TestCompletedJobAllLabels(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 + } + + exporter, ok := NewExporter(config).(*exporter) + require.True(t, ok, "expected exporter to be of type *exporter") + require.NotNil(t, exporter) + + jobMessage := &actions.JobCompleted{ + JobMessageBase: actions.JobMessageBase{ + RepositoryName: "repo", + OwnerName: "org", + EventName: "", + JobDisplayName: "test-job", + JobWorkflowRef: "org/repo/.github/workflows/test.yml", + }, + Result: "success", + RunnerId: 1, + RunnerName: "runner1", + } + + labels := exporter.completedJobLabels(jobMessage) + + want := prometheus.Labels{ + labelKeyEnterprise: "", + labelKeyRepository: "repo", + labelKeyJobName: "test-job", + labelKeyOrganization: "org", + labelKeyEventName: "", + labelKeyJobWorkflowRef: "org/repo/.github/workflows/test.yml", + labelKeyJobResult: "success", + labelKeyRunnerName: "runner1", + } + + assert.Equal(t, want, labels) +} + +func TestStartedJobAllLabels(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 + } + + exporter, ok := NewExporter(config).(*exporter) + require.True(t, ok, "expected exporter to be of type *exporter") + require.NotNil(t, exporter) + + jobMessage := &actions.JobStarted{ + JobMessageBase: actions.JobMessageBase{ + RepositoryName: "repo", + OwnerName: "org", + EventName: "", + JobDisplayName: "test-job", + JobWorkflowRef: "org/repo/.github/workflows/test.yml", + }, + RunnerId: 1, + RunnerName: "runner1", + } + + labels := exporter.startedJobLabels(jobMessage) + + want := prometheus.Labels{ + labelKeyEnterprise: "", + labelKeyRepository: "repo", + labelKeyJobName: "test-job", + labelKeyOrganization: "org", + labelKeyEventName: "", + labelKeyJobWorkflowRef: "org/repo/.github/workflows/test.yml", + labelKeyRunnerName: "runner1", + } + + assert.Equal(t, want, labels) +} From 7a18c001460855082ef3167efa03964f4aadaf37 Mon Sep 17 00:00:00 2001 From: air-hand <45233435+air-hand@users.noreply.github.com> Date: Wed, 23 Jul 2025 21:02:19 +0900 Subject: [PATCH 4/4] feat: implement configurable label filtering with tests --- cmd/ghalistener/metrics/metrics.go | 23 +++++++++++------------ cmd/ghalistener/metrics/metrics_test.go | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/cmd/ghalistener/metrics/metrics.go b/cmd/ghalistener/metrics/metrics.go index 933f639d49..7f0c3714b8 100644 --- a/cmd/ghalistener/metrics/metrics.go +++ b/cmd/ghalistener/metrics/metrics.go @@ -415,6 +415,14 @@ func installMetrics(config v1alpha1.MetricsConfig, reg *prometheus.Registry, log return metrics } +func configuredLabelsOnly(allLabels prometheus.Labels, configuredLabels []string) prometheus.Labels { + labels := make(prometheus.Labels, len(configuredLabels)) + for _, label := range configuredLabels { + labels[label] = allLabels[label] + } + return labels +} + func (e *exporter) ListenAndServe(ctx context.Context) error { e.logger.Info("starting metrics server", "addr", e.srv.Addr) go func() { @@ -432,10 +440,7 @@ func (e *exporter) setGauge(name string, allLabels prometheus.Labels, val float6 if !ok { return } - labels := make(prometheus.Labels, len(m.config.Labels)) - for _, label := range m.config.Labels { - labels[label] = allLabels[label] - } + labels := configuredLabelsOnly(allLabels, m.config.Labels) m.gauge.With(labels).Set(val) } @@ -444,10 +449,7 @@ func (e *exporter) incCounter(name string, allLabels prometheus.Labels) { if !ok { return } - labels := make(prometheus.Labels, len(m.config.Labels)) - for _, label := range m.config.Labels { - labels[label] = allLabels[label] - } + labels := configuredLabelsOnly(allLabels, m.config.Labels) m.counter.With(labels).Inc() } @@ -456,10 +458,7 @@ func (e *exporter) observeHistogram(name string, allLabels prometheus.Labels, va if !ok { return } - labels := make(prometheus.Labels, len(m.config.Labels)) - for _, label := range m.config.Labels { - labels[label] = allLabels[label] - } + labels := configuredLabelsOnly(allLabels, m.config.Labels) m.histogram.With(labels).Observe(val) } diff --git a/cmd/ghalistener/metrics/metrics_test.go b/cmd/ghalistener/metrics/metrics_test.go index 5863ea1296..bf91074e0c 100644 --- a/cmd/ghalistener/metrics/metrics_test.go +++ b/cmd/ghalistener/metrics/metrics_test.go @@ -265,6 +265,26 @@ func TestExporterConfigDefaults(t *testing.T) { assert.Equal(t, want, config) } +func TestConfiguredLabelsOnly(t *testing.T) { + allLabels := prometheus.Labels{ + labelKeyRepository: "test-repo", + labelKeyOrganization: "test-org", + labelKeyJobName: "test-job", + } + + configuredLabels := []string{labelKeyRepository, labelKeyJobName} + + got := configuredLabelsOnly(allLabels, configuredLabels) + + want := prometheus.Labels{ + labelKeyRepository: "test-repo", + labelKeyJobName: "test-job", + } + + assert.Equal(t, want, got) + assert.NotContains(t, got, labelKeyOrganization) +} + func TestCompletedJobAllLabels(t *testing.T) { config := ExporterConfig{ ScaleSetName: "test-scale-set",