#!/usr/bin/env python3 # Copyright 2019 Google LLC # # Licensed under the the Apache License v2.0 with LLVM Exceptions (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://llvm.org/LICENSE.txt # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ----------------------------------------------------------------------------- # This script will collect all breakages of the main branch builds from # buildkite and format the results nicely. # Arguments: # llvm-path : folder where the LLVM checkout is kept # token : Access token for buildkite # ----------------------------------------------------------------------------- import requests import git import argparse import json import os import datetime class Build: def __init__(self, json_dict): self._json_dict = json_dict @property def number(self) -> int: return self._json_dict['number'] @property def web_url(self) -> str: return self._json_dict['web_url'] @property def state(self) -> str: return self._json_dict['state'] @property def has_passed(self) -> bool: return self.state == 'passed' @property def commit(self) -> str: return self._json_dict['commit'] @property def created_at(self) -> datetime.datetime: #example: 2019-11-07T14:13:07.942Z return datetime.datetime.fromisoformat(self._json_dict['created_at'].rstrip('Z')) class BuildKiteMasterStats: def __init__(self, llvm_repo: str, token_path: str): self._llvm_repo = llvm_repo with open(token_path, 'r') as token_file: self._token = token_file.read().strip() def get_stats(self, organisation: str, pipeline: str): url = "https://api.buildkite.com/v2/organizations/{}/pipelines/{}/builds".format(organisation, pipeline) return self._get_url(url) def _get_url(self, url: str): """Get paginated results from server.""" page = 1 results = [] while True: page_url = url + f"?api_key={self._token}&page={page}&per_page=100" new_results = requests.get(page_url).json() results.extend(new_results) print(len(new_results)) if len(new_results) < 100: break page += 1 return results def save_results(self, output_path:str, data): with open(output_path, 'w') as output_file: json.dump(data, output_file) def get_builds(self, json_path: str): with open(json_path) as json_file: builds = json.load(json_file) build_dict = {} for b in builds: build = Build(b) build_dict[build.number] = build return build_dict if __name__ == '__main__': parser = argparse.ArgumentParser(description='Process some integers.') parser.add_argument('llvm_path') parser.add_argument('token') args = parser.parse_args() CACHE_FILE = 'tmp/bklogs.json' bk = BuildKiteMasterStats(args.llvm_path, args.token) if not os.path.exists(CACHE_FILE): results = bk.get_stats('llvm-project','llvm-main-build') bk.save_results(CACHE_FILE, results) builds = bk.get_builds(CACHE_FILE) last_fail = None fail_count = 0 start_time = None for build_number in sorted(builds.keys()): if build_number < 50: # skip the first builds as they might not be mature enough continue build = builds[build_number] if build.has_passed: if last_fail is not None: print(f'* ends with [build {build.number}]({build.web_url})') print(f'* ends with commit {build.commit}') print(f'* ends on {build.created_at}') duration = build.created_at - start_time print(f'* duration: {duration} [h:m:s]') print('* cause: # TODO') print() last_fail = None else: if last_fail is None: print(f'# Outage {fail_count}') print() print(f'* starts with [build {build.number}]({build.web_url})') print(f'* starts with commit {build.commit}') print(f'* starts on {build.created_at}') fail_count += 1 start_time = build.created_at else: pass last_fail = build.number