diff --git a/README.md b/README.md index 493ef60..bee05f3 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ or GitHub App auth with env vars `GITHUB_APP_ID` (id), `GITHUB_APP_INSTALLATION_ | Metric | Description | |------------------------------------------------|-----------------------------------------------| +| `github_repository_info` | Repository info metric | +| `github_workflow_info` | Workflow info metric | | `github_workflow_latest_run` | Latest workflow run with conclusion as label | | `github_workflow_latest_run_timestamp_seconds` | Latest workflow run with timestamp as value | | `github_workflow_consecutive_failed_runs` | Count of consecutive failed runs per workflow | diff --git a/metrics_github_workflows.go b/metrics_github_workflows.go index 0b5f88e..d72346a 100644 --- a/metrics_github_workflows.go +++ b/metrics_github_workflows.go @@ -7,6 +7,7 @@ import ( "github.com/google/go-github/v61/github" "github.com/prometheus/client_golang/prometheus" "github.com/webdevops/go-common/prometheus/collector" + "github.com/webdevops/go-common/utils/to" ) type ( @@ -14,6 +15,8 @@ type ( collector.Processor prometheus struct { + repository *prometheus.GaugeVec + workflow *prometheus.GaugeVec workflowLatestRun *prometheus.GaugeVec workflowLatestRunTimestamp *prometheus.GaugeVec workflowConsecutiveFailures *prometheus.GaugeVec @@ -24,6 +27,34 @@ type ( func (m *MetricsCollectorGithubWorkflows) Setup(collector *collector.Collector) { m.Processor.Setup(collector) + m.prometheus.repository = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "github_repository_info", + Help: "GitHub repository info", + }, + []string{ + "org", + "repo", + "defaultBranch", + }, + ) + m.Collector.RegisterMetricList("repository", m.prometheus.repository, true) + + m.prometheus.workflow = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "github_workflow_info", + Help: "GitHub workflow info", + }, + []string{ + "org", + "repo", + "workflow", + "state", + "path", + }, + ) + m.Collector.RegisterMetricList("workflow", m.prometheus.workflow, true) + m.prometheus.workflowLatestRun = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "github_workflow_latest_run", @@ -32,7 +63,7 @@ func (m *MetricsCollectorGithubWorkflows) Setup(collector *collector.Collector) []string{ "org", "repo", - "workflowName", + "workflow", "event", "branch", "conclusion", @@ -48,7 +79,7 @@ func (m *MetricsCollectorGithubWorkflows) Setup(collector *collector.Collector) []string{ "org", "repo", - "workflowName", + "workflow", "event", "branch", "conclusion", @@ -64,7 +95,7 @@ func (m *MetricsCollectorGithubWorkflows) Setup(collector *collector.Collector) []string{ "org", "repo", - "workflowName", + "workflow", "branch", }, ) @@ -76,12 +107,12 @@ func (m *MetricsCollectorGithubWorkflows) Reset() {} func (m *MetricsCollectorGithubWorkflows) getRepoList(org string) ([]*github.Repository, error) { var repositories []*github.Repository - repoOpts := &github.RepositoryListByOrgOptions{ + opts := github.RepositoryListByOrgOptions{ ListOptions: github.ListOptions{PerPage: 100, Page: 1}, } for { - result, response, err := githubClient.Repositories.ListByOrg(m.Context(), org, repoOpts) + result, response, err := githubClient.Repositories.ListByOrg(m.Context(), org, &opts) var ghRateLimitError *github.RateLimitError if ok := errors.As(err, &ghRateLimitError); ok { m.Logger().Debugf("ListByOrg ratelimited. Pausing until %s", ghRateLimitError.Rate.Reset.Time.String()) @@ -97,16 +128,44 @@ func (m *MetricsCollectorGithubWorkflows) getRepoList(org string) ([]*github.Rep if response.NextPage == 0 { break } - repoOpts.Page = response.NextPage + opts.Page = response.NextPage } return repositories, nil } -func (m *MetricsCollectorGithubWorkflows) getRepoWorkflows(repo *github.Repository) ([]*github.WorkflowRun, error) { +func (m *MetricsCollectorGithubWorkflows) getRepoWorkflows(org, repo string) ([]*github.Workflow, error) { + var workflows []*github.Workflow + + opts := github.ListOptions{PerPage: 100, Page: 1} + + for { + result, response, err := githubClient.Actions.ListWorkflows(m.Context(), org, repo, &opts) + var ghRateLimitError *github.RateLimitError + if ok := errors.As(err, &ghRateLimitError); ok { + m.Logger().Debugf("ListWorkflows ratelimited. Pausing until %s", ghRateLimitError.Rate.Reset.Time.String()) + time.Sleep(time.Until(ghRateLimitError.Rate.Reset.Time)) + continue + } else if err != nil { + return workflows, err + } + + workflows = append(workflows, result.Workflows...) + + // calc next page + if response.NextPage == 0 { + break + } + opts.Page = response.NextPage + } + + return workflows, nil +} + +func (m *MetricsCollectorGithubWorkflows) getRepoWorkflowRuns(repo *github.Repository) ([]*github.WorkflowRun, error) { var workflowRuns []*github.WorkflowRun - workflowRunOpts := &github.ListWorkflowRunsOptions{ + opts := github.ListWorkflowRunsOptions{ Branch: repo.GetDefaultBranch(), ExcludePullRequests: true, ListOptions: github.ListOptions{PerPage: 100, Page: 1}, @@ -114,9 +173,9 @@ func (m *MetricsCollectorGithubWorkflows) getRepoWorkflows(repo *github.Reposito } for { - m.Logger().Debugf(`fetching list of workflow runs for repo "%s" with page "%d"`, repo.GetName(), workflowRunOpts.Page) + m.Logger().Debugf(`fetching list of workflow runs for repo "%s" with page "%d"`, repo.GetName(), opts.Page) - result, response, err := githubClient.Actions.ListRepositoryWorkflowRuns(m.Context(), Opts.GitHub.Organization, *repo.Name, workflowRunOpts) + result, response, err := githubClient.Actions.ListRepositoryWorkflowRuns(m.Context(), Opts.GitHub.Organization, *repo.Name, &opts) var ghRateLimitError *github.RateLimitError if ok := errors.As(err, &ghRateLimitError); ok { m.Logger().Debugf("ListRepositoryWorkflowRuns ratelimited. Pausing until %s", ghRateLimitError.Rate.Reset.Time.String()) @@ -132,14 +191,19 @@ func (m *MetricsCollectorGithubWorkflows) getRepoWorkflows(repo *github.Reposito if response.NextPage == 0 { break } - workflowRunOpts.Page = response.NextPage + opts.Page = response.NextPage } return workflowRuns, nil } func (m *MetricsCollectorGithubWorkflows) Collect(callback chan<- func()) { - repositories, err := m.getRepoList(Opts.GitHub.Organization) + repositoryMetric := m.Collector.GetMetricList("repository") + workflowMetric := m.Collector.GetMetricList("workflow") + + org := Opts.GitHub.Organization + + repositories, err := m.getRepoList(org) if err != nil { panic(err) } @@ -147,19 +211,42 @@ func (m *MetricsCollectorGithubWorkflows) Collect(callback chan<- func()) { for _, repo := range repositories { var workflowRuns []*github.WorkflowRun + repositoryMetric.AddInfo(prometheus.Labels{ + "org": org, + "repo": repo.GetName(), + "defaultBranch": to.String(repo.DefaultBranch), + }) + if repo.GetDefaultBranch() == "" { // repo doesn't have default branch continue } - workflowRuns, err := m.getRepoWorkflows(repo) + workflows, err := m.getRepoWorkflows(org, repo.GetName()) if err != nil { panic(err) } + for _, workflow := range workflows { + workflowMetric.AddInfo(prometheus.Labels{ + "org": org, + "repo": repo.GetName(), + "workflow": workflow.GetName(), + "state": workflow.GetState(), + "path": workflow.GetPath(), + }) + } + if len(workflowRuns) >= 1 { - m.collectLatestRun(Opts.GitHub.Organization, repo, workflowRuns, callback) - m.collectConsecutiveFailures(Opts.GitHub.Organization, repo, workflowRuns, callback) + workflowRuns, err := m.getRepoWorkflowRuns(repo) + if err != nil { + panic(err) + } + + if len(workflowRuns) >= 1 { + m.collectLatestRun(Opts.GitHub.Organization, repo, workflowRuns, callback) + m.collectConsecutiveFailures(Opts.GitHub.Organization, repo, workflowRuns, callback) + } } } } @@ -201,12 +288,12 @@ func (m *MetricsCollectorGithubWorkflows) collectLatestRun(org string, repo *git for _, workflowRun := range latestJobs { labels := prometheus.Labels{ - "org": org, - "repo": repo.GetName(), - "workflowName": workflowRun.GetName(), - "event": workflowRun.GetEvent(), - "branch": workflowRun.GetHeadBranch(), - "conclusion": workflowRun.GetConclusion(), + "org": org, + "repo": repo.GetName(), + "workflow": workflowRun.GetName(), + "event": workflowRun.GetEvent(), + "branch": workflowRun.GetHeadBranch(), + "conclusion": workflowRun.GetConclusion(), } runMetric.AddInfo(labels) @@ -258,10 +345,10 @@ func (m *MetricsCollectorGithubWorkflows) collectConsecutiveFailures(org string, } labels := prometheus.Labels{ - "org": org, - "repo": repo.GetName(), - "workflowName": workflowRun.GetName(), - "branch": workflowRun.GetHeadBranch(), + "org": org, + "repo": repo.GetName(), + "workflow": workflowRun.GetName(), + "branch": workflowRun.GetHeadBranch(), } consecutiveFailuresMetric.Add(labels, float64(consecutiveFailMap[workflowId])) }