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 os 20import time 21 22from acts.metrics.core import ProtoMetric 23from acts.metrics.logger import MetricLogger 24from acts_contrib.test_utils.bt.loggers.protos import bluetooth_metric_pb2 25 26 27def recursive_assign(proto, dct): 28 """Assign values in dct to proto recursively.""" 29 for metric in dir(proto): 30 if metric in dct: 31 if (isinstance(dct[metric], dict) and 32 isinstance(getattr(proto, metric), message.Message)): 33 recursive_assign(getattr(proto, metric), dct[metric]) 34 else: 35 setattr(proto, metric, dct[metric]) 36 37 38class BluetoothMetricLogger(MetricLogger): 39 """A logger for gathering Bluetooth test metrics 40 41 Attributes: 42 proto_module: Module used to store Bluetooth metrics in a proto 43 results: Stores ProtoMetrics to be published for each logger context 44 proto_map: Maps test case names to the appropriate protos for each case 45 """ 46 47 def __init__(self, event): 48 super().__init__(event=event) 49 self.proto_module = bluetooth_metric_pb2 50 self.results = [] 51 self.start_time = int(time.time()) 52 53 self.proto_map = {'BluetoothPairAndConnectTest': self.proto_module 54 .BluetoothPairAndConnectTestResult(), 55 'BluetoothReconnectTest': self.proto_module 56 .BluetoothReconnectTestResult(), 57 'BluetoothThroughputTest': self.proto_module 58 .BluetoothDataTestResult(), 59 'BluetoothLatencyTest': self.proto_module 60 .BluetoothDataTestResult(), 61 'BtCodecSweepTest': self.proto_module 62 .BluetoothAudioTestResult(), 63 'BtRangeCodecTest': self.proto_module 64 .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 = {'device_class': 'phone', 86 'device_model': device.model, 87 'android_release_id': device.build_info['build_id'], 88 'android_build_type': device.build_info['build_type'], 89 'android_build_number': device.build_info[ 90 'incremental_build_id'], 91 'android_branch_name': 'git_qt-release', 92 'software_version': device.build_info['build_id']} 93 94 if device.__class__.__name__ == 'ParentDevice': 95 data = {'device_class': 'headset', 96 'device_model': device.dut_type, 97 'software_version': device.get_version()[1][ 98 'Fw Build Label'], 99 'android_build_number': device.version} 100 101 return data 102 103 def add_config_data_to_proto(self, proto, pri_device, conn_device=None): 104 """Add to configuration data field of proto. 105 106 Adds test start time and device configuration info. 107 Args: 108 proto: protobuf to add configuration data to. 109 pri_device: some controller object. 110 conn_device: optional second controller object. 111 """ 112 pri_device_proto = proto.configuration_data.primary_device 113 conn_device_proto = proto.configuration_data.connected_device 114 proto.configuration_data.test_date_time = self.start_time 115 116 pri_config = self.get_configuration_data(pri_device) 117 118 for metric in dir(pri_device_proto): 119 if metric in pri_config: 120 setattr(pri_device_proto, metric, pri_config[metric]) 121 122 if conn_device: 123 conn_config = self.get_configuration_data(conn_device) 124 125 for metric in dir(conn_device_proto): 126 if metric in conn_config: 127 setattr(conn_device_proto, metric, conn_config[metric]) 128 129 def get_proto_dict(self, test, proto): 130 """Return dict with proto, readable ascii proto, and test name.""" 131 return {'proto': base64.b64encode(ProtoMetric(test, proto) 132 .get_binary()).decode('utf-8'), 133 'proto_ascii': ProtoMetric(test, proto).get_ascii(), 134 'test_name': test} 135 136 def add_proto_to_results(self, proto, test): 137 """Adds proto as ProtoMetric object to self.results.""" 138 self.results.append(ProtoMetric(test, proto)) 139 140 def get_results(self, results, test, pri_device, conn_device=None): 141 """Gets the metrics associated with each test case. 142 143 Gets the test case metrics and configuration data for each test case and 144 stores them for publishing. 145 146 Args: 147 results: A dictionary containing test metrics. 148 test: The name of the test case associated with these results. 149 pri_device: The primary AndroidDevice object for the test. 150 conn_device: The connected AndroidDevice object for the test, if 151 applicable. 152 153 """ 154 155 proto_result = self.proto_map[test] 156 recursive_assign(proto_result, results) 157 self.add_config_data_to_proto(proto_result, pri_device, conn_device) 158 self.add_proto_to_results(proto_result, test) 159 return self.get_proto_dict(test, proto_result) 160 161 def end(self, event): 162 return self.publisher.publish(self.results) 163