Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GitHub monitoring mixin with dashboards #64

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ language: go
go:
- "1.14"

before_script:
# For mixin
- go install github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb
- go install github.com/monitoring-mixins/mixtool/cmd/mixtool
- go install github.com/google/go-jsonnet/cmd/jsonnetfmt

script:
- diff -u <(echo -n) <(gofmt -s -d ./)
- diff -u <(echo -n) <(go vet ./...)
- go test -v ./...
- make -C github-mixin install lint build

env:
- GO111MODULE=on
11 changes: 7 additions & 4 deletions exporter/metrics.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package exporter

import "github.com/prometheus/client_golang/prometheus"
import "strconv"
import (
"strconv"

"github.com/prometheus/client_golang/prometheus"
)

// AddMetrics - Add's all of the metrics to a map of strings, returns the map.
func AddMetrics() map[string]*prometheus.Desc {
Expand All @@ -21,7 +24,7 @@ func AddMetrics() map[string]*prometheus.Desc {
APIMetrics["PullRequestCount"] = prometheus.NewDesc(
prometheus.BuildFQName("github", "repo", "pull_request_count"),
"Total number of pull requests for given repository",
[]string{"repo"}, nil,
[]string{"repo", "user"}, nil,
)
APIMetrics["Watchers"] = prometheus.NewDesc(
prometheus.BuildFQName("github", "repo", "watchers"),
Expand Down Expand Up @@ -85,7 +88,7 @@ func (e *Exporter) processMetrics(data []*Datum, rates *RateLimits, ch chan<- pr
ch <- prometheus.MustNewConstMetric(e.APIMetrics["OpenIssues"], prometheus.GaugeValue, (x.OpenIssues - float64(prCount)), x.Name, x.Owner.Login, strconv.FormatBool(x.Private), strconv.FormatBool(x.Fork), strconv.FormatBool(x.Archived), x.License.Key, x.Language)

// prCount
ch <- prometheus.MustNewConstMetric(e.APIMetrics["PullRequestCount"], prometheus.GaugeValue, float64(prCount), x.Name)
ch <- prometheus.MustNewConstMetric(e.APIMetrics["PullRequestCount"], prometheus.GaugeValue, float64(prCount), x.Name, x.Owner.Login)
}

// Set Rate limit stats
Expand Down
5 changes: 5 additions & 0 deletions github-mixin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/alerts.yaml
/rules.yaml
dashboards_out
vendor
jsonnetfile.lock.json
26 changes: 26 additions & 0 deletions github-mixin/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
JSONNET_FMT := jsonnetfmt -n 2 --max-blank-lines 2 --string-style s --comment-style s

default: build

all: install fmt lint build

fmt:
find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \
xargs -n 1 -- $(JSONNET_FMT) -i

lint:
find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \
while read f; do \
$(JSONNET_FMT) "$$f" | diff -u "$$f" -; \
done

mixtool lint mixin.libsonnet

install:
jb install

build:
mixtool generate dashboards mixin.libsonnet -d dashboards_out

clean:
rm -rf dashboards_out
27 changes: 27 additions & 0 deletions github-mixin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# GitHub Mixin

## Overview
Mixins are a collection of configurable, reusable Prometheus rules, alerts and/or Grafana dashboards for a particular system, usually created by experts in that system. By applying them to Prometheus and Grafana, you can quickly set up appropriate monitoring for your systems.

The GitHub mixin currently provides simple dashboards for visualizing GitHub metrics emitted by the exporter.

To use them, you need to have `jb`, `mixtool` and `jsonnetfmt` installed. If you have a working Go development environment, it's easiest to run the following:
```bash
$ go get github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb
$ go get github.com/monitoring-mixins/mixtool/cmd/mixtool
$ go get github.com/google/go-jsonnet/cmd/jsonnetfmt
```

You can then build a directory `dashboard_out` with the JSON dashboard files for Grafana:
```bash
$ make all
```

For more advanced uses of mixins, see https://github.com/monitoring-mixins/docs.

## Dashboards
* GitHub Repository Stats - Graphs GitHub metrics for a given repository. Any repository monitored by the exporter can be selected on this dashboard.
* GitHub API Usage - GitHub enforces rate limiting on the API used by the exporter. This dashboard can be used to monitor if the exporter is running out of requests.

## Future Development
The mixin can be extended with recording and alerting rules for Prometheus.
41 changes: 41 additions & 0 deletions github-mixin/dashboards/api-usage.libsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
local common = import 'common.libsonnet';
local grafana = import 'github.com/grafana/grafonnet-lib/grafonnet/grafana.libsonnet';

grafana.dashboard.new('GitHub API Usage', uid='github-api-usage', editable=true)
.addTemplate(
grafana.template.datasource(
'datasource',
'prometheus',
'Prometheus'
)
)
.addPanels(
[
grafana.text.new('GitHub Request Limits', content=|||
GitHub metrics are generated by calling the GitHub API, which will throttle if too many requests are made in an
hour. When this happens, gaps will appear for the metrics.

This dashboard monitors the API usage, so you can tell if you are running out of requests. If this does
become a problem, consider reducing the set of repositories being monitored or the number of metrics being
generated, in order to reduce the request rate.
|||) +
{ gridPos: { x: 0, y: 0, w: 24, h: 4 } },

grafana.graphPanel.new(
'API Usage',
min=0,
)
.addTarget(grafana.prometheus.target('github_rate_remaining', legendFormat='Remaining Requests'))
.addTarget(grafana.prometheus.target('github_rate_limit', legendFormat='Max Requests')) +
{ gridPos: { x: 8, y: 4, w: 16, h: 10 } },

common.latestSingleStatPanel('Current Remaining Requests in Time Window')
.addTarget(grafana.prometheus.target('github_rate_remaining')) +
{ gridPos: { x: 0, y: 4, w: 8, h: 5 } },

common.latestSingleStatPanel('Current Max Requests Per Hour')
.addTarget(grafana.prometheus.target('github_rate_limit')) +
{ gridPos: { x: 0, y: 9, w: 8, h: 5 } },

]
)
17 changes: 17 additions & 0 deletions github-mixin/dashboards/common.libsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
local grafana = import 'github.com/grafana/grafonnet-lib/grafonnet/grafana.libsonnet';

{
latestSingleStatPanel(title, format='none')::
grafana.statPanel.new(title, reducerFunction='last', graphMode='none') +
{
fieldConfig: {
defaults: {
thresholds: {
mode: 'absolute',
steps: [],
},
unit: format,
},
},
},
}
96 changes: 96 additions & 0 deletions github-mixin/dashboards/repository-stats.libsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
local common = import 'common.libsonnet';
local grafana = import 'github.com/grafana/grafonnet-lib/grafonnet/grafana.libsonnet';

local dashboardWidth = 24;

local metric(metric_name, title, format='none') = {
name: metric_name,
title: title,
format: format,
};

local latestRepoStatPanel(metric) =
common.latestSingleStatPanel(metric.title, metric.format)
.addTarget(grafana.prometheus.target(metric.name + '{user=~"$user",repo=~"$repo"}'));

local graphPanel(metric) =
grafana.graphPanel.new(
metric.title,
min=0,
legend_show=false,
format=metric.format
)
.addTarget(grafana.prometheus.target(metric.name + '{user=~"$user",repo=~"$repo"}'));

// Calculates positions of an array of panels which have the same dimensions and
// should be displayed together.
// Assumes the area above startY has been "filled in" - Grafana moves panels up
// automatically if there is empty space.
local setGridPos(panels, startY, panelWidth, panelHeight) =
if panelWidth > dashboardWidth then
error 'panelWidth cannot be larger than dashboardWidth'
else
local panelsPerRow = std.floor(dashboardWidth / panelWidth);
local calculate(index) = {
gridPos: {
x: (index % panelsPerRow) * panelWidth,
y: startY + (std.floor(index / panelsPerRow) * panelHeight),
w: panelWidth,
h: panelHeight,
},
};

std.mapWithIndex(function(index, panel) panel + calculate(index), panels);

local maxY(panels) = std.foldl(std.max, [p.gridPos.y + p.gridPos.h for p in panels], 0);

local repoPanels(metrics) =
local statPanels = std.map(latestRepoStatPanel, metrics);
local statPanelsWithGridPos = setGridPos(statPanels, 0, 4, 4);

local statPanelsMaxY = maxY(statPanelsWithGridPos);

local graphRowPanel = { title: 'Graphs', type: 'row' };
local graphRowPanelWithGridPos = setGridPos([graphRowPanel], statPanelsMaxY, dashboardWidth, 1);

local graphPanels = std.map(graphPanel, metrics);
local graphPanelsWithGridPos = setGridPos(graphPanels, statPanelsMaxY + 1, 8, 8);

std.flattenArrays([statPanelsWithGridPos, graphRowPanelWithGridPos, graphPanelsWithGridPos]);

grafana.dashboard.new('GitHub Repository Stats', uid='github-repo-stats', editable=true)
.addTemplate(
grafana.template.datasource(
'datasource',
'prometheus',
'Prometheus'
)
)
.addTemplate(
grafana.template.new(
'user',
'$datasource',
'label_values(user)',
refresh='load'
)
)
.addTemplate(
grafana.template.new(
'repo',
'$datasource',
'label_values(github_repo_open_issues{user="$user"}, repo)',
refresh='load'
)
)
.addPanels(
repoPanels(
[
metric('github_repo_open_issues', 'Open Issues'),
metric('github_repo_pull_request_count', 'Open Pull Requests'),
metric('github_repo_forks', 'Forks'),
metric('github_repo_stars', 'Stars'),
metric('github_repo_watchers', 'Watchers'),
metric('github_repo_size_kb', 'Repository Size', format='deckbytes'),
]
)
)
15 changes: 15 additions & 0 deletions github-mixin/jsonnetfile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"version": 1,
"dependencies": [
{
"source": {
"git": {
"remote": "https://github.com/grafana/grafonnet-lib.git",
"subdir": "grafonnet"
}
},
"version": "master"
}
],
"legacyImports": true
}
6 changes: 6 additions & 0 deletions github-mixin/mixin.libsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
grafanaDashboards: {
'api-usage.json': (import 'dashboards/api-usage.libsonnet'),
'repository-stats.json': (import 'dashboards/repository-stats.libsonnet'),
},
}
13 changes: 7 additions & 6 deletions test/github_exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ package test

import (
"fmt"
"github.com/infinityworks/github-exporter/config"
"github.com/infinityworks/github-exporter/exporter"
web "github.com/infinityworks/github-exporter/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/steinfletcher/apitest"
"io/ioutil"
"net/http"
"os"
"strings"
"testing"

"github.com/infinityworks/github-exporter/config"
"github.com/infinityworks/github-exporter/exporter"
web "github.com/infinityworks/github-exporter/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/steinfletcher/apitest"
)

func TestHomepage(t *testing.T) {
Expand Down Expand Up @@ -41,7 +42,7 @@ func TestGithubExporter(t *testing.T) {
Assert(bodyContains(`github_rate_remaining 60`)).
Assert(bodyContains(`github_rate_reset 1.566853865e+09`)).
Assert(bodyContains(`github_repo_forks{archived="false",fork="false",language="Go",license="mit",private="false",repo="myRepo",user="myOrg"} 10`)).
Assert(bodyContains(`github_repo_pull_request_count{repo="myRepo"} 3`)).
Assert(bodyContains(`github_repo_pull_request_count{repo="myRepo",user="myOrg"} 3`)).
Assert(bodyContains(`github_repo_open_issues{archived="false",fork="false",language="Go",license="mit",private="false",repo="myRepo",user="myOrg"} 2`)).
Assert(bodyContains(`github_repo_size_kb{archived="false",fork="false",language="Go",license="mit",private="false",repo="myRepo",user="myOrg"} 946`)).
Assert(bodyContains(`github_repo_stars{archived="false",fork="false",language="Go",license="mit",private="false",repo="myRepo",user="myOrg"} 120`)).
Expand Down