1"""Extracts media info metrics from media info data points.""" 2 3import collections 4import enum 5 6 7# Direction and type numbers map to constants in the DataPoint group in 8# callstats.proto 9class Direction(enum.Enum): 10 """ 11 Possible directions for media entries of a data point. 12 """ 13 SENDER = 0 14 RECEIVER = 1 15 BANDWIDTH_ESTIMATION = 2 16 17 18class MediaType(enum.Enum): 19 """ 20 Possible media types for media entries of a data point. 21 """ 22 AUDIO = 1 23 VIDEO = 2 24 DATA = 3 25 26 27TimestampedValues = collections.namedtuple('TimestampedValues', 28 ['TimestampEpochSeconds', 29 'ValueList']) 30 31 32class MediaInfoMetricsExtractor(object): 33 """ 34 Extracts media metrics from a list of raw media info data points. 35 36 Media info datapoints are expected to be dictionaries in the format returned 37 by cfm_facade.get_media_info_data_points. 38 """ 39 40 def __init__(self, data_points): 41 """ 42 Initializes with a set of data points. 43 44 @param data_points Data points as a list of dictionaries. Dictionaries 45 should be in the format returned by 46 cfm_facade.get_media_info_data_points. I.e., as returned when 47 querying the Browser Window for datapoints when the ExportMediaInfo 48 mod is active. 49 """ 50 self._data_points = data_points 51 52 def get_top_level_metric(self, name): 53 """ 54 Gets top level metrics. 55 56 Gets metrics that are global for one datapoint. I.e., metrics that 57 are not in the media list, such as CPU usage. 58 59 @param name Name of the metric. Names map to the names in the DataPoint 60 group in callstats.proto. 61 62 @returns A list with TimestampedValues. The ValueList is guaranteed to 63 not contain any None values. 64 """ 65 metrics = [] 66 for data_point in self._data_points: 67 value = data_point.get(name) 68 if value is not None: 69 timestamp = data_point['timestamp'] 70 metrics.append(TimestampedValues(timestamp, [value])) 71 return metrics 72 73 def get_media_metric(self, 74 name, 75 direction=None, 76 media_type=None, 77 post_process_func=lambda x: x): 78 """ 79 Gets media metrics. 80 81 Gets metrics that are in the media part of the datapoint. A DataPoint 82 often contains many media entries, why there are multiple values for a 83 specific name. 84 85 @param name Name of the metric. Names map to the names in the DataPoint 86 group in callstats.proto. 87 @param direction: Only include metrics with this direction in the media 88 stream. See the Direction constants in this module. If None, all 89 directions are included. 90 @param media_type: Only include metrics with this media type in the 91 media stream. See the MediaType constants in this module. If None, 92 all media types are included. 93 @param post_process_func: Function that takes a list of values and 94 optionally transforms it, returning the updated list or a scalar 95 value. Default is to return the unmodified list. This method is 96 called for the list of values in the same datapoint. Example usage 97 is to sum the received audio bytes for all streams for one logged 98 line. 99 100 @returns A list with TimestampedValues. The ValueList is guaranteed to 101 not contain any None values. 102 """ 103 metrics = [] 104 for data_point in self._data_points: 105 timestamp = data_point['timestamp'] 106 values = [ 107 media[name] for media in data_point['media'] 108 if name in media 109 and _media_matches(media, direction, media_type) 110 ] 111 # Filter None values and only add the metric if there is at least 112 # one value left. 113 values = [x for x in values if x is not None] 114 if values: 115 values = post_process_func(values) 116 values = values if isinstance(values, list) else [values] 117 metrics.append(TimestampedValues(timestamp, values)) 118 return metrics 119 120def _media_matches(media, direction, media_type): 121 direction_match = (True if direction is None 122 else media['direction'] == direction.value) 123 type_match = (True if media_type is None 124 else media['mediatype'] == media_type.value) 125 return direction_match and type_match 126