Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | |
| 3 | # This script greps the JSON files for the buildbots on the LLVM official |
| 4 | # build master by name and prints an HTML page with the links to the bots |
| 5 | # and the status. |
| 6 | # |
| 7 | # Multiple masters can be used, as well as multiple groups of bots and |
| 8 | # multiple bots per group, all in a json file. See linaro.json in this |
| 9 | # repository to have an idea how the config file is. |
| 10 | |
| 11 | import sys |
| 12 | import os |
| 13 | import argparse |
| 14 | import json |
| 15 | import tempfile |
| 16 | import logging |
David Spickett | 55cc06c | 2023-07-26 15:29:20 +0100 | [diff] [blame] | 17 | import pickle |
David Spickett | aa155be | 2021-02-25 14:30:09 +0000 | [diff] [blame] | 18 | import shutil |
David Spickett | 8822bbd | 2023-06-12 14:02:41 +0100 | [diff] [blame] | 19 | import time |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 20 | from datetime import datetime, timedelta |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 21 | |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 22 | # The requests allows HTTP keep-alive which re-uses the same TCP connection |
| 23 | # to download multiple files. |
| 24 | import requests |
David Spickett | 55449c6 | 2021-12-13 12:57:33 +0000 | [diff] [blame] | 25 | from textwrap import dedent |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 26 | from make_table import Table |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 27 | |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 28 | def ignored(s): |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 29 | return "ignore" in s and s["ignore"] |
| 30 | |
| 31 | |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 32 | def not_ignored(s): |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 33 | return not ignored(s) |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 34 | |
| 35 | |
David Spickett | e88fe59 | 2021-03-22 12:25:13 +0000 | [diff] [blame] | 36 | # Returns the parsed json URL or raises an exception |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 37 | def wget(session, url): |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 38 | got = session.get(url) |
| 39 | got.raise_for_status() |
| 40 | return got.json() |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 41 | |
| 42 | |
Oliver Stannard | 91688ff | 2021-01-07 10:27:27 +0000 | [diff] [blame] | 43 | # Map from buildbot status codes we want to treat as errors to the color they |
| 44 | # should be shown in. The codes are documented at |
| 45 | # https://docs.buildbot.net/latest/developer/results.html#build-result-codes, |
| 46 | # and these colors match the suggested ones there. |
| 47 | RESULT_COLORS = { |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 48 | 2: "red", # Error |
| 49 | 4: "purple", # Exception |
| 50 | 5: "purple", # Retry |
| 51 | 6: "pink", # Cancelled |
Oliver Stannard | 91688ff | 2021-01-07 10:27:27 +0000 | [diff] [blame] | 52 | } |
| 53 | |
David Spickett | e88fe59 | 2021-03-22 12:25:13 +0000 | [diff] [blame] | 54 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 55 | def get_bot_failing_steps(session, base_url, buildid): |
| 56 | try: |
| 57 | contents = wget(session, "{}/api/v2/builds/{}/steps".format(base_url, buildid)) |
| 58 | except requests.exceptions.RequestException: |
| 59 | return "" |
| 60 | |
| 61 | for step in contents["steps"]: |
| 62 | if step["results"] in RESULT_COLORS: |
| 63 | yield (step["name"], step["results"]) |
Oliver Stannard | 91688ff | 2021-01-07 10:27:27 +0000 | [diff] [blame] | 64 | |
| 65 | |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 66 | # Get the status of a individual bot BOT. Returns a dict with the |
| 67 | # information. |
| 68 | def get_bot_status(session, bot, base_url, builder_url, build_url): |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 69 | try: |
| 70 | builds = wget( |
| 71 | session, "{}/api/v2/{}/{}/{}".format(base_url, builder_url, bot, build_url) |
| 72 | ) |
| 73 | except requests.exceptions.RequestException as e: |
| 74 | logging.debug(" Couldn't get builds for bot {}!".format(bot)) |
| 75 | return {"valid": False} |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 76 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 77 | reversed_builds = iter(sorted(builds["builds"], key=lambda b: -b["number"])) |
| 78 | next_build = None |
| 79 | for build in reversed_builds: |
| 80 | if not build["complete"]: |
| 81 | next_build = build |
| 82 | continue |
David Spickett | 30a986f | 2021-04-29 09:37:00 +0100 | [diff] [blame] | 83 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 84 | time_since = int(datetime.now().timestamp()) - int(build["complete_at"]) |
| 85 | duration = int(build["complete_at"]) - int(build["started_at"]) |
| 86 | agent_url = "{}/#/{}/{}".format(base_url, builder_url, build["builderid"]) |
David Spickett | 30a986f | 2021-04-29 09:37:00 +0100 | [diff] [blame] | 87 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 88 | status = { |
| 89 | "builder_url": agent_url, |
| 90 | "number": build["number"], |
| 91 | "build_url": "{}/builds/{}".format(agent_url, build["number"]), |
| 92 | "state": build["state_string"], |
| 93 | "time_since": timedelta(seconds=time_since), |
| 94 | "duration": timedelta(seconds=duration), |
| 95 | "fail": build["state_string"] != "build successful", |
David Spickett | f850243 | 2023-07-07 15:52:26 +0100 | [diff] [blame] | 96 | "next_in_progress": None |
| 97 | if next_build is None |
| 98 | else "{}/builds/{}".format(agent_url, next_build["number"]), |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 99 | } |
David Spickett | 7f18f4d | 2021-03-22 11:49:17 +0000 | [diff] [blame] | 100 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 101 | if status["fail"]: |
| 102 | buildid = build["buildid"] |
| 103 | status["steps"] = list(get_bot_failing_steps(session, base_url, buildid)) |
David Spickett | 86f2d47 | 2023-06-12 11:21:16 +0100 | [diff] [blame] | 104 | |
Antoine Moynault | 0cbe02e | 2023-06-21 08:13:57 +0000 | [diff] [blame] | 105 | # find the start of the failure streak |
| 106 | first_fail = build |
| 107 | for build in reversed_builds: |
| 108 | if build["state_string"] == "build successful": |
| 109 | status["first_fail_number"] = first_fail["number"] |
| 110 | status["first_fail_url"] = "{}/builds/{}".format( |
| 111 | agent_url, first_fail["number"] |
| 112 | ) |
David Spickett | 5255529 | 2023-12-07 11:28:52 +0000 | [diff] [blame^] | 113 | # Occasionaly we find a finished build without complete_at, |
| 114 | # it may be an intermitent issue on Buildbot's side. |
| 115 | complete_at = first_fail.get("complete_at") |
| 116 | if complete_at is not None: |
| 117 | fail_since = int(datetime.now().timestamp()) - int(complete_at) |
| 118 | status["fail_since"] = timedelta(seconds=fail_since) |
Antoine Moynault | 0cbe02e | 2023-06-21 08:13:57 +0000 | [diff] [blame] | 119 | break |
| 120 | first_fail = build |
| 121 | else: |
| 122 | pass # fails since forever? |
| 123 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 124 | return status |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 125 | |
| 126 | |
David Spickett | 85355fa | 2021-03-22 15:41:41 +0000 | [diff] [blame] | 127 | # Get status for all bots named in the config |
| 128 | # Return a dictionary of (base_url, bot name) -> status info |
David Spickett | 30a986f | 2021-04-29 09:37:00 +0100 | [diff] [blame] | 129 | def get_buildbot_bots_status(config): |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 130 | session = requests.Session() |
| 131 | bot_cache = {} |
David Spickett | f006c37 | 2021-03-22 12:54:12 +0000 | [diff] [blame] | 132 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 133 | for server in filter(not_ignored, config): |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 134 | base_url = server["base_url"] |
| 135 | logging.debug("Parsing server {}...".format(server["name"])) |
| 136 | for builder in server["builders"]: |
| 137 | logging.debug(" Parsing builders {}...".format(builder["name"])) |
| 138 | for bot in builder["bots"]: |
| 139 | bot_key = (base_url, bot["name"]) |
| 140 | if bot_key in bot_cache: |
| 141 | continue |
David Spickett | 85355fa | 2021-03-22 15:41:41 +0000 | [diff] [blame] | 142 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 143 | logging.debug(" Parsing bot {}...".format(bot["name"])) |
| 144 | status = get_bot_status( |
| 145 | session, |
| 146 | bot["name"], |
| 147 | base_url, |
| 148 | server["builder_url"], |
| 149 | server["build_url"], |
| 150 | ) |
| 151 | if status is not None: |
| 152 | if status.get("valid", True): |
| 153 | logging.debug( |
| 154 | " Bot status: " + ("FAIL" if status["fail"] else "PASS") |
| 155 | ) |
| 156 | bot_cache[bot_key] = status |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 157 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 158 | return bot_cache |
David Spickett | 85355fa | 2021-03-22 15:41:41 +0000 | [diff] [blame] | 159 | |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 160 | |
David Spickett | 85355fa | 2021-03-22 15:41:41 +0000 | [diff] [blame] | 161 | def write_bot_status(config, output_file, bots_status): |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 162 | temp = tempfile.NamedTemporaryFile(mode="w+", delete=False) |
David Spickett | f006c37 | 2021-03-22 12:54:12 +0000 | [diff] [blame] | 163 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 164 | temp.write( |
| 165 | dedent( |
| 166 | """\ |
David Spickett | 64161b0 | 2022-11-01 09:51:30 +0000 | [diff] [blame] | 167 | <!DOCTYPE html> |
David Spickett | 55449c6 | 2021-12-13 12:57:33 +0000 | [diff] [blame] | 168 | <style> |
| 169 | /* Combine the border between cells to prevent 1px gaps |
| 170 | in the row background colour. */ |
| 171 | table, td, th { |
| 172 | border-collapse: collapse; |
| 173 | } |
| 174 | /* Colour every other row in a table body grey. */ |
| 175 | tbody tr:nth-child(even) td { |
| 176 | background-color: #ededed; |
| 177 | } |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 178 | </style>""" |
| 179 | ) |
| 180 | ) |
David Spickett | 55449c6 | 2021-12-13 12:57:33 +0000 | [diff] [blame] | 181 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 182 | column_titles = [ |
| 183 | "Buildbot", |
| 184 | "Status", |
| 185 | "T Since", |
| 186 | "Duration", |
Antoine Moynault | 0cbe02e | 2023-06-21 08:13:57 +0000 | [diff] [blame] | 187 | "Latest", |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 188 | "Failing steps", |
| 189 | "Build In Progress", |
Antoine Moynault | 0cbe02e | 2023-06-21 08:13:57 +0000 | [diff] [blame] | 190 | "1st Failing", |
| 191 | "Failing Since", |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 192 | ] |
| 193 | num_columns = len(column_titles) |
David Spickett | e44441b | 2023-06-12 12:23:28 +0100 | [diff] [blame] | 194 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 195 | # The first table should also say when this was generated. |
| 196 | # If we were to put this in its own header only table, it would |
| 197 | # not align with the rest because it has no content. |
| 198 | first = True |
David Spickett | e44441b | 2023-06-12 12:23:28 +0100 | [diff] [blame] | 199 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 200 | # Dump all servers / bots |
| 201 | for server in filter(not_ignored, config): |
| 202 | with Table(temp) as table: |
| 203 | table.Border(0).Cellspacing(1).Cellpadding(2) |
David Spickett | ec94cc2 | 2021-12-13 13:16:05 +0000 | [diff] [blame] | 204 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 205 | table.AddRow().AddCell().Colspan(num_columns) |
David Spickett | e44441b | 2023-06-12 12:23:28 +0100 | [diff] [blame] | 206 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 207 | if first: |
| 208 | table.AddRow().AddHeader( |
| 209 | "Generated {} ({})".format( |
| 210 | datetime.today().ctime(), time.tzname[time.daylight] |
| 211 | ) |
| 212 | ).Colspan(num_columns) |
| 213 | table.AddRow().AddCell().Colspan(num_columns) |
| 214 | first = False |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 215 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 216 | table.AddRow().AddHeader(server["name"]).Colspan(num_columns) |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 217 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 218 | for builder in server["builders"]: |
| 219 | table.AddRow().AddCell().Colspan(num_columns) |
| 220 | table.AddRow().AddHeader(builder["name"]).Colspan(num_columns) |
| 221 | title_row = table.AddRow() |
| 222 | for title in column_titles: |
| 223 | title_row.AddHeader(title) |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 224 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 225 | table.BeginBody() |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 226 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 227 | for bot in builder["bots"]: |
| 228 | logging.debug("Writing out status for {}".format(bot["name"])) |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 229 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 230 | row = table.AddRow() |
| 231 | base_url = server["base_url"] |
| 232 | try: |
| 233 | status = bots_status[(base_url, bot["name"])] |
| 234 | except KeyError: |
| 235 | row.AddCell("{} is offline!".format(bot["name"])).Colspan( |
| 236 | num_columns |
| 237 | ) |
| 238 | continue |
| 239 | else: |
| 240 | if not status.get("valid", True): |
| 241 | row.AddCell( |
| 242 | "Could not read status for {}!".format(bot["name"]) |
| 243 | ).Colspan(num_columns) |
| 244 | continue |
David Spickett | f2c82dd | 2021-06-24 10:01:33 +0100 | [diff] [blame] | 245 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 246 | row.AddCell( |
| 247 | "<a href='{}'>{}</a>".format(status["builder_url"], bot["name"]) |
| 248 | ) |
Antoine Moynault | 0cbe02e | 2023-06-21 08:13:57 +0000 | [diff] [blame] | 249 | |
| 250 | status_cell = row.AddCell() |
| 251 | if status["fail"]: |
| 252 | status_cell.Style("color:red").Content("FAIL") |
| 253 | else: |
| 254 | status_cell.Style("color:green").Content("PASS") |
David Spickett | 187f796 | 2022-02-09 12:35:00 +0000 | [diff] [blame] | 255 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 256 | time_since_cell = row.AddCell() |
| 257 | if "time_since" in status: |
| 258 | time_since = status["time_since"] |
| 259 | # No build should be taking more than a day |
| 260 | if time_since > timedelta(hours=24): |
Antoine Moynault | 0cbe02e | 2023-06-21 08:13:57 +0000 | [diff] [blame] | 261 | time_since_cell.Style("color:red") |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 262 | time_since_cell.Content(time_since) |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 263 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 264 | duration_cell = row.AddCell() |
| 265 | if "duration" in status: |
| 266 | duration_cell.Content(status["duration"]) |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 267 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 268 | number_cell = row.AddCell() |
| 269 | if "number" in status: |
| 270 | number_cell.Content( |
| 271 | "<a href='{}'>{}</a>".format( |
| 272 | status["build_url"], status["number"] |
| 273 | ) |
| 274 | ) |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 275 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 276 | steps_cell = row.AddCell() |
| 277 | if "steps" in status and status["steps"]: |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 278 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 279 | def render_step(name, result): |
| 280 | return "<font color='{}'>{}</font>".format( |
| 281 | RESULT_COLORS[result], name |
| 282 | ) |
David Spickett | 82c94b2 | 2023-06-12 16:18:33 +0100 | [diff] [blame] | 283 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 284 | step_list = ", ".join( |
| 285 | render_step(name, result) |
| 286 | for name, result in status["steps"] |
| 287 | ) |
| 288 | steps_cell.Style("text-align:center").Content(step_list) |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 289 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 290 | next_in_progress_cell = row.AddCell() |
| 291 | if "next_in_progress" in status: |
David Spickett | f850243 | 2023-07-07 15:52:26 +0100 | [diff] [blame] | 292 | next_build = status["next_in_progress"] |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 293 | next_in_progress_cell.Content( |
David Spickett | f850243 | 2023-07-07 15:52:26 +0100 | [diff] [blame] | 294 | "No" |
| 295 | if next_build is None |
| 296 | else "<a href='{}'>Yes</a>".format(next_build) |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 297 | ) |
| 298 | |
Antoine Moynault | 0cbe02e | 2023-06-21 08:13:57 +0000 | [diff] [blame] | 299 | first_fail_cell = row.AddCell() |
| 300 | if "first_fail_number" in status: |
| 301 | first_fail_cell.Content( |
| 302 | "<a href='{}'>{}</a>".format( |
| 303 | status["first_fail_url"], status["first_fail_number"] |
| 304 | ) |
| 305 | ) |
| 306 | |
| 307 | fail_since_cell = row.AddCell() |
| 308 | if "fail_since" in status: |
| 309 | fail_since = status["fail_since"] |
| 310 | # No build should fail for more than a day |
| 311 | if fail_since > timedelta(hours=24): |
| 312 | fail_since_cell.Style("color:red") |
| 313 | fail_since_cell.Content(fail_since) |
| 314 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 315 | table.EndBody() |
| 316 | |
| 317 | # Move temp to main (atomic change) |
| 318 | temp.close() |
| 319 | shutil.move(temp.name, output_file) |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 320 | |
| 321 | |
| 322 | if __name__ == "__main__": |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 323 | parser = argparse.ArgumentParser() |
| 324 | parser.add_argument( |
| 325 | "-d", dest="debug", action="store_true", help="show debug log messages" |
| 326 | ) |
David Spickett | 55cc06c | 2023-07-26 15:29:20 +0100 | [diff] [blame] | 327 | parser.add_argument( |
| 328 | "--cachefile", |
| 329 | required=False, |
| 330 | help="Location of bot status data cache file (a pickled Python object). If it exists use it, " |
| 331 | "if it does not, read the status from the network and write it to this path.", |
| 332 | ) |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 333 | parser.add_argument("config_file", help="Bots description in JSON format") |
| 334 | parser.add_argument("output_file", help="output HTML path") |
| 335 | args = parser.parse_args() |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 336 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 337 | if args.debug: |
| 338 | logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 339 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 340 | try: |
| 341 | with open(args.config_file, "r") as f: |
| 342 | config = json.load(f) |
| 343 | except IOError as e: |
| 344 | print("error: failed to read {} config file: {}".format(args.config_file, e)) |
| 345 | sys.exit(os.EX_CONFIG) |
Adhemerval Zanella | d3e8c48 | 2020-10-12 11:31:48 -0300 | [diff] [blame] | 346 | |
David Spickett | 55cc06c | 2023-07-26 15:29:20 +0100 | [diff] [blame] | 347 | status = None |
| 348 | if args.cachefile and os.path.exists(args.cachefile): |
| 349 | logging.debug("Using cache file {}".format(args.cachefile)) |
| 350 | with open(args.cachefile, "rb") as f: |
| 351 | status = pickle.load(f) |
| 352 | else: |
| 353 | status = get_buildbot_bots_status(config) |
David Spickett | 55cc06c | 2023-07-26 15:29:20 +0100 | [diff] [blame] | 354 | if args.cachefile: |
| 355 | logging.debug("Writing status to cache file {}".format(args.cachefile)) |
| 356 | with open(args.cachefile, "wb") as f: |
| 357 | pickle.dump(status, f) |
| 358 | |
David Spickett | 4f932d1 | 2023-06-13 12:32:06 +0100 | [diff] [blame] | 359 | write_bot_status(config, args.output_file, status) |