2010-05-09 17:42:35 +00:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
2010-05-12 21:24:52 +00:00
|
|
|
const Gio = imports.gi.Gio;
|
2010-05-09 17:42:35 +00:00
|
|
|
const Mainloop = imports.mainloop;
|
|
|
|
|
|
|
|
const Meta = imports.gi.Meta;
|
|
|
|
const Shell = imports.gi.Shell;
|
|
|
|
|
|
|
|
// This module provides functionality for driving the shell user interface
|
|
|
|
// in an automated fashion. The primary current use case for this is
|
|
|
|
// automated performance testing (see runPerfScript()), but it could
|
|
|
|
// be applied to other forms of automation, such as testing for
|
|
|
|
// correctness as well.
|
|
|
|
//
|
|
|
|
// When scripting an automated test we want to make a series of calls
|
|
|
|
// in a linear fashion, but we also want to be able to let the main
|
|
|
|
// loop run so actions can finish. For this reason we write the script
|
|
|
|
// as a generator function that yields when it want to let the main
|
|
|
|
// loop run.
|
|
|
|
//
|
|
|
|
// yield Scripting.sleep(1000);
|
|
|
|
// main.overview.show();
|
|
|
|
// yield Scripting.waitLeisure();
|
|
|
|
//
|
|
|
|
// While it isn't important to the person writing the script, the actual
|
|
|
|
// yielded result is a function that the caller uses to provide the
|
|
|
|
// callback for resuming the script.
|
|
|
|
|
|
|
|
/**
|
|
|
|
* sleep:
|
|
|
|
* @milliseconds: number of milliseconds to wait
|
|
|
|
*
|
|
|
|
* Used within an automation script to pause the the execution of the
|
|
|
|
* current script for the specified amount of time. Use as
|
|
|
|
* 'yield Scripting.sleep(500);'
|
|
|
|
*/
|
|
|
|
function sleep(milliseconds) {
|
|
|
|
let cb;
|
|
|
|
|
|
|
|
Mainloop.timeout_add(milliseconds, function() {
|
|
|
|
if (cb)
|
|
|
|
cb();
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
return function(callback) {
|
|
|
|
cb = callback;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* waitLeisure:
|
|
|
|
*
|
|
|
|
* Used within an automation script to pause the the execution of the
|
|
|
|
* current script until the shell is completely idle. Use as
|
|
|
|
* 'yield Scripting.waitLeisure();'
|
|
|
|
*/
|
|
|
|
function waitLeisure() {
|
|
|
|
let cb;
|
|
|
|
|
|
|
|
global.run_at_leisure(function() {
|
|
|
|
if (cb)
|
|
|
|
cb();
|
|
|
|
});
|
|
|
|
|
|
|
|
return function(callback) {
|
|
|
|
cb = callback;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* defineScriptEvent
|
|
|
|
* @name: The event will be called script.<name>
|
|
|
|
* @description: Short human-readable description of the event
|
|
|
|
*
|
|
|
|
* Convenience function to define a zero-argument performance event
|
|
|
|
* within the 'script' namespace that is reserved for events defined locally
|
|
|
|
* within a performance automation script
|
|
|
|
*/
|
|
|
|
function defineScriptEvent(name, description) {
|
|
|
|
Shell.PerfLog.get_default().define_event("script." + name,
|
|
|
|
description,
|
|
|
|
"");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* scriptEvent
|
|
|
|
* @name: Name registered with defineScriptEvent()
|
|
|
|
*
|
|
|
|
* Convenience function to record a script-local performance event
|
|
|
|
* previously defined with defineScriptEvent
|
|
|
|
*/
|
|
|
|
function scriptEvent(name) {
|
|
|
|
Shell.PerfLog.get_default().event("script." + name);
|
|
|
|
}
|
|
|
|
|
2010-05-11 19:53:55 +00:00
|
|
|
/**
|
|
|
|
* collectStatistics
|
|
|
|
*
|
|
|
|
* Convenience function to trigger statistics collection
|
|
|
|
*/
|
|
|
|
function collectStatistics() {
|
|
|
|
Shell.PerfLog.get_default().collect_statistics();
|
|
|
|
}
|
|
|
|
|
2010-05-09 17:42:35 +00:00
|
|
|
function _step(g, finish, onError) {
|
|
|
|
try {
|
|
|
|
let waitFunction = g.next();
|
|
|
|
waitFunction(function() {
|
|
|
|
_step(g, finish, onError);
|
|
|
|
});
|
|
|
|
} catch (err if err instanceof StopIteration) {
|
|
|
|
if (finish)
|
|
|
|
finish();
|
|
|
|
} catch (err) {
|
|
|
|
if (onError)
|
|
|
|
onError(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function _collect(scriptModule, outputFile) {
|
|
|
|
let eventHandlers = {};
|
|
|
|
|
|
|
|
for (let f in scriptModule) {
|
|
|
|
let m = /([A-Za-z]+)_([A-Za-z]+)/.exec(f);
|
|
|
|
if (m)
|
|
|
|
eventHandlers[m[1] + "." + m[2]] = scriptModule[f];
|
|
|
|
}
|
|
|
|
|
|
|
|
Shell.PerfLog.get_default().replay(
|
|
|
|
function(time, eventName, signature, arg) {
|
|
|
|
if (eventName in eventHandlers)
|
|
|
|
eventHandlers[eventName](time, arg);
|
|
|
|
});
|
|
|
|
|
|
|
|
if ('finish' in scriptModule)
|
|
|
|
scriptModule.finish();
|
|
|
|
|
|
|
|
if (outputFile) {
|
2010-05-12 21:24:52 +00:00
|
|
|
let f = Gio.file_new_for_path(outputFile);
|
|
|
|
let raw = f.replace(null, false,
|
|
|
|
Gio.FileCreateFlags.NONE,
|
|
|
|
null);
|
|
|
|
let out = Gio.BufferedOutputStream.new_sized (raw, 4096);
|
|
|
|
Shell.write_string_to_stream (out, "{\n");
|
|
|
|
|
|
|
|
Shell.write_string_to_stream(out, '"events":\n');
|
|
|
|
Shell.PerfLog.get_default().dump_events(out);
|
|
|
|
|
2010-05-18 23:13:56 +00:00
|
|
|
let monitors = global.get_monitors()
|
|
|
|
let primary = global.get_primary_monitor()
|
|
|
|
Shell.write_string_to_stream(out, ',\n"monitors":\n[');
|
|
|
|
for (let i = 0; i < monitors.length; i++) {
|
|
|
|
let monitor = monitors[i];
|
|
|
|
let is_primary = (monitor.x == primary.x &&
|
|
|
|
monitor.y == primary.y &&
|
|
|
|
monitor.width == primary.width &&
|
|
|
|
monitor.height == primary.height);
|
|
|
|
if (i != 0)
|
|
|
|
Shell.write_string_to_stream(out, ', ');
|
|
|
|
Shell.write_string_to_stream(out, '"%s%dx%d+%d+%d"'.format(is_primary ? "*" : "",
|
|
|
|
monitor.width, monitor.height,
|
|
|
|
monitor.x, monitor.y));
|
|
|
|
}
|
|
|
|
Shell.write_string_to_stream(out, ' ]');
|
|
|
|
|
2010-05-12 21:24:52 +00:00
|
|
|
Shell.write_string_to_stream(out, ',\n"metrics":\n[ ');
|
|
|
|
let first = true;
|
|
|
|
for (let name in scriptModule.METRICS) {
|
2010-05-17 18:04:09 +00:00
|
|
|
let metric = scriptModule.METRICS[name];
|
2010-05-12 21:24:52 +00:00
|
|
|
|
|
|
|
if (!first)
|
|
|
|
Shell.write_string_to_stream(out, ',\n ');
|
|
|
|
first = false;
|
|
|
|
|
|
|
|
Shell.write_string_to_stream(out,
|
|
|
|
'{ "name": ' + JSON.stringify(name) + ',\n' +
|
2010-05-17 18:04:09 +00:00
|
|
|
' "description": ' + JSON.stringify(metric.description) + ',\n' +
|
|
|
|
' "units": ' + JSON.stringify(metric.units) + ',\n' +
|
|
|
|
' "value": ' + JSON.stringify(metric.value) + ' }');
|
2010-05-09 17:42:35 +00:00
|
|
|
}
|
2010-05-12 21:24:52 +00:00
|
|
|
Shell.write_string_to_stream(out, ' ]');
|
2010-05-09 17:42:35 +00:00
|
|
|
|
2010-05-12 21:24:52 +00:00
|
|
|
Shell.write_string_to_stream (out, ',\n"log":\n');
|
|
|
|
Shell.PerfLog.get_default().dump_log(out);
|
|
|
|
|
|
|
|
Shell.write_string_to_stream (out, '\n}\n');
|
|
|
|
out.close(null);
|
2010-05-09 17:42:35 +00:00
|
|
|
} else {
|
|
|
|
let metrics = [];
|
|
|
|
for (let metric in scriptModule.METRICS)
|
|
|
|
metrics.push(metric);
|
|
|
|
|
|
|
|
metrics.sort();
|
|
|
|
|
|
|
|
print ('------------------------------------------------------------');
|
|
|
|
for (let i = 0; i < metrics.length; i++) {
|
|
|
|
let metric = metrics[i];
|
|
|
|
print ('# ' + scriptModule.METRIC_DESCRIPTIONS[metric]);
|
|
|
|
print (metric + ': ' + scriptModule.METRICS[metric]);
|
|
|
|
}
|
|
|
|
print ('------------------------------------------------------------');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* runPerfScript
|
|
|
|
* @scriptModule: module object with run and finish functions
|
|
|
|
* and event handlers
|
|
|
|
*
|
|
|
|
* Runs a script for automated collection of performance data. The
|
|
|
|
* script is defined as a Javascript module with specified contents.
|
|
|
|
*
|
|
|
|
* First the run() function within the module will be called as a
|
|
|
|
* generator to automate a series of actions. These actions will
|
|
|
|
* trigger performance events and the script can also record its
|
|
|
|
* own performance events.
|
|
|
|
*
|
|
|
|
* Then the recorded event log is replayed using handler functions
|
|
|
|
* within the module. The handler for the event 'foo.bar' is called
|
|
|
|
* foo_bar().
|
|
|
|
*
|
|
|
|
* Finally if the module has a function called finish(), that will
|
|
|
|
* be called.
|
|
|
|
*
|
|
|
|
* The event handler and finish functions are expected to fill in
|
2010-05-17 18:04:09 +00:00
|
|
|
* metrics to an object within the module called METRICS. Each
|
|
|
|
* property of this object represents an individual metric. The
|
|
|
|
* name of the property is the name of the metric, the value
|
|
|
|
* of the property is an object with the following properties:
|
|
|
|
*
|
|
|
|
* description: human readable description of the metric
|
|
|
|
* units: a string representing the units of the metric. It has
|
|
|
|
* the form '<unit> <unit> ... / <unit> / <unit> ...'. Certain
|
|
|
|
* unit values are recognized: s, ms, us, B, KiB, MiB. Other
|
|
|
|
* values can appear but are uninterpreted. Examples 's',
|
|
|
|
* '/ s', 'frames', 'frames / s', 'MiB / s / frame'
|
|
|
|
* value: computed value of the metric
|
2010-05-09 17:42:35 +00:00
|
|
|
*
|
|
|
|
* The resulting metrics will be written to @outputFile as JSON, or,
|
|
|
|
* if @outputFile is not provided, logged.
|
|
|
|
*
|
|
|
|
* After running the script and collecting statistics from the
|
|
|
|
* event log, GNOME Shell will exit.
|
|
|
|
**/
|
|
|
|
function runPerfScript(scriptModule, outputFile) {
|
|
|
|
Shell.PerfLog.get_default().set_enabled(true);
|
|
|
|
|
|
|
|
let g = scriptModule.run();
|
|
|
|
|
|
|
|
_step(g,
|
|
|
|
function() {
|
|
|
|
_collect(scriptModule, outputFile);
|
2010-05-11 22:08:50 +00:00
|
|
|
Meta.exit(Meta.ExitCode.SUCCESS);
|
2010-05-09 17:42:35 +00:00
|
|
|
},
|
|
|
|
function(err) {
|
|
|
|
log("Script failed: " + err + "\n" + err.stack);
|
|
|
|
Meta.exit(Meta.ExitCode.ERROR);
|
|
|
|
});
|
|
|
|
}
|