1*9c5db199SXin Li#!/usr/bin/python3 2*9c5db199SXin Li 3*9c5db199SXin Li# Copyright 2017 The Chromium OS Authors. All rights reserved. 4*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 5*9c5db199SXin Li# found in the LICENSE file. 6*9c5db199SXin Li 7*9c5db199SXin Li"""Load generator for devserver.""" 8*9c5db199SXin Li 9*9c5db199SXin Lifrom __future__ import absolute_import 10*9c5db199SXin Lifrom __future__ import division 11*9c5db199SXin Lifrom __future__ import print_function 12*9c5db199SXin Li 13*9c5db199SXin Liimport argparse 14*9c5db199SXin Liimport itertools 15*9c5db199SXin Liimport json 16*9c5db199SXin Liimport re 17*9c5db199SXin Liimport sys 18*9c5db199SXin Li 19*9c5db199SXin Liimport common 20*9c5db199SXin Li 21*9c5db199SXin Li 22*9c5db199SXin Li# Default keys to skip displaying. 23*9c5db199SXin LiDEFAULT_SKIP = [ 24*9c5db199SXin Li 'build_name', 25*9c5db199SXin Li 'devserver', 26*9c5db199SXin Li 'name', 27*9c5db199SXin Li 'parent', 28*9c5db199SXin Li 'quick_provision', 29*9c5db199SXin Li 'trigger_response', 30*9c5db199SXin Li] 31*9c5db199SXin Li 32*9c5db199SXin Li# List of commandline arguments for easy filtering. 33*9c5db199SXin LiFILTER_ARGS = [ 34*9c5db199SXin Li 'board', 35*9c5db199SXin Li 'build_name', 36*9c5db199SXin Li 'devserver', 37*9c5db199SXin Li 'name', 38*9c5db199SXin Li 'status', 39*9c5db199SXin Li] 40*9c5db199SXin Li 41*9c5db199SXin Li 42*9c5db199SXin Lidef get_parser(): 43*9c5db199SXin Li """Creates the argparse parser.""" 44*9c5db199SXin Li parser = argparse.ArgumentParser(description=__doc__) 45*9c5db199SXin Li parser.add_argument('infile', nargs='*', type=argparse.FileType('r'), 46*9c5db199SXin Li help='Path to JSON file to read.', 47*9c5db199SXin Li default=[sys.stdin]) 48*9c5db199SXin Li parser.add_argument('--boards', type=str, action='store', 49*9c5db199SXin Li help='Boards to show.') 50*9c5db199SXin Li parser.add_argument('--group', type=str, action='store', 51*9c5db199SXin Li help='Comma-spearated list of keys to group by.') 52*9c5db199SXin Li parser.add_argument('--dump', action='store_true', 53*9c5db199SXin Li help='Dump all filtered entries.') 54*9c5db199SXin Li parser.add_argument('--skip', type=str, action='store', 55*9c5db199SXin Li help='Comma-separated list of keys to skip displaying.', 56*9c5db199SXin Li default=','.join(DEFAULT_SKIP)) 57*9c5db199SXin Li parser.add_argument('--filter', type=str, action='store', 58*9c5db199SXin Li help='Filter expression to apply to each node.') 59*9c5db199SXin Li for arg in FILTER_ARGS: 60*9c5db199SXin Li parser.add_argument('--%s' % arg, type=str, action='store', 61*9c5db199SXin Li help='Comma-separated list of %s to filter by.' % 62*9c5db199SXin Li arg) 63*9c5db199SXin Li parser.add_argument('--no-summary', action='store_false', dest='summary', 64*9c5db199SXin Li help='Disable summary.') 65*9c5db199SXin Li 66*9c5db199SXin Li return parser 67*9c5db199SXin Li 68*9c5db199SXin Lidef summarize_entries(entries, skip=set()): 69*9c5db199SXin Li """Summarize a list of entries.""" 70*9c5db199SXin Li TAG_KEYS = [ 71*9c5db199SXin Li 'board', 'build_name', 'devserver', 'name', 72*9c5db199SXin Li 'parent', 'quick_provision', 'status' 73*9c5db199SXin Li ] 74*9c5db199SXin Li VALUE_KEYS = [ 75*9c5db199SXin Li 'avg_active', 'elapsed', 76*9c5db199SXin Li ] 77*9c5db199SXin Li summary = { 78*9c5db199SXin Li 'COUNT': len(entries), 79*9c5db199SXin Li } 80*9c5db199SXin Li summary.update({key: summarize_tags(entries, key) for key in TAG_KEYS 81*9c5db199SXin Li if key not in skip}) 82*9c5db199SXin Li summary.update({key: summarize_values(entries, key) for key in VALUE_KEYS 83*9c5db199SXin Li if key not in skip}) 84*9c5db199SXin Li return summary 85*9c5db199SXin Li 86*9c5db199SXin Lidef summarize_tags(entries, key): 87*9c5db199SXin Li """Summarize all the different string values for a given key.""" 88*9c5db199SXin Li tags = {str(entry[key]) for entry in entries} 89*9c5db199SXin Li return list(tags) 90*9c5db199SXin Li 91*9c5db199SXin Lidef summarize_values(entries, key): 92*9c5db199SXin Li """Summarize the numeric values for a given key.""" 93*9c5db199SXin Li if entries is None or len(entries) == 0: 94*9c5db199SXin Li return None 95*9c5db199SXin Li 96*9c5db199SXin Li values = [entry[key] for entry in entries if key in entry] 97*9c5db199SXin Li summary = {} 98*9c5db199SXin Li num_values = len(values) 99*9c5db199SXin Li if num_values: 100*9c5db199SXin Li summary['min'] = min(values) 101*9c5db199SXin Li summary['max'] = max(values) 102*9c5db199SXin Li summary['avg'] = sum(values) / num_values 103*9c5db199SXin Li num_skipped = len(entries) - num_values 104*9c5db199SXin Li if num_skipped: 105*9c5db199SXin Li summary['num'] = num_values 106*9c5db199SXin Li summary['skipped'] = num_skipped 107*9c5db199SXin Li return summary 108*9c5db199SXin Li 109*9c5db199SXin Lidef group_entries(keys, entries): 110*9c5db199SXin Li """Group entries based on different values of given keys. 111*9c5db199SXin Li 112*9c5db199SXin Li @param keys: A list of keys to group by. 113*9c5db199SXin Li @param entries: A list of entries to split into groups. 114*9c5db199SXin Li 115*9c5db199SXin Li @return A list of list of entries, where each list has a different key 116*9c5db199SXin Li value. 117*9c5db199SXin Li """ 118*9c5db199SXin Li if not keys: 119*9c5db199SXin Li return [entries] 120*9c5db199SXin Li 121*9c5db199SXin Li # Divide the group based on the first key. 122*9c5db199SXin Li indexed = {} 123*9c5db199SXin Li for entry in entries: 124*9c5db199SXin Li value = str(entry[keys[0]]) 125*9c5db199SXin Li indexed.setdefault(value, []).append(entry) 126*9c5db199SXin Li groups = [indexed[value] for value in sorted(indexed.keys())] 127*9c5db199SXin Li 128*9c5db199SXin Li # Recursively subdivide all the groups based on the rest of the keys. 129*9c5db199SXin Li subgroups = [] 130*9c5db199SXin Li for group in groups: 131*9c5db199SXin Li subgroups.extend(group_entries(keys[1:], group)) 132*9c5db199SXin Li return subgroups 133*9c5db199SXin Li 134*9c5db199SXin Lidef main(argv): 135*9c5db199SXin Li """Load generator for a devserver.""" 136*9c5db199SXin Li parser = get_parser() 137*9c5db199SXin Li options = parser.parse_args(argv) 138*9c5db199SXin Li 139*9c5db199SXin Li # Read entries from the specified file. 140*9c5db199SXin Li all_entries = [] 141*9c5db199SXin Li for f in options.infile: 142*9c5db199SXin Li all_entries.extend([json.loads(line) for line in f]) 143*9c5db199SXin Li 144*9c5db199SXin Li # Filter entries: 145*9c5db199SXin Li # - Ignore non-provisions. 146*9c5db199SXin Li # - Filter via the specified FILTER_ARGS arguments. 147*9c5db199SXin Li # - Filter via explicit filter request. 148*9c5db199SXin Li entries = [x for x in all_entries if x['name'] != 'Runner'] 149*9c5db199SXin Li for arg in FILTER_ARGS: 150*9c5db199SXin Li if options.__dict__.get(arg): 151*9c5db199SXin Li entries = [x for x in entries if x[arg] in 152*9c5db199SXin Li options.__dict__[arg].split(',')] 153*9c5db199SXin Li if options.filter: 154*9c5db199SXin Li entries = [x for x in entries if eval(options.filter, {'re': re}, x)] 155*9c5db199SXin Li 156*9c5db199SXin Li # Group the entries based on specified keys. 157*9c5db199SXin Li groups = group_entries(options.group.split(',') if options.group else None, 158*9c5db199SXin Li entries) 159*9c5db199SXin Li 160*9c5db199SXin Li # Dump all filtered entries as groups, including their parents. 161*9c5db199SXin Li if options.dump: 162*9c5db199SXin Li dump_entries = itertools.chain(*groups) 163*9c5db199SXin Li # Dump all entries, tracking needed parents. 164*9c5db199SXin Li parents = [] 165*9c5db199SXin Li for entry in dump_entries: 166*9c5db199SXin Li print(json.dumps(entry)) 167*9c5db199SXin Li if 'parent' in entry and entry['parent'] not in parents: 168*9c5db199SXin Li parents.append(entry['parent']) 169*9c5db199SXin Li # Dump all parents. 170*9c5db199SXin Li for entry in all_entries: 171*9c5db199SXin Li if entry['id'] in parents: 172*9c5db199SXin Li print(json.dumps(entry)) 173*9c5db199SXin Li 174*9c5db199SXin Li # Summarize the entries, group by group. 175*9c5db199SXin Li if options.summary: 176*9c5db199SXin Li skip = options.skip.split(',') if options.skip else set() 177*9c5db199SXin Li summaries = [summarize_entries(group, skip) for group in groups] 178*9c5db199SXin Li print(json.dumps(summaries, indent=2)) 179*9c5db199SXin Li 180*9c5db199SXin Liif __name__ == '__main__': 181*9c5db199SXin Li sys.exit(main(sys.argv[1:])) 182