Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericcitaire/409e66a043ff14a80b3a94cfaa8ffa38 to your computer and use it in GitHub Desktop.
Save ericcitaire/409e66a043ff14a80b3a94cfaa8ffa38 to your computer and use it in GitHub Desktop.
Jenkins - Build status history for multiple jobs
class Dashing.Buildhistory extends Dashing.Widget
ready: ->
# This is fired when the widget is done being rendered
onData: (data) ->
# This is fired when the widget receives data
#extract widget paramaters
@max_displayed_samples = @get('max_samples')
@display_job_titles = @get('show_job_titles')
@display_sample_spacers = @get('sample_spacers')
container = $(@node).parent()
# code from graph.coffee
widget_width = (Dashing.widget_base_dimensions[0] * container.data("sizex")) + Dashing.widget_margins[0] * 2 * (container.data("sizex") - 1)
widget_height = (Dashing.widget_base_dimensions[1] * container.data("sizey"))
chart_inner_margin = 40
#calculate canvas size
canvas_width = widget_width - chart_inner_margin
canvas_height = widget_height - chart_inner_margin
#calculate sample bar width
sample_bar_width = 10
unless @max_displayed_samples is 0
#adjust if spacers between samples
if @display_sample_spacers
sample_bar_width = (canvas_width - @max_displayed_samples) / @max_displayed_samples
else
sample_bar_width = canvas_width / @max_displayed_samples
#calculate sample bar height
row_height = 30
@number_jenkins_jobs = data.jenkins_jobs.length
unless @number_jenkins_jobs is 0
#adjust if spacers between samples
if @display_sample_spacers
row_height = (canvas_height - @number_jenkins_jobs) / @number_jenkins_jobs
else
row_height = canvas_height / @number_jenkins_jobs
#hash Jenkins build status values ('ball color' => HTML colour values)
#reference: https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/BallColor.java
#use a green sample colour for success as opposed to native Jenkins 'blue'
#NOTE: disabled, aborted and not built states map to the same colour for simplicity
#all 'xxx_anime' colours represent currently building jobs.
#here, they all map to the same sample colour for simplicity
@jenkins_job_status_colour_map =
red: "#FE2E2E"
yellow: "#FACC2E"
blue: "#64FE2E"
grey: "#A4A4A4"
disabled: "#6E6E6E"
aborted: "#6E6E6E"
nobuilt: "#6E6E6E"
red_anime: "#D8D8D8"
blue_anime: "#D8D8D8"
grey_anime: "#D8D8D8"
disabled_anime: "#D8D8D8"
aborted_anime: "#D8D8D8"
nobuilt_anime: "#D8D8D8"
#remove child elements of previously rendered canvas object
if $("#buildhistorychart")
$("#buildhistorychart").empty()
#create new canvas object
@raphael_canvas = Raphael("buildhistorychart", canvas_width, canvas_height )
y_index = 0
#loop through job status data and render to widget
for job_entry of data.jenkins_jobs
extracted_job_entry = data.jenkins_jobs[job_entry]
#render status history for an entire job
@_render_job_status_row @raphael_canvas, 0, y_index, sample_bar_width, row_height, extracted_job_entry
#increment row
y_index = y_index + row_height
#add spacer if required
if @display_sample_spacers
y_index = y_index + 1
# This function renders the build status history for a single job
_render_job_status_row: (@raphael_canvas, row_x, row_y, sample_bar_width, height, job_object) ->
#draw status sample bars
num_samples = job_object.build_status.length
x_index = row_x
#only draw samples if there is data to be drawn...
unless num_samples is 0
for job_status_entry of job_object.build_status
job_status = job_object.build_status[job_status_entry]
#render rectangle for single sample
rect_attributes =
stroke: "none"
fill: @jenkins_job_status_colour_map[job_status.status]
@raphael_canvas.rect(x_index, row_y, sample_bar_width, height).attr rect_attributes
#draw job title if required
if @display_job_titles
@_render_job_title(@raphael_canvas, row_x, row_y, height, job_object.job_name)
#increment X index
x_index = x_index + sample_bar_width
#add spacer if required
if @display_sample_spacers
x_index = x_index + 1
#this function draws a job title
_render_job_title: (@raphael_canvas, row_x, row_y, height, title) ->
#job title text font
txt_attributes =
font: "8px Fontin-Sans, Arial"
stroke: "none"
fill: "#000"
"text-anchor": "start"
opacity: 0.03
#render job title text
@raphael_canvas.text(row_x, row_y + (height / 2), title).attr txt_attributes
<div id="buildhistorychart"></div>
require 'csv'
require 'net/http'
require 'json'
#constants
JOB_STATUS_HISTORY_FILENAME = 'job_status_history.csv'
NUMBER_SAMPLES_IN_HISTORY = 100
JENKINS_BUILD_STATUS_HISTORY_URI = URI.parse("http://<YOUR-JENKINS-URL>:8080")
#Trim thresholds (widget display)
MAX_FILENAME_LENGTH = 30
FILENAME_TAIL_LENGTH = 20
#array of all unit test jobs to be processed
$jenkins_jobs_to_be_tracked = [
'My main build',
'My auxiliary build',
'Sanity tests',
'Unit tests'
'Code coverage'
]
###################################################################################
# Sub routines
###################################################################################
#helper function that trims file names
#for long filenames, this function keeps all chars up to the
#trim length, inserts an ellipsis and then keeps the "tail" of the file name
def trim_filename(filename)
filename_length = filename.length
#trim 'n' splice if necessary
if filename_length > MAX_FILENAME_LENGTH
filename = filename.to_s[0..MAX_FILENAME_LENGTH] + '...' + filename.to_s[(filename_length - FILENAME_TAIL_LENGTH)..filename_length]
end
return filename
end
#this helper function loads the CSV file and creates a JSON message containing build status history info
def get_build_status_json_from_csv_file
build_status_history = Hash.new
jenkins_job_entries = Array.new
job_index = 0
CSV.foreach(JOB_STATUS_HISTORY_FILENAME) do |job_status_history_row|
#extract job name
job_name = job_status_history_row[0]
#create object for Jenkins job
job_history_entry = Hash.new
job_history_entry["job_name"] = trim_filename(job_name)
job_status_entries = Array.new
#iterate across status values for the job_status_history_row
for status_index in 1 ... job_status_history_row.size
job_status_item = job_status_history_row[status_index]
job_status_entries << {"status" => job_status_item}
end
job_history_entry.merge!("build_status" => job_status_entries)
jenkins_job_entries << job_history_entry
#increment job index
job_index = job_index + 1
end
build_status_history.merge!("jenkins_jobs" => jenkins_job_entries)
end
# This function appends a new set of job status into to the working file
# and trims any old data.
# latest_jenkins_job_status_map is a hash containing Jenkins job name & most recent Jenkins status value )
def update_job_status_history_csv_file(latest_jenkins_job_status_map)
job_status_history = Array.new
#check if file exists - if not, create a RAM structure
if (File.file?(JOB_STATUS_HISTORY_FILENAME) == false)
#build array
$jenkins_jobs_to_be_tracked.each do |job_name|
job_entry = Array.new
job_entry.push job_name
job_status_history.push job_entry
end
else
job_status_history = CSV.read(JOB_STATUS_HISTORY_FILENAME)
#clean out source file
File.delete(JOB_STATUS_HISTORY_FILENAME)
end
#loop through rows of job status info and process
job_index = 0
job_status_history.each do |job_status_history_row|
#extract job name
job_name = job_status_history_row[0]
#check if we need to trim the job status history
if (job_status_history_row.size > NUMBER_SAMPLES_IN_HISTORY)
#delete old history item
job_status_history_row.delete_at(1)
#append new status value to end of row
job_status_history_row << latest_jenkins_job_status_map[job_name]
else
#less than a full set of status history - simply append the new status info
job_status_history_row << latest_jenkins_job_status_map[job_name]
job_index = job_index + 1
end
#write out data to file
out_file = File.new(JOB_STATUS_HISTORY_FILENAME, "a")
element_index = 1
job_status_history_row.each do |element|
out_file << element
if (element_index == job_status_history_row.length)
out_file << "\n"
else
out_file << ","
end
element_index = element_index + 1
end
out_file.close
end
end
#this helper function searches through the jobs entries looking for a specific name
# extracting the build status if found
def search_for_job_in_full_job_list_json(json_summary, job_name, job_status_hash_table)
json_summary["jobs"].each do |job_entry|
if (job_entry["name"] == job_name)
#push new entry into hash table
job_status_entry = {job_entry["name"] => job_entry["color"]}
job_status_hash_table.merge!(job_status_entry)
else
#print "No match [#{job_entry}]\n"
end
end
end
SCHEDULER.every '60s', :first_in => 0 do
num_jobs = $jenkins_jobs_to_be_tracked.count()
if num_jobs == 0
raise 'Number of jobs is zero - you must monitor at least one job!'
end
if NUMBER_SAMPLES_IN_HISTORY == 0
raise 'Number of samples in history is zero - you must have at least one sample!'
end
#Fetch job status info from Jenkins (full job list)
http = Net::HTTP.new(JENKINS_BUILD_STATUS_HISTORY_URI.host, JENKINS_BUILD_STATUS_HISTORY_URI.port)
request = Net::HTTP::Get.new("/api/json?tree=jobs[name,color]")
response = http.request(request)
build_info = JSON.parse(response.body)
$most_recent_jenkins_status_map = Hash.new
#Search for specific jobs within overall job list
$jenkins_jobs_to_be_tracked.each do |jenkins_job_name|
search_for_job_in_full_job_list_json(build_info, jenkins_job_name, $most_recent_jenkins_status_map)
end
print "Updating job status history (file) ...\n"
update_job_status_history_csv_file($most_recent_jenkins_status_map)
print "loading job status history ...\n"
$build_status_history_json = get_build_status_json_from_csv_file
print "Sending event to build history widget ...\n"
send_event('buildhistory', $build_status_history_json)
end
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #454746;
// ----------------------------------------------------------------------------
// Widget-image styles
// ----------------------------------------------------------------------------
.widget-buildhistory {
background-color: $background-color;
}

