Skip to content
This repository was archived by the owner on May 2, 2022. It is now read-only.

Commit 4f5c950

Browse files
authored
Merge pull request #7 from jtilander/fix_for_folders
Fixed exporter for jobs that are nested in subfolders in jenkins.
2 parents 4c5151c + 7bdd313 commit 4f5c950

3 files changed

Lines changed: 69 additions & 25 deletions

File tree

Dockerfile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ RUN mkdir -p /usr/src/app
44
WORKDIR /usr/src/app
55

66
COPY requirements.txt /usr/src/app
7-
COPY jenkins_exporter.py /usr/src/app
87
RUN pip install --no-cache-dir -r requirements.txt
98

9+
COPY jenkins_exporter.py /usr/src/app
10+
1011
EXPOSE 9118
12+
ENV JENKINS_SERVER=http://jenkins:8080 VIRTUAL_PORT=9118 DEBUG=0
1113

12-
ENTRYPOINT [ "python", "./jenkins_exporter.py" ]
13-
CMD ["-j", "http://jenkins:8080", "-p", "9118"]
14+
ENTRYPOINT [ "python", "-u", "./jenkins_exporter.py" ]
15+
CMD []

Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
IMAGENAME?=lovoo/jenkins_exporter
2+
TAG?=latest
3+
JENKINS_SERVER?=https://myjenkins
4+
5+
debug: image
6+
docker run --rm -p 9118:9118 -e DEBUG=1 -e JENKINS_SERVER=$(JENKINS_SERVER) -e VIRTUAL_PORT=9118 $(IMAGENAME):$(TAG)
7+
8+
image:
9+
docker build -t $(IMAGENAME):$(TAG) .
10+
11+
push: image
12+
docker push $(IMAGENAME):$(TAG)
13+
14+
15+
.PHONY: image push debug

jenkins_exporter.py

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
import time
55
import requests
66
import argparse
7+
from pprint import pprint
78

9+
import os
810
from sys import exit
911
from prometheus_client import start_http_server
1012
from prometheus_client.core import GaugeMetricFamily, REGISTRY
1113

14+
DEBUG = int(os.environ.get('DEBUG', '0'))
15+
1216

1317
class JenkinsCollector(object):
1418
# The build statuses we want to export about.
@@ -21,12 +25,15 @@ def __init__(self, target):
2125

2226
def collect(self):
2327
# Request data from Jenkins
24-
jenkins_data = self._request_data()
28+
jobs = self._request_data()
2529

2630
self._setup_empty_prometheus_metrics()
2731

28-
for job in jenkins_data['jobs']:
32+
for job in jobs:
2933
name = job['name']
34+
if DEBUG:
35+
print "Found Job: %s" % name
36+
pprint(job)
3037
self._get_metrics(name, job)
3138

3239
for status in self.statuses:
@@ -38,16 +45,29 @@ def _request_data(self):
3845
url = '{0}/api/json'.format(self._target)
3946
jobs = "[number,timestamp,duration,actions[queuingDurationMillis,totalDurationMillis," \
4047
"skipCount,failCount,totalCount,passCount]]"
41-
tree = 'jobs[name,{0}]'.format(','.join([s + jobs for s in self.statuses]))
48+
tree = 'jobs[name,url,{0}]'.format(','.join([s + jobs for s in self.statuses]))
4249
params = {
4350
'tree': tree,
4451
}
45-
# params = tree: jobs[name,lastBuild[number,timestamp,duration,actions[queuingDurationMillis...
46-
response = requests.get(url, params=params)
47-
if response.status_code != requests.codes.ok:
48-
raise Exception('Response Status ({0}): {1}'.format(response.status_code, response.text))
49-
result = response.json()
50-
return result
52+
53+
def parsejobs(myurl):
54+
# params = tree: jobs[name,lastBuild[number,timestamp,duration,actions[queuingDurationMillis...
55+
response = requests.get(myurl, params=params)
56+
if response.status_code != requests.codes.ok:
57+
return[]
58+
result = response.json()
59+
if DEBUG:
60+
pprint(result)
61+
62+
jobs = []
63+
for job in result['jobs']:
64+
if job['_class'] == 'com.cloudbees.hudson.plugins.folder.Folder':
65+
jobs += parsejobs(job['url'] + '/api/json')
66+
else:
67+
jobs.append(job)
68+
return jobs
69+
70+
return parsejobs(url)
5171

