Skip to content

Instantly share code, notes, and snippets.

@jhinrichsen
Created April 12, 2018 11:54
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 jhinrichsen/814115aff65c5dc9fe933c57c4cc37c4 to your computer and use it in GitHub Desktop.
Save jhinrichsen/814115aff65c5dc9fe933c57c4cc37c4 to your computer and use it in GitHub Desktop.
Jenkins: Show average duration of all builds for a given freestyle job
// Show average duration of all builds for a given Jenkins freestyle job
// return codes:
// 1: bad usage
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path"
"strings"
"time"
)
// JSON encoding does not do the allBuilds>url trick => nested structs
type JobResponse struct {
// FreeStyleProject `json:"freeStyleProject"`
Class string `json:"_class"`
AllBuilds []Build `json:"allBuilds"`
}
type Build struct {
DurationMs int `json:"duration"`
Id string `json:"id"` // Is a number but represented as string
Url string `json:"url"`
Result string `json:"result"`
}
func (a Build) Duration() time.Duration {
// Jenkins duration is in milliseconds
d := time.Duration(a.DurationMs) * time.Millisecond
return d
}
func die(err error) {
if err != nil {
log.Fatal(err)
}
}
// Builds converts a JSON structure into a list of Builds
func Builds(buf []byte) []Build {
var e JobResponse
err := json.Unmarshal(buf, &e)
die(err)
// log.Printf("unmarshal: %+v\n", e)
return e.AllBuilds
}
// Filter out broken builds, only consider SUCCESS and UNSTABLE
func Filter(b Build) bool {
return b.Result == "FAILURE"
}
// FetchBuilds returns Jenkins JSON 'allBuilds' structure, including duration,
// id, result, and url
func FetchBuilds(jenkins string, job string) []byte {
var sb strings.Builder
// path.Join() cannot be used for URLs
// base := path.Join(jenkins, "job", job, "api", "json")
u, err := url.Parse(jenkins)
die(err)
rel, _ := url.Parse(path.Join("job", job, "api", "json"))
sb.WriteString(u.ResolveReference(rel).String())
sb.WriteString("?tree=allBuilds[duration,id,result,url]&pretty=true")
s := sb.String()
log.Printf("requesting %s\n", s)
res, err := http.Get(s)
die(err)
defer res.Body.Close()
buf, err := ioutil.ReadAll(res.Body)
log.Printf("buf: %s\n", buf)
die(err)
return buf
}
// Show presents some statistics for given job.
func Show(job string, n int, total time.Duration) {
d := time.Duration(total.Nanoseconds() / int64(n))
fmt.Printf("job: %s, %d succesful builds, average: %v\n",
job, n, d)
}
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s <job> ...\n", os.Args[0])
flag.PrintDefaults()
}
jenkins := flag.String("jenkins", "http://localhost:8080/jenkins",
"fully qualified URL for Jenkins instance")
flag.Parse()
// Arguments are job names
if len(flag.Args()) == 0 {
flag.Usage()
os.Exit(1)
}
for _, job := range flag.Args() {
log.Printf("Processing job %s\n", job)
jsonBuf := FetchBuilds(*jenkins, job)
bs := Builds(jsonBuf)
sum := time.Duration(0)
n := 0
for _, b := range bs {
if Filter(b) {
continue
}
n++
sum = time.Duration(sum.Nanoseconds() +
b.Duration().Nanoseconds())
}
Show(job, n, sum)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment