blob: dd38f9e50ef4974e8d4bebe0aef043a854d137ac [file] [log] [blame]
# Copyright 2019 The Cobalt Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import time
import argparse
import random
import websocket
try:
import collectd
except ImportError:
collectd = None
conf_ = {'host': 'localhost', 'port': 9222}
def config(conf):
print('config called {!r}'.format(conf))
conf_['collectd_conf'] = conf
collectd.info('config called {!r}'.format(conf))
def reconnect():
ws = websocket.create_connection('ws://{}:{}'.format(conf_['host'],
conf_['port']))
ws.settimeout(3)
setattr(ws, 'message_id', 1)
conf_['ws'] = ws
def init():
conf = conf_['collectd_conf']
for child in conf.children:
collectd.info('conf.child key {} values {}'.format(child.key, child.values))
if child.key.lower() == 'address':
host, port = child.values[0].split(':')
conf_['host'] = host
conf_['port'] = int(port)
reconnect()
def wait_result(ws, result_id, timeout):
start_time = time.time()
messages = []
matching_result = None
while True:
now = time.time()
if now - start_time > timeout:
break
try:
message = ws.recv()
parsed_message = json.loads(message)
messages.append(parsed_message)
if 'result' in parsed_message and parsed_message['id'] == result_id:
matching_result = parsed_message
break
except:
break
return (matching_result, messages)
def exec_js(ws, expr):
ws.message_id += 1
call_obj = {
'id': ws.message_id,
'method': 'Runtime.evaluate',
'params': {
'expression': expr,
'returnByValue': True
}
}
ws.send(json.dumps(call_obj))
result, _ = wait_result(ws, ws.message_id, 10)
return result['result']['result']
def report_value(plugin, type, instance, value, plugin_instance=None):
vl = collectd.Values(plugin=plugin, type=type, type_instance=instance)
if plugin_instance:
vl.plugin_instance = plugin_instance
vl.dispatch(values=[value])
def report_cobalt_stat(key, collectd_type):
val = exec_js(conf_['ws'], 'h5vcc.cVal.getValue("' + key + '")')
try:
value = int(val['value'])
# collectd.warning('key {} value: {}'.format(key, value))
report_value('cobalt', collectd_type, key, value)
except TypeError:
collectd.warning('Failed to collect: {} {}'.format(key, val))
# Tuple of a Cobalt Cval, and data type
# Collectd frontends like CGP group values to graphs by the reported data type. The types here are
# simple gauges grouped by their rough estimated orders of magnitude, so that all plots are still
# somewhat discernible.
tracked_stats = [('Cobalt.Lifetime', 'lifetime'),
('Count.DOM.ActiveJavaScriptEvents', 'gauge'),
('Count.DOM.Attrs', 'gauge_10k'),
('Count.DOM.EventListeners', 'gauge_10k'),
('Count.DOM.HtmlCollections', 'gauge_10k'),
('Count.DOM.NodeLists', 'gauge_10k'),
('Count.DOM.NodeMaps', 'gauge_10k'),
('Count.DOM.Nodes', 'gauge_10k'),
('Count.DOM.StringMaps', 'gauge_100'),
('Count.DOM.TokenLists', 'gauge_10k'),
('Count.MainWebModule.DOM.HtmlElement', 'gauge_10k'),
('Count.MainWebModule.DOM.HtmlElement.Document', 'gauge_10k'),
('Count.MainWebModule.DOM.HtmlScriptElement.Execute', 'gauge'),
('Count.Renderer.Rasterize.NewRenderTree', 'gauge_100k'),
('Count.XHR', 'gauge_100'), ('Memory.CPU.Free', 'gauge_1G'),
('Memory.CPU.Used', 'gauge_100M'),
('Memory.DebugConsoleWebModule.DOM.HtmlScriptElement.Execute',
'gauge_100k'),
('Memory.Font.LocalTypefaceCache.Capacity', 'gauge_10M'),
('Memory.Font.LocalTypefaceCache.Size', 'gauge_10M'),
('Memory.JS', 'gauge_10M'),
('Memory.MainWebModule.DOM.HtmlScriptElement.Execute',
'gauge_1M'), ('Memory.XHR', 'gauge_1M'),
('Memory.MainWebModule.ImageCache.Size', 'gauge_10M'),
('Renderer.SubmissionQueueSize', 'gauge')]
def cobalt_read():
for name, type in tracked_stats:
report_cobalt_stat(name, type)
def read(*args, **kwargs):
try:
cobalt_read()
except Exception:
reconnect()
if collectd:
collectd.register_config(config)
collectd.register_init(init)
collectd.register_read(read)
# Debugcode to verify plugin connection
if __name__ == '__main__':
class Bunch:
def __init__(self, **kwds):
self.__dict__.update(kwds)
def debugprint(*args,**kwargs):
print('{!r} {!r}'.format(args,kwargs))
def debugvalues(**kwargs):
debugprint(**kwargs)
return Bunch(dispatch=debugprint,**kwargs)
collectd = Bunch(info=debugprint,warning=debugprint,Values=debugvalues)
parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost')
parser.add_argument('--port', type=int, default=9222)
args = parser.parse_args()
conf = Bunch(children=[Bunch( key='address',
values=['{}:{}'.format(args.host,args.port)])])
conf_['collectd_conf'] = conf
init()
cobalt_read()