Blame js/ui/scripting.js

Packit Service ed5168
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
Packit Service ed5168
Packit Service ed5168
const { Gio, GLib, Meta, Shell } = imports.gi;
Packit Service ed5168
const Mainloop = imports.mainloop;
Packit Service ed5168
rpm-build b04d0e
const Config = imports.misc.config;
Packit Service ed5168
const Main = imports.ui.main;
Packit Service ed5168
const Params = imports.misc.params;
rpm-build b04d0e
const Util = imports.misc.util;
Packit Service ed5168
Packit Service ed5168
const { loadInterfaceXML } = imports.misc.fileUtils;
Packit Service ed5168
Packit Service ed5168
// This module provides functionality for driving the shell user interface
Packit Service ed5168
// in an automated fashion. The primary current use case for this is
Packit Service ed5168
// automated performance testing (see runPerfScript()), but it could
Packit Service ed5168
// be applied to other forms of automation, such as testing for
Packit Service ed5168
// correctness as well.
Packit Service ed5168
//
Packit Service ed5168
// When scripting an automated test we want to make a series of calls
Packit Service ed5168
// in a linear fashion, but we also want to be able to let the main
Packit Service ed5168
// loop run so actions can finish. For this reason we write the script
Packit Service ed5168
// as a generator function that yields when it want to let the main
Packit Service ed5168
// loop run.
Packit Service ed5168
//
Packit Service ed5168
//    yield Scripting.sleep(1000);
Packit Service ed5168
//    main.overview.show();
Packit Service ed5168
//    yield Scripting.waitLeisure();
Packit Service ed5168
//
Packit Service ed5168
// While it isn't important to the person writing the script, the actual
Packit Service ed5168
// yielded result is a function that the caller uses to provide the
Packit Service ed5168
// callback for resuming the script.
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * sleep:
Packit Service ed5168
 * @milliseconds: number of milliseconds to wait
Packit Service ed5168
 *
Packit Service ed5168
 * Used within an automation script to pause the the execution of the
Packit Service ed5168
 * current script for the specified amount of time. Use as
Packit Service ed5168
 * 'yield Scripting.sleep(500);'
Packit Service ed5168
 */
