# # Copyright (C) 2018 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import logging import operator import os import time from vts.runners.host import logger from vts.utils.python.instrumentation import test_framework_instrumentation_categories as tfic from vts.utils.python.instrumentation import test_framework_instrumentation_event as tfie # global category listing categories = tfic.TestFrameworkInstrumentationCategories() # TODO(yuexima): use data class counts = {} DEFAULT_CATEGORY = 'Misc' DEFAULT_FILE_NAME_TEXT_RESULT = 'instrumentation_data.txt' def Begin(name, category=DEFAULT_CATEGORY, enable_logging=None, disable_subevent_logging=False): """Marks the beginning of an event. Params: name: string, name of the event. category: string, category of the event. Default category will be used if not specified. enable_logging: bool or None. Whether to put the event in logging. Should be set to False when timing small pieces of code that could take very short time to run. If not specified or is None, global configuration will be used. disable_subevent_logging: bool, whether to disable logging for events created after this event begins and before this event ends. This will overwrite subevent's logging setting if set to True. Returns: Event object representing the event """ event = tfie.TestFrameworkInstrumentationEvent(name, category) event.Begin(enable_logging=enable_logging, disable_subevent_logging=disable_subevent_logging) return event def End(name, category=DEFAULT_CATEGORY): """Marks the end of an event. This function tries to find an event in internal event stack by calling FindEvent method with the given category and name. Will log error and return None if no match is found. If multiple event with the same category and name are found, the last one will be used. Use this function with caution if there are multiple events began with the same name and category. It is highly recommended to call End() method from the Event object directly. Params: name: string, name of the event. category: string, category of the event. Default category will be used if not specified. Returns: Event object representing the event. None if cannot find an active matching event """ event = FindEvent(name, category) if not event: logging.error('Event with category %s and name %s either does not ' 'exists or has already ended. Skipping...', name, category) return None event.End() return event def FindEvent(name, category=DEFAULT_CATEGORY): """Finds an existing event that has started given the names. Use this function with caution if there are multiple events began with the same name and category. It is highly recommended to call End() method from the Event object directly. Params: name: string, name of the event. category: string, category of the event. Default category will be used if not specified. Returns: TestFrameworkInstrumentationEvent object if found; None otherwise. """ for event in reversed(tfie.event_stack): if event.Match(name, category): return event return None def Count(name, category=DEFAULT_CATEGORY): """Counts the occurrence of an event. Events will be mapped using name and category as key. Params: name: string, name of the event. category: string, category of the event. Default category will be used if not specified. """ name, category = tfie.NormalizeNameCategory(name, category) # TODO(yuexima): give warning when there's illegal char, but only once for each combination.' if (name, category) not in counts: counts[(name, category)] = [time.time()] else: counts[name, category].append(time.time()) def GenerateTextReport(): """Compile instrumentation results into a simple text output format for visualization. Returns: a string containing result text. """ class EventItem: """Temporary data storage class for instrumentation text result generation. Attributes: name: string, event name category: string, event category time_cpu: float, CPU time of the event (can be begin or end) time_wall: float, wall time of the event (can be begin or end) type: string, begin or end """ name = '' category = '' time_cpu = -1 time_wall = -1 type = '' duration = -1 results = [] for event in tfie.event_data: ei = EventItem() ei.name = event.name ei.category = event.category ei.type = 'begin' ei.time_cpu = event.timestamp_begin_cpu ei.time_wall = event.timestamp_begin_wall results.append(ei) for event in tfie.event_data: ei = EventItem() ei.name = event.name ei.category = event.category ei.type = 'end' ei.time_cpu = event.timestamp_end_cpu ei.time_wall = event.timestamp_end_wall ei.duration = event.timestamp_end_wall - event.timestamp_begin_wall results.append(ei) results.sort(key=operator.attrgetter('time_cpu')) result_text = [] level = 0 for e in results: if e.type == 'begin': s = ('Begin [%s @ %s] ' % (e.name, e.category) + datetime.datetime.fromtimestamp(e.time_wall).strftime('%Y-%m-%d %H:%M:%S_%f')) result_text.append(' '*level + s) level += 1 else: s = ('End [%s @ %s] ' % (e.name, e.category) + datetime.datetime.fromtimestamp(e.time_wall).strftime('%Y-%m-%d %H:%M:%S_%f')) level -= 1 result_text.append(' '*level + s) result_text.append('\n') result_text.append(' '*level + "%.4f" % e.duration) result_text.append('\n') return ''.join(result_text) def CompileResults(directory=None, filename=DEFAULT_FILE_NAME_TEXT_RESULT): """Writes instrumentation results into a simple text output format for visualization. Args: directory: string, parent path of result filename: string, result file name """ if not directory: directory = logging.log_path with open(os.path.join(directory, filename), 'w') as f: f.write(GenerateTextReport())