Merge "ui: Add initial plugin architecture"
diff --git a/tools/gen_ui_imports b/tools/gen_ui_imports
index 1f10a38..2a5f5b4 100755
--- a/tools/gen_ui_imports
+++ b/tools/gen_ui_imports
@@ -13,15 +13,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Generates TypeScript files that import all subdirectories.
-This is useful for plugins/extensions. If you have two modules:
+"""Generates TypeScript files that import all subdirectories and
+registers them with plugin registry. If you have three modules:
- core/
-- plugins/myplugin/
+- plugins/foo_plugin/
+- plugins/bar_plugin/
In general you would like the dependency to only go one way:
-- plugins/myplugin/ -> core/
-But you still need some index file to import all plugins for the sake of
-bundling. This avoids having to manually edit core/ for every
-plugin you add.
+- plugins/foo_plugin/ -> core/
+We want to avoid manually editing core/ for every plugin.
+
+This generates code like:
+
+import {pluginRegistry} from '../common/plugins';
+
+import {plugin as fooPlugin} from '../plugins/foo_plugin';
+import {plugin as barPlugin} from '../plugins/bar_plugin';
+
+pluginRegistry.register(fooPlugin);
+pluginRegistry.register(barPlugin);
"""
from __future__ import print_function
@@ -33,17 +42,34 @@
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
UI_SRC_DIR = os.path.join(ROOT_DIR, 'ui', 'src')
+PLUGINS_PATH = os.path.join(UI_SRC_DIR, 'common', 'plugins')
+
+def to_camel_case(s):
+ first, *rest = s.split('_')
+ return first + ''.join(x.title() for x in rest)
def gen_imports(input_dir, output_path):
paths = [os.path.join(input_dir, p) for p in os.listdir(input_dir)]
paths = [p for p in paths if os.path.isdir(p)]
paths.sort()
- lines = []
+ output_dir = os.path.dirname(output_path)
+ rel_plugins_path = os.path.relpath(PLUGINS_PATH, output_dir)
+
+ imports = []
+ registrations = []
for path in paths:
- rel_path = os.path.relpath(path, os.path.dirname(output_path))
- lines.append(f"import '{rel_path}';")
- expected = '\n'.join(lines)
+ rel_path = os.path.relpath(path, output_dir)
+ snake_name = os.path.basename(path)
+ camel_name = to_camel_case(snake_name)
+ imports.append(f"import {{plugin as {camel_name}}} from '{rel_path}';")
+ registrations.append(f"pluginRegistry.register({camel_name});")
+
+ header = f"import {{pluginRegistry}} from '{rel_plugins_path}';"
+ import_text = '\n'.join(imports)
+ registration_text = '\n'.join(registrations)
+
+ expected = f"{header}\n\n{import_text}\n\n{registration_text}"
with open(output_path, 'w') as f:
f.write(expected)
diff --git a/ui/src/common/plugin_api.ts b/ui/src/common/plugin_api.ts
new file mode 100644
index 0000000..913e190
--- /dev/null
+++ b/ui/src/common/plugin_api.ts
@@ -0,0 +1,18 @@
+// Copyright (C) 2022 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.
+
+export interface PluginInfo {
+ pluginId: string;
+ activate: () => void;
+}
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
new file mode 100644
index 0000000..ea1e6e8
--- /dev/null
+++ b/ui/src/common/plugins.ts
@@ -0,0 +1,20 @@
+// Copyright (C) 2022 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 {PluginInfo} from './plugin_api';
+import {Registry} from './registry';
+
+export const pluginRegistry = new Registry<PluginInfo>((info) => {
+ return info.pluginId;
+});
diff --git a/ui/src/common/recordingV2/target_factory_registry.ts b/ui/src/common/recordingV2/target_factory_registry.ts
index f8be843..8f8f9b1 100644
--- a/ui/src/common/recordingV2/target_factory_registry.ts
+++ b/ui/src/common/recordingV2/target_factory_registry.ts
@@ -38,4 +38,6 @@
}
}
-export const targetFactoryRegistry = new TargetFactoryRegistry();
+export const targetFactoryRegistry = new TargetFactoryRegistry((f) => {
+ return f.kind;
+});
diff --git a/ui/src/common/registry.ts b/ui/src/common/registry.ts
index 2c36a96..1658834 100644
--- a/ui/src/common/registry.ts
+++ b/ui/src/common/registry.ts
@@ -14,15 +14,21 @@
export interface HasKind { kind: string; }
-export class Registry<T extends HasKind> {
+export class Registry<T> {
+ private key: (t: T) => string;
protected registry: Map<string, T>;
- constructor() {
+ static kindRegistry<T extends HasKind>(): Registry<T> {
+ return new Registry<T>((t) => t.kind);
+ }
+
+ constructor(key: (t: T) => string) {
this.registry = new Map<string, T>();
+ this.key = key;
}
register(registrant: T) {
- const kind = registrant.kind;
+ const kind = this.key(registrant);
if (this.registry.has(kind)) {
throw new Error(`Registrant ${kind} already exists in the registry`);
}
@@ -41,6 +47,11 @@
return registrant;
}
+ // Support iteration: for (const foo of fooRegistry.values()) { ... }
+ * values() {
+ yield* this.registry.values();
+ }
+
unregisterAllForTesting(): void {
this.registry.clear();
}
diff --git a/ui/src/common/registry_unittest.ts b/ui/src/common/registry_unittest.ts
index 672b5a9..58a04e4 100644
--- a/ui/src/common/registry_unittest.ts
+++ b/ui/src/common/registry_unittest.ts
@@ -1,4 +1,3 @@
-
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +20,7 @@
}
test('registry returns correct registrant', () => {
- const registry = new Registry<Registrant>();
+ const registry = Registry.kindRegistry<Registrant>();
const a: Registrant = {kind: 'a', n: 1};
const b: Registrant = {kind: 'b', n: 2};
@@ -33,7 +32,7 @@
});
test('registry throws error on kind collision', () => {
- const registry = new Registry<Registrant>();
+ const registry = Registry.kindRegistry<Registrant>();
const a1: Registrant = {kind: 'a', n: 1};
const a2: Registrant = {kind: 'a', n: 2};
@@ -43,6 +42,19 @@
});
test('registry throws error on non-existent track', () => {
- const registry = new Registry<Registrant>();
+ const registry = Registry.kindRegistry<Registrant>();
expect(() => registry.get('foo')).toThrow();
});
+
+test('registry allows iteration', () => {
+ const registry = Registry.kindRegistry<Registrant>();
+ const a: Registrant = {kind: 'a', n: 1};
+ const b: Registrant = {kind: 'b', n: 2};
+ registry.register(a);
+ registry.register(b);
+
+ const values = [...registry.values()];
+ expect(values.length).toBe(2);
+ expect(values.includes(a)).toBe(true);
+ expect(values.includes(b)).toBe(true);
+});
diff --git a/ui/src/controller/track_controller.ts b/ui/src/controller/track_controller.ts
index bf56a35..9f8593a 100644
--- a/ui/src/controller/track_controller.ts
+++ b/ui/src/controller/track_controller.ts
@@ -289,4 +289,5 @@
kind: string;
}
-export const trackControllerRegistry = new Registry<TrackControllerFactory>();
+export const trackControllerRegistry =
+ Registry.kindRegistry<TrackControllerFactory>();
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index a86e2d1..f59f224 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -21,6 +21,7 @@
import {createEmptyState} from '../common/empty_state';
import {RECORDING_V2_FLAG} from '../common/feature_flags';
import {initializeImmerJs} from '../common/immer_init';
+import {pluginRegistry} from '../common/plugins';
import {State} from '../common/state';
import {initWasm} from '../common/wasm_engine_proxy';
import {ControllerWorkerInitMessage} from '../common/worker_messages';
@@ -296,6 +297,11 @@
if (globals.testing) {
document.body.classList.add('testing');
}
+
+ // Initialize all plugins:
+ for (const plugin of pluginRegistry.values()) {
+ plugin.activate();
+ }
}
diff --git a/ui/src/frontend/track_registry.ts b/ui/src/frontend/track_registry.ts
index f6c9401..92007d7 100644
--- a/ui/src/frontend/track_registry.ts
+++ b/ui/src/frontend/track_registry.ts
@@ -18,4 +18,4 @@
/**
* Global registry that maps types to TrackCreator.
*/
-export const trackRegistry = new Registry<TrackCreator>();
+export const trackRegistry = Registry.kindRegistry<TrackCreator>();
diff --git a/ui/src/tracks/actual_frames/index.ts b/ui/src/tracks/actual_frames/index.ts
index 308406e..7cfe94c 100644
--- a/ui/src/tracks/actual_frames/index.ts
+++ b/ui/src/tracks/actual_frames/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.ActualFrames',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/android_log/index.ts b/ui/src/tracks/android_log/index.ts
index 308406e..8bbdc7c 100644
--- a/ui/src/tracks/android_log/index.ts
+++ b/ui/src/tracks/android_log/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.AndroidLog',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/async_slices/index.ts b/ui/src/tracks/async_slices/index.ts
index 308406e..b2393fd 100644
--- a/ui/src/tracks/async_slices/index.ts
+++ b/ui/src/tracks/async_slices/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.AsyncSlices',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/chrome_slices/index.ts b/ui/src/tracks/chrome_slices/index.ts
index 308406e..cd99ee0 100644
--- a/ui/src/tracks/chrome_slices/index.ts
+++ b/ui/src/tracks/chrome_slices/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.ChromeSlices',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index 308406e..c510075 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.Counter',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index 308406e..239985b 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.CpuFreq',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index 308406e..02c3cac 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.CpuProfile',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 308406e..2f74b2d 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.CpuSlices',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/debug_slices/index.ts b/ui/src/tracks/debug_slices/index.ts
index 308406e..681f3d2 100644
--- a/ui/src/tracks/debug_slices/index.ts
+++ b/ui/src/tracks/debug_slices/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.DebugSlices',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/expected_frames/index.ts b/ui/src/tracks/expected_frames/index.ts
index 308406e..c87320b 100644
--- a/ui/src/tracks/expected_frames/index.ts
+++ b/ui/src/tracks/expected_frames/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.ExpectedFrames',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/generic_slice_track/index.ts b/ui/src/tracks/generic_slice_track/index.ts
index 3c66953..25c6c7a 100644
--- a/ui/src/tracks/generic_slice_track/index.ts
+++ b/ui/src/tracks/generic_slice_track/index.ts
@@ -46,3 +46,8 @@
}
trackRegistry.register(GenericSliceTrack);
+
+export const plugin = {
+ pluginId: 'perfetto.GenericSliceTrack',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/heap_profile/index.ts b/ui/src/tracks/heap_profile/index.ts
index 308406e..bb45aca 100644
--- a/ui/src/tracks/heap_profile/index.ts
+++ b/ui/src/tracks/heap_profile/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.HeapProfile',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/null_track/index.ts b/ui/src/tracks/null_track/index.ts
index c0f4b7e..5a9483a 100644
--- a/ui/src/tracks/null_track/index.ts
+++ b/ui/src/tracks/null_track/index.ts
@@ -36,3 +36,8 @@
}
trackRegistry.register(NullTrack);
+
+export const plugin = {
+ pluginId: 'perfetto.NullTrack',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/perf_samples_profile/index.ts b/ui/src/tracks/perf_samples_profile/index.ts
index 308406e..abadb05 100644
--- a/ui/src/tracks/perf_samples_profile/index.ts
+++ b/ui/src/tracks/perf_samples_profile/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.PerfSamplesProfile',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/process_scheduling/index.ts b/ui/src/tracks/process_scheduling/index.ts
index 308406e..126071f 100644
--- a/ui/src/tracks/process_scheduling/index.ts
+++ b/ui/src/tracks/process_scheduling/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.ProcessScheduling',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/process_summary/index.ts b/ui/src/tracks/process_summary/index.ts
index 308406e..e6e93c0 100644
--- a/ui/src/tracks/process_summary/index.ts
+++ b/ui/src/tracks/process_summary/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.ProcessSummary',
+ activate: () => {},
+};
diff --git a/ui/src/tracks/thread_state/index.ts b/ui/src/tracks/thread_state/index.ts
index 308406e..ceecf98 100644
--- a/ui/src/tracks/thread_state/index.ts
+++ b/ui/src/tracks/thread_state/index.ts
@@ -14,3 +14,8 @@
import './controller';
import './frontend';
+
+export const plugin = {
+ pluginId: 'perfetto.ThreadState',
+ activate: () => {},
+};