Ori.livneh has uploaded a new change for review.
https://gerrit.wikimedia.org/r/189166
Change subject: vbench improvements
......................................................................
vbench improvements
* Encapsulate profile data processing in a new ChromeCPUProfiler class.
* Use Twisted log observer to log debug data.
* Add --latency and --hidpi options.
Change-Id: I856c68256f08fd6ef944eabec6bad74e6d3c2ebd
---
M files/ve/vbench
1 file changed, 86 insertions(+), 56 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/operations/puppet
refs/changes/66/189166/1
diff --git a/files/ve/vbench b/files/ve/vbench
index 6fed63b..f44b0e8 100755
--- a/files/ve/vbench
+++ b/files/ve/vbench
@@ -37,6 +37,7 @@
sys.setdefaultencoding('utf-8')
import argparse
+import inspect
import itertools
import json
import logging
@@ -75,7 +76,7 @@
YELLOW, WHITE, RESET = '\033[93m', '\033[1;37m', '\033[0m'
logging.basicConfig(format='[%s%%(asctime)s%s] %%(message)s' % (YELLOW, RESET),
- datefmt='%I:%M:%S', level=logging.INFO)
+ datefmt='%I:%M:%S', level=logging.DEBUG)
measures = (min, max, mean, median, std)
@@ -94,29 +95,48 @@
return ''.join(string.ljust(item, 30) for item in items)
-def find_function(root, func='(program)'):
- """Breadth-first search for a function."""
- if root['functionName'] == func:
- return root
- for child in root['children']:
- result = find_function(child, func)
- if result is not None:
- return result
+def apply_safe(f, kwargs):
+ """Apply a function with only those arguments that it would accept."""
+ # If the function takes a '**' arg, all keyword args are safe.
+ # If it doesn't, we have to remove any arguments that are not
+ # present in the function's signature.
+ sig = inspect.getargspec(f)
+ sig_args = sig.args[1:] if f.__self__ else sig.args
+ if sig.keywords is None:
+ kwargs = {k: v for k, v in kwargs.items() if k in sig_args}
+ if sig.defaults is not None:
+ args = [kwargs.pop(k) for k in sig_args[:-len(sig.defaults)]]
+ else:
+ args = [kwargs.pop(k) for k in sig_args]
+ return f(*args, **kwargs)
-def get_cpu_time(profile):
- """Get time spent executing JavaScript, in ms."""
- idle = find_function(profile['head'], '(idle)')
- idle_samples = idle['hitCount']
- all_samples = len(profile['samples'])
- percent_active = 100.0 * (all_samples - idle_samples) / all_samples
- wall_time = profile['timestamps'][-1] - profile['timestamps'][0]
- cpu_time = (percent_active * wall_time) / 1e5
- return cpu_time
+class ChromeCPUProfile(object):
+ def __init__(self, data):
+ self.data = data
+ self.cpu_time = self.get_cpu_time()
+ self.wall_time = self.get_wall_time()
-def get_wall_time_ms(profile):
- return 1000.0 * (profile['endTime'] - profile['startTime'])
+ def grep(self, function_name, root=None):
+ matches = []
+ root = root or self.data['head']
+ if root.get('functionName') == function_name:
+ matches.append(root)
+ for child in root.get('children', ()):
+ matches.extend(self.grep(function_name, child))
+ return matches
+
+ def get_cpu_time(self):
+ idle = self.grep('(idle)')[0]
+ idle_samples = idle['hitCount']
+ all_samples = len(self.data['samples'])
+ percent_active = 100.0 * (all_samples - idle_samples) / all_samples
+ total_time = self.data['timestamps'][-1] - self.data['timestamps'][0]
+ return (percent_active * total_time) / 1e5
+
+ def get_wall_time(self):
+ return (self.data['endTime'] - self.data['startTime']) * 1e3
class ChromeRemoteDebuggingProtocol(WebSocketClientProtocol):
@@ -139,24 +159,28 @@
logging.info('Loading %s', highlight(self.factory.target_url))
self.sendCommand('Page.enable')
self.sendCommand('Page.addScriptToEvaluateOnLoad', scriptSource=JS)
+ if self.factory.hidpi:
+ self.sendCommand('Page.setDeviceMetricsOverride', width=1280,
+ height=800, deviceScaleFactor=2,
+ emulateViewport=False, fitWindow=False,
+ mobile=False)
self.sendCommand('Network.enable')
+ if self.factory.latency:
+ self.sendCommand('Network.emulateNetworkConditions', offline=False,
+ latency=100, downloadThroughput=262144,
+ uploadThroughput=262144)
self.sendCommand('Profiler.enable')
self.sendCommand('Profiler.setSamplingInterval',
interval=100,
callback=self.onProfilerReady)
- def onNetworkRequestServedFromCache(self, params=None):
- del self.requests[params['requestId']]
- # self.requests.pop(params['requestId'], None)
+ def onNetworkRequestServedFromCache(self, requestId):
+ del self.requests[requestId]
- def onNetworkRequestWillBeSent(self, params=None):
- self.requests[params['requestId']] = params['request']
+ def onNetworkRequestWillBeSent(self, requestId, request):
+ self.requests[requestId] = request
- def doReset(self):
- self.requests.clear()
-
- def onProfilerReady(self, params=None):
- self.capturing = False
+ def onProfilerReady(self):
self.sendCommand('Network.clearBrowserCache')
self.sendCommand('Page.navigate', url=self.factory.target_url)
@@ -165,19 +189,14 @@
with open(name, 'w') as f:
json.dump(data, f)
- def onProfilerConsoleProfileStarted(self, params=None):
- self.doReset()
- self.capturing = True
+ def onProfilerConsoleProfileStarted(self):
+ self.requests.clear()
- def onProfilerConsoleProfileFinished(self, params=None):
- self.capturing = False
- data = params['profile']
+ def onProfilerConsoleProfileFinished(self, profile):
+ profile = ChromeCPUProfile(profile)
if self.factory.write:
self.writeProfilerData(data)
-
- wall_time = get_wall_time_ms(data)
- cpu_time = get_cpu_time(data)
if self.factory.show_uncached_requests:
for req in self.requests.values():
@@ -185,22 +204,18 @@
if self.factory.warmups:
self.factory.warmups -= 1
- logging.info('(warm-up) CPU: %.2fms Wall: %.2fms',
- cpu_time, wall_time)
- return self.onProfilerReady()
- self.cpu_times.append(cpu_time)
- self.wall_times.append(wall_time)
- logging.info('%s of %s: CPU: %.2fms Wall: %.2fms',
- len(self.cpu_times), self.factory.reps, cpu_time,
- wall_time)
- if len(self.cpu_times) < self.factory.reps:
+ else:
+ self.cpu_times.append(profile.cpu_time)
+ self.wall_times.append(profile.wall_time)
+
+ reps = len(self.cpu_times)
+ logging.info('{:02d}/{:02d}: CPU: {: >7.2f} Wall: {: >7.2f}'.format(
+ reps, self.factory.reps, profile.cpu_time, profile.wall_time))
+ if reps < self.factory.reps:
self.onProfilerReady()
else:
- logging.info('CPU : %s',
- format_summary(summarize(self.cpu_times)))
- logging.info('Wall : %s',
- format_summary(summarize(self.wall_times)))
- reactor.stop() # Shut down.
+ reactor.stop()
+ self.showSummary()
def getMethodHandler(self, method):
object, method = method.split('.')
@@ -218,13 +233,19 @@
id = message.get('id')
if id in self.callbacks:
callback = self.callbacks.pop(id)
- return callback(message.get('response'))
+ response = message.get('response', {})
+ return apply_safe(callback, response)
method = message.get('method')
if method is not None:
handler = self.getMethodHandler(method)
if handler is not None:
- return handler(message.get('params'))
+ params = message.get('params', {})
+ return apply_safe(handler, params)
+
+ def showSummary(self):
+ logging.info('CPU: %s', format_summary(summarize(self.cpu_times)))
+ logging.info('Wall: %s', format_summary(summarize(self.wall_times)))
def onError(self, error):
logging.error(error)
@@ -233,7 +254,8 @@
class ChromeRemoteDebuggingFactory(WebSocketClientFactory):
def __init__(self, url, host='localhost', port=9222, reps=20,
- warmups=0, write=False, show_uncached_requests=False):
+ warmups=0, write=False, show_uncached_requests=False,
+ hidpi=False, latency=False):
self.host = host
self.port = port
self.reps = reps
@@ -241,6 +263,8 @@
self.target_url = url.replace('https://', 'http://', 1) # force HTTP
self.write = write
self.show_uncached_requests = show_uncached_requests
+ self.hidpi = hidpi
+ self.latency = latency
tab_url = next(tab['webSocketDebuggerUrl'] for tab in self.getTabs()
if 'webSocketDebuggerUrl' in tab)
WebSocketClientFactory.__init__(self, tab_url)
@@ -262,11 +286,17 @@
help='log uncached network requests')
ap.add_argument('--write', action='store_true', default=False,
help='save profiling data as .cpuprofile files.')
+ap.add_argument('--hidpi', action='store_true', default=False,
+ help='force high DPI')
+ap.add_argument('--latency', action='store_true', default=False,
+ help='simulate realistic network conditions')
ap.add_argument('url', help='URL to load')
args = ap.parse_args()
if __name__ == '__main__':
+ observer = log.PythonLoggingObserver()
+ observer.start()
factory = ChromeRemoteDebuggingFactory(**vars(args))
reactor.connectTCP(args.host, args.port, factory)
reactor.run()
--
To view, visit https://gerrit.wikimedia.org/r/189166
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I856c68256f08fd6ef944eabec6bad74e6d3c2ebd
Gerrit-PatchSet: 1
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Ori.livneh <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits