1#!/usr/bin/env python3 2 3# Copyright (C) 2022 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16import csv 17import datetime 18import logging 19import re 20import statistics 21import subprocess 22import textwrap 23from pathlib import Path 24from typing import Callable 25 26from typing.io import TextIO 27 28import util 29 30 31def normalize_rebuild(line: dict) -> dict: 32 line['description'] = re.sub(r'^(rebuild)-[\d+](.*)$', '\\1\\2', 33 line['description']) 34 return line 35 36 37def groupby(xs: list[dict], keyfn: Callable[[dict], str]) -> dict[ 38 str, list[dict]]: 39 grouped = {} 40 for x in xs: 41 k = keyfn(x) 42 grouped.setdefault(k, []).append(x) 43 return grouped 44 45 46def write_table(out: TextIO, rows: list[list[str]]): 47 for r in rows: 48 for c in r: 49 out.write(str(c) + ',') 50 out.write('\n') 51 return 52 53 54def _get_build_types(xs: list[dict]) -> list[str]: 55 build_types = [] 56 for x in xs: 57 b = x["build_type"] 58 if b not in build_types: 59 build_types.append(b) 60 return build_types 61 62 63def summarize_metrics(log_dir: Path): 64 filename = log_dir if log_dir.is_file() else log_dir.joinpath( 65 util.METRICS_TABLE) 66 with open(filename) as f: 67 csv_lines = [normalize_rebuild(line) for line in csv.DictReader(f)] 68 69 lines: list[dict] = [] 70 for line in csv_lines: 71 if line["build_result"] == "FAILED": 72 logging.warning(f"{line['description']} / {line['build_type']}") 73 else: 74 lines.append(line) 75 76 build_types = _get_build_types(lines) 77 headers = ["cuj", "targets"] + build_types 78 rows: list[list[str]] = [headers] 79 80 by_cuj = groupby(lines, lambda l: l["description"]) 81 for (cuj, cuj_rows) in by_cuj.items(): 82 for (targets, target_rows) in groupby(cuj_rows, 83 lambda l: l["targets"]).items(): 84 row = [cuj, targets] 85 by_build_type = groupby(target_rows, lambda l: l["build_type"]) 86 for build_type in build_types: 87 selected_lines = by_build_type.get(build_type) 88 if not selected_lines: 89 row.append('') 90 else: 91 times = [util.period_to_seconds(sl['time']) for sl in selected_lines] 92 cell = util.hhmmss( 93 datetime.timedelta(seconds=statistics.median(times))) 94 if len(selected_lines) > 1: 95 cell = f'{cell}[N={len(selected_lines)}]' 96 row.append(cell) 97 rows.append(row) 98 99 with open(log_dir.joinpath(util.SUMMARY_TABLE), mode='wt') as f: 100 write_table(f, rows) 101 102 103def display_summarized_metrics(log_dir: Path): 104 f = log_dir.joinpath(util.SUMMARY_TABLE) 105 cmd = f'grep -v rebuild {f} | grep -v WARMUP | column -t -s,' 106 output = subprocess.check_output(cmd, shell=True, text=True) 107 logging.info(textwrap.dedent(f''' 108 %s 109 TIPS: 110 To view condensed summary: 111 %s 112 '''), output, cmd) 113