• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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