Skip to content

Commit

Permalink
Implement static tracks & adapter for migrating old tracks.
Browse files Browse the repository at this point in the history
- Implement addTrack() in TracePluginContext.
- Add new adapter classes for adapting old controller based tracks.
- Use adapter classes to adapt ftrace tracks.

Change-Id: I0a40cfb4652997b89f9cf0edd05a6dd2728c3c1f
  • Loading branch information
stevegolton committed Aug 25, 2023
1 parent fdaee3f commit f095db1
Show file tree
Hide file tree
Showing 9 changed files with 479 additions and 89 deletions.
127 changes: 76 additions & 51 deletions ui/src/common/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ import {
PluginClass,
PluginContext,
PluginInfo,
PluginTrackInfo,
StatefulPlugin,
Store,
TracePluginContext,
TrackInfo,
TrackLike,
Viewer,
} from '../public';

Expand All @@ -45,29 +47,28 @@ import {Registry} from './registry';
// plugins.
// The PluginContext exists for the whole duration a plugin is active.
export class PluginContextImpl implements PluginContext, Disposable {
readonly pluginId: string;
readonly viewer: ViewerProxy;
private trash = new Trash();
private alive = true;

constructor(pluginId: string, viewer: ViewerProxy) {
this.pluginId = pluginId;

this.viewer = viewer;
constructor(readonly pluginId: string, readonly viewer: ViewerProxy) {
this.trash.add(viewer);
}

registerTrackController(track: TrackControllerFactory): void {
if (!this.alive) return;
const unregister = trackControllerRegistry.register(track);
this.trash.add(unregister);
}

registerTrack(track: TrackCreator): void {
if (!this.alive) return;
const unregister = trackRegistry.register(track);
this.trash.add(unregister);
}

dispose(): void {
this.trash.dispose();
this.alive = false;
}
}

Expand All @@ -76,35 +77,51 @@ export class PluginContextImpl implements PluginContext, Disposable {
// The TracePluginContext exists for the whole duration a plugin is active AND a
// trace is loaded.
class TracePluginContextImpl<T> implements TracePluginContext<T>, Disposable {
private ctx: PluginContext;
readonly engine: EngineProxy;
readonly store: Store<T>;
private trash = new Trash();
private alive = true;

constructor(ctx: PluginContext, store: Store<T>, engine: EngineProxy) {
this.ctx = ctx;

this.engine = engine;
constructor(
private ctx: PluginContext, readonly store: Store<T>,
readonly engine: EngineProxy,
private trackRegistry: Map<string, PluginTrackInfo>) {
this.trash.add(engine);

this.store = store;
this.trash.add(store);
}

registerTrackController(track: TrackControllerFactory): void {
// Silently ignore if context is dead.
if (!this.alive) return;
this.ctx.registerTrackController(track);
}

registerTrack(track: TrackCreator): void {
// Silently ignore if context is dead.
if (!this.alive) return;
this.ctx.registerTrack(track);
}

get viewer(): Viewer {
return this.ctx.viewer;
}

// Register a new track in this context.
// All tracks registered through this method are removed when this context is
// destroyed, i.e. when the trace is unloaded.
addTrack(trackDetails: PluginTrackInfo): void {
// Silently ignore if context is dead.
if (!this.alive) return;
const {uri} = trackDetails;
this.trackRegistry.set(uri, trackDetails);
this.trash.add({
dispose: () => {
this.trackRegistry.delete(uri);
},
});
}

dispose(): void {
this.trash.dispose();
this.alive = false;
}
}

Expand Down Expand Up @@ -146,6 +163,7 @@ export class PluginManager {
private registry: PluginRegistry;
private plugins: Map<string, PluginDetails<unknown>>;
private engine?: Engine;
readonly trackRegistry = new Map<string, PluginTrackInfo>();

constructor(registry: PluginRegistry) {
this.registry = registry;
Expand Down Expand Up @@ -173,7 +191,7 @@ export class PluginManager {
// If a trace is already loaded when plugin is activated, make sure to
// call onTraceLoad().
if (this.engine) {
doPluginTraceLoad(pluginDetails, this.engine, id);
this.doPluginTraceLoad(pluginDetails, this.engine, id);
}

this.plugins.set(id, pluginDetails);
Expand Down Expand Up @@ -216,7 +234,7 @@ export class PluginManager {
onTraceLoad(engine: Engine): void {
this.engine = engine;
for (const [id, pluginDetails] of this.plugins) {
doPluginTraceLoad(pluginDetails, engine, id);
this.doPluginTraceLoad(pluginDetails, engine, id);
}
}

Expand Down Expand Up @@ -254,49 +272,56 @@ export class PluginManager {
}
});
}
}

function isStatefulPlugin<T>(plugin: BasePlugin<T>|
StatefulPlugin<T>): plugin is StatefulPlugin<T> {
return 'migrate' in plugin;
}
// Create a new plugin track object from its ID.
// Returns undefined if no such track is registered.
createTrack(id: string): TrackLike|undefined {
const trackInfo = pluginManager.trackRegistry.get(id);
return trackInfo && trackInfo.trackFactory();
}

function doPluginTraceLoad<T>(
pluginDetails: PluginDetails<T>, engine: Engine, pluginId: string): void {
const {plugin, context} = pluginDetails;
private doPluginTraceLoad<T>(
pluginDetails: PluginDetails<T>, engine: Engine, pluginId: string): void {
const {plugin, context} = pluginDetails;

const engineProxy = engine.getProxy(pluginId);
const engineProxy = engine.getProxy(pluginId);

// Migrate state & write back to store.
if (isStatefulPlugin(plugin)) {
const initialState = globals.store.state.plugins[pluginId];
const migratedState = plugin.migrate(initialState);
globals.store.edit((draft) => {
draft.plugins[pluginId] = migratedState;
});
// Migrate state & write back to store.
if (isStatefulPlugin(plugin)) {
const initialState = globals.store.state.plugins[pluginId];
const migratedState = plugin.migrate(initialState);
globals.store.edit((draft) => {
draft.plugins[pluginId] = migratedState;
});

const proxyStore = globals.store.createProxy<T>(['plugins', pluginId]);
const traceCtx =
new TracePluginContextImpl(context, proxyStore, engineProxy);
pluginDetails.traceContext = traceCtx;
const proxyStore = globals.store.createProxy<T>(['plugins', pluginId]);
const traceCtx = new TracePluginContextImpl(
context, proxyStore, engineProxy, this.trackRegistry);
pluginDetails.traceContext = traceCtx;

// TODO(stevegolton): Await onTraceLoad.
plugin.onTraceLoad && plugin.onTraceLoad(traceCtx);
} else {
// Stateless plugin i.e. the plugin's state type is undefined.
// Just provide a store proxy over this plugin's state, the plugin can work
// the state out for itself if it wants to, but we're not going to help it
// out by calling migrate().
const proxyStore = globals.store.createProxy<T>(['plugins', pluginId]);
const traceCtx =
new TracePluginContextImpl(context, proxyStore, engineProxy);
pluginDetails.traceContext = traceCtx;

// TODO(stevegolton): Await onTraceLoad.
plugin.onTraceLoad && plugin.onTraceLoad(traceCtx);
// TODO(stevegolton): Await onTraceLoad.
plugin.onTraceLoad && plugin.onTraceLoad(traceCtx);
} else {
// Stateless plugin i.e. the plugin's state type is undefined.
// Just provide a store proxy over this plugin's state, the plugin can
// work the state out for itself if it wants to, but we're not going to
// help it out by calling migrate().
const proxyStore = globals.store.createProxy<T>(['plugins', pluginId]);
const traceCtx = new TracePluginContextImpl(
context, proxyStore, engineProxy, this.trackRegistry);
pluginDetails.traceContext = traceCtx;

// TODO(stevegolton): Await onTraceLoad.
plugin.onTraceLoad && plugin.onTraceLoad(traceCtx);
}
}
}

function isStatefulPlugin<T>(plugin: BasePlugin<T>|
StatefulPlugin<T>): plugin is StatefulPlugin<T> {
return 'migrate' in plugin;
}

function maybeDoPluginTraceUnload(pluginDetails: PluginDetails<unknown>): void {
const {traceContext, plugin} = pluginDetails;

Expand Down
12 changes: 10 additions & 2 deletions ui/src/common/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ import {Disposable} from '../base/disposable';

export interface HasKind { kind: string; }

export class RegistryError extends Error {
constructor(message?: string) {
super(message);
this.name = this.constructor.name;
}
}

export class Registry<T> {
private key: (t: T) => string;
protected registry: Map<string, T>;
Expand All @@ -32,7 +39,8 @@ export class Registry<T> {
register(registrant: T): Disposable {
const kind = this.key(registrant);
if (this.registry.has(kind)) {
throw new Error(`Registrant ${kind} already exists in the registry`);
throw new RegistryError(
`Registrant ${kind} already exists in the registry`);
}
this.registry.set(kind, registrant);

Expand All @@ -48,7 +56,7 @@ export class Registry<T> {
get(kind: string): T {
const registrant = this.registry.get(kind);
if (registrant === undefined) {
throw new Error(`${kind} has not been registered.`);
throw new RegistryError(`${kind} has not been registered.`);
}
return registrant;
}
Expand Down
Loading

0 comments on commit f095db1

Please sign in to comment.