summaryrefslogtreecommitdiffstats
path: root/js/perf/core.js
blob: bd7e220f194557d8c52d4e0dffbd5014d1e4473e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported run, script_overviewShowStart, script_overviewShowDone,
            script_applicationsShowStart, script_applicationsShowDone,
            script_afterShowHide, malloc_usedSize, glx_swapComplete,
            clutter_stagePaintDone */
/* eslint camelcase: ["error", { properties: "never", allow: ["^script_", "^malloc", "^glx", "^clutter"] }] */

const System = imports.system;

const Main = imports.ui.main;
const Scripting = imports.ui.scripting;

// This performance script measure the most important (core) performance
// metrics for the shell. By looking at the output metrics of this script
// someone should be able to get an idea of how well the shell is performing
// on a particular system.

var METRICS = {
    overviewLatencyFirst: {
        description: 'Time to first frame after triggering overview, first time',
        units: 'us',
    },
    overviewFpsFirst: {
        description: 'Frame rate when going to the overview, first time',
        units: 'frames / s',
    },
    overviewLatencySubsequent: {
        description: 'Time to first frame after triggering overview, second time',
        units: 'us',
    },
    overviewFpsSubsequent: {
        description: 'Frames rate when going to the overview, second time',
        units: 'frames / s',
    },
    overviewFps5Windows: {
        description: 'Frames rate when going to the overview, 5 windows open',
        units: 'frames / s',
    },
    overviewFps10Windows: {
        description: 'Frames rate when going to the overview, 10 windows open',
        units: 'frames / s',
    },
    overviewFps5Maximized: {
        description: 'Frames rate when going to the overview, 5 maximized windows open',
        units: 'frames / s',
    },
    overviewFps10Maximized: {
        description: 'Frames rate when going to the overview, 10 maximized windows open',
        units: 'frames / s',
    },
    overviewFps5Alpha: {
        description: 'Frames rate when going to the overview, 5 alpha-transparent windows open',
        units: 'frames / s',
    },
    overviewFps10Alpha: {
        description: 'Frames rate when going to the overview, 10 alpha-transparent windows open',
        units: 'frames / s',
    },
    usedAfterOverview: {
        description: "Malloc'ed bytes after the overview is shown once",
        units: 'B',
    },
    leakedAfterOverview: {
        description: "Additional malloc'ed bytes the second time the overview is shown",
        units: 'B',
    },
    applicationsShowTimeFirst: {
        description: 'Time to switch to applications view, first time',
        units: 'us',
    },
    applicationsShowTimeSubsequent: {
        description: 'Time to switch to applications view, second time',
        units: 'us',
    },
};

const WINDOW_CONFIGS = [{
    width: 640, height: 480,
    alpha: false, maximized: false, count: 1,  metric: 'overviewFpsSubsequent',
}, {
    width: 640, height: 480,
    alpha: false, maximized: false, count: 5,  metric: 'overviewFps5Windows',
}, {
    width: 640, height: 480,
    alpha: false, maximized: false, count: 10, metric: 'overviewFps10Windows',
}, {
    width: 640, height: 480,
    alpha: false, maximized: true,  count: 5,  metric: 'overviewFps5Maximized',
}, {
    width: 640, height: 480,
    alpha: false, maximized: true,  count: 10, metric: 'overviewFps10Maximized',
}, {
    width: 640, height: 480,
    alpha: true,  maximized: false, count: 5,  metric: 'overviewFps5Alpha',
}, {
    width: 640, height: 480,
    alpha: true,  maximized: false, count: 10, metric: 'overviewFps10Alpha',
}];

