1# 2# Copyright (C) 2018 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import datetime 17import logging 18import operator 19import os 20import time 21 22from vts.runners.host import logger 23from vts.utils.python.instrumentation import test_framework_instrumentation_categories as tfic 24from vts.utils.python.instrumentation import test_framework_instrumentation_event as tfie 25 26 27# global category listing 28categories = tfic.TestFrameworkInstrumentationCategories() 29# TODO(yuexima): use data class 30counts = {} 31 32DEFAULT_CATEGORY = 'Misc' 33DEFAULT_FILE_NAME_TEXT_RESULT = 'instrumentation_data.txt' 34 35 36def Begin(name, category=DEFAULT_CATEGORY, enable_logging=None, disable_subevent_logging=False): 37 """Marks the beginning of an event. 38 39 Params: 40 name: string, name of the event. 41 category: string, category of the event. Default category will be used if not specified. 42 enable_logging: bool or None. Whether to put the event in logging. 43 Should be set to False when timing small pieces of code that could take 44 very short time to run. 45 If not specified or is None, global configuration will be used. 46 disable_subevent_logging: bool, whether to disable logging for events created after this 47 event begins and before this event ends. This will overwrite 48 subevent's logging setting if set to True. 49 50 Returns: 51 Event object representing the event 52 """ 53 event = tfie.TestFrameworkInstrumentationEvent(name, category) 54 event.Begin(enable_logging=enable_logging, disable_subevent_logging=disable_subevent_logging) 55 return event 56 57 58def End(name, category=DEFAULT_CATEGORY): 59 """Marks the end of an event. 60 61 This function tries to find an event in internal event stack by calling FindEvent 62 method with the given category and name. 63 64 Will log error and return None if no match is found. 65 66 If multiple event with the same category and name are found, the last one will be used. 67 68 Use this function with caution if there are multiple events began with the same name and 69 category. It is highly recommended to call End() method from the Event object directly. 70 71 Params: 72 name: string, name of the event. 73 category: string, category of the event. Default category will be used if not specified. 74 75 Returns: 76 Event object representing the event. None if cannot find an active matching event 77 """ 78 event = FindEvent(name, category) 79 if not event: 80 logging.error('Event with category %s and name %s either does not ' 81 'exists or has already ended. Skipping...', name, category) 82 return None 83 84 event.End() 85 return event 86 87 88def FindEvent(name, category=DEFAULT_CATEGORY): 89 """Finds an existing event that has started given the names. 90 91 Use this function with caution if there are multiple events began with the same name and 92 category. It is highly recommended to call End() method from the Event object directly. 93 94 Params: 95 name: string, name of the event. 96 category: string, category of the event. Default category will be used if not specified. 97 98 Returns: 99 TestFrameworkInstrumentationEvent object if found; None otherwise. 100 """ 101 for event in reversed(tfie.event_stack): 102 if event.Match(name, category): 103 return event 104 105 return None 106 107 108def Count(name, category=DEFAULT_CATEGORY): 109 """Counts the occurrence of an event. 110 111 Events will be mapped using name and category as key. 112 113 Params: 114 name: string, name of the event. 115 category: string, category of the event. Default category will be used if not specified. 116 """ 117 name, category = tfie.NormalizeNameCategory(name, category) 118 # TODO(yuexima): give warning when there's illegal char, but only once for each combination.' 119 if (name, category) not in counts: 120 counts[(name, category)] = [time.time()] 121 else: 122 counts[name, category].append(time.time()) 123 124 125def GenerateTextReport(): 126 """Compile instrumentation results into a simple text output format for visualization. 127 128 Returns: 129 a string containing result text. 130 """ 131 class EventItem: 132 """Temporary data storage class for instrumentation text result generation. 133 134 Attributes: 135 name: string, event name 136 category: string, event category 137 time_cpu: float, CPU time of the event (can be begin or end) 138 time_wall: float, wall time of the event (can be begin or end) 139 type: string, begin or end 140 """ 141 name = '' 142 category = '' 143 time_cpu = -1 144 time_wall = -1 145 type = '' 146 duration = -1 147 148 results = [] 149 150 for event in tfie.event_data: 151 ei = EventItem() 152 ei.name = event.name 153 ei.category = event.category 154 ei.type = 'begin' 155 ei.time_cpu = event.timestamp_begin_cpu 156 ei.time_wall = event.timestamp_begin_wall 157 results.append(ei) 158 159 for event in tfie.event_data: 160 ei = EventItem() 161 ei.name = event.name 162 ei.category = event.category 163 ei.type = 'end' 164 ei.time_cpu = event.timestamp_end_cpu 165 ei.time_wall = event.timestamp_end_wall 166 ei.duration = event.timestamp_end_wall - event.timestamp_begin_wall 167 results.append(ei) 168 169 results.sort(key=operator.attrgetter('time_cpu')) 170 171 result_text = [] 172 173 level = 0 174 for e in results: 175 if e.type == 'begin': 176 s = ('Begin [%s @ %s] ' % (e.name, e.category) + 177 datetime.datetime.fromtimestamp(e.time_wall).strftime('%Y-%m-%d %H:%M:%S_%f')) 178 result_text.append(' '*level + s) 179 level += 1 180 else: 181 s = ('End [%s @ %s] ' % (e.name, e.category) + 182 datetime.datetime.fromtimestamp(e.time_wall).strftime('%Y-%m-%d %H:%M:%S_%f')) 183 level -= 1 184 result_text.append(' '*level + s) 185 result_text.append('\n') 186 result_text.append(' '*level + "%.4f" % e.duration) 187 result_text.append('\n') 188 189 return ''.join(result_text) 190 191 192def CompileResults(directory=None, filename=DEFAULT_FILE_NAME_TEXT_RESULT): 193 """Writes instrumentation results into a simple text output format for visualization. 194 195 Args: 196 directory: string, parent path of result 197 filename: string, result file name 198 """ 199 if not directory: 200 directory = logging.log_path 201 with open(os.path.join(directory, filename), 'w') as f: 202 f.write(GenerateTextReport()) 203