Jenkins Build Status History - screenshot

Description

The Jenkins Build Status History widget periodically fetches a snapshot of build status information for a specified list of jobs on a Jenkins CI server.

As time progresses, new build status samples are added to the right, while older samples are removed from the left. This view allows you to quickly see the health of your jobs as well as any time trends.

Calls are made to the Jenkins API to retrieve the name and color properties of listed jobs in a JSON form. The color of a given job represents its current build status (i.e. "blue" => successful build, "red" => failing build and so on).

The build colours are rendered within the widget using the Raphaël JavaScript library for vector graphics.

This widget borrows some code from the Rickshaw widget.

Installing the widget

Raphaël

You need the Raphaël library to use the widget. Get the latest version here.

Place raphael.min.js in your assets/javascripts folder

Widget files

Place the following files in a folder called widgets/buildhistory:

  • buildhistory.coffee
  • buildhistory.html
  • buildhistory.scss

Build history retrieval job

Place the following file in your jobs/ folder:

  • buildhistory.rb

Adding the Jenkins Last Commit widget to your dashboard

To add this widget to your dashboard, add the following to your <Dashboard-filename>.erb file:

    <li data-row="1" data-col="1" data-sizex="1" data-sizey="1">
        <div data-id="buildhistory" data-view="Buildhistory" data-max_samples="100" data-show_job_titles="true" data-sample_spacers="true"></div>
    </li>

Notice that the widget has a few parameters:

Parameter Meaning
data-max_samples Number of build status samples to store before discarding old values
data-show_job_titles true if job titles must be rendered on widget, false if no titles to be rendered
data-sample_spacers If true then a single space is inserted between samples & rows.

Configuring the widget for use with your Jenkins instance

There are a few parameters that must be set up before using this widget with your Jenkins instance.

In the buildhistory.rb file, modify the following parameters according to your needs:

Parameter Meaning
JENKINS_BUILD_STATUS_HISTORY_URI Jenkins base URL
$jenkins_jobs_to_be_tracked This is an array containing job names to be tracked on Jenkins
@tulasirm
Copy link

tulasirm commented Jan 6, 2019

Hi is this still working, somehow i am getting only blank black screen with a white dot, everything i followed as described in this wiki, could you please help me what could be wrong?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment