fix(trace viewer): limit the number of contexts loaded in sw

UI mode may load many traces, one for each test, in the same
service worker. This change introduces an upper limit to the
number of traces stored in sw.
This commit is contained in:
Dmitry Gozman 2024-10-23 13:13:53 +01:00
parent 29ca54eb38
commit 669ef05940
5 changed files with 23 additions and 12 deletions

View file

@ -36,16 +36,16 @@ const scopePath = new URL(self.registration.scope).pathname;
const loadedTraces = new Map<string, { traceModel: TraceModel, snapshotServer: SnapshotServer }>(); const loadedTraces = new Map<string, { traceModel: TraceModel, snapshotServer: SnapshotServer }>();
const clientIdToTraceUrls = new Map<string, Set<string>>(); const clientIdToTraceUrls = new Map<string, { limit: number | undefined, traceUrls: Set<string> }>();
async function loadTrace(traceUrl: string, traceFileName: string | null, clientId: string, progress: (done: number, total: number) => undefined): Promise<TraceModel> { async function loadTrace(traceUrl: string, traceFileName: string | null, clientId: string, limit: number | undefined, progress: (done: number, total: number) => undefined): Promise<TraceModel> {
await gc(); await gc();
let set = clientIdToTraceUrls.get(clientId); let data = clientIdToTraceUrls.get(clientId);
if (!set) { if (!data) {
set = new Set(); data = { limit, traceUrls: new Set() };
clientIdToTraceUrls.set(clientId, set); clientIdToTraceUrls.set(clientId, data);
} }
set.add(traceUrl); data.traceUrls.add(traceUrl);
const traceModel = new TraceModel(); const traceModel = new TraceModel();
try { try {
@ -97,7 +97,8 @@ async function doFetch(event: FetchEvent): Promise<Response> {
if (relativePath === '/contexts') { if (relativePath === '/contexts') {
try { try {
const traceModel = await loadTrace(traceUrl!, url.searchParams.get('traceFileName'), event.clientId, (done: number, total: number) => { const limit = url.searchParams.has('limit') ? +url.searchParams.get('limit')! : undefined;
const traceModel = await loadTrace(traceUrl!, url.searchParams.get('traceFileName'), event.clientId, limit, (done: number, total: number) => {
client.postMessage({ method: 'progress', params: { done, total } }); client.postMessage({ method: 'progress', params: { done, total } });
}); });
return new Response(JSON.stringify(traceModel!.contextEntries), { return new Response(JSON.stringify(traceModel!.contextEntries), {
@ -179,12 +180,18 @@ async function gc() {
const clients = await self.clients.matchAll(); const clients = await self.clients.matchAll();
const usedTraces = new Set<string>(); const usedTraces = new Set<string>();
for (const [clientId, traceUrls] of clientIdToTraceUrls) { for (const [clientId, data] of clientIdToTraceUrls) {
// @ts-ignore // @ts-ignore
if (!clients.find(c => c.id === clientId)) if (!clients.find(c => c.id === clientId)) {
clientIdToTraceUrls.delete(clientId); clientIdToTraceUrls.delete(clientId);
else continue;
traceUrls.forEach(url => usedTraces.add(url)); }
if (data.limit !== undefined) {
const ordered = [...data.traceUrls];
// Leave the newest requested traces.
data.traceUrls = new Set(ordered.slice(ordered.length - data.limit));
}
data.traceUrls.forEach(url => usedTraces.add(url));
} }
for (const traceUrl of loadedTraces.keys()) { for (const traceUrl of loadedTraces.keys()) {

View file

@ -65,6 +65,7 @@ export const EmbeddedWorkbenchLoader: React.FunctionComponent = () => {
const url = traceURLs[i]; const url = traceURLs[i];
const params = new URLSearchParams(); const params = new URLSearchParams();
params.set('trace', url); params.set('trace', url);
params.set('limit', String(traceURLs.length));
const response = await fetch(`contexts?${params.toString()}`); const response = await fetch(`contexts?${params.toString()}`);
if (!response.ok) { if (!response.ok) {
setProcessingErrorMessage((await response.json()).error); setProcessingErrorMessage((await response.json()).error);

View file

@ -58,6 +58,7 @@ export const ModelProvider: React.FunctionComponent<React.PropsWithChildren<{
async function loadSingleTraceFile(url: string): Promise<{ model: MultiTraceModel, sha1: string }> { async function loadSingleTraceFile(url: string): Promise<{ model: MultiTraceModel, sha1: string }> {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.set('trace', url); params.set('trace', url);
params.set('limit', '1');
const response = await fetch(`contexts?${params.toString()}`); const response = await fetch(`contexts?${params.toString()}`);
const contextEntries = await response.json() as ContextEntry[]; const contextEntries = await response.json() as ContextEntry[];

View file

@ -111,6 +111,7 @@ const outputDirForTestCase = (testCase: reporterTypes.TestCase): string | undefi
async function loadSingleTraceFile(url: string): Promise<MultiTraceModel> { async function loadSingleTraceFile(url: string): Promise<MultiTraceModel> {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.set('trace', url); params.set('trace', url);
params.set('limit', '1');
const response = await fetch(`contexts?${params.toString()}`); const response = await fetch(`contexts?${params.toString()}`);
const contextEntries = await response.json() as ContextEntry[]; const contextEntries = await response.json() as ContextEntry[];
return new MultiTraceModel(contextEntries); return new MultiTraceModel(contextEntries);

View file

@ -131,6 +131,7 @@ export const WorkbenchLoader: React.FunctionComponent<{
params.set('trace', url); params.set('trace', url);
if (uploadedTraceNames.length) if (uploadedTraceNames.length)
params.set('traceFileName', uploadedTraceNames[i]); params.set('traceFileName', uploadedTraceNames[i]);
params.set('limit', String(traceURLs.length));
const response = await fetch(`contexts?${params.toString()}`); const response = await fetch(`contexts?${params.toString()}`);
if (!response.ok) { if (!response.ok) {
if (!isServer) if (!isServer)