forked from google/perfetto
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcache_manager.ts
More file actions
186 lines (171 loc) · 6.06 KB
/
cache_manager.ts
File metadata and controls
186 lines (171 loc) · 6.06 KB
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* This file deals with caching traces in the browser's Cache storage. The
* traces are cached so that the UI can gracefully reload a trace when the tab
* containing it is discarded by Chrome (e.g. because the tab was not used for
* a long time) or when the user accidentally hits reload.
*/
import {ignoreCacheUnactionableErrors} from './errors';
import {TraceArrayBufferSource, TraceSource} from './state';
import {globals} from '../frontend/globals';
const TRACE_CACHE_NAME = 'cached_traces';
const TRACE_CACHE_SIZE = 10;
let LAZY_CACHE: Cache|undefined = undefined;
async function getCache(): Promise<Cache|undefined> {
if (self.caches === undefined) {
// The browser doesn't support cache storage or the page is opened from
// a non-secure origin.
return undefined;
}
if (LAZY_CACHE !== undefined) {
return LAZY_CACHE;
}
LAZY_CACHE = await caches.open(TRACE_CACHE_NAME);
return LAZY_CACHE;
}
async function cacheDelete(key: Request): Promise<boolean> {
try {
const cache = await getCache();
if (cache === undefined) return false; // Cache storage not supported.
return cache.delete(key);
} catch (e) {
return ignoreCacheUnactionableErrors(e, false);
}
}
async function cachePut(key: string, value: Response): Promise<void> {
try {
const cache = await getCache();
if (cache === undefined) return; // Cache storage not supported.
cache.put(key, value);
} catch (e) {
ignoreCacheUnactionableErrors(e, undefined);
}
}
async function cacheMatch(key: Request|string): Promise<Response|undefined> {
try {
const cache = await getCache();
if (cache === undefined) return undefined; // Cache storage not supported.
return cache.match(key);
} catch (e) {
return ignoreCacheUnactionableErrors(e, undefined);
}
}
async function cacheKeys(): Promise<readonly Request[]> {
try {
const cache = await getCache();
if (cache === undefined) return []; // Cache storage not supported.
return cache.keys();
} catch (e) {
return ignoreCacheUnactionableErrors(e, []);
}
}
export async function cacheTrace(
globalsContext: string, traceSource: TraceSource, traceUuid: string): Promise<boolean> {
let trace;
let title = '';
let fileName = '';
let url = '';
let contentLength = 0;
let localOnly = false;
switch (traceSource.type) {
case 'ARRAY_BUFFER':
trace = traceSource.buffer;
title = traceSource.title;
fileName = traceSource.fileName || '';
url = traceSource.url || '';
contentLength = traceSource.buffer.byteLength;
localOnly = traceSource.localOnly || false;
break;
case 'FILE':
trace = await traceSource.file.arrayBuffer();
title = traceSource.file.name;
contentLength = traceSource.file.size;
break;
default:
return false;
}
const headers = new Headers([
['x-trace-title', title],
['x-trace-url', url],
['x-trace-filename', fileName],
['x-trace-local-only', `${localOnly}`],
['content-type', 'application/octet-stream'],
['content-length', `${contentLength}`],
[
'expires',
// Expires in a week from now (now = upload time)
(new Date((new Date()).getTime() + (1000 * 60 * 60 * 24 * 7)))
.toUTCString(),
],
]);
await deleteStaleEntries();
await cachePut(
`${globals(globalsContext).cachePrefix}/_${TRACE_CACHE_NAME}/${traceUuid}`, new Response(trace, {headers}));
return true;
}
export async function tryGetTrace(globalsContext: string, traceUuid: string):
Promise<TraceArrayBufferSource|undefined> {
await deleteStaleEntries();
const response = await cacheMatch(`${globals(globalsContext).cachePrefix}/_${TRACE_CACHE_NAME}/${traceUuid}`);
if (!response) return undefined;
return {
type: 'ARRAY_BUFFER',
buffer: await response.arrayBuffer(),
title: response.headers.get('x-trace-title') || '',
fileName: response.headers.get('x-trace-filename') || undefined,
url: response.headers.get('x-trace-url') || undefined,
uuid: traceUuid,
localOnly: response.headers.get('x-trace-local-only') === 'true',
};
}
async function deleteStaleEntries() {
// Loop through stored traces and invalidate all but the most recent
// TRACE_CACHE_SIZE.
const keys = await cacheKeys();
const storedTraces: Array<{key: Request, date: Date}> = [];
const now = new Date();
const deletions = [];
for (const key of keys) {
const existingTrace = await cacheMatch(key);
if (existingTrace === undefined) {
continue;
}
const expires = existingTrace.headers.get('expires');
if (expires === undefined || expires === null) {
// Missing `expires`, so give up and delete which is better than
// keeping it around forever.
deletions.push(cacheDelete(key));
continue;
}
const expiryDate = new Date(expires);
if (expiryDate < now) {
deletions.push(cacheDelete(key));
} else {
storedTraces.push({key, date: expiryDate});
}
}
// Sort the traces descending by time, such that most recent ones are placed
// at the beginning. Then, take traces from TRACE_CACHE_SIZE onwards and
// delete them from cache.
const oldTraces =
storedTraces.sort((a, b) => b.date.getTime() - a.date.getTime())
.slice(TRACE_CACHE_SIZE);
for (const oldTrace of oldTraces) {
deletions.push(cacheDelete(oldTrace.key));
}
// TODO(hjd): Wrong Promise.all here, should use the one that
// ignores failures but need to upgrade TypeScript for that.
await Promise.all(deletions);
}