blob: f2de0eaf2fd4fa87db0e117fcedc64e4d9288017 [file] [log] [blame]
Renato Golin94cc1042016-04-26 11:02:23 +01001#!/usr/bin/env perl
2
3# This script greps the JSON files for the buildbots on
4# the LLVM official build master by name and prints an
5# HTML page with the links to the bots and the status.
6#
7# Multiple masters can be used, as well as multiple groups of bots
8# and multiple bots per group, all in a json file. See linaro.json
9# in this repository to have an idea how the config file is.
10#
11# Module JSON needs to be installed, either from cpan or packages.
12
13push @INC, `dirname $0`;
14
15use strict;
16use warnings;
17# Core modules
18use File::Temp qw/tempfile/;
19use File::Copy;
20# This is not part of core, but you really *need* it.
21use JSON;
22# This can be replaced by `wget/curl`
23use LWP;
24use LWP::UserAgent;
25# We don't have DateTime everywhere...
26my $date = `date`;
27# DEBUG
28my $DEBUG = 0;
29
30######################################################### Initialisation
31# Option checking
32my $syntax = "$0 config-file.json output-file.html\n";
33die $syntax unless (scalar @ARGV == 2);
34# Read config file
35my ($config, $error) = &read_file($ARGV[0]);
36die $error if ($error);
37($config, $error) = &decode($config);
38die $error if ($error);
39
40# Setup HTML output file
41my $output = $ARGV[1];
42my ($temp, $tempname) = tempfile();
43
44
45######################################################### Main Logic
46# Get status for all bots
47my %bot_cache;
48my $fail = 0;
49foreach my $server (@$config) {
50 next if (defined $server->{'ignore'} and $server->{'ignore'} eq "true");
51 my ($BASE_URL, $BUILDER_URL, $BUILD_URL) =
52 ($server->{'base_url'}, $server->{'builder_url'}, $server->{'build_url'});
53 &debug("Parsing server ".$server->{'name'}."...\n");
54 foreach my $builder (@{$server->{'builders'}}) {
55 &debug(" Parsing builder ".$builder->{'name'}."...\n");
56 foreach my $bot (@{$builder->{'bots'}}) {
57 &debug(" Parsing bot ".$bot->{'name'}."...\n");
58 next if defined $bot_cache{$bot->{'name'}};
59 my $status = &get_status($bot->{'name'}, $BASE_URL, $BUILDER_URL, $BUILD_URL);
60 if (!defined $bot->{'ignore'} or $bot->{'ignore'} ne "true") {
61 $fail = 1 if ($status->{'fail'});
62 } else {
63 &debug(" Ignoring...\n");
64 }
65 &debug($status->{'fail'} ? " FAIL\n" : " PASS\n");
66 $bot_cache{$bot->{'name'}} = $status;
67 }
68 }
69}
70
71# Dump all servers / bots
72foreach my $server (@$config) {
73 next if (defined $server->{'ignore'} and $server->{'ignore'} eq "true");
74 my ($BASE_URL, $BUILDER_URL, $BUILD_URL) =
75 ($server->{'base_url'}, $server->{'builder_url'}, $server->{'build_url'});
76 # Favicon
77 my $favicon = $fail ? "fail.ico" : "ok.ico";
78 print $temp "<link rel=\"shortcut icon\" href=\"$favicon\" type=\"image/x-icon\"/>\n";
79 # Header
80 print $temp "<table cellspacing=1 cellpadding=2>\n";
81 print $temp "<tr><td colspan=5>&nbsp;</td><tr>\n";
82 print $temp "<tr><th colspan=5>$server->{'name'} @ $date</td><tr>\n";
83 ## Main loop
84 foreach my $builder (@{$server->{'builders'}}) {
85 print $temp "<tr><td colspan=5>&nbsp;</td><tr>\n";
86 print $temp "<tr><th colspan=5>$builder->{'name'}</td><tr>\n";
87 print $temp "<tr><th>Buildbot</th><th>Status</th><th>Comments</th>".
88 "<th>Build #</th><th>Commits</th></tr>\n";
89 foreach my $bot (@{$builder->{'bots'}}) {
90 print $temp "<tr>\n";
91 my $status = $bot_cache{$bot->{'name'}};
92 my $url = "$BASE_URL/$BUILDER_URL/$bot->{'name'}";
93 print $temp " <td><a href='$url'>$bot->{'name'}</a></td>\n";
94 if ($status->{'fail'}) {
95 print $temp " <td><font color='red'>FAIL</font></td>\n".
96 " <td>$status->{'fail'}</td>\n";
97 } else {
98 print $temp " <td><font color='green'>PASS</font></td>\n".
99 " <td>&nbsp;</td>\n";
100 }
101 if (defined $status->{'build'} and
102 defined $status->{'from'} and
103 defined $status->{'to'}) {
104 print $temp " <td>$status->{'build'}</td>\n".
105 " <td>$status->{'from'}-$status->{'to'}</td>\n";
106 } else {
107 print $temp " <td colspan=2>&nbsp;</td>\n";
108 }
109 print $temp "</tr>\n";
110 }
111 }
112 # Footer
113 print $temp "</table>\n";
114}
115close $temp;
116
117# Move temp to main (atomic change)
118move($tempname, $output);
119exit;
120
121######################################################### Subs
122
123# GET STATUS: get the status of an individual bot
124# (botname, base url, builder url, build url) -> (status)
125sub get_status() {
126 my ($bot, $BASE_URL, $BUILDER_URL, $BUILD_URL) = @_;
127 my ($err, $contents, $json);
128 my %status;
129
130 # Get buildbot main JSON
131 ($contents, $err) = wget("$BASE_URL/json/$BUILDER_URL/$bot");
132 $status{'fail'} = $err;
133 return \%status if $err;
134 ($json, $err) = decode($contents);
135 $status{'fail'} = $err;
136 return \%status if $err;
137
138 # Find recent builds
139 my $cached_builds = scalar @{$json->{'cachedBuilds'}};
140 my $running_builds = scalar @{$json->{'currentBuilds'}};
141 my $last_build = $json->{'cachedBuilds'}[$cached_builds - $running_builds - 1];
142 return \%status if (not defined $last_build);
143
144 # Get most recent build
145 ($contents, $err) = wget("$BASE_URL/json/$BUILDER_URL/$bot/$BUILD_URL/$last_build");
146 $status{'fail'} = $err;
147 return \%status if $err;
148 ($json, $err) = decode($contents);
149 $status{'fail'} = $err;
150 return \%status if $err;
151
152 # Build number
153 $status{'build'} = $json->{'number'};
154
155 # Status of the last build
156 # "text" : [ "build", "successful" ],
157 # "text" : [ "failed", "svn-llvm" ],
158 my $failed = 0;
159 foreach (@{$json->{'text'}}) {
160 $status{'fail'} .= $_." " if ($failed);
161 $failed = 1 if (/failed|exception/);
162 }
163 $status{'fail'} =~ s/ $//;
164
165 # Commit range
166 # [
167 # 'revision',
168 # '238202',
169 # 'Build'
170 # ],
171 my $max = 0;
172 foreach (@{$json->{'properties'}}) {
173 if ($_->[0] eq 'revision') {
174 $max = $_->[1];
175 last;
176 }
177 }
178 # Min is in the changes' section
179 my $min = $max;
180 foreach (@{$json->{'sourceStamp'}->{'changes'}}) {
181 my $commit = $_->{'revision'};
182 $min = $commit if ($commit < $min);
183 }
184 $status{'from'} = $min;
185 $status{'to'} = $max;
186
187 return \%status;
188}
189
190# WGET: uses LWP to get an URL, returns contents (or error).
191# (url) -> (contents, error)
192sub wget() {
193 my ($url) = @_;
194 my ($contents, $error) = ("", "");
195
196 my $ua = LWP::UserAgent->new;
197 $ua->agent("LLVM BotMonitor/0.1");
198 my $req = HTTP::Request->new(GET => $url);
199 my $res = $ua->request($req);
200
201 if ($res->is_success) {
202 $contents = $res->content;
203 } else {
204 $error = $res->status_line;
205 }
206 return ($contents, $error);
207}
208
209# READ FILE: Reads a local file, returns contents
210# (filename) -> (contents)
211sub read_file() {
212 my ($file) = @_;
213 my ($contents, $error) = ("", "");
214 if (open FH, $file) {
215 while (<FH>) { $contents .= $_; }
216 close FH;
217 } else {
218 $error = "Can't open config file $file: $!";
219 }
220 return ($contents, $error);
221}
222
223# DECODE: Reads contents, returns JSON output (or error)
224# (contents) -> (JSON, error)
225sub decode() {
226 my ($contents) = @_;
227 my ($json, $error) = ("", "");
228 eval { $json = decode_json($contents); };
229 if ($@) {
230 if ($DEBUG) {
231 $error = $@;
232 } else {
233 $error = "JSON error";
234 }
235 }
236 return ($json, $error);
237}
238
239# DEBUG: Prints debug messages if debug enabled
240# (msg) -> ()
241sub debug () {
242 my ($msg) = @_;
243 print STDERR $msg if ($DEBUG);
244}