Index: telemetry/telemetry/timeline/trace_data.py |
diff --git a/telemetry/telemetry/timeline/trace_data.py b/telemetry/telemetry/timeline/trace_data.py |
index 86947c640ea51d96d40b65117554c9f51dba25e4..e6dcf5ee9e05e60ea7619fa1ca8d6ca68d29e4e4 100644 |
--- a/telemetry/telemetry/timeline/trace_data.py |
+++ b/telemetry/telemetry/timeline/trace_data.py |
@@ -4,9 +4,18 @@ |
import copy |
import json |
+import logging |
import os |
+import shutil |
+import subprocess |
import tempfile |
-import zipfile |
+ |
+from telemetry.core import util |
+ |
+ |
+_TRACE2HTML_PATH = os.path.join( |
+ util.GetCatapultDir(), 'tracing', 'bin', 'trace2html') |
+ |
class NonSerializableTraceData(Exception): |
"""Raised when raw trace data cannot be serialized to TraceData.""" |
@@ -62,6 +71,25 @@ def _HasTraceFor(part, raw): |
return len(raw[part.raw_field_name]) > 0 |
+def _GetFilePathForTrace(trace, dir_path): |
+ """ Return path to a file that contains |trace|. |
+ |
+ Note: if |trace| is an instance of TraceFileHandle, this reuses the trace path |
+ that the trace file handle holds. Otherwise, it creates a new trace file |
+ in |dir_path| directory. |
+ """ |
+ if isinstance(trace, TraceFileHandle): |
+ return trace.file_path |
+ with tempfile.NamedTemporaryFile(mode='w', dir=dir_path, delete=False) as fp: |
+ if isinstance(trace, basestring): |
+ fp.write(trace) |
+ elif isinstance(trace, dict) or isinstance(trace, list): |
+ json.dump(trace, fp) |
+ else: |
+ raise TypeError('Trace is of unknown type.') |
+ return fp.name |
+ |
+ |
class TraceData(object): |
""" TraceData holds a collection of traces from multiple sources. |
@@ -100,56 +128,69 @@ class TraceData(object): |
return _HasTraceFor(part, self._raw_data) |
def GetTracesFor(self, part): |
+ """ Return the list of traces for |part| in string or dictionary forms. |
+ |
+ Note: since this API return the traces that can be directly accessed in |
+ memory, it may require lots of memory usage as some of the trace can be |
+ very big. |
+ For references, we have cases where Telemetry is OOM'ed because the memory |
+ required for processing the trace in Python is too big (crbug.com/672097). |
+ """ |
+ assert isinstance(part, TraceDataPart) |
if not self.HasTracesFor(part): |
return [] |
- assert isinstance(part, TraceDataPart) |
- return self._raw_data[part.raw_field_name] |
+ traces_list = self._raw_data[part.raw_field_name] |
+ # Since this API return the traces in memory form, and since the memory |
+ # bottleneck of Telemetry is for keeping trace in memory, there is no uses |
+ # in keeping the on-disk form of tracing beyond this point. Hence we convert |
+ # all traces for part of form TraceFileHandle to the JSON form. |
+ for i, data in enumerate(traces_list): |
+ if isinstance(data, TraceFileHandle): |
+ traces_list[i] = data.AsTraceData() |
+ return traces_list |
def GetTraceFor(self, part): |
assert isinstance(part, TraceDataPart) |
- traces = self._raw_data[part.raw_field_name] |
+ traces = self.GetTracesFor(part) |
assert len(traces) == 1 |
- if isinstance(traces[0], TraceFileHandle): |
- return traces[0].AsTraceData() |
- else: |
- return traces[0] |
+ return traces[0] |
+ def CleanUpAllTraces(self): |
+ """ Remove all the traces that this has handles to. |
- # TODO(nedn): unifying this code with |
- # telemetry.value.trace.TraceValue._GetTempFileHandle so that we have one |
- # single space efficient method to Serialize this to trace. |
- def Serialize(self, f, gzip_result=False): |
- """Serializes the trace result to a file-like object. |
+ Those include traces stored in memory & on disk. After invoking this, |
+ one can no longer uses this object for collecting the traces. |
+ """ |
+ for traces_list in self._raw_data.itervalues(): |
+ for trace in traces_list: |
+ if isinstance(trace, TraceFileHandle): |
+ trace.Clean() |
+ self._raw_data = {} |
+ |
+ def Serialize(self, file_path, trace_title=''): |
+ """Serializes the trace result to |file_path|. |
- Write in trace container format if gzip_result=False. |
- Writes to a .zip file if gzip_result=True. |
""" |
- raw_data = {} |
- for k, v in self._raw_data.iteritems(): |
- for data in v: |
- if isinstance(data, TraceFileHandle): |
- with open(data.file_path) as trace_file: |
- trace = json.load(trace_file) |
- else: |
- trace = data |
- if k not in raw_data: |
- raw_data[k] = trace |
- else: |
- raw_data[k] += trace |
- if gzip_result: |
- zip_file = zipfile.ZipFile(f, mode='w') |
- try: |
- for part in self.active_parts: |
- tmp_file_name = None |
- with tempfile.NamedTemporaryFile(delete=False) as tmp_file: |
- tmp_file_name = tmp_file.name |
- tmp_file.write(str(raw_data[part.raw_field_name])) |
- zip_file.write(tmp_file_name, arcname=part.raw_field_name) |
- os.remove(tmp_file_name) |
- finally: |
- zip_file.close() |
- else: |
- json.dump(raw_data, f) |
+ if not self._raw_data: |
+ logging.warning('No traces to convert to html.') |
+ return |
+ temp_dir = tempfile.mkdtemp() |
+ trace_files = [] |
+ try: |
+ trace_size_data = {} |
+ for part, traces_list in self._raw_data.iteritems(): |
+ for trace in traces_list: |
+ path = _GetFilePathForTrace(trace, temp_dir) |
+ trace_size_data.setdefault(part, 0) |
+ trace_size_data[part] += os.path.getsize(path) |
+ trace_files.append(path) |
+ logging.info('Trace sizes in bytes: %s', trace_size_data) |
+ |
+ cmd = (['python', _TRACE2HTML_PATH] + trace_files + |
+ ['--output', file_path] + ['--title', trace_title]) |
+ subprocess.check_output(cmd) |
+ finally: |
+ shutil.rmtree(temp_dir) |
class TraceFileHandle(object): |