44import time
55import requests
66import argparse
7+ from pprint import pprint
78
9+ import os
810from sys import exit
911from prometheus_client import start_http_server
1012from prometheus_client .core import GaugeMetricFamily , REGISTRY
1113
14+ DEBUG = int (os .environ .get ('DEBUG' , '0' ))
15+
1216
1317class 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+
118139def 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