src/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts

483 LOC · 204 covered · 279 uncovered · 24 ranges · 6771 concepts · 1 introducers · 3467 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- devToolsLogger.ts ×24
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { AutorunObserver, AutorunState } from '../../reactions/autorunImpl.js';
7 > import { TransactionImpl } from '../../transaction.js';
8 > import { IChangeInformation, IObservableLogger } from '../logging.js';
9 > import { formatValue } from '../consoleObservableLogger.js';
10 > import { ObsDebuggerApi, IObsDeclaration, ObsInstanceId, ObsStateUpdate, ITransactionState, ObserverInstanceState } from './debuggerApi.js';
11 > import { registerDebugChannel } from './debuggerRpc.js';
12 > import { deepAssign, deepAssignDeleteNulls, Throttler } from './utils.js';
13 > import { isDefined } from '../../../types.js';
14 > import { FromEventObservable } from '../../observables/observableFromEvent.js';
15 > import { BugIndicatingError, onUnexpectedError } from '../../../errors.js';
16 > import { IObservable, IObserver } from '../../base.js';
17 > import { BaseObservable } from '../../observables/baseObservable.js';
18 > import { Derived, DerivedState } from '../../observables/derivedImpl.js';
19 > import { ObservableValue } from '../../observables/observableValue.js';
20 > import { DebugLocation } from '../../debugLocation.js';
21 >
22 > interface IInstanceInfo {
23 > declarationId: number;
24 > instanceId: number;
25 > }
26 >
27 > interface IObservableInfo extends IInstanceInfo {
28 > listenerCount: number;
29 > lastValue: string | undefined;
30 > updateCount: number;
31 > changedObservables: Set<IObservable<any>>;
32 > }
33 >
34 > interface IAutorunInfo extends IInstanceInfo {
35 > updateCount: number;
36 > changedObservables: Set<IObservable<any>>;
37 > }
38 >
39 > export class DevToolsLogger implements IObservableLogger {
40 > private static _instance: DevToolsLogger | undefined = undefined;
41 > public static getInstance(): DevToolsLogger {
42 if (DevToolsLogger._instance === undefined) {
43 DevToolsLogger._instance = new DevToolsLogger();
44 }
45 return DevToolsLogger._instance;
46 }
48 > private _declarationId = 0;
49 > private _instanceId = 0;
50 >
51 > private readonly _declarations = new Map</* declarationId + type */string, IObsDeclaration>();
52 > private readonly _instanceInfos = new WeakMap<object, IObservableInfo | IAutorunInfo>();
53 > private readonly _aliveInstances = new Map<ObsInstanceId, IObservable<any> | AutorunObserver>();
54 > private readonly _activeTransactions = new Set<TransactionImpl>();
55 >
56 > private readonly _channel = registerDebugChannel<ObsDebuggerApi>('observableDevTools', () => {
57 > return {
58 > notifications: {
59 > setDeclarationIdFilter: declarationIds => {
60 >
61 > },
62 > logObservableValue: (observableId) => {
63 > console.log('logObservableValue', observableId);
64 > },
65 > flushUpdates: () => {
66 > this._flushUpdates();
67 > },
68 > resetUpdates: () => {
69 > this._pendingChanges = null;
70 > this._channel.api.notifications.handleChange(this._fullState, true);
71 > },
72 > },
73 > requests: {
74 > getDeclarations: () => {
75 > const result: Record<string, IObsDeclaration> = {};
76 > for (const decl of this._declarations.values()) {
77 > result[decl.id] = decl;
78 > }
79 > return { decls: result };
80 > },
81 > getSummarizedInstances: () => {
82 > return null!;
83 > },
84 > getObservableValueInfo: instanceId => {
85 > const obs = this._aliveInstances.get(instanceId) as BaseObservable<any>;
86 > return {
87 > observers: [...obs.debugGetObservers()].map(d => this._formatObserver(d)).filter(isDefined),
88 > };
89 > },
90 > getDerivedInfo: instanceId => {
91 > const d = this._aliveInstances.get(instanceId) as Derived<any>;
92 > return {
93 > dependencies: [...d.debugGetState().dependencies].map(d => this._formatObservable(d)).filter(isDefined),
94 > observers: [...d.debugGetObservers()].map(d => this._formatObserver(d)).filter(isDefined),
95 > };
96 > },
97 > getAutorunInfo: instanceId => {
98 > const obs = this._aliveInstances.get(instanceId) as AutorunObserver;
99 > return {
100 > dependencies: [...obs.debugGetState().dependencies].map(d => this._formatObservable(d)).filter(isDefined),
101 > };
102 > },
103 > getTransactionState: () => {
104 > return this.getTransactionState();
105 > },
106 > setValue: (instanceId, jsonValue) => {
107 > const obs = this._aliveInstances.get(instanceId) as BaseObservable<any>;
108 >
109 > if (obs instanceof Derived) {
110 > obs.debugSetValue(jsonValue);
111 > } else if (obs instanceof ObservableValue) {
112 > obs.debugSetValue(jsonValue);
113 > } else if (obs instanceof FromEventObservable) {
114 > obs.debugSetValue(jsonValue);
115 > } else {
116 > throw new BugIndicatingError('Observable is not supported');
117 > }
118 >
119 > const observers = [...obs.debugGetObservers()];
120 > for (const d of observers) {
121 > d.beginUpdate(obs);
122 > }
123 > for (const d of observers) {
124 > d.handleChange(obs, undefined);
125 > }
126 > for (const d of observers) {
127 > d.endUpdate(obs);
128 > }
129 > },
130 > getValue: instanceId => {
131 > const obs = this._aliveInstances.get(instanceId) as BaseObservable<any>;
132 > if (obs instanceof Derived) {
133 > return formatValue(obs.debugGetState().value, 200);
134 > } else if (obs instanceof ObservableValue) {
135 > return formatValue(obs.debugGetState().value, 200);
136 > }
137 >
138 > return undefined;
139 > },
140 > logValue: (instanceId) => {
141 > const obs = this._aliveInstances.get(instanceId);
142 > if (obs && 'get' in obs) {
143 > console.log('Logged Value:', obs.get());
144 > } else {
145 > throw new BugIndicatingError('Observable is not supported');
146 > }
147 > },
148 > rerun: (instanceId) => {
149 > const obs = this._aliveInstances.get(instanceId);
150 > if (obs instanceof Derived) {
151 > obs.debugRecompute();
152 > } else if (obs instanceof AutorunObserver) {
153 > obs.debugRerun();
154 > } else {
155 > throw new BugIndicatingError('Observable is not supported');
156 > }
157 > },
158 > }
159 > };
160 > });
161 >
162 > private getTransactionState(): ITransactionState | undefined {
163 const affected: ObserverInstanceState[] = [];
164 const txs = [...this._activeTransactions];
165 if (txs.length === 0) {
166 return undefined;
167 }
168 const observerQueue = txs.flatMap(t => t.debugGetUpdatingObservers() ?? []).map(o => o.observer);
169 const processedObservers = new Set<IObserver>();
170 while (observerQueue.length > 0) {
171 const observer = observerQueue.shift()!;
172 if (processedObservers.has(observer)) {
173 continue;
174 }
175 processedObservers.add(observer);
176
177 const state = this._getInfo(observer, d => {
178 if (!processedObservers.has(d)) {
179 observerQueue.push(d);
180 }
181 });
182
183 if (state) {
184 affected.push(state);
185 }
186 }
187
188 return { names: txs.map(t => t.getDebugName() ?? 'tx'), affected };
189 }
191 > private _getObservableInfo(observable: IObservable<any>): IObservableInfo | undefined {
192 const info = this._instanceInfos.get(observable);
193 if (!info) {
194 onUnexpectedError(new BugIndicatingError('No info found'));
195 return undefined;
196 }
197 return info as IObservableInfo;
198 }
200 > private _getAutorunInfo(autorun: AutorunObserver): IAutorunInfo | undefined {
201 const info = this._instanceInfos.get(autorun);
202 if (!info) {
203 onUnexpectedError(new BugIndicatingError('No info found'));
204 return undefined;
205 }
206 return info as IAutorunInfo;
207 }
209 > private _getInfo(observer: IObserver, queue: (observer: IObserver) => void): ObserverInstanceState | undefined {
210 if (observer instanceof Derived) {
211 const observersToUpdate = [...observer.debugGetObservers()];
212 for (const o of observersToUpdate) {
213 queue(o);
214 }
215
216 const info = this._getObservableInfo(observer);
217 if (!info) { return; }
218
219 const observerState = observer.debugGetState();
220
221 const base = { name: observer.debugName, instanceId: info.instanceId, updateCount: observerState.updateCount };
222 const changedDependencies = [...info.changedObservables].map(o => this._instanceInfos.get(o)?.instanceId).filter(isDefined);
223 if (observerState.isComputing) {
224 return { ...base, type: 'observable/derived', state: 'updating', changedDependencies, initialComputation: false };
225 }
226 switch (observerState.state) {
227 case DerivedState.initial:
228 return { ...base, type: 'observable/derived', state: 'noValue' };
229 case DerivedState.upToDate:
230 return { ...base, type: 'observable/derived', state: 'upToDate' };
231 case DerivedState.stale:
232 return { ...base, type: 'observable/derived', state: 'stale', changedDependencies };
233 case DerivedState.dependenciesMightHaveChanged:
234 return { ...base, type: 'observable/derived', state: 'possiblyStale' };
235 }
236 } else if (observer instanceof AutorunObserver) {
237 const info = this._getAutorunInfo(observer);
238 if (!info) { return undefined; }
239
240 const base = { name: observer.debugName, instanceId: info.instanceId, updateCount: info.updateCount };
241 const changedDependencies = [...info.changedObservables].map(o => this._instanceInfos.get(o)!.instanceId);
242 if (observer.debugGetState().isRunning) {
243 return { ...base, type: 'autorun', state: 'updating', changedDependencies };
244 }
245 switch (observer.debugGetState().state) {
246 case AutorunState.upToDate:
247 return { ...base, type: 'autorun', state: 'upToDate' };
248 case AutorunState.stale:
249 return { ...base, type: 'autorun', state: 'stale', changedDependencies };
250 case AutorunState.dependenciesMightHaveChanged:
251 return { ...base, type: 'autorun', state: 'possiblyStale' };
252 }
253
254 }
255 return undefined;
256 }
258 > private _formatObservable(obs: IObservable<any>): { name: string; instanceId: ObsInstanceId } | undefined {
259 const info = this._getObservableInfo(obs);
260 if (!info) { return undefined; }
261 return { name: obs.debugName, instanceId: info.instanceId };
262 }
264 > private _formatObserver(obs: IObserver): { name: string; instanceId: ObsInstanceId } | undefined {
265 if (obs instanceof Derived) {
266 return { name: obs.toString(), instanceId: this._getObservableInfo(obs)?.instanceId! };
267 }
268 const autorunInfo = this._getAutorunInfo(obs as AutorunObserver);
269 if (autorunInfo) {
270 return { name: obs.toString(), instanceId: autorunInfo.instanceId };
271 }
272
273 return undefined;
274 }
276 > private constructor() {
277 DebugLocation.enable();
278 }
280 > private _pendingChanges: ObsStateUpdate | null = null;
281 > private readonly _changeThrottler = new Throttler();
282 >
283 > private readonly _fullState = {};
284 >
285 > private _handleChange(update: ObsStateUpdate): void {
286 deepAssignDeleteNulls(this._fullState, update);
287
288 if (this._pendingChanges === null) {
289 this._pendingChanges = update;
290 } else {
291 deepAssign(this._pendingChanges, update);
292 }
293
294 this._changeThrottler.throttle(this._flushUpdates, 10);
295 }
297 > private readonly _flushUpdates = () => {
298 > if (this._pendingChanges !== null) {
299 > this._channel.api.notifications.handleChange(this._pendingChanges, false);
300 > this._pendingChanges = null;
301 > }
302 > };
303 >
304 > private _getDeclarationId(type: IObsDeclaration['type'], location: DebugLocation): number {
305 if (!location) {
306 return -1;
307 }
308
309 let decInfo = this._declarations.get(location.id);
310 if (decInfo === undefined) {
311 decInfo = {
312 id: this._declarationId++,
313 type,
314 url: location.fileName,
315 line: location.line,
316 column: location.column,
317 };
318 this._declarations.set(location.id, decInfo);
319
320 this._handleChange({ decls: { [decInfo.id]: decInfo } });
321 }
322 return decInfo.id;
323 }
325 > handleObservableCreated(observable: IObservable<any>, location: DebugLocation): void {
326 const declarationId = this._getDeclarationId('observable/value', location);
327
328 const info: IObservableInfo = {
329 declarationId,
330 instanceId: this._instanceId++,
331 listenerCount: 0,
332 lastValue: undefined,
333 updateCount: 0,
334 changedObservables: new Set(),
335 };
336 this._instanceInfos.set(observable, info);
337 }
339 > handleOnListenerCountChanged(observable: IObservable<any>, newCount: number): void {
340 const info = this._getObservableInfo(observable);
341 if (!info) { return; }
342
343 if (info.listenerCount === 0 && newCount > 0) {
344 const type: IObsDeclaration['type'] =
345 observable instanceof Derived ? 'observable/derived' : 'observable/value';
346 this._aliveInstances.set(info.instanceId, observable);
347 this._handleChange({
348 instances: {
349 [info.instanceId]: {
350 instanceId: info.instanceId,
351 declarationId: info.declarationId,
352 formattedValue: info.lastValue,
353 type,
354 name: observable.debugName,
355 }
356 }
357 });
358 } else if (info.listenerCount > 0 && newCount === 0) {
359 this._handleChange({
360 instances: { [info.instanceId]: null }
361 });
362 this._aliveInstances.delete(info.instanceId);
363 }
364 info.listenerCount = newCount;
365 }
367 > handleObservableUpdated(observable: IObservable<any>, changeInfo: IChangeInformation): void {
368 if (observable instanceof Derived) {
369 this._handleDerivedRecomputed(observable, changeInfo);
370 return;
371 }
372
373 const info = this._getObservableInfo(observable);
374 if (info) {
375 if (changeInfo.didChange) {
376 info.lastValue = formatValue(changeInfo.newValue, 30);
377 if (info.listenerCount > 0) {
378 this._handleChange({
379 instances: { [info.instanceId]: { formattedValue: info.lastValue } }
380 });
381 }
382 }
383 }
384 }
386 > handleAutorunCreated(autorun: AutorunObserver, location: DebugLocation): void {
387 const declarationId = this._getDeclarationId('autorun', location);
388 const info: IAutorunInfo = {
389 declarationId,
390 instanceId: this._instanceId++,
391 updateCount: 0,
392 changedObservables: new Set(),
393 };
394 this._instanceInfos.set(autorun, info);
395 this._aliveInstances.set(info.instanceId, autorun);
396 if (info) {
397 this._handleChange({
398 instances: {
399 [info.instanceId]: {
400 instanceId: info.instanceId,
401 declarationId: info.declarationId,
402 runCount: 0,
403 type: 'autorun',
404 name: autorun.debugName,
405 }
406 }
407 });
408 }
409 }
410 > handleAutorunDisposed(autorun: AutorunObserver): void { devToolsLogger.ts ×24
411 const info = this._getAutorunInfo(autorun);
412 if (!info) { return; }
413
414 this._handleChange({
415 instances: { [info.instanceId]: null }
416 });
417 this._instanceInfos.delete(autorun);
418 this._aliveInstances.delete(info.instanceId);
419 }
420 > handleAutorunDependencyChanged(autorun: AutorunObserver, observable: IObservable<any>, change: unknown): void { devToolsLogger.ts ×24
421 const info = this._getAutorunInfo(autorun);
422 if (!info) { return; }
423
424 info.changedObservables.add(observable);
425 }
426 > handleAutorunStarted(autorun: AutorunObserver): void { devToolsLogger.ts ×24
427
428 }
429 > handleAutorunFinished(autorun: AutorunObserver): void { devToolsLogger.ts ×24
430 const info = this._getAutorunInfo(autorun);
431 if (!info) { return; }
432
433 info.changedObservables.clear();
434 info.updateCount++;
435 this._handleChange({
436 instances: { [info.instanceId]: { runCount: info.updateCount } }
437 });
438 }
440 > handleDerivedDependencyChanged(derived: Derived<any>, observable: IObservable<any>, change: unknown): void {
441 const info = this._getObservableInfo(derived);
442 if (info) {
443 info.changedObservables.add(observable);
444 }
445 }
446 > _handleDerivedRecomputed(observable: Derived<any>, changeInfo: IChangeInformation): void { devToolsLogger.ts ×24
447 const info = this._getObservableInfo(observable);
448 if (!info) { return; }
449
450 const formattedValue = formatValue(changeInfo.newValue, 30);
451 info.updateCount++;
452 info.changedObservables.clear();
453
454 info.lastValue = formattedValue;
455 if (info.listenerCount > 0) {
456 this._handleChange({
457 instances: { [info.instanceId]: { formattedValue: formattedValue, recomputationCount: info.updateCount } }
458 });
459 }
460 }
461 > handleDerivedCleared(observable: Derived<any>): void { devToolsLogger.ts ×24
462 const info = this._getObservableInfo(observable);
463 if (!info) { return; }
464
465 info.lastValue = undefined;
466 info.changedObservables.clear();
467 if (info.listenerCount > 0) {
468 this._handleChange({
469 instances: {
470 [info.instanceId]: {
471 formattedValue: undefined,
472 }
473 }
474 });
475 }
476 }
477 > handleBeginTransaction(transaction: TransactionImpl): void { devToolsLogger.ts ×24
478 this._activeTransactions.add(transaction);
479 }
480 > handleEndTransaction(transaction: TransactionImpl): void { devToolsLogger.ts ×24
481 this._activeTransactions.delete(transaction);
482 }