#!/usr/bin/env perl # This script greps the JSON files for the buildbots on # the LLVM official build master by name and prints an # HTML page with the links to the bots and the status. # # Multiple masters can be used, as well as multiple groups of bots # and multiple bots per group, all in a json file. See linaro.json # in this repository to have an idea how the config file is. # # Module JSON needs to be installed, either from cpan or packages. push @INC, `dirname $0`; use strict; use warnings; # Core modules use File::Temp qw/tempfile/; use File::Copy; # This is not part of core, but you really *need* it. use JSON; # This can be replaced by `wget/curl` use LWP; use LWP::UserAgent; # We don't have DateTime everywhere... my $date = `date`; # DEBUG my $DEBUG = 0; ######################################################### Initialisation # Option checking my $syntax = "$0 config-file.json output-file.html\n"; die $syntax unless (scalar @ARGV == 2); # Read config file my ($config, $error) = &read_file($ARGV[0]); die $error if ($error); ($config, $error) = &decode($config); die $error if ($error); # Setup HTML output file my $output = $ARGV[1]; my ($temp, $tempname) = tempfile(); ######################################################### Main Logic # Get status for all bots my %bot_cache; my $fail = 0; foreach my $server (@$config) { next if (defined $server->{'ignore'} and $server->{'ignore'} eq "true"); my ($BASE_URL, $BUILDER_URL, $BUILD_URL) = ($server->{'base_url'}, $server->{'builder_url'}, $server->{'build_url'}); &debug("Parsing server ".$server->{'name'}."...\n"); foreach my $builder (@{$server->{'builders'}}) { &debug(" Parsing builder ".$builder->{'name'}."...\n"); foreach my $bot (@{$builder->{'bots'}}) { &debug(" Parsing bot ".$bot->{'name'}."...\n"); next if defined $bot_cache{$bot->{'name'}}; my $status = &get_status($bot->{'name'}, $BASE_URL, $BUILDER_URL, $BUILD_URL); if (!defined $bot->{'ignore'} or $bot->{'ignore'} ne "true") { $fail = 1 if ($status->{'fail'}); } else { &debug(" Ignoring...\n"); } &debug($status->{'fail'} ? " FAIL\n" : " PASS\n"); $bot_cache{$BASE_URL.'/'.$bot->{'name'}} = $status; } } } # Dump all servers / bots foreach my $server (@$config) { next if (defined $server->{'ignore'} and $server->{'ignore'} eq "true"); my ($BASE_URL, $BUILDER_URL, $BUILD_URL) = ($server->{'base_url'}, $server->{'builder_url'}, $server->{'build_url'}); # Favicon my $favicon = $fail ? "fail.ico" : "ok.ico"; print $temp "\n"; # Header print $temp "\n"; print $temp "\n"; print $temp "\n"; ## Main loop foreach my $builder (@{$server->{'builders'}}) { print $temp "\n"; print $temp "\n"; print $temp "". "\n"; foreach my $bot (@{$builder->{'bots'}}) { print $temp "\n"; my $status = $bot_cache{$BASE_URL.'/'.$bot->{'name'}}; my $url = "$BASE_URL/$BUILDER_URL/$bot->{'name'}"; print $temp " \n"; if ($status->{'fail'}) { print $temp " \n". " \n"; } else { print $temp " \n". " \n"; } if (defined $status->{'build'}) { my $build_url = $url."/builds/".$status->{'build'}; print $temp " \n"; } else { print $temp " \n"; } if (defined $status->{'from'} and defined $status->{'to'}) { print $temp " \n"; } else { print $temp " \n"; } if (defined $status->{'time'}) { my $time = sprintf("%.0f", $status->{'time'} / 60); print $temp " \n"; } else { print $temp " \n"; } print $temp "\n"; } } # Footer print $temp "
 
$server->{'name'} @ $date
 
$builder->{'name'}
BuildbotStatusCommentsBuild #CommitsTime (minutes)
$bot->{'name'}FAIL$status->{'fail'}PASS $status->{'build'} $status->{'from'}-$status->{'to'} $time 
\n"; } close $temp; # Move temp to main (atomic change) move($tempname, $output); exit; ######################################################### Subs # GET STATUS: get the status of an individual bot # (botname, base url, builder url, build url) -> (status) sub get_status() { my ($bot, $BASE_URL, $BUILDER_URL, $BUILD_URL) = @_; my ($err, $contents, $json); my %status; # Get buildbot main JSON ($contents, $err) = wget("$BASE_URL/json/$BUILDER_URL/$bot"); $status{'fail'} = $err; return \%status if $err; ($json, $err) = decode($contents); $status{'fail'} = $err; return \%status if $err; # Find recent builds my $cached_builds = scalar @{$json->{'cachedBuilds'}}; my $running_builds = scalar @{$json->{'currentBuilds'}}; my $last_build = $json->{'cachedBuilds'}[$cached_builds - $running_builds - 1]; return \%status if (not defined $last_build); # Get most recent build ($contents, $err) = wget("$BASE_URL/json/$BUILDER_URL/$bot/$BUILD_URL/$last_build"); $status{'fail'} = $err; return \%status if $err; ($json, $err) = decode($contents); $status{'fail'} = $err; return \%status if $err; # Build number $status{'build'} = $json->{'number'}; # Status of the last build # "text" : [ "build", "successful" ], # "text" : [ "failed", "svn-llvm" ], my $failed = 0; foreach (@{$json->{'text'}}) { $status{'fail'} .= $_." " if ($failed); $failed = 1 if (/failed|exception/); } $status{'fail'} =~ s/ $//; # Commit range. All LLVM repositories are in git now, so truncate the hashes # to 8 characters for display. my @commits = @{$json->{'sourceStamp'}->{'changes'}}; my $first_rev = $commits[0]->{'revision'}; my $last_rev = $commits[-1]->{'revision'}; $status{'from'} = substr($first_rev, 0, 8); $status{'to'} = substr($last_rev, 0, 8); # Elapsed time of the last build. $status{'time'} = $json->{'times'}[1] - $json->{'times'}[0]; return \%status; } # WGET: uses LWP to get an URL, returns contents (or error). # (url) -> (contents, error) sub wget() { my ($url) = @_; my ($contents, $error) = ("", ""); my $ua = LWP::UserAgent->new; $ua->agent("LLVM BotMonitor/0.1"); my $req = HTTP::Request->new(GET => $url); my $res = $ua->request($req); if ($res->is_success) { $contents = $res->content; } else { $error = $res->status_line; } return ($contents, $error); } # READ FILE: Reads a local file, returns contents # (filename) -> (contents) sub read_file() { my ($file) = @_; my ($contents, $error) = ("", ""); if (open FH, $file) { while () { $contents .= $_; } close FH; } else { $error = "Can't open config file $file: $!"; } return ($contents, $error); } # DECODE: Reads contents, returns JSON output (or error) # (contents) -> (JSON, error) sub decode() { my ($contents) = @_; my ($json, $error) = ("", ""); eval { $json = decode_json($contents); }; if ($@) { if ($DEBUG) { $error = $@; } else { $error = "JSON error"; } } return ($json, $error); } # DEBUG: Prints debug messages if debug enabled # (msg) -> () sub debug () { my ($msg) = @_; print STDERR $msg if ($DEBUG); }