Packit Service ed5168
function sleep(milliseconds) {
Packit Service ed5168
    return new Promise(resolve => {
Packit Service ed5168
        let id = Mainloop.timeout_add(milliseconds, () => {
Packit Service ed5168
            resolve();
Packit Service ed5168
            return GLib.SOURCE_REMOVE;
Packit Service ed5168
        });
Packit Service ed5168
        GLib.Source.set_name_by_id(id, '[gnome-shell] sleep');
Packit Service ed5168
    });
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * waitLeisure:
Packit Service ed5168
 *
Packit Service ed5168
 * Used within an automation script to pause the the execution of the
Packit Service ed5168
 * current script until the shell is completely idle. Use as
Packit Service ed5168
 * 'yield Scripting.waitLeisure();'
Packit Service ed5168
 */
Packit Service ed5168
function waitLeisure() {
Packit Service ed5168
    return new Promise(resolve => {
Packit Service ed5168
        global.run_at_leisure(resolve);
Packit Service ed5168
    });
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
const PerfHelperIface = loadInterfaceXML('org.gnome.Shell.PerfHelper');
Packit Service ed5168
var PerfHelperProxy = Gio.DBusProxy.makeProxyWrapper(PerfHelperIface);
Packit Service ed5168
function PerfHelper() {
Packit Service ed5168
    return new PerfHelperProxy(Gio.DBus.session, 'org.gnome.Shell.PerfHelper', '/org/gnome/Shell/PerfHelper');
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
let _perfHelper = null;
Packit Service ed5168
function _getPerfHelper() {
Packit Service ed5168
    if (_perfHelper == null)
Packit Service ed5168
        _perfHelper = new PerfHelper();
Packit Service ed5168
Packit Service ed5168
    return _perfHelper;
Packit Service ed5168
}
Packit Service ed5168
rpm-build b04d0e
function _spawnPerfHelper() {
rpm-build b04d0e
    let path = Config.LIBEXECDIR;
rpm-build b04d0e
    let command = `${path}/gnome-shell-perf-helper`;
rpm-build b04d0e
    Util.trySpawnCommandLine(command);
rpm-build b04d0e
}
rpm-build b04d0e
Packit Service ed5168
function _callRemote(obj, method, ...args) {
Packit Service ed5168
    return new Promise((resolve, reject) => {
Packit Service ed5168
        args.push((result, excp) => {
Packit Service ed5168
            if (excp)
Packit Service ed5168
                reject(excp);
Packit Service ed5168
            else
Packit Service ed5168
                resolve();
Packit Service ed5168
        });
Packit Service ed5168
Packit Service ed5168
        method.apply(obj, args);
Packit Service ed5168
    });
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * createTestWindow:
Packit Service ed5168
 * @params: options for window creation.
Packit Service ed5168
 *   width - width of window, in pixels (default 640)
Packit Service ed5168
 *   height - height of window, in pixels (default 480)
Packit Service ed5168
 *   alpha - whether the window should have an alpha channel (default false)
Packit Service ed5168
 *   maximized - whether the window should be created maximized (default false)
Packit Service ed5168
 *   redraws - whether the window should continually redraw itself (default false)
Packit Service ed5168
 * @maximized: whethe the window should be created maximized
Packit Service ed5168
 *
Packit Service ed5168
 * Creates a window using gnome-shell-perf-helper for testing purposes.
Packit Service ed5168
 * While this function can be used with yield in an automation
Packit Service ed5168
 * script to pause until the D-Bus call to the helper process returns,
Packit Service ed5168
 * because of the normal X asynchronous mapping process, to actually wait
Packit Service ed5168
 * until the window has been mapped and exposed, use waitTestWindows().
Packit Service ed5168
 */
Packit Service ed5168
function createTestWindow(params) {
Packit Service ed5168
    params = Params.parse(params, { width: 640,
Packit Service ed5168
                                    height: 480,
Packit Service ed5168
                                    alpha: false,
Packit Service ed5168
                                    maximized: false,
Packit Service ed5168
                                    redraws: false });
Packit Service ed5168
Packit Service ed5168
    let perfHelper = _getPerfHelper();
Packit Service ed5168
    return _callRemote(perfHelper, perfHelper.CreateWindowRemote,
Packit Service ed5168
                       params.width, params.height,
Packit Service ed5168
                       params.alpha, params.maximized, params.redraws);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * waitTestWindows:
Packit Service ed5168
 *
Packit Service ed5168
 * Used within an automation script to pause until all windows previously
Packit Service ed5168
 * created with createTestWindow have been mapped and exposed.
Packit Service ed5168
 */
Packit Service ed5168
function waitTestWindows() {
Packit Service ed5168
    let perfHelper = _getPerfHelper();
Packit Service ed5168
    return _callRemote(perfHelper, perfHelper.WaitWindowsRemote);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * destroyTestWindows:
Packit Service ed5168
 *
Packit Service ed5168
 * Destroys all windows previously created with createTestWindow().
Packit Service ed5168
 * While this function can be used with yield in an automation
Packit Service ed5168
 * script to pause until the D-Bus call to the helper process returns,
Packit Service ed5168
 * this doesn't guarantee that Mutter has actually finished the destroy
Packit Service ed5168
 * process because of normal X asynchronicity.
Packit Service ed5168
 */
Packit Service ed5168
function destroyTestWindows() {
Packit Service ed5168
    let perfHelper = _getPerfHelper();
Packit Service ed5168
    return _callRemote(perfHelper, perfHelper.DestroyWindowsRemote);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * defineScriptEvent
Packit Service ed5168
 * @name: The event will be called script.<name>
Packit Service ed5168
 * @description: Short human-readable description of the event
Packit Service ed5168
 *
Packit Service ed5168
 * Convenience function to define a zero-argument performance event
Packit Service ed5168
 * within the 'script' namespace that is reserved for events defined locally
Packit Service ed5168
 * within a performance automation script
Packit Service ed5168
 */
Packit Service ed5168
function defineScriptEvent(name, description) {
Packit Service ed5168
    Shell.PerfLog.get_default().define_event("script." + name,
Packit Service ed5168
                                             description,
Packit Service ed5168
                                             "");
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * scriptEvent
Packit Service ed5168
 * @name: Name registered with defineScriptEvent()
Packit Service ed5168
 *
Packit Service ed5168
 * Convenience function to record a script-local performance event
Packit Service ed5168
 * previously defined with defineScriptEvent
Packit Service ed5168
 */
Packit Service ed5168
function scriptEvent(name) {
Packit Service ed5168
    Shell.PerfLog.get_default().event("script." + name);
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
/**
Packit Service ed5168
 * collectStatistics
Packit Service ed5168
 *
Packit Service ed5168
 * Convenience function to trigger statistics collection
Packit Service ed5168
 */
Packit Service ed5168
function collectStatistics() {
Packit Service ed5168
    Shell.PerfLog.get_default().collect_statistics();
Packit Service ed5168
}
Packit Service ed5168
Packit Service ed5168
function _collect(scriptModule, outputFile) {
Packit Service ed5168
    let eventHandlers = {};
Packit Service ed5168
Packit Service ed5168
    for (let f in scriptModule) {
Packit Service ed5168
        let m = /([A-Za-z]+)_([A-Za-z]+)/.exec(f);
Packit Service ed5168
        if (m)
Packit Service ed5168
            eventHandlers[m[1] + "." + m[2]] = scriptModule[f];
Packit Service ed5168
    }
Packit Service ed5168
Packit Service ed5168
    Shell.PerfLog.get_default().replay(
Packit Service ed5168
        (time, eventName, signature, arg) => {
Packit Service ed5168
            if (eventName in eventHandlers)
Packit Service ed5168
                eventHandlers[eventName](time, arg);
Packit Service ed5168
        });
Packit Service ed5168
Packit Service ed5168
    if ('finish' in scriptModule)
Packit Service ed5168
        scriptModule.finish();
Packit Service ed5168
Packit Service ed5168
    if (outputFile) {
Packit Service ed5168
        let f = Gio.file_new_for_path(outputFile);
Packit Service ed5168
        let raw = f.replace(null, false,
Packit Service ed5168
                            Gio.FileCreateFlags.NONE,
Packit Service ed5168
                            null);
Packit Service ed5168
        let out = Gio.BufferedOutputStream.new_sized (raw, 4096);
Packit Service ed5168
        Shell.write_string_to_stream (out, "{\n");
Packit Service ed5168
Packit Service ed5168
        Shell.write_string_to_stream(out, '"events":\n');
Packit Service ed5168
        Shell.PerfLog.get_default().dump_events(out);
Packit Service ed5168
Packit Service ed5168
        let monitors = Main.layoutManager.monitors;
Packit Service ed5168
        let primary = Main.layoutManager.primaryIndex;
Packit Service ed5168
        Shell.write_string_to_stream(out, ',\n"monitors":\n[');
Packit Service ed5168
        for (let i = 0; i < monitors.length; i++) {
Packit Service ed5168
            let monitor = monitors[i];
Packit Service ed5168
            if (i != 0)
Packit Service ed5168
                Shell.write_string_to_stream(out, ', ');
Packit Service ed5168
            Shell.write_string_to_stream(out, '"%s%dx%d+%d+%d"'.format(i == primary ? "*" : "",
Packit Service ed5168
                                                                       monitor.width, monitor.height,
Packit Service ed5168
                                                                       monitor.x, monitor.y));
Packit Service ed5168
        }
Packit Service ed5168
        Shell.write_string_to_stream(out, ' ]');
Packit Service ed5168
Packit Service ed5168
        Shell.write_string_to_stream(out, ',\n"metrics":\n[ ');
Packit Service ed5168
        let first = true;
Packit Service ed5168
        for (let name in scriptModule.METRICS) {
Packit Service ed5168
            let metric = scriptModule.METRICS[name];
Packit Service ed5168
            // Extra checks here because JSON.stringify generates
Packit Service ed5168
            // invalid JSON for undefined values
Packit Service ed5168
            if (metric.description == null) {
Packit Service ed5168
                log("Error: No description found for metric " + name);
Packit Service ed5168
                continue;
Packit Service ed5168
            }
Packit Service ed5168
            if (metric.units == null) {
Packit Service ed5168
                log("Error: No units found for metric " + name);
Packit Service ed5168
                continue;
Packit Service ed5168
            }
Packit Service ed5168
            if (metric.value == null) {
Packit Service ed5168
                log("Error: No value found for metric " + name);
Packit Service ed5168
                continue;
Packit Service ed5168
            }
Packit Service ed5168
Packit Service ed5168
            if (!first)
Packit Service ed5168
                Shell.write_string_to_stream(out, ',\n  ');
Packit Service ed5168
            first = false;
Packit Service ed5168
Packit Service ed5168
            Shell.write_string_to_stream(out,
Packit Service ed5168
                                         '{ "name": ' + JSON.stringify(name) + ',\n' +
Packit Service ed5168
                                         '    "description": ' + JSON.stringify(metric.description) + ',\n' +
Packit Service ed5168
                                         '    "units": ' + JSON.stringify(metric.units) + ',\n' +
Packit Service ed5168
                                         '    "value": ' + JSON.stringify(metric.value) + ' }');
Packit Service ed5168
        }
Packit Service ed5168
        Shell.write_string_to_stream(out, ' ]');
Packit Service ed5168
Packit Service ed5168
        Shell.write_string_to_stream (out, ',\n"log":\n');
Packit Service ed5168
        Shell.PerfLog.get_default().dump_log(out);
Packit Service ed5168
Packit Service ed5168
        Shell.write_string_to_stream (out, '\n}\n');
Packit Service ed5168
        out.close(null);
Packit Service ed5168
    } else {
Packit Service ed5168
        let metrics = [];
Packit Service ed5168
        for (let metric in scriptModule.METRICS)
Packit Service ed5168
            metrics.push(metric);
Packit Service ed5168
Packit Service ed5168
        metrics.sort();
Packit Service ed5168
Packit Service ed5168
        print ('------------------------------------------------------------');
Packit Service ed5168
        for (let i = 0; i < metrics.length; i++) {
Packit Service ed5168
            let metric = metrics[i];
Packit Service ed5168
            print ('# ' + scriptModule.METRICS[metric].description);
Packit Service ed5168
            print (metric + ': ' +  scriptModule.METRICS[metric].value + scriptModule.METRICS[metric].units);
Packit Service ed5168
        }
Packit Service ed5168
        print ('------------------------------------------------------------');
Packit Service ed5168
    }
Packit Service ed5168
}
Packit Service ed5168
rpm-build b04d0e
async function _runPerfScript(scriptModule, outputFile) {
rpm-build b04d0e
    for (let step of scriptModule.run()) {
rpm-build b04d0e
        try {
rpm-build b04d0e
            await step; // eslint-disable-line no-await-in-loop
rpm-build b04d0e
        } catch (err) {
rpm-build b04d0e
            log(`Script failed: ${err}\n${err.stack}`);
rpm-build b04d0e
            Meta.exit(Meta.ExitCode.ERROR);
rpm-build b04d0e
        }
rpm-build b04d0e
    }
rpm-build b04d0e
rpm-build b04d0e
    try {
rpm-build b04d0e
        _collect(scriptModule, outputFile);
rpm-build b04d0e
    } catch (err) {
rpm-build b04d0e
        log(`Script failed: ${err}\n${err.stack}`);
rpm-build b04d0e
        Meta.exit(Meta.ExitCode.ERROR);
rpm-build b04d0e
    }
rpm-build b04d0e
    Meta.exit(Meta.ExitCode.SUCCESS);
rpm-build b04d0e
}
rpm-build b04d0e
Packit Service ed5168
/**
Packit Service ed5168
 * runPerfScript
Packit Service ed5168
 * @scriptModule: module object with run and finish functions
Packit Service ed5168
 *    and event handlers
Packit Service ed5168
 *
Packit Service ed5168
 * Runs a script for automated collection of performance data. The
Packit Service ed5168
 * script is defined as a Javascript module with specified contents.
Packit Service ed5168
 *
Packit Service ed5168
 * First the run() function within the module will be called as a
Packit Service ed5168
 * generator to automate a series of actions. These actions will
Packit Service ed5168
 * trigger performance events and the script can also record its
Packit Service ed5168
 * own performance events.
Packit Service ed5168
 *
Packit Service ed5168
 * Then the recorded event log is replayed using handler functions
Packit Service ed5168
 * within the module. The handler for the event 'foo.bar' is called
Packit Service ed5168
 * foo_bar().
Packit Service ed5168
 *
Packit Service ed5168
 * Finally if the module has a function called finish(), that will
Packit Service ed5168
 * be called.
Packit Service ed5168
 *
Packit Service ed5168
 * The event handler and finish functions are expected to fill in
Packit Service ed5168
 * metrics to an object within the module called METRICS. Each
Packit Service ed5168
 * property of this object represents an individual metric. The
Packit Service ed5168
 * name of the property is the name of the metric, the value
Packit Service ed5168
 * of the property is an object with the following properties:
Packit Service ed5168
 *
Packit Service ed5168
 *  description: human readable description of the metric
Packit Service ed5168
 *  units: a string representing the units of the metric. It has
Packit Service ed5168
 *   the form '<unit> <unit> ... / <unit> / <unit> ...'. Certain
Packit Service ed5168
 *   unit values are recognized: s, ms, us, B, KiB, MiB. Other
Packit Service ed5168
 *   values can appear but are uninterpreted. Examples 's',
Packit Service ed5168
 *   '/ s', 'frames', 'frames / s', 'MiB / s / frame'
Packit Service ed5168
 *  value: computed value of the metric
Packit Service ed5168
 *
Packit Service ed5168
 * The resulting metrics will be written to @outputFile as JSON, or,
Packit Service ed5168
 * if @outputFile is not provided, logged.
Packit Service ed5168
 *
Packit Service ed5168
 * After running the script and collecting statistics from the
Packit Service ed5168
 * event log, GNOME Shell will exit.
Packit Service ed5168
 **/
rpm-build b04d0e
function runPerfScript(scriptModule, outputFile) {
Packit Service ed5168
    Shell.PerfLog.get_default().set_enabled(true);
rpm-build b04d0e
    _spawnPerfHelper();
Packit Service ed5168
rpm-build b04d0e
    Gio.bus_watch_name(Gio.BusType.SESSION,
rpm-build b04d0e
        'org.gnome.Shell.PerfHelper',
rpm-build b04d0e
        Gio.BusNameWatcherFlags.NONE,
rpm-build b04d0e
        () => _runPerfScript(scriptModule, outputFile),
rpm-build b04d0e
        null);
Packit Service ed5168
}