Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(149)

Side by Side Diff: telemetry/telemetry/timeline/trace_data.py

Issue 2661573003: Refactor TraceData to encapsulate internal traces' representation (Closed)
Patch Set: Address Charlie's comments Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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()
OLDNEW
« no previous file with comments | « telemetry/telemetry/page/page_run_end_to_end_unittest.py ('k') | telemetry/telemetry/timeline/trace_data_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698