1#!/usr/bin/env python3 2# 3# Copyright 2020 - 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 17from functools import wraps 18 19from acts import context 20from acts.metrics.core import ProtoMetric 21from acts.metrics.core import ProtoMetricPublisher 22from acts.metrics.loggers.protos.gen import acts_usage_metadata_pb2 23 24 25_usage_map = {} 26 27 28def record_api_usage(f): 29 """Decorator method that wraps api functions to log their usage. 30 Reads __module__ and __name__ attributes of the function to pass 31 as args to log_usage 32 33 Args: 34 f: The function to record. 35 36 Returns: 37 A wrapper function with the same name as the original 38 function that records function calls. 39 """ 40 @wraps(f) 41 def wrapper(*args, **kwargs): 42 log_usage(f.__module__, f.__name__) 43 return f(*args, **kwargs) 44 45 return wrapper 46 47 48def log_usage(module_name, func_name): 49 """Creates a dict key from the params and the current context, and 50 uses them to update a counter for that key. 51 Key is an instance of UsageMetadataKey, where 52 method_name: module_name.func_name 53 test_context: current_context.identifier 54 55 Args: 56 module_name: function module 57 func_name: function name 58 """ 59 current_context = context.get_current_context() 60 61 name = '.'.join([module_name, func_name]) 62 63 usage_key = UsageMetadataKey(name, current_context.identifier) 64 65 _usage_map[usage_key] = _usage_map.get(usage_key, 0) + 1 66 67 68def _reset(): 69 """Clear the _usage_map. 70 """ 71 _usage_map.clear() 72 73 74class UsageMetadataPublisher(ProtoMetricPublisher): 75 """Publisher with the added ability to convert the _usage_map into a 76 ProtoMetric object for publishing. 77 """ 78 79 def __init__(self): 80 """Initializes the publisher, passing RootContext to the parent 81 implementation. 82 """ 83 super().__init__(context.RootContext()) 84 85 def publish(self): 86 """Create a ProtoMetric object and call _publish_single implementation, 87 typically to write ProtoMetric to a file. 88 """ 89 metric = self._usage_map_to_proto_metric() 90 self._publish_single(metric) 91 92 def _usage_map_to_proto_metric(self): 93 """Iterate over _usage_map, creating an ActsMethodUsageMetadata for 94 each entry. 95 96 Returns: 97 ProtoMetric wrapper object with name='acts_usage_metadata' and 98 data=ActsTestRunMetadata() 99 """ 100 data = acts_usage_metadata_pb2.ActsTestRunMetadata() 101 102 for usage in _usage_map: 103 method_count = data.usage.add() 104 method_count.test_context = usage.test_context 105 method_count.method_identifier = usage.method_name 106 method_count.count = _usage_map[usage] 107 108 return ProtoMetric(name='acts_usage_metadata', data=data) 109 110 111class UsageMetadataKey: 112 """Dict key for counting method invocations. Used as keys in _usage_map. 113 Simple tuple object with hash and eq. 114 115 Attributes: 116 test_context: Identifier for calling test 117 method_name: Identifier for api method 118 """ 119 120 def __init__(self, method_name, test_context): 121 """Initialize a UsageMetadataKey. 122 123 Args: 124 method_name: Fully qualified name of the method to record, 125 typically an ACTS api method. 126 test_context: Contextual identifier 127 """ 128 self.test_context = test_context 129 self.method_name = method_name 130 131 def __hash__(self): 132 return hash(self.test_context) ^ hash(self.method_name) 133 134 def __eq__(self, other): 135 return (self.test_context == other.test_context and 136 self.method_name == other.method_name) 137 138 def __repr__(self): 139 return ('UsageMetadataKey{' + self.method_name + ', ' + 140 self.test_context + '}') 141