203 lines
7.0 KiB
Python
203 lines
7.0 KiB
Python
#
|
|
# 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())
|