Blame src/gnome-shell-perf-tool.in

Packit d345d1
#!@PYTHON@
Packit d345d1
# -*- mode: Python; indent-tabs-mode: nil; -*-
Packit d345d1
Packit d345d1
import datetime
Packit d345d1
from gi.repository import GLib, GObject, Gio
Packit d345d1
try:
Packit d345d1
    import json
Packit d345d1
except ImportError:
Packit d345d1
    import simplejson as json
Packit d345d1
import optparse
Packit d345d1
import os
Packit d345d1
import re
Packit d345d1
import subprocess
Packit d345d1
import sys
Packit d345d1
import tempfile
Packit d345d1
import base64
Packit d345d1
from configparser import RawConfigParser
Packit d345d1
import hashlib
Packit d345d1
import hmac
Packit d345d1
from http import client
Packit d345d1
from urllib import parse
Packit d345d1
Packit d345d1
def show_version(option, opt_str, value, parser):
Packit d345d1
    print("GNOME Shell Performance Test @VERSION@")
Packit d345d1
    sys.exit()
Packit d345d1
Packit d345d1
def wait_for_dbus_name(wait_name):
Packit d345d1
    loop = GLib.MainLoop()
Packit d345d1
Packit d345d1
    def on_name_appeared(connection, name, new_owner, *args):
Packit d345d1
        if not (name == wait_name and new_owner != ''):
Packit d345d1
            return
Packit d345d1
        loop.quit()
Packit d345d1
        return
Packit d345d1
Packit d345d1
    watch_id = Gio.bus_watch_name(Gio.BusType.SESSION,
Packit d345d1
                                  wait_name,
Packit d345d1
                                  Gio.BusNameWatcherFlags.NONE,
Packit d345d1
                                  on_name_appeared,
Packit d345d1
                                  None)
Packit d345d1
Packit d345d1
    def on_timeout():
Packit d345d1
        print("\nFailed to start %s: timed out" % (wait_name,))
Packit d345d1
        sys.exit(1)
Packit d345d1
    GLib.timeout_add_seconds(7, on_timeout)
Packit d345d1
Packit d345d1
    loop.run()
Packit d345d1
    Gio.bus_unwatch_name(watch_id)
Packit d345d1
Packit d345d1
PERF_HELPER_NAME = "org.gnome.Shell.PerfHelper"
Packit d345d1
PERF_HELPER_IFACE = "org.gnome.Shell.PerfHelper"
Packit d345d1
PERF_HELPER_PATH = "/org/gnome/Shell/PerfHelper"
Packit d345d1
Packit d345d1
def start_perf_helper():
Packit d345d1
    self_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
Packit d345d1
    perf_helper_path = "@libexecdir@/gnome-shell-perf-helper"
Packit d345d1
Packit d345d1
    subprocess.Popen([perf_helper_path])
Packit d345d1
    wait_for_dbus_name (PERF_HELPER_NAME)
Packit d345d1
Packit d345d1
def stop_perf_helper():
Packit d345d1
    bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
Packit d345d1
Packit d345d1
    proxy = Gio.DBusProxy.new_sync(bus,
Packit d345d1
                                   Gio.DBusProxyFlags.NONE,
Packit d345d1
                                   None,
Packit d345d1
                                   PERF_HELPER_NAME,
Packit d345d1
                                   PERF_HELPER_PATH,
Packit d345d1
                                   PERF_HELPER_IFACE,
Packit d345d1
                                   None)
Packit d345d1
    proxy.Exit()
Packit d345d1
Packit d345d1
def start_shell(perf_output=None):
Packit d345d1
    # Set up environment
Packit d345d1
    env = dict(os.environ)
Packit d345d1
    env['SHELL_PERF_MODULE'] = options.perf
Packit d345d1
Packit d345d1
    filters = ['Gnome-shell-perf-helper'] + options.extra_filter
Packit d345d1
    env['MUTTER_WM_CLASS_FILTER'] = ','.join(filters)
Packit d345d1
Packit d345d1
    if perf_output is not None:
Packit d345d1
        env['SHELL_PERF_OUTPUT'] = perf_output
Packit d345d1
Packit d345d1
    # A fixed background image
Packit d345d1
    env['SHELL_BACKGROUND_IMAGE'] = '@pkgdatadir@/perf-background.xml'
Packit d345d1
Packit d345d1
    self_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
Packit d345d1
    args = []
Packit d345d1
    args.append(os.path.join(self_dir, 'gnome-shell'))
Packit d345d1
Packit d345d1
    if options.replace:
