1#!/usr/bin/env python3 2# Copyright 2020 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Emails the mage if PGO profile generation hasn't succeeded recently.""" 7 8import argparse 9import datetime 10import logging 11import subprocess 12import sys 13from typing import List, NamedTuple, Optional, Tuple 14 15PGO_BUILDBOT_LINK = ('https://ci.chromium.org/p/chromeos/builders/toolchain/' 16 'pgo-generate-llvm-next-orchestrator') 17 18 19class ProfdataInfo(NamedTuple): 20 """Data about an llvm profdata in our gs:// bucket.""" 21 date: datetime.datetime 22 location: str 23 24 25def parse_date(date: str) -> datetime.datetime: 26 time_format = '%Y-%m-%dT%H:%M:%SZ' 27 if not date.endswith('Z'): 28 time_format += '%z' 29 return datetime.datetime.strptime(date, time_format) 30 31 32def fetch_most_recent_profdata(arch: str) -> ProfdataInfo: 33 result = subprocess.run( 34 [ 35 'gsutil.py', 36 'ls', 37 '-l', 38 f'gs://chromeos-toolchain-artifacts/llvm-pgo/{arch}/' 39 '*.profdata.tar.xz', 40 ], 41 check=True, 42 stdout=subprocess.PIPE, 43 encoding='utf-8', 44 ) 45 46 # Each line will be a profdata; the last one is a summary, so drop it. 47 infos = [] 48 for rec in result.stdout.strip().splitlines()[:-1]: 49 _size, date, url = rec.strip().split() 50 infos.append(ProfdataInfo(date=parse_date(date), location=url)) 51 return max(infos) 52 53 54def compose_complaint( 55 out_of_date_profiles: List[Tuple[datetime.datetime, ProfdataInfo]] 56) -> Optional[str]: 57 if not out_of_date_profiles: 58 return None 59 60 if len(out_of_date_profiles) == 1: 61 body_lines = ['1 profile is out of date:'] 62 else: 63 body_lines = [f'{len(out_of_date_profiles)} profiles are out of date:'] 64 65 for arch, profdata_info in out_of_date_profiles: 66 body_lines.append( 67 f'- {arch} (most recent profile was from {profdata_info.date} at ' 68 f'{profdata_info.location!r})') 69 70 body_lines.append('\n') 71 body_lines.append( 72 'PTAL to see if the llvm-pgo-generate bots are functioning normally. ' 73 f'Their status can be found at {PGO_BUILDBOT_LINK}.') 74 return '\n'.join(body_lines) 75 76 77def main() -> None: 78 logging.basicConfig(level=logging.INFO) 79 80 parser = argparse.ArgumentParser( 81 description=__doc__, 82 formatter_class=argparse.RawDescriptionHelpFormatter) 83 parser.add_argument( 84 '--max_age_days', 85 # These builders run ~weekly. If we fail to generate two in a row, 86 # something's probably wrong. 87 default=15, 88 type=int, 89 help='How old to let profiles get before complaining, in days', 90 ) 91 args = parser.parse_args() 92 93 now = datetime.datetime.now() 94 logging.info('Start time is %r', now) 95 96 max_age = datetime.timedelta(days=args.max_age_days) 97 out_of_date_profiles = [] 98 for arch in ('arm', 'arm64', 'amd64'): 99 logging.info('Fetching most recent profdata for %r', arch) 100 most_recent = fetch_most_recent_profdata(arch) 101 logging.info('Most recent profdata for %r is %r', arch, most_recent) 102 103 age = now - most_recent.date 104 if age >= max_age: 105 out_of_date_profiles.append((arch, most_recent)) 106 107 complaint = compose_complaint(out_of_date_profiles) 108 if complaint: 109 logging.error('%s', complaint) 110 sys.exit(1) 111 112 logging.info('Nothing seems wrong') 113 114 115if __name__ == '__main__': 116 sys.exit(main()) 117