File size: 3,637 Bytes
68d7816 | 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 | import { IInstantiationService } from '#/_base/di/instantiation';
import type { InstantiationService } from '#/_base/di/instantiationService';
import type { LedgerEntryInfo } from '#/_base/lifecycle/ledger';
import { IEventService } from '#/app/event/event';
import { IEventBus } from '#/app/event/eventBus';
import { walkScopeContainers } from '#/debug/scopeTree';
import {
IDebugEventsService,
type DebugEventBusSnapshot,
type DebugEventSubscription,
type DebugEventSubscriptions,
} from './debugEvents';
interface UnitBookOwner {
readonly unitBook: { entries(): LedgerEntryInfo[] };
}
interface BusCountSource {
listenerCounts(): {
all: number;
perType: Record<string, number>;
perAgent: Record<string, number>;
};
}
interface GlobalCountSource {
readonly listenerCount: number;
}
export class DebugEventsService implements IDebugEventsService {
declare readonly _serviceBrand: undefined;
private readonly root: InstantiationService;
constructor(@IInstantiationService instantiation: IInstantiationService) {
this.root = instantiation as InstantiationService;
}
subscriptions(): DebugEventSubscriptions {
const subscriptions: DebugEventSubscription[] = [];
const buses: DebugEventBusSnapshot[] = [];
const seenUnits = new Set<object>();
const seenBuses = new Set<object>();
for (const info of walkScopeContainers(this.root)) {
for (const registration of info.container.servicesSnapshot()) {
const id = info.container.findIdentifier(registration.token);
if (id === undefined) {
continue;
}
const instance: unknown = info.container.fiberHost.materializedInstance(id);
if (typeof instance !== 'object' || instance === null || seenUnits.has(instance)) {
continue;
}
seenUnits.add(instance);
if ('unitBook' in instance) {
collectEventEntries((instance as UnitBookOwner).unitBook.entries(), subscriptions, {
scopePath: info.path,
unit: registration.token,
uid: registration.uid,
});
}
}
const bus: unknown = info.container.fiberHost.materializedInstance(IEventBus);
if (isBusCountSource(bus) && !seenBuses.has(bus)) {
seenBuses.add(bus);
buses.push({ scopePath: info.path, ...bus.listenerCounts() });
}
}
const globalEvents: unknown = this.root.fiberHost.materializedInstance(IEventService);
const globalListeners = isGlobalCountSource(globalEvents)
? globalEvents.listenerCount
: undefined;
return { subscriptions, buses, globalListeners };
}
}
function collectEventEntries(
entries: readonly LedgerEntryInfo[],
out: DebugEventSubscription[],
base: { scopePath: string; unit: string; uid?: number },
): void {
for (const entry of entries) {
if (isEventSubscriptionLabel(entry.label)) {
out.push({ ...base, label: entry.label, kind: entry.kind });
}
if (entry.children !== undefined) {
collectEventEntries(entry.children, out, base);
}
}
}
function isEventSubscriptionLabel(label: string): boolean {
return label.startsWith('on:') || label === 'disposable:EventSubscription';
}
function isBusCountSource(value: unknown): value is BusCountSource {
return (
typeof value === 'object' &&
value !== null &&
typeof (value as BusCountSource).listenerCounts === 'function'
);
}
function isGlobalCountSource(value: unknown): value is GlobalCountSource {
return (
typeof value === 'object' &&
value !== null &&
typeof (value as GlobalCountSource).listenerCount === 'number'
);
}
|