xref: /aosp_15_r20/system/update_engine/scripts/update_payload/histogram.py (revision 5a9231315b4521097b8dc3750bc806fcafe0c72f)
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