1effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch# Copyright 2014 The Chromium Authors. All rights reserved. 2a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 3a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# found in the LICENSE file. 4a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 5a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import HTMLParser 6a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import json 7a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import os 8a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import string 9a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from telemetry.web_components import web_components_project 11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from tvcm import generate 12a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 14effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochclass WebComponent(object): 15a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """An HTML-based viewer of data, built out of telemetry components. 16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 17effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch A WebComponent is used to visualize complex data that is produced from a 18effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch telemtry benchmark. A WebComponent is a Javascript class that can be 19effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch data-bound, plus python-side bindings that let us write HTML files that 20effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch instantiate that class and bind it to specific data. 21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 22effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch The primary job of the python side of a WebComponent is to implement the 23effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch WriteDataToFileAsJson. The data written here is passed to the 24effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch data_binding_property of the JS-side class. 25effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 26effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch The primary job of the javascript side of a WebComponent is visualization: it 27effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch takes the data from python and renders a UI for displaying that data in some 28effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch manner. 29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """ 31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def __init__(self, tvcm_module_name, js_class_name, data_binding_property): 32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._tvcm_module_name = tvcm_module_name 33a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._js_class_name = js_class_name 34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._data_binding_property = data_binding_property 35effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch self._data_to_view = None 36effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 37effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch @property 38effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch def data_to_view(self): 39effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return self._data_to_view 40effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 41effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch @data_to_view.setter 42effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch def data_to_view(self, data_to_view): 43effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch self._data_to_view = data_to_view 44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def WriteDataToFileAsJson(self, f): 46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) raise NotImplementedError() 47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 48effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch def GetDependentModuleNames(self): 49effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch return [self._tvcm_module_name] 50effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 51effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch def WriteWebComponentToFile(self, f): 52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) project = web_components_project.WebComponentsProject() 53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) load_sequence = project.CalcLoadSequenceForModuleNames( 54effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch self.GetDependentModuleNames()) 55a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) with open(os.path.join(os.path.dirname(__file__), 57effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 'web_component_bootstrap.js')) as bfile: 58a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) bootstrap_js_template = string.Template(bfile.read()) 59a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) bootstrap_js = bootstrap_js_template.substitute( 60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) js_class_name=self._js_class_name, 61a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) data_binding_property=self._data_binding_property) 62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) bootstrap_script = generate.ExtraScript(text_content=bootstrap_js) 64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 65effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch class WebComponentDataScript(generate.ExtraScript): 66a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def __init__(self, results_component): 67effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch super(WebComponentDataScript, self).__init__() 68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._results_component = results_component 69a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 70a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def WriteToFile(self, output_file): 71effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch output_file.write('<script id="telemetry-web-component-data" ' + 72effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 'type="application/json">\n') 73a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._results_component.WriteDataToFileAsJson(output_file) 74a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) output_file.write('</script>\n') 75a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 76a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 77a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) generate.GenerateStandaloneHTMLToFile( 78a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) f, load_sequence, 79a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) title='Telemetry results', 80effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch extra_scripts=[bootstrap_script, WebComponentDataScript(self)]) 81a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 82a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @staticmethod 83effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch def ReadDataObjectFromWebComponentFile(f): 84effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch """Reads the data inside a file written with WriteWebComponentToFile 85effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch 86effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch Returns None if the data wasn't found, the JSON.parse'd object on success. 87effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch Raises exception if the HTML file was corrupt. 88a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 89a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) """ 90a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) class MyHTMLParser(HTMLParser.HTMLParser): 91a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def __init__(self): 92a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) HTMLParser.HTMLParser.__init__(self) 93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._got_data_tag = False 94a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._in_data_tag = False 95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._data_records = [] 96a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def handle_starttag(self, tag, attrs): 98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if tag != 'script': 99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return 100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) id_attr = dict(attrs).get('id', None) 101effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch if id_attr == 'telemetry-web-component-data': 102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) assert not self._got_data_tag 103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._got_data_tag = True 104a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._in_data_tag = True 105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 106a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def handle_endtag(self, tag): 107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._in_data_tag = False 108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 109a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def handle_data(self, data): 110a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if self._in_data_tag: 111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) self._data_records.append(data) 112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) @property 114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) def data(self): 115a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if not self._got_data_tag: 116effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch raise Exception('Missing <script> with #telemetry-web-component-data') 117a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if self._in_data_tag: 118effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch raise Exception('Missing </script> on #telemetry-web-component-data') 119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return json.loads(''.join(self._data_records)) 120a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 121a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) parser = MyHTMLParser() 122a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for line in f: 123a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) parser.feed(line) 124a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) return parser.data 125