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