5272
def _setup_empty_prometheus_metrics(self):
5373
# The metrics we want to export.
@@ -93,28 +113,29 @@ def _get_metrics(self, name, job):
93113

94114
def _add_data_to_prometheus_structure(self, status, status_data, job, name):
95115
# If there's a null result, we want to pass.
96-
if status_data.get('duration'):
116+
if status_data.get('duration', 0):
97117
self._prometheus_metrics[status]['duration'].add_metric([name], status_data.get('duration') / 1000.0)
98-
if status_data.get('timestamp'):
118+
if status_data.get('timestamp', 0):
99119
self._prometheus_metrics[status]['timestamp'].add_metric([name], status_data.get('timestamp') / 1000.0)
100-
if status_data.get('number'):
120+
if status_data.get('number', 0):
101121
self._prometheus_metrics[status]['number'].add_metric([name], status_data.get('number'))
102122
actions_metrics = status_data.get('actions', [{}])
103123
for metric in actions_metrics:
104-
if metric.has_key('queuingDurationMillis') and metric.get('queuingDurationMillis'):
124+
if metric.get('queuingDurationMillis', False):
105125
self._prometheus_metrics[status]['queuingDurationMillis'].add_metric([name], metric.get('queuingDurationMillis') / 1000.0)
106-
if metric.has_key('totalDurationMillis') and metric.get('totalDurationMillis'):
126+
if metric.get('totalDurationMillis', False):
107127
self._prometheus_metrics[status]['totalDurationMillis'].add_metric([name], metric.get('totalDurationMillis') / 1000.0)
108-
if metric.has_key('skipCount') and metric.get('skipCount'):
128+
if metric.get('skipCount', False):
109129
self._prometheus_metrics[status]['skipCount'].add_metric([name], metric.get('skipCount'))
110-
if metric.has_key('failCount') and metric.get('failCount'):
130+
if metric.get('failCount', False):
111131
self._prometheus_metrics[status]['failCount'].add_metric([name], metric.get('failCount'))
112-
if metric.has_key('totalCount') and metric.get('totalCount'):
132+
if metric.get('totalCount', False):
113133
self._prometheus_metrics[status]['totalCount'].add_metric([name], metric.get('totalCount'))
114134
# Calculate passCount by subtracting fails and skips from totalCount
115135
passcount = metric.get('totalCount') - metric.get('failCount') - metric.get('skipCount')
116136
self._prometheus_metrics[status]['passCount'].add_metric([name], passcount)
117137

138+
118139
def parse_args():
119140
parser = argparse.ArgumentParser(
120141
description='jenkins exporter args jenkins address and port'
@@ -124,26 +145,32 @@ def parse_args():
124145
metavar='jenkins',
125146
required=False,
126147
help='server url from the jenkins api',
127-
default='http://jenkins:8080'
148+
default=os.environ.get('JENKINS_SERVER', 'http://jenkins:8080')
128149
)
129150
parser.add_argument(
130151
'-p', '--port',
131152
metavar='port',
132153
required=False,
133154
type=int,
134155
help='Listen to this port',
135-
default=9118
156+
default=int(os.environ.get('VIRTUAL_PORT', '9118'))
136157
)
137158
return parser.parse_args()
138159

139-
if __name__ == "__main__":
160+
161+
def main():
140162
try:
141163
args = parse_args()
142164
port = int(args.port)
143165
REGISTRY.register(JenkinsCollector(args.jenkins))
144166
start_http_server(port)
145-
print "Serving at port: ", port
146-
while True: time.sleep(1)
167+
print "Polling %s. Serving at port: %s" % (args.jenkins, port)
168+
while True:
169+
time.sleep(1)
147170
except KeyboardInterrupt:
148171
print(" Interrupted")
149172
exit(0)
173+
174+
175+
if __name__ == "__main__":
176+
main()

0 commit comments

Comments
 (0)