mirror of
https://github.com/ohwgiles/laminar.git
synced 2026-03-02 03:40:21 +00:00
resolves #45: new graphs
- regressions and recoveries: list of jobs whose run status changed, ordered first by currently failing jobs, secondly by count of jobs since the status change, descending for currently failing jobs and ascending for currently passing jobs - low pass rates: list of the jobs with the worst pass rates calculated over all time - run time changes: jobs with the largest changes in build time. This is calculated as the difference between the range and the standard deviation over the past 10 runs. - average run time distribution: shows the number of jobs in the system divided into buckets based on their average runtime
This commit is contained in:
@@ -47,6 +47,7 @@
|
||||
right: 10px;
|
||||
padding: 20px;
|
||||
}
|
||||
/* status icons */
|
||||
span.status {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
@@ -63,6 +64,14 @@
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
/* chart overlay */
|
||||
li.chart-overlay {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
background: white;
|
||||
border: 1px solid lightgray;
|
||||
padding: 3px;
|
||||
}
|
||||
/* sort indicators */
|
||||
a.sort {
|
||||
position: relative;
|
||||
@@ -132,7 +141,7 @@
|
||||
<canvas id="chartBpj"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div></div><div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Longest average run time per job this week</div>
|
||||
@@ -148,6 +157,42 @@
|
||||
<canvas id="chartUtil"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div></div><div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Regressions and recoveries</div>
|
||||
<div class="panel-body"><div style="position:relative">
|
||||
<canvas id="chartResultChanges"></canvas>
|
||||
<ul v-for="job in resultChanged">
|
||||
<li v-if="job.lastFailure>job.lastSuccess" :id="'rcd_'+job.name" class="chart-overlay"><router-link :to="'/jobs/'+job.name">{{job.name}}</router-link>: <span v-html="runIcon('failed')"></span> <router-link :to="'/jobs/'+job.name+'/'+job.lastFailure">#{{job.lastFailure}}</router-link> since <span v-html="runIcon('success')"></span> <router-link :to="'/jobs/'+job.name+'/'+job.lastSuccess">#{{job.lastSuccess}}</router-link></li>
|
||||
<li v-if="job.lastFailure<job.lastSuccess" :id="'rcd_'+job.name" class="chart-overlay"><router-link :to="'/jobs/'+job.name">{{job.name}}</router-link>: <span v-html="runIcon('success')"></span> <router-link :to="'/jobs/'+job.name+'/'+job.lastSuccess">#{{job.lastSuccess}}</router-link> since <span v-html="runIcon('failed')"></span> <router-link :to="'/jobs/'+job.name+'/'+job.lastFailure">#{{job.lastFailure}}</router-link></li>
|
||||
</ul>
|
||||
</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Low pass rates</div>
|
||||
<div class="panel-body">
|
||||
<canvas id="chartPassRates"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div></div><div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Run time changes</div>
|
||||
<div class="panel-body">
|
||||
<canvas id="chartBuildTimeChanges"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Average run time distribution</div>
|
||||
<div class="panel-body">
|
||||
<canvas id="chartBuildTimeDist"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div></div>
|
||||
</div></div>
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
* https://laminar.ohwg.net
|
||||
*/
|
||||
|
||||
String.prototype.hashCode = function() {
|
||||
for(var r=0, i=0; i<this.length; i++)
|
||||
r=(r<<5)-r+this.charCodeAt(i),r&=r;
|
||||
return r;
|
||||
};
|
||||
|
||||
Vue.filter('iecFileSize', function(bytes) {
|
||||
var exp = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (bytes / Math.pow(1024, exp)).toFixed(1) + ' ' +
|
||||
@@ -171,7 +177,8 @@ const ProgressUpdater = {
|
||||
const Home = function() {
|
||||
var state = {
|
||||
jobsQueued: [],
|
||||
jobsRecent: []
|
||||
jobsRecent: [],
|
||||
resultChanged: []
|
||||
};
|
||||
|
||||
var chtUtilization, chtBuildsPerDay, chtBuildsPerJob, chtTimePerJob;
|
||||
@@ -193,6 +200,7 @@ const Home = function() {
|
||||
state.jobsQueued = msg.queued;
|
||||
state.jobsRunning = msg.running;
|
||||
state.jobsRecent = msg.recent;
|
||||
state.resultChanged = msg.resultChanged;
|
||||
this.$forceUpdate();
|
||||
|
||||
// setup charts
|
||||
@@ -202,7 +210,7 @@ const Home = function() {
|
||||
labels: ["Busy", "Idle"],
|
||||
datasets: [{
|
||||
data: [ msg.executorsBusy, msg.executorsTotal - msg.executorsBusy ],
|
||||
backgroundColor: ["tan", "darkseagreen"]
|
||||
backgroundColor: ["darkgoldenrod", "forestgreen"]
|
||||
}]
|
||||
}
|
||||
});
|
||||
@@ -220,13 +228,13 @@ const Home = function() {
|
||||
})(),
|
||||
datasets: [{
|
||||
label: 'Successful Builds',
|
||||
backgroundColor: "rgba(143,188,143,0.65)", //darkseagreen at 0.65
|
||||
backgroundColor: "rgba(34,139,34,0.65)", //forestgreen at 0.65
|
||||
borderColor: "forestgreen",
|
||||
data: msg.buildsPerDay.map((e)=>{ return e.success || 0; })
|
||||
}, {
|
||||
label: 'Failed Builds',
|
||||
backgroundColor: "rgba(233,150,122,0.65)", //darkseagreen at 0.65
|
||||
borderColor: "crimson",
|
||||
backgroundColor: "rgba(178,34,34,0.65)", //firebrick at 0.65
|
||||
borderColor: "firebrick",
|
||||
data: msg.buildsPerDay.map((e)=>{ return e.failed || 0; })
|
||||
}]
|
||||
}
|
||||
@@ -237,7 +245,7 @@ const Home = function() {
|
||||
labels: Object.keys(msg.buildsPerJob),
|
||||
datasets: [{
|
||||
label: 'Most runs per job in last 24hrs',
|
||||
backgroundColor: "lightsteelblue",
|
||||
backgroundColor: "steelblue",
|
||||
data: Object.keys(msg.buildsPerJob).map((e)=>{ return msg.buildsPerJob[e]; })
|
||||
}]
|
||||
}
|
||||
@@ -248,11 +256,73 @@ const Home = function() {
|
||||
labels: Object.keys(msg.timePerJob),
|
||||
datasets: [{
|
||||
label: 'Longest average runtime this week',
|
||||
backgroundColor: "lightsteelblue",
|
||||
backgroundColor: "steelblue",
|
||||
data: Object.keys(msg.timePerJob).map((e)=>{ return msg.timePerJob[e]; })
|
||||
}]
|
||||
}
|
||||
});
|
||||
var chtResultChanges = new Chart(document.getElementById("chartResultChanges"), {
|
||||
type: 'horizontalBar',
|
||||
data: {
|
||||
labels: msg.resultChanged.map((e)=>{ return e.name; }),
|
||||
datasets: [{
|
||||
//label: '% Passed',
|
||||
backgroundColor: msg.resultChanged.map((e)=>{return e.lastFailure > e.lastSuccess ? 'firebrick' : 'forestgreen';}),
|
||||
data: msg.resultChanged.map((e)=>{ return e.lastSuccess - e.lastFailure; }),
|
||||
itemid: msg.resultChanged.map((e)=> { return 'rcd_' + e.name; })
|
||||
}]
|
||||
},
|
||||
options:{
|
||||
scales:{
|
||||
xAxes:[{ticks:{display: false}}],
|
||||
yAxes:[{ticks:{display: false}}]
|
||||
},
|
||||
tooltips:{
|
||||
enabled:false
|
||||
}
|
||||
}
|
||||
});
|
||||
var chtPassRates = new Chart(document.getElementById("chartPassRates"), {
|
||||
type: 'horizontalBar',
|
||||
data: {
|
||||
labels: msg.lowPassRates.map((e)=>{ return e.name }),
|
||||
datasets: [{
|
||||
stack: 'passrate',
|
||||
label: '% Passed',
|
||||
backgroundColor: "forestgreen",
|
||||
data: msg.lowPassRates.map((e)=>{ return e.passRate*100; })
|
||||
},{
|
||||
stack:'passrate',
|
||||
label: '% Failed',
|
||||
backgroundColor: "firebrick",
|
||||
data: msg.lowPassRates.map((e)=>{ return (1-e.passRate)*100; })
|
||||
}],
|
||||
}
|
||||
});
|
||||
var chtBuildTimeChanges = new Chart(document.getElementById("chartBuildTimeChanges"), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [...Array(10).keys()],
|
||||
datasets: msg.buildTimeChanges.map((e)=>{return {
|
||||
label: e.name,
|
||||
data: e.durations,
|
||||
borderColor: 'hsl('+(e.name.hashCode() % 360)+', 61%, 34%)',
|
||||
backgroundColor: 'transparent'
|
||||
}})
|
||||
},
|
||||
options:{legend:{display:true}}
|
||||
});
|
||||
var chtBuildTimeDist = new Chart(document.getElementById("chartBuildTimeDist"), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ['<30s','30s-1m','1m-5m','5m-10m','10m-20m','20m-40m','40m-60m','>60m'],
|
||||
datasets: [{
|
||||
label: 'Number jobs with average build time in range',
|
||||
data: msg.buildTimeDist,
|
||||
backgroundColor: "steelblue",
|
||||
}]
|
||||
}
|
||||
});
|
||||
},
|
||||
job_queued: function(data) {
|
||||
state.jobsQueued.splice(0, 0, data);
|
||||
@@ -562,7 +632,23 @@ const Run = function() {
|
||||
|
||||
// For all charts, set miniumum Y to 0
|
||||
Chart.scaleService.updateScaleDefaults('linear', {
|
||||
ticks: { min: 0 }
|
||||
ticks: { suggestedMin: 0 }
|
||||
});
|
||||
// Don't display legend by default
|
||||
Chart.defaults.global.legend.display = false;
|
||||
// Plugin to move a DOM item on top of a chart element
|
||||
Chart.plugins.register({
|
||||
afterDatasetsDraw: (chart) => {
|
||||
chart.data.datasets.forEach((dataset, i) => {
|
||||
var meta = chart.getDatasetMeta(i);
|
||||
if(dataset.itemid)
|
||||
meta.data.forEach((e,j) => {
|
||||
var pos = e.getCenterPoint();
|
||||
var node = document.getElementById(dataset.itemid[j]);
|
||||
node.style.top = (pos.y - node.clientHeight/2) + 'px';
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
new Vue({
|
||||
|
||||
Reference in New Issue
Block a user