1#!/usr/bin/env python3
2#
3#   Copyright 2016 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of 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,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import shutil
18
19from acts.metrics.core import ProtoMetric
20from acts.metrics.logger import MetricLogger
21
22
23class BlackboxMappedMetricLogger(MetricLogger):
24    """A MetricLogger for logging and publishing Blackbox metrics from a dict.
25    The dict maps the metric name to the metric value.
26
27    The logger will publish an ActsBlackboxMetricResult message, containing
28    data intended to be uploaded to Blackbox. The message itself contains only
29    minimal information specific to the metric, with the intention being that
30    all other metadata is extracted from the test_run_summary.json.
31
32    This logger will extract an attribute from the test class as the metric
33    result. The metric key will be either the context's identifier or a custom
34    value assigned to this class.
35
36    Attributes:
37        proto_module: The proto module for ActsBlackboxMetricResult.
38        metric_key: The metric key to use. If unset, the logger will use the
39                    context's identifier.
40        _metric_map: the map of metric_name -> metric_value to publish
41                to blackbox. If the metric value is set to None, the
42                metric will not be reported.
43    """
44
45    PROTO_FILE = 'protos/acts_blackbox.proto'
46
47    def __init__(self, metric_key=None, event=None, compiler_out=None):
48        """Initializes a logger for Blackbox metrics.
49
50        Args:
51            metric_key: The metric key to use. If unset, the logger will use
52                        the context's identifier.
53            event: The event triggering the creation of this logger.
54            compiler_out: The directory to store the compiled proto module.
55        """
56        super().__init__(event=event)
57        self.proto_module = self._compile_proto(self.PROTO_FILE,
58                                                compiler_out=compiler_out)
59        self.metric_key = metric_key
60        self._metric_map = {}
61
62    def _get_metric_key(self, metric_name):
63        """Gets the metric key to use.
64
65        If the metric_key is explicitly set, returns that value. Otherwise,
66        extracts an identifier from the context.
67
68        Args:
69            metric_name: The name of the metric to report.
70        """
71        if self.metric_key:
72            key = self.metric_key
73        else:
74            key = self._get_blackbox_identifier()
75        key = '%s.%s' % (key, metric_name)
76        return key
77
78    def set_metric_data(self, metric_map):
79        """Sets the map of metrics to be uploaded to Blackbox. Note that
80        this will overwrite all existing added by this function or add_metric.
81
82        Args:
83            metric_map: the map of metric_name -> metric_value to publish
84                to blackbox. If the metric value is set to None, the
85                metric will not be reported.
86        """
87        self._metric_map = metric_map
88
89    def add_metric(self, metric_name, metric_value):
90        """Adds a metric value to be published later.
91
92        Note that if the metric name has already been added, the metric value
93        will be overwritten.
94
95        Args:
96            metric_name: the name of the metric.
97            metric_value: the value of the metric.
98        """
99        self._metric_map[metric_name] = metric_value
100
101    def _get_blackbox_identifier(self):
102        """Returns the testcase identifier, as expected by Blackbox."""
103        # b/119787228: Blackbox requires function names to look like Java
104        # functions.
105        identifier = self.context.identifier
106        parts = identifier.rsplit('.', 1)
107        return '#'.join(parts)
108
109    def end(self, _):
110        """Creates and publishes a ProtoMetric with blackbox data.
111
112        Builds a list of ActsBlackboxMetricResult messages from the set
113        metric data, and sends them to the publisher.
114        """
115        metrics = []
116        for metric_name, metric_value in self._metric_map.items():
117            if metric_value is None:
118                continue
119            result = self.proto_module.ActsBlackboxMetricResult()
120            result.test_identifier = self._get_blackbox_identifier()
121            result.metric_key = self._get_metric_key(metric_name)
122            result.metric_value = metric_value
123
124            metrics.append(
125                ProtoMetric(name='blackbox_%s' % metric_name, data=result))
126
127        return self.publisher.publish(metrics)
128
129
130class BlackboxMetricLogger(BlackboxMappedMetricLogger):
131    """A MetricLogger for logging and publishing individual Blackbox metrics.
132
133    For additional information on reporting to Blackbox, see
134    BlackboxMappedMetricLogger.
135
136    Attributes:
137        proto_module: The proto module for ActsBlackboxMetricResult.
138        metric_name: The name of the metric, used to determine output filename.
139        metric_key: The metric key to use. If unset, the logger will use the
140                    context's identifier.
141        metric_value: The metric value.
142    """
143
144    def __init__(self, metric_name, metric_key=None, event=None,
145                 compiler_out=None):
146        """Initializes a logger for Blackbox metrics.
147
148        Args:
149            metric_name: The name of the metric.
150            metric_key: The metric key to use. If unset, the logger will use
151                        the context's identifier.
152            event: The event triggering the creation of this logger.
153            compiler_out: The directory to store the compiled proto module
154        """
155        super().__init__(metric_key=metric_key, event=event,
156                         compiler_out=compiler_out)
157        if not metric_name:
158            raise ValueError("metric_name must be supplied.")
159        self.metric_name = metric_name
160        self.metric_value = None
161
162    @property
163    def metric_value(self):
164        return self._metric_map[self.metric_name]
165
166    @metric_value.setter
167    def metric_value(self, value):
168        self.add_metric(self.metric_name, value)
169