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