1"""Metrics reporting module for Blueberry using protobuf.
2
3Internal reference
4"""
5
6from __future__ import absolute_import
7from __future__ import division
8from __future__ import print_function
9
10import base64
11import logging
12import time
13
14# Internal import
15
16
17class BluetoothMetricLogger(object):
18  """A class used for gathering metrics from tests and devices.
19
20  This class provides methods to allow test writers to easily export metrics
21  from their tests as protobuf messages.
22
23  Attributes:
24    _metrics: The Bluetooth test proto message to add metrics to.
25  """
26
27  def __init__(self, bluetooth_test_proto_message):
28    self._metrics = bluetooth_test_proto_message
29    self._start_time = int(time.time())
30
31  def add_primary_device_metrics(self, device):
32    """Adds primary device metrics to the test proto message.
33
34    Args:
35      device: The Bluetooth device object to gather device metrics from.
36    """
37    device_message = self._metrics.configuration_data.primary_device
38    message_fields = device_message.DESCRIPTOR.fields_by_name.keys()
39    try:
40      device_metrics_dict = device.get_device_info()
41    except AttributeError:
42      logging.info(
43          'Must implement get_device_info method for this controller in order to upload device metrics.'
44      )
45      return
46
47    for metric in device_metrics_dict:
48      if metric in message_fields:
49        setattr(device_message, metric, device_metrics_dict[metric])
50      else:
51        logging.info('%s is not a valid metric field.', metric)
52
53  def add_connected_device_metrics(self, device):
54    """Adds connected device metrics to the test proto message.
55
56    Args:
57      device: The Bluetooth device object to gather device metrics from.
58    """
59    device_message = self._metrics.configuration_data.connected_device
60    message_fields = device_message.DESCRIPTOR.fields_by_name.keys()
61    try:
62      device_metrics_dict = device.get_device_info()
63    except AttributeError:
64      logging.info(
65          'Must implement get_device_info method for this controller in order to upload device metrics.'
66      )
67      return
68
69    for metric in device_metrics_dict:
70      if metric in message_fields:
71        setattr(device_message, metric, device_metrics_dict[metric])
72      else:
73        logging.warning('%s is not a valid metric field.', metric)
74
75  def add_test_metrics(self, test_metrics_dict):
76    """Adds test metrics to the test proto message.
77
78    Args:
79      test_metrics_dict: A dictionary of metrics to add to the test proto
80      message. Metric will only be added if the key exists as a field in the
81      test proto message.
82    """
83    if hasattr(self._metrics, 'configuration_data'):
84      self._metrics.configuration_data.test_date_time = self._start_time
85    message_fields = self._metrics.DESCRIPTOR.fields_by_name.keys()
86    for metric in test_metrics_dict:
87      if metric in message_fields:
88        metric_value = test_metrics_dict[metric]
89        if isinstance(metric_value, (list, tuple)):
90          getattr(self._metrics, metric).extend(metric_value)
91        else:
92          setattr(self._metrics, metric, metric_value)
93      else:
94        logging.warning('%s is not a valid metric field.', metric)
95
96  def proto_message_to_base64(self):
97    """Converts a proto message to a base64 string.
98
99    Returns:
100    string, Message formatted as a base64 string.
101    """
102    return base64.b64encode(self._metrics.SerializeToString()).decode('utf-8')
103
104  def proto_message_to_ascii(self):
105    """Converts a proto message to an ASCII string.
106
107    Returns:
108      string, Message formatted as an ASCII string. Useful for debugging.
109    """
110    return text_format.MessageToString(self._metrics)
111