Skip to content

Commit 237c177

Browse files
ComponentViewer: Only use cached symbols while running (#888)
* ComponentViewer: Use cached symbols while running * moved caches into cache module * changes after review
1 parent 588feec commit 237c177

4 files changed

Lines changed: 193 additions & 30 deletions

File tree

src/views/component-viewer/scvd-debug-target-cache.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,24 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
// generated with AI
16+
// generated with AI, refactored
1717

18-
class StringNumberCache {
19-
private cache = new Map<string, number>();
20-
21-
public get(key: string): number | undefined {
22-
return this.cache.get(key);
23-
}
24-
25-
public set(key: string, value: number): void {
26-
this.cache.set(key, value);
27-
}
28-
29-
public clear(): void {
30-
this.cache.clear();
31-
}
32-
}
18+
type StringNumberCache = Map<string, number>;
19+
type StringValueCache = Map<string, string>;
3320

3421
export class SymbolCaches {
35-
private addressCache = new StringNumberCache();
36-
private sizeCache = new StringNumberCache();
37-
private arrayCountCache = new StringNumberCache();
22+
private addressCache: StringNumberCache = new Map();
23+
private sizeCache: StringNumberCache = new Map();
24+
private arrayCountCache: StringNumberCache = new Map();
25+
private symbolNameByAddressCache: StringValueCache = new Map();
26+
private symbolContextByAddressCache: StringValueCache = new Map();
3827

3928
public clearAll(): void {
4029
this.addressCache.clear();
4130
this.sizeCache.clear();
4231
this.arrayCountCache.clear();
32+
this.symbolNameByAddressCache.clear();
33+
this.symbolContextByAddressCache.clear();
4334
}
4435

4536
public async getAddress(
@@ -72,6 +63,22 @@ export class SymbolCaches {
7263
return this.getCached(this.arrayCountCache, symbol, compute);
7364
}
7465

66+
public getSymbolNameByAddress(address: string): string | undefined {
67+
return this.symbolNameByAddressCache.get(this.normalizeAddressKey(address));
68+
}
69+
70+
public setSymbolNameByAddress(address: string, symbolName: string): void {
71+
this.symbolNameByAddressCache.set(this.normalizeAddressKey(address), symbolName);
72+
}
73+
74+
public getSymbolContextByAddress(address: string): string | undefined {
75+
return this.symbolContextByAddressCache.get(this.normalizeAddressKey(address));
76+
}
77+
78+
public setSymbolContextByAddress(address: string, symbolContext: string): void {
79+
this.symbolContextByAddressCache.set(this.normalizeAddressKey(address), symbolContext);
80+
}
81+
7582
private async getCached(
7683
cache: StringNumberCache,
7784
symbol: string,
@@ -100,4 +107,8 @@ export class SymbolCaches {
100107
private normalizeKey(symbol: string): string {
101108
return symbol.trim();
102109
}
110+
111+
private normalizeAddressKey(address: string): string {
112+
return address.trim().toLowerCase();
113+
}
103114
}

src/views/component-viewer/scvd-debug-target.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export class ScvdDebugTarget {
120120
this.targetAccess.setActiveSession(session);
121121
this.debugTracker = tracker;
122122
this.subscribeToTargetRunningState(this.debugTracker);
123+
this.isTargetRunning = this.isUnsafeTargetState(session.targetState);
123124
this.symbolCaches.clearAll();
124125
}
125126

@@ -163,6 +164,18 @@ export class ScvdDebugTarget {
163164
});
164165
}
165166

167+
private isUnsafeTargetState(state: GDBTargetDebugSession['targetState'] | undefined): boolean {
168+
return state === 'running' || state === 'unknown';
169+
}
170+
171+
private isRunningMode(): boolean {
172+
return this.isTargetRunning || this.isUnsafeTargetState(this.activeSession?.targetState);
173+
}
174+
175+
private symbolAddressCacheKey(address: number | bigint): string {
176+
return this.formatAddress(address).toLowerCase();
177+
}
178+
166179
public async getSymbolInfo(symbol: string, existCheck: boolean = false): Promise<SymbolInfo | undefined> {
167180
componentViewerLogger.debug(`get Symbol Info: resolving ${symbol}`);
168181
if (symbol === undefined) {
@@ -175,6 +188,10 @@ export class ScvdDebugTarget {
175188
}
176189

177190
const addressInfo = await this.symbolCaches.getAddressWithName(symbol, async (symbolName) => {
191+
if (this.isRunningMode()) {
192+
componentViewerLogger.debug(`get Symbol Info: target running, cache miss for symbol ${symbolName}`);
193+
return undefined;
194+
}
178195
const gdbSymbol = toGdbSymbol(symbolName);
179196
const symbolAddressStr = await this.targetAccess.evaluateSymbolAddress(gdbSymbol, 'hover', existCheck);
180197
if (symbolAddressStr !== undefined) {
@@ -207,8 +224,20 @@ export class ScvdDebugTarget {
207224
return Promise.resolve(undefined);
208225
}
209226

227+
const cacheKey = this.symbolAddressCacheKey(address);
228+
if (this.isRunningMode()) {
229+
const cached = this.symbolCaches.getSymbolNameByAddress(cacheKey);
230+
if (cached === undefined) {
231+
componentViewerLogger.debug(`find Symbol Name at Address: target running, cache miss for ${this.formatAddress(address)}`);
232+
}
233+
return cached;
234+
}
235+
210236
try {
211237
const result = await this.targetAccess.evaluateSymbolName(address.toString());
238+
if (result !== undefined) {
239+
this.symbolCaches.setSymbolNameByAddress(cacheKey, result);
240+
}
212241
componentViewerLogger.debug(`find Symbol Name at Address: ${this.formatAddress(address)} resolved to ${result}`);
213242
return result;
214243
} catch (error: unknown) {
@@ -225,8 +254,20 @@ export class ScvdDebugTarget {
225254
return Promise.resolve(undefined);
226255
}
227256

257+
const cacheKey = this.symbolAddressCacheKey(address);
258+
if (this.isRunningMode()) {
259+
const cached = this.symbolCaches.getSymbolContextByAddress(cacheKey);
260+
if (cached === undefined) {
261+
componentViewerLogger.debug(`find Symbol Context at Address: target running, cache miss for ${this.formatAddress(address)}`);
262+
}
263+
return cached;
264+
}
265+
228266
try {
229267
const result = await this.targetAccess.evaluateSymbolContext(address.toString());
268+
if (result !== undefined) {
269+
this.symbolCaches.setSymbolContextByAddress(cacheKey, result);
270+
}
230271
componentViewerLogger.debug(`find Symbol Context at Address: ${this.formatAddress(address)} resolved to ${result}`);
231272
return result;
232273
} catch (error: unknown) {
@@ -247,6 +288,10 @@ export class ScvdDebugTarget {
247288
return undefined;
248289
}
249290
return this.symbolCaches.getArrayCount(symbol, async (symbolName) => {
291+
if (this.isRunningMode()) {
292+
componentViewerLogger.debug(`get Num Array Elements: target running, cache miss for ${symbolName}`);
293+
return undefined;
294+
}
250295
const gdbSymbol = toGdbSymbol(symbolName);
251296
const result = await this.targetAccess.evaluateNumberOfArrayElements(gdbSymbol);
252297
componentViewerLogger.debug(`get Num Array Elements: ${symbolName} resolved to ${result}`);
@@ -259,7 +304,7 @@ export class ScvdDebugTarget {
259304
if (!this.activeSession) {
260305
return false;
261306
}
262-
return this.isTargetRunning;
307+
return this.isRunningMode();
263308
}
264309

265310
public async findSymbolAddress(symbol: string, existCheck: boolean = false): Promise<number | undefined> {
@@ -280,6 +325,10 @@ export class ScvdDebugTarget {
280325
}
281326

282327
return this.symbolCaches.getSize(symbol, async (symbolName) => {
328+
if (this.isRunningMode()) {
329+
componentViewerLogger.debug(`get Symbol Size: target running, cache miss for ${symbolName}`);
330+
return undefined;
331+
}
283332
const gdbSymbol = toGdbSymbol(symbolName);
284333
const size = await this.targetAccess.evaluateSymbolSize(gdbSymbol);
285334
if (typeof size === 'number' && size >= 0) {
@@ -553,6 +602,10 @@ export class ScvdDebugTarget {
553602
componentViewerLogger.debug('read Register: name is undefined');
554603
return undefined;
555604
}
605+
if (this.isRunningMode()) {
606+
componentViewerLogger.debug(`read Register: skipping register read while target is running (${name})`);
607+
return undefined;
608+
}
556609

557610
const gdbName = gdbNameFor(name);
558611
if (gdbName === undefined) {

src/views/component-viewer/test/integration/scvd-debug-target.test.ts

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ type AccessMock = {
3535
evaluateRegisterValue: jest.Mock;
3636
};
3737

38+
type TrackerWithCallbacks = {
39+
onContinued: (cb: (event: { session: GDBTargetDebugSession }) => void) => void;
40+
onStopped: (cb: (event: { session: GDBTargetDebugSession }) => void) => void;
41+
_continued?: (event: { session: GDBTargetDebugSession }) => void;
42+
_stopped?: (event: { session: GDBTargetDebugSession }) => void;
43+
};
44+
45+
const createTrackerWithCallbacks = (): TrackerWithCallbacks => {
46+
const tracker: TrackerWithCallbacks = {
47+
onContinued: (cb) => { tracker._continued = cb; },
48+
onStopped: (cb) => { tracker._stopped = cb; },
49+
};
50+
return tracker;
51+
};
52+
3853
let accessMock: AccessMock;
3954
jest.mock('../../component-viewer-target-access', () => ({
4055
ComponentViewerTargetAccess: jest.fn(() => accessMock),
@@ -130,16 +145,7 @@ describe('scvd-debug-target', () => {
130145
});
131146

132147
it('handles array length and running state tracking', async () => {
133-
type TrackerWithCallbacks = {
134-
onContinued: (cb: (event: { session: GDBTargetDebugSession }) => void) => void;
135-
onStopped: (cb: (event: { session: GDBTargetDebugSession }) => void) => void;
136-
_continued?: (event: { session: GDBTargetDebugSession }) => void;
137-
_stopped?: (event: { session: GDBTargetDebugSession }) => void;
138-
};
139-
const tracker: TrackerWithCallbacks = {
140-
onContinued: (cb) => { tracker._continued = cb; },
141-
onStopped: (cb) => { tracker._stopped = cb; },
142-
};
148+
const tracker = createTrackerWithCallbacks();
143149

144150
const target = new ScvdDebugTarget();
145151
target.init(session, tracker as unknown as GDBTargetDebugTracker);
@@ -161,6 +167,75 @@ describe('scvd-debug-target', () => {
161167
expect(await target.getTargetIsRunning()).toBe(false);
162168
});
163169

170+
it('uses cached symbol metadata while running and skips new symbol evaluations', async () => {
171+
const tracker = createTrackerWithCallbacks();
172+
const target = new ScvdDebugTarget();
173+
target.init(session, tracker as unknown as GDBTargetDebugTracker);
174+
175+
accessMock.evaluateSymbolAddress.mockResolvedValue('0x200');
176+
accessMock.evaluateNumberOfArrayElements.mockResolvedValue(8);
177+
accessMock.evaluateSymbolSize.mockResolvedValue(32);
178+
accessMock.evaluateSymbolName.mockResolvedValue('main');
179+
accessMock.evaluateSymbolContext.mockResolvedValue('main.c:10');
180+
181+
await expect(target.findSymbolAddress('foo')).resolves.toBe(0x200);
182+
await expect(target.getNumArrayElements('foo')).resolves.toBe(8);
183+
await expect(target.getSymbolSize('foo')).resolves.toBe(32);
184+
await expect(target.findSymbolNameAtAddress(0x200)).resolves.toBe('main');
185+
await expect(target.findSymbolContextAtAddress(0x200)).resolves.toBe('main.c:10');
186+
187+
accessMock.evaluateSymbolAddress.mockClear();
188+
accessMock.evaluateNumberOfArrayElements.mockClear();
189+
accessMock.evaluateSymbolSize.mockClear();
190+
accessMock.evaluateSymbolName.mockClear();
191+
accessMock.evaluateSymbolContext.mockClear();
192+
await tracker._continued?.({ session });
193+
194+
await expect(target.findSymbolAddress('foo')).resolves.toBe(0x200);
195+
await expect(target.getNumArrayElements('foo')).resolves.toBe(8);
196+
await expect(target.getSymbolSize('foo')).resolves.toBe(32);
197+
await expect(target.findSymbolNameAtAddress(0x200)).resolves.toBe('main');
198+
await expect(target.findSymbolContextAtAddress(0x200)).resolves.toBe('main.c:10');
199+
200+
await expect(target.findSymbolAddress('bar')).resolves.toBeUndefined();
201+
await expect(target.getNumArrayElements('bar')).resolves.toBeUndefined();
202+
await expect(target.getSymbolSize('bar')).resolves.toBeUndefined();
203+
await expect(target.findSymbolNameAtAddress(0x201)).resolves.toBeUndefined();
204+
await expect(target.findSymbolContextAtAddress(0x201)).resolves.toBeUndefined();
205+
206+
expect(accessMock.evaluateSymbolAddress).not.toHaveBeenCalled();
207+
expect(accessMock.evaluateNumberOfArrayElements).not.toHaveBeenCalled();
208+
expect(accessMock.evaluateSymbolSize).not.toHaveBeenCalled();
209+
expect(accessMock.evaluateSymbolName).not.toHaveBeenCalled();
210+
expect(accessMock.evaluateSymbolContext).not.toHaveBeenCalled();
211+
212+
await tracker._stopped?.({ session });
213+
});
214+
215+
it('treats unknown target state as unsafe and skips symbol evaluation on cache miss', async () => {
216+
const unknownSession = {
217+
session: { id: 'sess-unknown' },
218+
targetState: 'unknown'
219+
} as unknown as GDBTargetDebugSession;
220+
const tracker = { onContinued: jest.fn(), onStopped: jest.fn() } as unknown as GDBTargetDebugTracker;
221+
const target = new ScvdDebugTarget();
222+
target.init(unknownSession, tracker);
223+
224+
await expect(target.getTargetIsRunning()).resolves.toBe(true);
225+
226+
accessMock.evaluateSymbolAddress.mockResolvedValue('0x200');
227+
accessMock.evaluateSymbolName.mockResolvedValue('main');
228+
accessMock.evaluateSymbolContext.mockResolvedValue('main.c:10');
229+
230+
await expect(target.findSymbolAddress('foo')).resolves.toBeUndefined();
231+
await expect(target.findSymbolNameAtAddress(0x200)).resolves.toBeUndefined();
232+
await expect(target.findSymbolContextAtAddress(0x200)).resolves.toBeUndefined();
233+
234+
expect(accessMock.evaluateSymbolAddress).not.toHaveBeenCalled();
235+
expect(accessMock.evaluateSymbolName).not.toHaveBeenCalled();
236+
expect(accessMock.evaluateSymbolContext).not.toHaveBeenCalled();
237+
});
238+
164239
it('finds symbol address and size', async () => {
165240
accessMock.evaluateSymbolAddress.mockResolvedValue('0x200');
166241
accessMock.evaluateSymbolSize.mockResolvedValue(16);
@@ -312,6 +387,17 @@ describe('scvd-debug-target', () => {
312387
await expect(target.readRegister(undefined as unknown as string)).resolves.toBeUndefined();
313388
});
314389

390+
it('skips register reads while running', async () => {
391+
const tracker = createTrackerWithCallbacks();
392+
const target = new ScvdDebugTarget();
393+
target.init(session, tracker as unknown as GDBTargetDebugTracker);
394+
await tracker._continued?.({ session });
395+
396+
accessMock.evaluateRegisterValue.mockResolvedValue(1);
397+
await expect(target.readRegister('r0')).resolves.toBeUndefined();
398+
expect(accessMock.evaluateRegisterValue).not.toHaveBeenCalled();
399+
});
400+
315401
it('covers cache hits, misses, and normalization paths', async () => {
316402
const tracker = { onContinued: jest.fn(), onStopped: jest.fn() } as unknown as GDBTargetDebugTracker;
317403
const target = new ScvdDebugTarget();

src/views/component-viewer/test/unit/scvd-debug-target-cache.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,23 @@ describe('SymbolCaches', () => {
4747
await caches.getAddress('foo', async () => 1);
4848
await caches.getSize('foo', async () => 2);
4949
await caches.getArrayCount('foo', async () => 3);
50+
caches.setSymbolNameByAddress('0x200', 'foo');
51+
caches.setSymbolContextByAddress('0x200', 'main.c:10');
5052
caches.clearAll();
5153

5254
await expect(caches.getAddress('foo', async () => 4)).resolves.toBe(4);
5355
await expect(caches.getSize('foo', async () => 5)).resolves.toBe(5);
5456
await expect(caches.getArrayCount('foo', async () => 6)).resolves.toBe(6);
57+
expect(caches.getSymbolNameByAddress('0x200')).toBeUndefined();
58+
expect(caches.getSymbolContextByAddress('0x200')).toBeUndefined();
59+
});
60+
61+
it('stores and normalizes address-keyed symbol metadata', () => {
62+
const caches = new SymbolCaches();
63+
caches.setSymbolNameByAddress(' 0X200 ', 'main');
64+
caches.setSymbolContextByAddress(' 0X200 ', 'main.c:10');
65+
66+
expect(caches.getSymbolNameByAddress('0x200')).toBe('main');
67+
expect(caches.getSymbolContextByAddress('0x200')).toBe('main.c:10');
5568
});
5669
});

0 commit comments

Comments
 (0)