#!/usr/bin/env python3 # Copyright 2023 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import argparse import json import os import re import subprocess import sys import time import urllib.request THIS_DIR = os.path.abspath(os.path.dirname(__file__)) CHROMIUM_REPO = os.path.abspath(os.path.join(THIS_DIR, '..', '..', '..')) LLVM_REPO = '' # This gets filled in by main(). # This script produces the dashboard at # https://commondatastorage.googleapis.com/chromium-browser-clang/toolchain-dashboard.html # # Usage: # # ./dashboard.py > /tmp/toolchain-dashboard.html # gsutil.py cp -a public-read /tmp/toolchain-dashboard.html gs://chromium-browser-clang/ #TODO: Add Rust and libc++ graphs. #TODO: Plot 30-day moving averages. #TODO: Overview with current age of each toolchain component. #TODO: Tables of last N rolls for each component. #TODO: Link to next roll bug, count of blockers, etc. def timestamp_to_str(timestamp): '''Return a string representation of a Unix timestamp.''' return time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(timestamp)) def get_git_timestamp(repo, revision): '''Get the Unix timestamp of a git commit.''' out = subprocess.check_output([ 'git', '-C', repo, 'show', '--date=unix', '--pretty=fuller', '--no-patch', revision ]).decode('utf-8') DATE_RE = re.compile(r'^CommitDate: (\d+)$', re.MULTILINE) m = DATE_RE.search(out) return int(m.group(1)) svn2git_dict = None def svn2git(svn_rev): global svn2git_dict if not svn2git_dict: # The JSON was generated with: # $ ( echo '{' && git rev-list 40c47680eb2a1cb9bb7f8598c319335731bd5204 | while read commit ; do SVNREV=$(git log --format=%B -n 1 $commit | grep '^llvm-svn: [0-9]*$' | awk '{print $2 }') ; [[ ! -z '$SVNREV' ]] && echo "\"$SVNREV\": \"$commit\"," ; done && echo '}' ) | tee /tmp/llvm_svn2git.json # and manually removing the trailing comma of the last entry. with urllib.request.urlopen( 'https://commondatastorage.googleapis.com/chromium-browser-clang/llvm_svn2git.json' ) as url: svn2git_dict = json.load(url) # For branch commits, use the most recent commit to main instead. svn2git_dict['324578'] = '93505707b6d3ec117e555c5a48adc2cc56470e38' svn2git_dict['149886'] = '60fc2425457f43f38edf5b310551f996f4f42df8' svn2git_dict['145240'] = '12330650f843cf7613444e345a4ecfcf06923761' return svn2git_dict[svn_rev] def clang_rolls(): '''Return a dict from timestamp to clang revision rolled in at that time.''' FIRST_ROLL = 'd78457ce2895e5b98102412983a979f1896eca90' log = subprocess.check_output([ 'git', '-C', CHROMIUM_REPO, 'log', '--date=unix', '--pretty=fuller', '-p', f'{FIRST_ROLL}..origin/main', '--', 'tools/clang/scripts/update.py', 'tools/clang/scripts/update.sh' ]).decode('utf-8') # AuthorDate is when a commit was first authored; CommitDate (part of # --pretty=fuller) is when a commit was last updated. We use the latter since # it's more likely to reflect when the commit become part of upstream. DATE_RE = re.compile(r'^CommitDate: (\d+)$') VERSION_RE = re.compile( r'^\+CLANG_REVISION = \'llvmorg-\d+-init-\d+-g([0-9a-f]+)\'$') VERSION_RE_OLD = re.compile(r'^\+CLANG_REVISION = \'([0-9a-f]{10,})\'$') # +CLANG_REVISION=125186 VERSION_RE_SVN = re.compile(r'^\+CLANG_REVISION ?= ?\'?(\d{1,6})\'?$') rolls = {} date = None for line in log.splitlines(): m = DATE_RE.match(line) if m: date = int(m.group(1)) next rev = None if m := VERSION_RE.match(line): rev = m.group(1) elif m := VERSION_RE_OLD.match(line): rev = m.group(1) elif m := VERSION_RE_SVN.match(line): rev = svn2git(m.group(1)) if rev: assert (date) rolls[date] = rev date = None return rolls def clang_roll_ages(rolls): '''Given a dict from timestamps to clang revisions, return a list of pairs of timestamp string and clang revision age in days at that timestamp.''' ages = [] def add(timestamp, rev): ages.append((timestamp_to_str(timestamp), (timestamp - get_git_timestamp(LLVM_REPO, rev)) / (3600 * 24))) assert (rolls) prev_roll_rev = None for roll_time, roll_rev in sorted(rolls.items()): if prev_roll_rev: add(roll_time - 1, prev_roll_rev) add(roll_time, roll_rev) prev_roll_rev = roll_rev add(time.time(), prev_roll_rev) return ages def print_dashboard(): rolls = clang_rolls() ages = clang_roll_ages(rolls) print('''
Last updated: {timestamp_to_str(time.time())} UTC
') print('''