async function run() {
    /* eslint-disable no-await-in-loop */
    Scripting.defineScriptEvent("overviewShowStart", "Starting to show the overview");
    Scripting.defineScriptEvent("overviewShowDone", "Overview finished showing");
    Scripting.defineScriptEvent("afterShowHide", "After a show/hide cycle for the overview");
    Scripting.defineScriptEvent("applicationsShowStart", "Starting to switch to applications view");
    Scripting.defineScriptEvent("applicationsShowDone", "Done switching to applications view");

    // Enable recording of timestamps for different points in the frame cycle
    global.frame_timestamps = true;

    Main.overview.connect('shown', () => {
        Scripting.scriptEvent('overviewShowDone');
    });

    await Scripting.sleep(1000);

    for (let i = 0; i < 2 * WINDOW_CONFIGS.length; i++) {
        // We go to the overview twice for each configuration; the first time
        // to calculate the mipmaps for the windows, the second time to get
        // a clean set of numbers.
        if ((i % 2) == 0) {
            let config = WINDOW_CONFIGS[i / 2];
            await Scripting.destroyTestWindows();

            for (let k = 0; k < config.count; k++) {
                await Scripting.createTestWindow({
                    width: config.width,
                    height: config.height,
                    alpha: config.alpha,
                    maximized: config.maximized,
                });
            }

            await Scripting.waitTestWindows();
            await Scripting.sleep(1000);
            await Scripting.waitLeisure();
        }

        Scripting.scriptEvent('overviewShowStart');
        Main.overview.show();

        await Scripting.waitLeisure();
        Main.overview.hide();
        await Scripting.waitLeisure();

        System.gc();
        await Scripting.sleep(1000);
        Scripting.collectStatistics();
        Scripting.scriptEvent('afterShowHide');
    }

    await Scripting.destroyTestWindows();
    await Scripting.sleep(1000);

    Main.overview.show();
    await Scripting.waitLeisure();

    for (let i = 0; i < 2; i++) {
        Scripting.scriptEvent('applicationsShowStart');
        // eslint-disable-next-line require-atomic-updates
        Main.overview.dash.showAppsButton.checked = true;
        await Scripting.waitLeisure();
        Scripting.scriptEvent('applicationsShowDone');
        // eslint-disable-next-line require-atomic-updates
        Main.overview.dash.showAppsButton.checked = false;
        await Scripting.waitLeisure();
    }
    /* eslint-enable no-await-in-loop */
}

let showingOverview = false;
let finishedShowingOverview = false;
let overviewShowStart;
let overviewFrames;
let overviewLatency;
let mallocUsedSize = 0;
let overviewShowCount = 0;
let haveSwapComplete = false;
let applicationsShowStart;
let applicationsShowCount = 0;

function script_overviewShowStart(time) {
    showingOverview = true;
    finishedShowingOverview = false;
    overviewShowStart = time;
    overviewFrames = 0;
}

function script_overviewShowDone(_time) {
    // We've set up the state at the end of the zoom out, but we
    // need to wait for one more frame to paint before we count
    // ourselves as done.
    finishedShowingOverview = true;
}

function script_applicationsShowStart(time) {
    applicationsShowStart = time;
}

function script_applicationsShowDone(time) {
    applicationsShowCount++;
    if (applicationsShowCount == 1)
        METRICS.applicationsShowTimeFirst.value = time - applicationsShowStart;
    else
        METRICS.applicationsShowTimeSubsequent.value = time - applicationsShowStart;
}

function script_afterShowHide(_time) {
    if (overviewShowCount == 1)
        METRICS.usedAfterOverview.value = mallocUsedSize;
    else
        METRICS.leakedAfterOverview.value = mallocUsedSize - METRICS.usedAfterOverview.value;
}

function malloc_usedSize(time, bytes) {
    mallocUsedSize = bytes;
}

function _frameDone(time) {
    if (showingOverview) {
        if (overviewFrames == 0)
            overviewLatency = time - overviewShowStart;

        overviewFrames++;
    }

    if (finishedShowingOverview) {
        showingOverview = false;
        finishedShowingOverview = false;
        overviewShowCount++;

        let dt = (time - (overviewShowStart + overviewLatency)) / 1000000;

        // If we see a start frame and an end frame, that would
        // be 1 frame for a FPS computation, hence the '- 1'
        let fps = (overviewFrames - 1) / dt;

        if (overviewShowCount == 1) {
            METRICS.overviewLatencyFirst.value = overviewLatency;
            METRICS.overviewFpsFirst.value = fps;
        } else if (overviewShowCount == 2) {
            METRICS.overviewLatencySubsequent.value = overviewLatency;
        }

        // Other than overviewFpsFirst, we collect FPS metrics the second
        // we show each window configuration. overviewShowCount is 1,2,3...
        if (overviewShowCount % 2 == 0) {
            let config = WINDOW_CONFIGS[(overviewShowCount / 2) - 1];
            METRICS[config.metric].value = fps;
        }
    }
}

function glx_swapComplete(time, swapTime) {
    haveSwapComplete = true;

    _frameDone(swapTime);
}

function clutter_stagePaintDone(time) {
    // If we aren't receiving GLXBufferSwapComplete events, then we approximate
    // the time the user sees a frame with the time we finished doing drawing
    // commands for the frame. This doesn't take into account the time for
    // the GPU to finish painting, and the time for waiting for the buffer
    // swap, but if this are uniform - every frame takes the same time to draw -
    // then it won't upset our FPS calculation, though the latency value
    // will be slightly too low.

    if (!haveSwapComplete)
        _frameDone(time);
}