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
3# found in the LICENSE file.
4
5import datetime
6import logging
7import os
8import random
9import shutil
10import StringIO
11import sys
12import tempfile
13
14from catapult_base import cloud_storage  # pylint: disable=import-error
15
16from telemetry.internal.util import file_handle
17from telemetry.timeline import trace_data as trace_data_module
18from telemetry import value as value_module
19
20from tracing_build import trace2html
21
22
23class TraceValue(value_module.Value):
24  def __init__(self, page, trace_data, important=False, description=None):
25    """A value that contains a TraceData object and knows how to
26    output it.
27
28    Adding TraceValues and outputting as JSON will produce a directory full of
29    HTML files called trace_files. Outputting as chart JSON will also produce
30    an index, files.html, linking to each of these files.
31    """
32    super(TraceValue, self).__init__(
33        page, name='trace', units='', important=important,
34        description=description, tir_label=None, grouping_keys=None)
35    self._temp_file = self._GetTempFileHandle(trace_data)
36    self._cloud_url = None
37    self._serialized_file_handle = None
38
39  def _GetTempFileHandle(self, trace_data):
40    if self.page:
41      title = self.page.display_name
42    else:
43      title = ''
44    content = StringIO.StringIO()
45    trace2html.WriteHTMLForTraceDataToFile(
46        [trace_data.GetEventsFor(trace_data_module.CHROME_TRACE_PART)],
47        title,
48        content)
49    tf = tempfile.NamedTemporaryFile(delete=False, suffix='.html')
50    tf.write(content.getvalue().encode('utf-8'))
51    tf.close()
52    return file_handle.FromTempFile(tf)
53
54  def __repr__(self):
55    if self.page:
56      page_name = self.page.display_name
57    else:
58      page_name = 'None'
59    return 'TraceValue(%s, %s)' % (page_name, self.name)
60
61  def CleanUp(self):
62    """Cleans up tempfile after it is no longer needed.
63
64    A cleaned up TraceValue cannot be used for further operations. CleanUp()
65    may be called more than once without error.
66    """
67    if self._temp_file is None:
68      return
69    os.remove(self._temp_file.GetAbsPath())
70    self._temp_file = None
71
72  def __enter__(self):
73    return self
74
75  def __exit__(self, _, __, ___):
76    self.CleanUp()
77
78  @property
79  def cleaned_up(self):
80    return self._temp_file is None
81
82  @property
83  def filename(self):
84    return self._temp_file.GetAbsPath()
85
86  def GetBuildbotDataType(self, output_context):
87    return None
88
89  def GetBuildbotValue(self):
90    return None
91
92  def GetRepresentativeNumber(self):
93    return None
94
95  def GetRepresentativeString(self):
96    return None
97
98  @staticmethod
99  def GetJSONTypeName():
100    return 'trace'
101
102  @classmethod
103  def MergeLikeValuesFromSamePage(cls, values):
104    assert len(values) > 0
105    return values[0]
106
107  @classmethod
108  def MergeLikeValuesFromDifferentPages(cls, values):
109    return None
110
111  def AsDict(self):
112    if self._temp_file is None:
113      raise ValueError('Tried to serialize TraceValue without tempfile.')
114    d = super(TraceValue, self).AsDict()
115    if self._serialized_file_handle:
116      d['file_id'] = self._serialized_file_handle.id
117    if self._cloud_url:
118      d['cloud_url'] = self._cloud_url
119    return d
120
121  def Serialize(self, dir_path):
122    if self._temp_file is None:
123      raise ValueError('Tried to serialize nonexistent trace.')
124    file_name = str(self._temp_file.id) + self._temp_file.extension
125    file_path = os.path.abspath(os.path.join(dir_path, file_name))
126    shutil.copy(self._temp_file.GetAbsPath(), file_path)
127    self._serialized_file_handle = file_handle.FromFilePath(file_path)
128    return self._serialized_file_handle
129
130  def UploadToCloud(self, bucket):
131    if self._temp_file is None:
132      raise ValueError('Tried to upload nonexistent trace to Cloud Storage.')
133    try:
134      if self._serialized_file_handle:
135        fh = self._serialized_file_handle
136      else:
137        fh = self._temp_file
138      remote_path = ('trace-file-id_%s-%s-%d%s' % (
139          fh.id,
140          datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
141          random.randint(1, 100000),
142          fh.extension))
143      self._cloud_url = cloud_storage.Insert(
144          bucket, remote_path, fh.GetAbsPath())
145      sys.stderr.write(
146          'View generated trace files online at %s for page %s\n' %
147          (self._cloud_url, self.page.url if self.page else 'unknown'))
148      return self._cloud_url
149    except cloud_storage.PermissionError as e:
150      logging.error('Cannot upload trace files to cloud storage due to '
151                    ' permission error: %s' % e.message)
152