Packit d345d1
        args.append('--replace')
Packit d345d1
Packit d345d1
    return subprocess.Popen(args, env=env)
Packit d345d1
Packit d345d1
def run_shell(perf_output=None):
Packit d345d1
    # we do no additional supervision of gnome-shell,
Packit d345d1
    # beyond that of wait
Packit d345d1
    # in particular, we don't kill the shell upon
Packit d345d1
    # receving a KeyboardInterrupt, as we expect to be
Packit d345d1
    # in the same process group
Packit d345d1
    shell = start_shell(perf_output=perf_output)
Packit d345d1
    shell.wait()
Packit d345d1
    return shell.returncode == 0
Packit d345d1
Packit d345d1
def restore_shell():
Packit d345d1
    pid = os.fork()
Packit d345d1
    if (pid == 0):
Packit d345d1
        os.execlp("gnome-shell", "gnome-shell", "--replace")
Packit d345d1
    else:
Packit d345d1
        sys.exit(0)
Packit d345d1
Packit d345d1
def upload_performance_report(report_text):
Packit d345d1
    try:
Packit d345d1
        config_home = os.environ['XDG_CONFIG_HOME']
Packit d345d1
    except KeyError:
Packit d345d1
        config_home = None
Packit d345d1
Packit d345d1
    if not config_home:
Packit d345d1
        config_home = os.path.expanduser("~/.config")
Packit d345d1
Packit d345d1
    config_file = os.path.join(config_home, "gnome-shell/perf.ini")
Packit d345d1
Packit d345d1
    try:
Packit d345d1
        config = RawConfigParser()
Packit d345d1
        f = open(config_file)
Packit d345d1
        config.readfp(f)
Packit d345d1
        f.close()
Packit d345d1
Packit d345d1
        base_url = config.get('upload', 'url')
Packit d345d1
        system_name = config.get('upload', 'name')
Packit d345d1
        secret_key = config.get('upload', 'key')
Packit d345d1
    except Exception as e:
Packit d345d1
        print("Can't read upload configuration from %s: %s" % (config_file, str(e)))
Packit d345d1
        sys.exit(1)
Packit d345d1
Packit d345d1
    # Determine host, port and upload URL from provided data, we're
Packit d345d1
    # a bit extra-careful about normalization since the URL is part
Packit d345d1
    # of the signature.
Packit d345d1
Packit d345d1
    split = parse.urlsplit(base_url)
Packit d345d1
    scheme = split[0].lower()
Packit d345d1
    netloc = split[1]
Packit d345d1
    base_path = split[2]
Packit d345d1
Packit d345d1
    m = re.match(r'^(.*?)(?::(\d+))?$', netloc)
Packit d345d1
    if m.group(2):
Packit d345d1
        host, port = m.group(1), int(m.group(2))
Packit d345d1
    else:
Packit d345d1
        host, port = m.group(1), None
Packit d345d1
Packit d345d1
    if scheme != "http":
Packit d345d1
        print("'%s' is not a HTTP URL" % base_url)
Packit d345d1
        sys.exit(1)
Packit d345d1
Packit d345d1
    if port is None:
Packit d345d1
        port = 80
Packit d345d1
Packit d345d1
    if base_path.endswith('/'):
Packit d345d1
        base_path = base_path[:-1]
Packit d345d1
Packit d345d1
    if port == 80:
Packit d345d1
        normalized_base = "%s://%s%s" % (scheme, host, base_path)
Packit d345d1
    else:
Packit d345d1
        normalized_base = "%s://%s:%d%s" % (scheme, host, port, base_path)
Packit d345d1
Packit d345d1
    upload_url = normalized_base + '/system/%s/upload' % system_name
Packit d345d1
    upload_path = parse.urlsplit(upload_url)[2] # path portion
Packit d345d1
Packit d345d1
    # Create signature based on upload URL and the report data
Packit d345d1
Packit d345d1
    signature_data = 'POST&' + upload_url + "&&"
Packit d345d1
    h = hmac.new(secret_key, digestmod=hashlib.sha1)
Packit d345d1
    h.update(signature_data)
Packit d345d1
    h.update(report_text)
Packit d345d1
    signature = parse.quote(base64.b64encode(h.digest()), "~")
Packit d345d1
Packit d345d1
    headers = {
Packit d345d1
        'User-Agent': 'gnome-shell-performance-tool/@VERSION@',
Packit d345d1
        'Content-Type': 'application/json',
Packit d345d1
        'X-Shell-Signature': 'HMAC-SHA1 ' + signature
Packit d345d1
    };
