1*5a923131SAndroid Build Coastguard Worker# 2*5a923131SAndroid Build Coastguard Worker# Copyright (C) 2013 The Android Open Source Project 3*5a923131SAndroid Build Coastguard Worker# 4*5a923131SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*5a923131SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*5a923131SAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*5a923131SAndroid Build Coastguard Worker# 8*5a923131SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*5a923131SAndroid Build Coastguard Worker# 10*5a923131SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*5a923131SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*5a923131SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*5a923131SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*5a923131SAndroid Build Coastguard Worker# limitations under the License. 15*5a923131SAndroid Build Coastguard Worker# 16*5a923131SAndroid Build Coastguard Worker 17*5a923131SAndroid Build Coastguard Worker"""Histogram generation tools.""" 18*5a923131SAndroid Build Coastguard Worker 19*5a923131SAndroid Build Coastguard Workerfrom __future__ import absolute_import 20*5a923131SAndroid Build Coastguard Workerfrom __future__ import division 21*5a923131SAndroid Build Coastguard Worker 22*5a923131SAndroid Build Coastguard Workerfrom collections import defaultdict 23*5a923131SAndroid Build Coastguard Worker 24*5a923131SAndroid Build Coastguard Workerfrom update_payload import format_utils 25*5a923131SAndroid Build Coastguard Worker 26*5a923131SAndroid Build Coastguard Worker 27*5a923131SAndroid Build Coastguard Workerclass Histogram(object): 28*5a923131SAndroid Build Coastguard Worker """A histogram generating object. 29*5a923131SAndroid Build Coastguard Worker 30*5a923131SAndroid Build Coastguard Worker This object serves the sole purpose of formatting (key, val) pairs as an 31*5a923131SAndroid Build Coastguard Worker ASCII histogram, including bars and percentage markers, and taking care of 32*5a923131SAndroid Build Coastguard Worker label alignment, scaling, etc. In addition to the standard __init__ 33*5a923131SAndroid Build Coastguard Worker interface, two static methods are provided for conveniently converting data 34*5a923131SAndroid Build Coastguard Worker in different formats into a histogram. Histogram generation is exported via 35*5a923131SAndroid Build Coastguard Worker its __str__ method, and looks as follows: 36*5a923131SAndroid Build Coastguard Worker 37*5a923131SAndroid Build Coastguard Worker Yes |################ | 5 (83.3%) 38*5a923131SAndroid Build Coastguard Worker No |### | 1 (16.6%) 39*5a923131SAndroid Build Coastguard Worker 40*5a923131SAndroid Build Coastguard Worker TODO(garnold) we may want to add actual methods for adding data or tweaking 41*5a923131SAndroid Build Coastguard Worker the output layout and formatting. For now, though, this is fine. 42*5a923131SAndroid Build Coastguard Worker 43*5a923131SAndroid Build Coastguard Worker """ 44*5a923131SAndroid Build Coastguard Worker 45*5a923131SAndroid Build Coastguard Worker def __init__(self, data, scale=20, formatter=None): 46*5a923131SAndroid Build Coastguard Worker """Initialize a histogram object. 47*5a923131SAndroid Build Coastguard Worker 48*5a923131SAndroid Build Coastguard Worker Args: 49*5a923131SAndroid Build Coastguard Worker data: list of (key, count) pairs constituting the histogram 50*5a923131SAndroid Build Coastguard Worker scale: number of characters used to indicate 100% 51*5a923131SAndroid Build Coastguard Worker formatter: function used for formatting raw histogram values 52*5a923131SAndroid Build Coastguard Worker 53*5a923131SAndroid Build Coastguard Worker """ 54*5a923131SAndroid Build Coastguard Worker self.data = data 55*5a923131SAndroid Build Coastguard Worker self.scale = scale 56*5a923131SAndroid Build Coastguard Worker self.formatter = formatter or str 57*5a923131SAndroid Build Coastguard Worker self.max_key_len = max([len(str(key)) for key, count in self.data]) 58*5a923131SAndroid Build Coastguard Worker self.total = sum([count for key, count in self.data]) 59*5a923131SAndroid Build Coastguard Worker 60*5a923131SAndroid Build Coastguard Worker @staticmethod 61*5a923131SAndroid Build Coastguard Worker def FromCountDict(count_dict, scale=20, formatter=None, key_names=None): 62*5a923131SAndroid Build Coastguard Worker """Takes a dictionary of counts and returns a histogram object. 63*5a923131SAndroid Build Coastguard Worker 64*5a923131SAndroid Build Coastguard Worker This simply converts a mapping from names to counts into a list of (key, 65*5a923131SAndroid Build Coastguard Worker count) pairs, optionally translating keys into name strings, then 66*5a923131SAndroid Build Coastguard Worker generating and returning a histogram for them. This is a useful convenience 67*5a923131SAndroid Build Coastguard Worker call for clients that update a dictionary of counters as they (say) scan a 68*5a923131SAndroid Build Coastguard Worker data stream. 69*5a923131SAndroid Build Coastguard Worker 70*5a923131SAndroid Build Coastguard Worker Args: 71*5a923131SAndroid Build Coastguard Worker count_dict: dictionary mapping keys to occurrence counts 72*5a923131SAndroid Build Coastguard Worker scale: number of characters used to indicate 100% 73*5a923131SAndroid Build Coastguard Worker formatter: function used for formatting raw histogram values 74*5a923131SAndroid Build Coastguard Worker key_names: dictionary mapping keys to name strings 75*5a923131SAndroid Build Coastguard Worker Returns: 76*5a923131SAndroid Build Coastguard Worker A histogram object based on the given data. 77*5a923131SAndroid Build Coastguard Worker 78*5a923131SAndroid Build Coastguard Worker """ 79*5a923131SAndroid Build Coastguard Worker namer = None 80*5a923131SAndroid Build Coastguard Worker if key_names: 81*5a923131SAndroid Build Coastguard Worker namer = lambda key: key_names[key] 82*5a923131SAndroid Build Coastguard Worker else: 83*5a923131SAndroid Build Coastguard Worker namer = lambda key: key 84*5a923131SAndroid Build Coastguard Worker 85*5a923131SAndroid Build Coastguard Worker hist = [(namer(key), count) for key, count in count_dict.items()] 86*5a923131SAndroid Build Coastguard Worker return Histogram(hist, scale, formatter) 87*5a923131SAndroid Build Coastguard Worker 88*5a923131SAndroid Build Coastguard Worker @staticmethod 89*5a923131SAndroid Build Coastguard Worker def FromKeyList(key_list, scale=20, formatter=None, key_names=None): 90*5a923131SAndroid Build Coastguard Worker """Takes a list of (possibly recurring) keys and returns a histogram object. 91*5a923131SAndroid Build Coastguard Worker 92*5a923131SAndroid Build Coastguard Worker This converts the list into a dictionary of counters, then uses 93*5a923131SAndroid Build Coastguard Worker FromCountDict() to generate the actual histogram. For example: 94*5a923131SAndroid Build Coastguard Worker 95*5a923131SAndroid Build Coastguard Worker ['a', 'a', 'b', 'a', 'b'] --> {'a': 3, 'b': 2} --> ... 96*5a923131SAndroid Build Coastguard Worker 97*5a923131SAndroid Build Coastguard Worker Args: 98*5a923131SAndroid Build Coastguard Worker key_list: list of (possibly recurring) keys 99*5a923131SAndroid Build Coastguard Worker scale: number of characters used to indicate 100% 100*5a923131SAndroid Build Coastguard Worker formatter: function used for formatting raw histogram values 101*5a923131SAndroid Build Coastguard Worker key_names: dictionary mapping keys to name strings 102*5a923131SAndroid Build Coastguard Worker Returns: 103*5a923131SAndroid Build Coastguard Worker A histogram object based on the given data. 104*5a923131SAndroid Build Coastguard Worker 105*5a923131SAndroid Build Coastguard Worker """ 106*5a923131SAndroid Build Coastguard Worker count_dict = defaultdict(int) # Unset items default to zero 107*5a923131SAndroid Build Coastguard Worker for key in key_list: 108*5a923131SAndroid Build Coastguard Worker count_dict[key] += 1 109*5a923131SAndroid Build Coastguard Worker return Histogram.FromCountDict(count_dict, scale, formatter, key_names) 110*5a923131SAndroid Build Coastguard Worker 111*5a923131SAndroid Build Coastguard Worker def __str__(self): 112*5a923131SAndroid Build Coastguard Worker hist_lines = [] 113*5a923131SAndroid Build Coastguard Worker hist_bar = '|' 114*5a923131SAndroid Build Coastguard Worker for key, count in self.data: 115*5a923131SAndroid Build Coastguard Worker if self.total: 116*5a923131SAndroid Build Coastguard Worker bar_len = count * self.scale // self.total 117*5a923131SAndroid Build Coastguard Worker hist_bar = '|%s|' % ('#' * bar_len).ljust(self.scale) 118*5a923131SAndroid Build Coastguard Worker 119*5a923131SAndroid Build Coastguard Worker line = '%s %s %s' % ( 120*5a923131SAndroid Build Coastguard Worker str(key).ljust(self.max_key_len), 121*5a923131SAndroid Build Coastguard Worker hist_bar, 122*5a923131SAndroid Build Coastguard Worker self.formatter(count)) 123*5a923131SAndroid Build Coastguard Worker percent_str = format_utils.NumToPercent(count, self.total) 124*5a923131SAndroid Build Coastguard Worker if percent_str: 125*5a923131SAndroid Build Coastguard Worker line += ' (%s)' % percent_str 126*5a923131SAndroid Build Coastguard Worker hist_lines.append(line) 127*5a923131SAndroid Build Coastguard Worker 128*5a923131SAndroid Build Coastguard Worker return '\n'.join(hist_lines) 129*5a923131SAndroid Build Coastguard Worker 130*5a923131SAndroid Build Coastguard Worker def GetKeys(self): 131*5a923131SAndroid Build Coastguard Worker """Returns the keys of the histogram.""" 132*5a923131SAndroid Build Coastguard Worker return [key for key, _ in self.data] 133