1# /usr/bin/env python3 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# 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, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16 17import base64 18from google.protobuf import message 19import time 20 21from acts.metrics.core import ProtoMetric 22from acts.metrics.logger import MetricLogger 23from acts_contrib.test_utils.bt.loggers.protos import bluetooth_metric_pb2 24 25 26def recursive_assign(proto, dct): 27 """Assign values in dct to proto recursively.""" 28 for metric in dir(proto): 29 if metric in dct: 30 if (isinstance(dct[metric], dict) 31 and isinstance(getattr(proto, metric), message.Message)): 32 recursive_assign(getattr(proto, metric), dct[metric]) 33 else: 34 setattr(proto, metric, dct[metric]) 35 36 37class BluetoothMetricLogger(MetricLogger): 38 """A logger for gathering Bluetooth test metrics 39 40 Attributes: 41 proto_module: Module used to store Bluetooth metrics in a proto 42 results: Stores ProtoMetrics to be published for each logger context 43 proto_map: Maps test case names to the appropriate protos for each case 44 """ 45 46 def __init__(self, event): 47 super().__init__(event=event) 48 self.proto_module = bluetooth_metric_pb2 49 self.results = [] 50 self.start_time = int(time.time()) 51 52 self.proto_map = { 53 'BluetoothPairAndConnectTest': 54 self.proto_module.BluetoothPairAndConnectTestResult(), 55 'BluetoothReconnectTest': 56 self.proto_module.BluetoothReconnectTestResult(), 57 'BluetoothThroughputTest': 58 self.proto_module.BluetoothDataTestResult(), 59 'BluetoothLatencyTest': 60 self.proto_module.BluetoothDataTestResult(), 61 'BtCodecSweepTest': 62 self.proto_module.BluetoothAudioTestResult(), 63 'BtRangeCodecTest': 64 self.proto_module.BluetoothAudioTestResult(), 65 } 66 67 @staticmethod 68 def get_configuration_data(device): 69 """Gets the configuration data of a device. 70 71 Gets the configuration data of a device and organizes it in a 72 dictionary. 73 74 Args: 75 device: The device object to get the configuration data from. 76 77 Returns: 78 A dictionary containing configuration data of a device. 79 """ 80 # TODO(b/126931820): Genericize and move to lib when generic DUT interface is implemented 81 data = {'device_class': device.__class__.__name__} 82 83 if device.__class__.__name__ == 'AndroidDevice': 84 # TODO(b/124066126): Add remaining config data 85 data = { 86 'device_class': 'phone', 87 'device_model': device.model, 88 'android_release_id': device.build_info['build_id'], 89 'android_build_type': device.build_info['build_type'], 90 'android_build_number': 91 device.build_info['incremental_build_id'], 92 'android_branch_name': 'git_qt-release', 93 'software_version': device.build_info['build_id'] 94 } 95 96 if device.__class__.__name__ == 'ParentDevice': 97 data = { 98 'device_class': 'headset', 99 'device_model': device.dut_type, 100 'software_version': device.get_version()[1]['Fw Build Label'], 101 'android_build_number': device.version 102 } 103 104 return data 105 106 def add_config_data_to_proto(self, proto, pri_device, conn_device=None): 107 """Add to configuration data field of proto. 108 109 Adds test start time and device configuration info. 110 Args: 111 proto: protobuf to add configuration data to. 112 pri_device: some controller object. 113 conn_device: optional second controller object. 114 """ 115 pri_device_proto = proto.configuration_data.primary_device 116 conn_device_proto = proto.configuration_data.connected_device 117 proto.configuration_data.test_date_time = self.start_time 118 119 pri_config = self.get_configuration_data(pri_device) 120 121 for metric in dir(pri_device_proto): 122 if metric in pri_config: 123 setattr(pri_device_proto, metric, pri_config[metric]) 124 125 if conn_device: 126 conn_config = self.get_configuration_data(conn_device) 127 128 for metric in dir(conn_device_proto): 129 if metric in conn_config: 130 setattr(conn_device_proto, metric, conn_config[metric]) 131 132 def get_proto_dict(self, test, proto): 133 """Return dict with proto, readable ascii proto, and test name.""" 134 return { 135 'proto': 136 base64.b64encode(ProtoMetric(test, 137 proto).get_binary()).decode('utf-8'), 138 'proto_ascii': 139 ProtoMetric(test, proto).get_ascii(), 140 'test_name': 141 test 142 } 143 144 def add_proto_to_results(self, proto, test): 145 """Adds proto as ProtoMetric object to self.results.""" 146 self.results.append(ProtoMetric(test, proto)) 147 148 def get_results(self, results, test, pri_device, conn_device=None): 149 """Gets the metrics associated with each test case. 150 151 Gets the test case metrics and configuration data for each test case and 152 stores them for publishing. 153 154 Args: 155 results: A dictionary containing test metrics. 156 test: The name of the test case associated with these results. 157 pri_device: The primary AndroidDevice object for the test. 158 conn_device: The connected AndroidDevice object for the test, if 159 applicable. 160 161 """ 162 163 proto_result = self.proto_map[test] 164 recursive_assign(proto_result, results) 165 self.add_config_data_to_proto(proto_result, pri_device, conn_device) 166 self.add_proto_to_results(proto_result, test) 167 return self.get_proto_dict(test, proto_result) 168 169 def end(self, event): 170 return self.publisher.publish(self.results) 171