Packit d345d1
Packit d345d1
    connection = client.HTTPConnection(host, port)
Packit d345d1
    connection.request('POST', upload_path, report_text, headers)
Packit d345d1
    response = connection.getresponse()
Packit d345d1
Packit d345d1
    if response.status == 200:
Packit d345d1
        print("Performance report upload succeeded")
Packit d345d1
    else:
Packit d345d1
        print("Performance report upload failed with status %d" % response.status)
Packit d345d1
        print(response.read())
Packit d345d1
Packit d345d1
def gnome_hwtest_log(*args):
Packit d345d1
    command = ['gnome-hwtest-log', '-t', 'gnome-shell-perf-tool']
Packit d345d1
    command.extend(args)
Packit d345d1
    subprocess.check_call(command)
Packit d345d1
Packit d345d1
def run_performance_test():
Packit d345d1
    iters = options.perf_iters
Packit d345d1
    if options.perf_warmup:
Packit d345d1
        iters += 1
Packit d345d1
Packit d345d1
    logs = []
Packit d345d1
    metric_summaries = {}
Packit d345d1
Packit d345d1
    start_perf_helper()
Packit d345d1
Packit d345d1
    for i in range(0, iters):
Packit d345d1
        # We create an empty temporary file that the shell will overwrite
Packit d345d1
        # with the contents.
Packit d345d1
        handle, output_file = tempfile.mkstemp(".json", "gnome-shell-perf.")
Packit d345d1
        os.close(handle)
Packit d345d1
Packit d345d1
        # Run the performance test and collect the output as JSON
Packit d345d1
        normal_exit = False
Packit d345d1
        try:
Packit d345d1
            normal_exit = run_shell(perf_output=output_file)
Packit d345d1
        except:
Packit d345d1
            stop_perf_helper()
Packit d345d1
            raise
Packit d345d1
        finally:
Packit d345d1
            if not normal_exit:
Packit d345d1
                os.remove(output_file)
Packit d345d1
Packit d345d1
        if not normal_exit:
Packit d345d1
            stop_perf_helper()
Packit d345d1
            return False
Packit d345d1
Packit d345d1
        try:
Packit d345d1
            f = open(output_file)
Packit d345d1
            output = json.load(f)
Packit d345d1
            f.close()
Packit d345d1
        except:
Packit d345d1
            stop_perf_helper()
Packit d345d1
            raise
Packit d345d1
        finally:
Packit d345d1
            os.remove(output_file)
Packit d345d1
Packit d345d1
        # Grab the event definitions and monitor layout the first time around
Packit d345d1
        if i == 0:
Packit d345d1
            events = output['events']
Packit d345d1
            monitors = output['monitors']
Packit d345d1
Packit d345d1
        if options.perf_warmup and i == 0:
Packit d345d1
            continue
Packit d345d1
Packit d345d1
        for metric in output['metrics']:
Packit d345d1
            name = metric['name']
Packit d345d1
            if not name in metric_summaries:
Packit d345d1
                summary = {}
Packit d345d1
                summary['description'] = metric['description']
Packit d345d1
                summary['units'] = metric['units']
Packit d345d1
                summary['values'] = []
Packit d345d1
                metric_summaries[name] = summary
Packit d345d1
            else:
Packit d345d1
                summary = metric_summaries[name]
Packit d345d1
Packit d345d1
            summary['values'].append(metric['value'])
Packit d345d1
Packit d345d1
        logs.append(output['log'])
Packit d345d1
Packit d345d1
    stop_perf_helper()
Packit d345d1
Packit d345d1
    if options.perf_output or options.perf_upload:
Packit d345d1
        # Write a complete report, formatted as JSON. The Javascript/C code that
Packit d345d1
        # generates the individual reports we are summarizing here is very careful
Packit d345d1
        # to format them nicely, but we just dump out a compressed no-whitespace
Packit d345d1
        # version here for simplicity. Using json.dump(indent=0) doesn't real
Packit d345d1
        # improve the readability of the output much.
Packit d345d1
        report = {
Packit d345d1
            'date': datetime.datetime.utcnow().isoformat() + 'Z',
Packit d345d1
            'events': events,
Packit d345d1
            'monitors': monitors,
Packit d345d1
            'metrics': metric_summaries,
Packit d345d1
            'logs': logs
Packit d345d1
        }
Packit d345d1
Packit d345d1
        # Add the Git revision if available
Packit d345d1
        self_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
