diff --git a/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-process.png.sha256 b/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-process.png.sha256 new file mode 100644 index 0000000000..f411fafdfb --- /dev/null +++ b/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-process.png.sha256 @@ -0,0 +1 @@ +f34bf2a4e27e532ae99819fc3f9595be853383bf929756f1261a311d6ff3a572 \ No newline at end of file diff --git a/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-thread.png.sha256 b/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-thread.png.sha256 new file mode 100644 index 0000000000..1b81a92b09 --- /dev/null +++ b/test/data/ui-screenshots/wattson.test.ts/sched-aggregations/sched-aggr-thread.png.sha256 @@ -0,0 +1 @@ +af2e494f4ac1ca9a1121d276509e36fda60caa5b03c5538d024ee89297d34d31 \ No newline at end of file diff --git a/test/data/ui-screenshots/wattson.test.ts/wattson-aggregations/wattson-estimate-aggr.png.sha256 b/test/data/ui-screenshots/wattson.test.ts/wattson-aggregations/wattson-estimate-aggr.png.sha256 new file mode 100644 index 0000000000..f46669a97e --- /dev/null +++ b/test/data/ui-screenshots/wattson.test.ts/wattson-aggregations/wattson-estimate-aggr.png.sha256 @@ -0,0 +1 @@ +2795614d6dd948b0a0150e116c29a51a5e85abf45982ccde6a93d24b0f44dfdf \ No newline at end of file diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts index c218db6738..55c017c9b5 100644 --- a/ui/src/common/plugins.ts +++ b/ui/src/common/plugins.ts @@ -154,6 +154,10 @@ export class PluginManager { } } + hasPlugin(pluginId: string): boolean { + return pluginRegistry.has(pluginId); + } + isActive(pluginId: string): boolean { return this.getPluginContext(pluginId) !== undefined; } diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts index 8dcd714807..02d8f69f51 100644 --- a/ui/src/frontend/index.ts +++ b/ui/src/frontend/index.ts @@ -376,6 +376,13 @@ function onCssLoaded() { // Initialize plugins, now that we are ready to go pluginManager.initialize(); + + const route = Router.parseUrl(window.location.href); + for (const pluginId of (route.args.enablePlugins ?? '').split(',')) { + if (pluginManager.hasPlugin(pluginId)) { + pluginManager.activatePlugin(pluginId); + } + } } // If the URL is /#!?rpc_port=1234, change the default RPC port. diff --git a/ui/src/frontend/router.ts b/ui/src/frontend/router.ts index 5df1840d10..e865b07e98 100644 --- a/ui/src/frontend/router.ts +++ b/ui/src/frontend/router.ts @@ -76,6 +76,9 @@ const ROUTE_SCHEMA = z // Should we hide the sidebar? hideSidebar: z.boolean().optional().catch(undefined), + // A comma-separated list of plugins to enable for the current session. + enablePlugins: z.string().optional().catch(undefined), + // Deep link support ts: z.string().optional().catch(undefined), dur: z.string().optional().catch(undefined), diff --git a/ui/src/test/perfetto_ui_test_helper.ts b/ui/src/test/perfetto_ui_test_helper.ts index 40bbe40310..27ea7a1e71 100644 --- a/ui/src/test/perfetto_ui_test_helper.ts +++ b/ui/src/test/perfetto_ui_test_helper.ts @@ -47,8 +47,12 @@ export class PerfettoTestHelper { await this.page.click('body'); } - async openTraceFile(traceName: string): Promise { - await this.page.goto('/?testing=1'); + async openTraceFile(traceName: string, args?: {}): Promise { + args = {testing: '1', ...args}; + const qs = Object.entries(args ?? {}) + .map(([k, v]) => `${k}=${v}`) + .join('&'); + await this.page.goto('/?' + qs); const file = await this.page.waitForSelector('input.trace_file', { state: 'attached', }); diff --git a/ui/src/test/wattson.test.ts b/ui/src/test/wattson.test.ts new file mode 100644 index 0000000000..7f660d2c13 --- /dev/null +++ b/ui/src/test/wattson.test.ts @@ -0,0 +1,74 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {test, Page} from '@playwright/test'; +import {PerfettoTestHelper} from './perfetto_ui_test_helper'; +import {assertExists} from '../base/logging'; + +test.describe.configure({mode: 'serial'}); + +let pth: PerfettoTestHelper; +let page: Page; + +// Clip only the bottom half of the UI. When dealing with area selection, the +// time-width of the mouse-based region (which then is showed up in the upper +// ruler) is not 100% reproducible. +const SCREEN_CLIP = { + clip: { + x: 230, + y: 500, + width: 1920, + height: 1080, + }, +}; + +test.beforeAll(async ({browser}, _testInfo) => { + page = await browser.newPage(); + pth = new PerfettoTestHelper(page); + await pth.openTraceFile('wattson_dsu_pmu.pb', { + enablePlugins: 'org.kernel.Wattson', + }); +}); + +test('wattson aggregations', async () => { + const wattsonGrp = pth.locateTrackGroup('Wattson'); + await wattsonGrp.scrollIntoViewIfNeeded(); + await pth.toggleTrackGroup(wattsonGrp); + const cpuEstimate = pth.locateTrack('Wattson/Cpu0 Estimate', wattsonGrp); + const coords = assertExists(await cpuEstimate.boundingBox()); + await page.keyboard.press('Escape'); + await page.mouse.move(600, coords.y + 10); + await page.mouse.down(); + await page.mouse.move(1000, coords.y + 80); + await page.mouse.up(); + await pth.waitForIdleAndScreenshot('wattson-estimate-aggr.png', SCREEN_CLIP); + await page.keyboard.press('Escape'); +}); + +test('sched aggregations', async () => { + await page.keyboard.press('Escape'); + await page.mouse.move(600, 250); + await page.mouse.down(); + await page.mouse.move(800, 350); + await page.mouse.up(); + await pth.waitForPerfettoIdle(); + + await page.click('button[label="Wattson by thread"]'); + await pth.waitForIdleAndScreenshot('sched-aggr-thread.png', SCREEN_CLIP); + + await page.click('button[label="Wattson by process"]'); + await pth.waitForIdleAndScreenshot('sched-aggr-process.png', SCREEN_CLIP); + + await page.keyboard.press('Escape'); +});