From cb86313c66489126c8a0f14bb51c4e1443180305 Mon Sep 17 00:00:00 2001 From: Peter Halasz Date: Sat, 3 Feb 2024 15:40:03 +0100 Subject: [PATCH 1/2] Add gauge metric for number of self-hosted org runners Signed-off-by: Peter Halasz --- internal/server/billing_metrics_exporter.go | 8 +-- internal/server/github_client.go | 25 ++++++++ internal/server/metrics.go | 14 ++++- internal/server/runner_metrics.go | 63 +++++++++++++++++++++ internal/server/server.go | 6 ++ main.go | 4 ++ 6 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 internal/server/github_client.go create mode 100644 internal/server/runner_metrics.go diff --git a/internal/server/billing_metrics_exporter.go b/internal/server/billing_metrics_exporter.go index 90f6afc..06d4520 100644 --- a/internal/server/billing_metrics_exporter.go +++ b/internal/server/billing_metrics_exporter.go @@ -8,7 +8,6 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/google/go-github/v50/github" - "golang.org/x/oauth2" ) type BillingMetricsExporter struct { @@ -18,12 +17,7 @@ type BillingMetricsExporter struct { } func NewBillingMetricsExporter(logger log.Logger, opts Opts) *BillingMetricsExporter { - ctx := context.Background() - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: opts.GitHubAPIToken}, - ) - tc := oauth2.NewClient(ctx, ts) - client := github.NewClient(tc) + client := getGithubClient(opts.GitHubAPIToken) return &BillingMetricsExporter{ Logger: logger, diff --git a/internal/server/github_client.go b/internal/server/github_client.go new file mode 100644 index 0000000..78728cd --- /dev/null +++ b/internal/server/github_client.go @@ -0,0 +1,25 @@ +package server + +import ( + "context" + + "github.com/google/go-github/v50/github" + "golang.org/x/oauth2" +) + +var client *github.Client = nil + +func getGithubClient(githubToken string) *github.Client { + if client != nil { + return client + } + + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: githubToken}, + ) + tc := oauth2.NewClient(ctx, ts) + client = github.NewClient(tc) + + return client +} diff --git a/internal/server/metrics.go b/internal/server/metrics.go index b5a694f..93841cf 100644 --- a/internal/server/metrics.go +++ b/internal/server/metrics.go @@ -63,21 +63,21 @@ var ( totalMinutesUsedUbuntuActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "actions_total_minutes_used_ubuntu_minutes", - Help: "Total minutes used for Ubuntu type for the GitHub Actions. To be deprecate, use actions_total_minutes_used_by_host_minutes", + Help: "Total minutes used for Ubuntu type for the GitHub Actions. To be deprecated, use actions_total_minutes_used_by_host_minutes", }, []string{"org", "user"}, ) totalMinutesUsedMacOSActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "actions_total_minutes_used_macos_minutes", - Help: "Total minutes used for MacOS type for the GitHub Actions. To be deprecate, use actions_total_minutes_used_by_host_minutes", + Help: "Total minutes used for MacOS type for the GitHub Actions. To be deprecated, use actions_total_minutes_used_by_host_minutes", }, []string{"org", "user"}, ) totalMinutesUsedWindowsActions = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "actions_total_minutes_used_windows_minutes", - Help: "Total minutes used for Windows type for the GitHub Actions. To be deprecate, use actions_total_minutes_used_by_host_minutes", + Help: "Total minutes used for Windows type for the GitHub Actions. To be deprecated, use actions_total_minutes_used_by_host_minutes", }, []string{"org", "user"}, ) @@ -88,6 +88,13 @@ var ( }, []string{"org", "user", "host_type"}, ) + + numberOfSelfHostedRunners = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "github_actions_num_self_hosted_org_runners", + Help: "Number of self-hosted org runners for the GitHub Actions.", + }, + []string{"org"}, + ) ) func init() { @@ -104,6 +111,7 @@ func init() { prometheus.MustRegister(totalMinutesUsedMacOSActions) prometheus.MustRegister(totalMinutesUsedWindowsActions) prometheus.MustRegister(totalMinutesUsedByHostTypeActions) + prometheus.MustRegister(numberOfSelfHostedRunners) } type WorkflowObserver interface { diff --git a/internal/server/runner_metrics.go b/internal/server/runner_metrics.go new file mode 100644 index 0000000..fdc1896 --- /dev/null +++ b/internal/server/runner_metrics.go @@ -0,0 +1,63 @@ +package server + +import ( + "context" + "time" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/google/go-github/v50/github" +) + +type RunnerMetricsExporter struct { + GHClient *github.Client + Logger log.Logger + Opts Opts +} + +func NewRunnerMetricsExporter(logger log.Logger, opts Opts) *RunnerMetricsExporter { + client := getGithubClient(opts.GitHubAPIToken) + + return &RunnerMetricsExporter{ + GHClient: client, + Logger: logger, + Opts: opts, + } +} + +func (e *RunnerMetricsExporter) StartOrgRunnerMetricsCollection(ctx context.Context) error { + if e.Opts.GitHubOrg == "" { + level.Info(e.Logger).Log("msg", "Github org is not set, no org runner metrics will be collected.") + return nil + } + if e.Opts.GitHubAPIToken == "" { + level.Info(e.Logger).Log("msg", "Github token is not set, no org runner metrics will be collected.") + return nil + } + + ticker := time.NewTicker(time.Duration(e.Opts.RunnersAPIPollSeconds) * time.Second) + go func() { + for { + select { + case <-ticker.C: + e.collectOrgRunnerMetrics(ctx) + case <-ctx.Done(): + ticker.Stop() + level.Info(e.Logger).Log("msg", "Stopped polling for org runner metrics.") + return + } + } + }() + + return nil +} + +func (e *RunnerMetricsExporter) collectOrgRunnerMetrics(ctx context.Context) { + runners, _, err := e.GHClient.Actions.ListOrganizationRunners(ctx, e.Opts.GitHubOrg, nil) + + if err != nil { + e.Logger.Log("msg", "Failed to retrieve org runners for org ", e.Opts.GitHubOrg, " ", err) + } + + numberOfSelfHostedRunners.WithLabelValues(e.Opts.GitHubOrg).Set(float64(runners.TotalCount)) +} diff --git a/internal/server/server.go b/internal/server/server.go index 6ad5811..65b553f 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -27,6 +27,9 @@ type Opts struct { GitHubOrg string GitHubUser string BillingAPIPollSeconds int + + GitHubRunnersRepos []string + RunnersAPIPollSeconds int } type Server struct { @@ -55,6 +58,9 @@ func NewServer(logger log.Logger, opts Opts) *Server { _ = level.Info(logger).Log("msg", fmt.Sprintf("not exporting user billing: %v", err)) } + runnerMetricsExporter := NewRunnerMetricsExporter(logger, opts) + runnerMetricsExporter.StartOrgRunnerMetricsCollection(context.TODO()) + muxIngress := http.NewServeMux() httpServerIngress := &http.Server{ Handler: muxIngress, diff --git a/main.go b/main.go index 486d10b..16cf08b 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,8 @@ var ( gitHubOrg = kingpin.Flag("gh.github-org", "GitHub Organization.").Envar("GITHUB_ORG").Default("").String() gitHubUser = kingpin.Flag("gh.github-user", "GitHub User.").Default("").String() gitHubBillingPollingSeconds = kingpin.Flag("gh.billing-poll-seconds", "Frequency at which to poll billing API.").Default("5").Int() + gitHubRunnersRepos = kingpin.Flag("gh.runners-repos", "The name of the repositories where runners are configures.").Default("").Strings() + gitHubRunnersPollingSeconds = kingpin.Flag("gh.runners-poll-seconds", "Frequency at which to poll runners API.").Default("5").Int() ) func init() { @@ -62,6 +64,8 @@ func main() { GitHubUser: *gitHubUser, GitHubOrg: *gitHubOrg, BillingAPIPollSeconds: *gitHubBillingPollingSeconds, + GitHubRunnersRepos: *gitHubRunnersRepos, + RunnersAPIPollSeconds: *gitHubRunnersPollingSeconds, }) go func() { err := srv.Serve(context.Background()) From 1a6b437e8edf7ad4ecdca56188b2d980d2381b54 Mon Sep 17 00:00:00 2001 From: Peter Halasz Date: Sat, 3 Feb 2024 15:53:26 +0100 Subject: [PATCH 2/2] Linter fixes Signed-off-by: Peter Halasz --- internal/server/github_client.go | 2 +- internal/server/runner_metrics.go | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/internal/server/github_client.go b/internal/server/github_client.go index 78728cd..3540ed7 100644 --- a/internal/server/github_client.go +++ b/internal/server/github_client.go @@ -7,7 +7,7 @@ import ( "golang.org/x/oauth2" ) -var client *github.Client = nil +var client *github.Client func getGithubClient(githubToken string) *github.Client { if client != nil { diff --git a/internal/server/runner_metrics.go b/internal/server/runner_metrics.go index fdc1896..43bf1a7 100644 --- a/internal/server/runner_metrics.go +++ b/internal/server/runner_metrics.go @@ -25,14 +25,14 @@ func NewRunnerMetricsExporter(logger log.Logger, opts Opts) *RunnerMetricsExport } } -func (e *RunnerMetricsExporter) StartOrgRunnerMetricsCollection(ctx context.Context) error { +func (e *RunnerMetricsExporter) StartOrgRunnerMetricsCollection(ctx context.Context) { if e.Opts.GitHubOrg == "" { - level.Info(e.Logger).Log("msg", "Github org is not set, no org runner metrics will be collected.") - return nil + _ = level.Info(e.Logger).Log("msg", "Github org is not set, no org runner metrics will be collected.") + return } if e.Opts.GitHubAPIToken == "" { - level.Info(e.Logger).Log("msg", "Github token is not set, no org runner metrics will be collected.") - return nil + _ = level.Info(e.Logger).Log("msg", "Github token is not set, no org runner metrics will be collected.") + return } ticker := time.NewTicker(time.Duration(e.Opts.RunnersAPIPollSeconds) * time.Second) @@ -43,20 +43,18 @@ func (e *RunnerMetricsExporter) StartOrgRunnerMetricsCollection(ctx context.Cont e.collectOrgRunnerMetrics(ctx) case <-ctx.Done(): ticker.Stop() - level.Info(e.Logger).Log("msg", "Stopped polling for org runner metrics.") + _ = level.Info(e.Logger).Log("msg", "Stopped polling for org runner metrics.") return } } }() - - return nil } func (e *RunnerMetricsExporter) collectOrgRunnerMetrics(ctx context.Context) { runners, _, err := e.GHClient.Actions.ListOrganizationRunners(ctx, e.Opts.GitHubOrg, nil) if err != nil { - e.Logger.Log("msg", "Failed to retrieve org runners for org ", e.Opts.GitHubOrg, " ", err) + _ = e.Logger.Log("msg", "Failed to retrieve org runners for org ", e.Opts.GitHubOrg, " ", err) } numberOfSelfHostedRunners.WithLabelValues(e.Opts.GitHubOrg).Set(float64(runners.TotalCount))