OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import copy | 5 import copy |
6 import json | 6 import json |
| 7 import logging |
7 import os | 8 import os |
| 9 import shutil |
| 10 import subprocess |
8 import tempfile | 11 import tempfile |
9 import zipfile | 12 |
| 13 from telemetry.core import util |
| 14 |
| 15 |
| 16 _TRACE2HTML_PATH = os.path.join( |
| 17 util.GetCatapultDir(), 'tracing', 'bin', 'trace2html') |
| 18 |
10 | 19 |
11 class NonSerializableTraceData(Exception): | 20 class NonSerializableTraceData(Exception): |
12 """Raised when raw trace data cannot be serialized to TraceData.""" | 21 """Raised when raw trace data cannot be serialized to TraceData.""" |
13 pass | 22 pass |
14 | 23 |
15 | 24 |
16 class TraceDataPart(object): | 25 class TraceDataPart(object): |
17 """TraceData can have a variety of events. | 26 """TraceData can have a variety of events. |
18 | 27 |
19 These are called "parts" and are accessed by the following fixed field names. | 28 These are called "parts" and are accessed by the following fixed field names. |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
55 | 64 |
56 ALL_TRACE_PARTS_RAW_NAMES = set(k.raw_field_name for k in ALL_TRACE_PARTS) | 65 ALL_TRACE_PARTS_RAW_NAMES = set(k.raw_field_name for k in ALL_TRACE_PARTS) |
57 | 66 |
58 def _HasTraceFor(part, raw): | 67 def _HasTraceFor(part, raw): |
59 assert isinstance(part, TraceDataPart) | 68 assert isinstance(part, TraceDataPart) |
60 if part.raw_field_name not in raw: | 69 if part.raw_field_name not in raw: |
61 return False | 70 return False |
62 return len(raw[part.raw_field_name]) > 0 | 71 return len(raw[part.raw_field_name]) > 0 |
63 | 72 |
64 | 73 |
| 74 def _GetFilePathForTrace(trace, dir_path): |
| 75 """ Return path to a file that contains |trace|. |
| 76 |
| 77 Note: if |trace| is an instance of TraceFileHandle, this reuses the trace path |
| 78 that the trace file handle holds. Otherwise, it creates a new trace file |
| 79 in |dir_path| directory. |
| 80 """ |
| 81 if isinstance(trace, TraceFileHandle): |
| 82 return trace.file_path |
| 83 with tempfile.NamedTemporaryFile(mode='w', dir=dir_path, delete=False) as fp: |
| 84 if isinstance(trace, basestring): |
| 85 fp.write(trace) |
| 86 elif isinstance(trace, dict) or isinstance(trace, list): |
| 87 json.dump(trace, fp) |
| 88 else: |
| 89 raise TypeError('Trace is of unknown type.') |
| 90 return fp.name |
| 91 |
| 92 |
65 class TraceData(object): | 93 class TraceData(object): |
66 """ TraceData holds a collection of traces from multiple sources. | 94 """ TraceData holds a collection of traces from multiple sources. |
67 | 95 |
68 A TraceData can have multiple active parts. Each part represents traces | 96 A TraceData can have multiple active parts. Each part represents traces |
69 collected from a different trace agent. | 97 collected from a different trace agent. |
70 """ | 98 """ |
71 def __init__(self): | 99 def __init__(self): |
72 """Creates TraceData from the given data.""" | 100 """Creates TraceData from the given data.""" |
73 self._raw_data = {} | 101 self._raw_data = {} |
74 self._events_are_safely_mutable = False | 102 self._events_are_safely_mutable = False |
(...skipping 18 matching lines...) Expand all Loading... |
93 return self._events_are_safely_mutable | 121 return self._events_are_safely_mutable |
94 | 122 |
95 @property | 123 @property |
96 def active_parts(self): | 124 def active_parts(self): |
97 return {p for p in ALL_TRACE_PARTS if p.raw_field_name in self._raw_data} | 125 return {p for p in ALL_TRACE_PARTS if p.raw_field_name in self._raw_data} |
98 | 126 |
99 def HasTracesFor(self, part): | 127 def HasTracesFor(self, part): |
100 return _HasTraceFor(part, self._raw_data) | 128 return _HasTraceFor(part, self._raw_data) |
101 | 129 |
102 def GetTracesFor(self, part): | 130 def GetTracesFor(self, part): |
| 131 """ Return the list of traces for |part| in string or dictionary forms. |
| 132 |
| 133 Note: since this API return the traces that can be directly accessed in |
| 134 memory, it may require lots of memory usage as some of the trace can be |
| 135 very big. |
| 136 For references, we have cases where Telemetry is OOM'ed because the memory |
| 137 required for processing the trace in Python is too big (crbug.com/672097). |
| 138 """ |
| 139 assert isinstance(part, TraceDataPart) |
103 if not self.HasTracesFor(part): | 140 if not self.HasTracesFor(part): |
104 return [] | 141 return [] |
105 assert isinstance(part, TraceDataPart) | 142 traces_list = self._raw_data[part.raw_field_name] |
106 return self._raw_data[part.raw_field_name] | 143 # Since this API return the traces in memory form, and since the memory |
| 144 # bottleneck of Telemetry is for keeping trace in memory, there is no uses |
| 145 # in keeping the on-disk form of tracing beyond this point. Hence we convert |
| 146 # all traces for part of form TraceFileHandle to the JSON form. |
| 147 for i, data in enumerate(traces_list): |
| 148 if isinstance(data, TraceFileHandle): |
| 149 traces_list[i] = data.AsTraceData() |
| 150 return traces_list |
107 | 151 |
108 def GetTraceFor(self, part): | 152 def GetTraceFor(self, part): |
109 assert isinstance(part, TraceDataPart) | 153 assert isinstance(part, TraceDataPart) |
110 traces = self._raw_data[part.raw_field_name] | 154 traces = self.GetTracesFor(part) |
111 assert len(traces) == 1 | 155 assert len(traces) == 1 |
112 if isinstance(traces[0], TraceFileHandle): | 156 return traces[0] |
113 return traces[0].AsTraceData() | |
114 else: | |
115 return traces[0] | |
116 | 157 |
| 158 def CleanUpAllTraces(self): |
| 159 """ Remove all the traces that this has handles to. |
117 | 160 |
118 # TODO(nedn): unifying this code with | 161 Those include traces stored in memory & on disk. After invoking this, |
119 # telemetry.value.trace.TraceValue._GetTempFileHandle so that we have one | 162 one can no longer uses this object for collecting the traces. |
120 # single space efficient method to Serialize this to trace. | 163 """ |
121 def Serialize(self, f, gzip_result=False): | 164 for traces_list in self._raw_data.itervalues(): |
122 """Serializes the trace result to a file-like object. | 165 for trace in traces_list: |
| 166 if isinstance(trace, TraceFileHandle): |
| 167 trace.Clean() |
| 168 self._raw_data = {} |
123 | 169 |
124 Write in trace container format if gzip_result=False. | 170 def Serialize(self, file_path, trace_title=''): |
125 Writes to a .zip file if gzip_result=True. | 171 """Serializes the trace result to |file_path|. |
| 172 |
126 """ | 173 """ |
127 raw_data = {} | 174 if not self._raw_data: |
128 for k, v in self._raw_data.iteritems(): | 175 logging.warning('No traces to convert to html.') |
129 for data in v: | 176 return |
130 if isinstance(data, TraceFileHandle): | 177 temp_dir = tempfile.mkdtemp() |
131 with open(data.file_path) as trace_file: | 178 trace_files = [] |
132 trace = json.load(trace_file) | 179 try: |
133 else: | 180 trace_size_data = {} |
134 trace = data | 181 for part, traces_list in self._raw_data.iteritems(): |
135 if k not in raw_data: | 182 for trace in traces_list: |
136 raw_data[k] = trace | 183 path = _GetFilePathForTrace(trace, temp_dir) |
137 else: | 184 trace_size_data.setdefault(part, 0) |
138 raw_data[k] += trace | 185 trace_size_data[part] += os.path.getsize(path) |
139 if gzip_result: | 186 trace_files.append(path) |
140 zip_file = zipfile.ZipFile(f, mode='w') | 187 logging.info('Trace sizes in bytes: %s', trace_size_data) |
141 try: | 188 |
142 for part in self.active_parts: | 189 cmd = (['python', _TRACE2HTML_PATH] + trace_files + |
143 tmp_file_name = None | 190 ['--output', file_path] + ['--title', trace_title]) |
144 with tempfile.NamedTemporaryFile(delete=False) as tmp_file: | 191 subprocess.check_output(cmd) |
145 tmp_file_name = tmp_file.name | 192 finally: |
146 tmp_file.write(str(raw_data[part.raw_field_name])) | 193 shutil.rmtree(temp_dir) |
147 zip_file.write(tmp_file_name, arcname=part.raw_field_name) | |
148 os.remove(tmp_file_name) | |
149 finally: | |
150 zip_file.close() | |
151 else: | |
152 json.dump(raw_data, f) | |
153 | 194 |
154 | 195 |
155 class TraceFileHandle(object): | 196 class TraceFileHandle(object): |
156 """A trace file handle object allows storing trace data on disk. | 197 """A trace file handle object allows storing trace data on disk. |
157 | 198 |
158 TraceFileHandle API allows one to collect traces from Chrome into disk instead | 199 TraceFileHandle API allows one to collect traces from Chrome into disk instead |
159 of keeping them in memory. This is important for keeping memory usage of | 200 of keeping them in memory. This is important for keeping memory usage of |
160 Telemetry low to avoid OOM (see: | 201 Telemetry low to avoid OOM (see: |
161 https://github.com/catapult-project/catapult/issues/3119). | 202 https://github.com/catapult-project/catapult/issues/3119). |
162 | 203 |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
285 b.AddTraceFor(TraceDataPart(k), json_data[k]) | 326 b.AddTraceFor(TraceDataPart(k), json_data[k]) |
286 # Delete the data for extra keys to form trace data for Chrome part only. | 327 # Delete the data for extra keys to form trace data for Chrome part only. |
287 for k in trace_parts_keys: | 328 for k in trace_parts_keys: |
288 del json_data[k] | 329 del json_data[k] |
289 b.AddTraceFor(CHROME_TRACE_PART, json_data) | 330 b.AddTraceFor(CHROME_TRACE_PART, json_data) |
290 elif isinstance(json_data, list): | 331 elif isinstance(json_data, list): |
291 b.AddTraceFor(CHROME_TRACE_PART, {'traceEvents': json_data}) | 332 b.AddTraceFor(CHROME_TRACE_PART, {'traceEvents': json_data}) |
292 else: | 333 else: |
293 raise NonSerializableTraceData('Unrecognized data format.') | 334 raise NonSerializableTraceData('Unrecognized data format.') |
294 return b.AsData() | 335 return b.AsData() |
OLD | NEW |