| 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 |