Skip to content

Commit

Permalink
add repo and workflow info metrics
Browse files Browse the repository at this point in the history
Signed-off-by: Markus Blaschke <[email protected]>
  • Loading branch information
mblaschke committed May 13, 2024
1 parent 3f06132 commit dbfb412
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 25 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
137 changes: 112 additions & 25 deletions metrics_github_workflows.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ 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 (
MetricsCollectorGithubWorkflows struct {
collector.Processor

prometheus struct {
repository *prometheus.GaugeVec
workflow *prometheus.GaugeVec
workflowLatestRun *prometheus.GaugeVec
workflowLatestRunTimestamp *prometheus.GaugeVec
workflowConsecutiveFailures *prometheus.GaugeVec
Expand All @@ -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",
Expand All @@ -32,7 +63,7 @@ func (m *MetricsCollectorGithubWorkflows) Setup(collector *collector.Collector)
[]string{
"org",
"repo",
"workflowName",
"workflow",
"event",
"branch",
"conclusion",
Expand All @@ -48,7 +79,7 @@ func (m *MetricsCollectorGithubWorkflows) Setup(collector *collector.Collector)
[]string{
"org",
"repo",
"workflowName",
"workflow",
"event",
"branch",
"conclusion",
Expand All @@ -64,7 +95,7 @@ func (m *MetricsCollectorGithubWorkflows) Setup(collector *collector.Collector)
[]string{
"org",
"repo",
"workflowName",
"workflow",
"branch",
},
)
Expand All @@ -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())
Expand All @@ -97,26 +128,54 @@ 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},
Created: ">=" + time.Now().Add(-Opts.GitHub.Workflows.Timeframe).Format(time.RFC3339),
}

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())
Expand All @@ -132,34 +191,62 @@ 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)
}

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)
}
}
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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]))
}
Expand Down

0 comments on commit dbfb412

Please sign in to comment.