Packit d345d1
        if os.path.exists(os.path.join(self_dir, 'gnome-shell-jhbuild.in')):
Packit d345d1
            top_dir = os.path.dirname(self_dir)
Packit d345d1
            git_dir = os.path.join(top_dir, '.git')
Packit d345d1
            if os.path.exists(git_dir):
Packit d345d1
                env = dict(os.environ)
Packit d345d1
                env['GIT_DIR'] = git_dir
Packit d345d1
                revision = subprocess.Popen(['git', 'rev-parse', 'HEAD'],
Packit d345d1
                                            env=env,
Packit d345d1
                                            stdout=subprocess.PIPE).communicate()[0].strip()
Packit d345d1
                report['revision'] = revision
Packit d345d1
Packit d345d1
        if options.perf_output:
Packit d345d1
            f = open(options.perf_output, 'w')
Packit d345d1
            json.dump(report, f)
Packit d345d1
            f.close()
Packit d345d1
Packit d345d1
        if options.perf_upload:
Packit d345d1
            upload_performance_report(json.dumps(report))
Packit d345d1
    elif options.hwtest:
Packit d345d1
        # Log to systemd journal
Packit d345d1
        for metric in sorted(metric_summaries.keys()):
Packit d345d1
            summary = metric_summaries[metric]
Packit d345d1
            gnome_hwtest_log('--metric=' + metric + '=' + str(summary['values'][0]) + summary['units'],
Packit d345d1
                             '--metric-description=' + summary['description'])
Packit d345d1
        gnome_hwtest_log('--finished')
Packit d345d1
    else:
Packit d345d1
        # Write a human readable summary
Packit d345d1
        print('------------------------------------------------------------')
Packit d345d1
        for metric in sorted(metric_summaries.keys()):
Packit d345d1
            summary = metric_summaries[metric]
Packit d345d1
            print("#", summary['description'])
Packit d345d1
            print(metric, ", ".join((str(x) for x in summary['values'])))
Packit d345d1
        print('------------------------------------------------------------')
Packit d345d1
Packit d345d1
    return True
Packit d345d1
Packit d345d1
# Main program
Packit d345d1
Packit d345d1
parser = optparse.OptionParser()
Packit d345d1
parser.add_option("", "--perf", metavar="PERF_MODULE",
Packit d345d1
		  help="Specify the name of a performance module to run")
Packit d345d1
parser.add_option("", "--perf-iters", type="int", metavar="ITERS",
Packit d345d1
		  help="Numbers of iterations of performance module to run",
Packit d345d1
                  default=1)
Packit d345d1
parser.add_option("", "--perf-warmup", action="store_true",
Packit d345d1
		  help="Run a dry run before performance tests")
Packit d345d1
parser.add_option("", "--perf-output", metavar="OUTPUT_FILE",
Packit d345d1
		  help="Output file to write performance report")
Packit d345d1
parser.add_option("", "--perf-upload", action="store_true",
Packit d345d1
		  help="Upload performance report to server")
Packit d345d1
parser.add_option("", "--extra-filter", action="append",
Packit d345d1
                  help="add an extra window class that should be allowed")
Packit d345d1
parser.add_option("", "--hwtest", action="store_true",
Packit d345d1
		  help="Log results appropriately for GNOME Hardware Testing")
Packit d345d1
parser.add_option("", "--version", action="callback", callback=show_version,
Packit d345d1
                  help="Display version and exit")
Packit d345d1
Packit d345d1
parser.add_option("-r", "--replace", action="store_true",
Packit d345d1
                  help="Replace the running window manager")
Packit d345d1
Packit d345d1
options, args = parser.parse_args()
Packit d345d1
Packit d345d1
if options.perf == None:
Packit d345d1
    if options.hwtest:
Packit d345d1
        options.perf = 'hwtest'
Packit d345d1
    else:
Packit d345d1
        options.perf = 'core'
Packit d345d1
Packit d345d1
if options.extra_filter is None:
Packit d345d1
    options.extra_filter = []
Packit d345d1
Packit d345d1
if options.perf == 'hwtest':
Packit d345d1
    options.extra_filter.append('Gedit')
Packit d345d1
Packit d345d1
if args:
Packit d345d1
    parser.print_usage()
Packit d345d1
    sys.exit(1)
Packit d345d1
Packit d345d1
normal_exit = run_performance_test()
Packit d345d1
if normal_exit:
Packit d345d1
    if not options.hwtest:
Packit d345d1
        restore_shell()
Packit d345d1
else:
Packit d345d1
    sys.exit(1)