blob: 9a1d6686e15a2001afbdf83985334dc4c37e45f2 [file] [log] [blame]
const CACHE_BUST_COMMENT = "/*ThouShaltNotCache*/";
const CACHE_BUST_COMMENT_RE = new RegExp(
`\n${RegExp.escape(CACHE_BUST_COMMENT)}\n`,
"g"
);
class StartupBenchmark {
// Total iterations for this benchmark.
#iterationCount = 0;
// Original source code.
#sourceCode;
// quickHahs(this.#sourceCode) for use in custom validate() methods.
#sourceHash = 0;
// Number of no-cache comments in the original #sourceCode.
#expectedCacheCommentCount = 0;
// How many times (separate iterations) should we reuse the source code.
// Use 0 to skip and only use a single #sourceCode string.
#sourceCodeReuseCount = 1;
// #sourceCode for each iteration, number of unique sources is controlled
// by codeReuseCount;
#iterationSourceCodes = [];
constructor({
iterationCount,
expectedCacheCommentCount,
sourceCodeReuseCount = 1,
} = {}) {
console.assert(
iterationCount > 0,
`Expected iterationCount to be positive, but got ${iterationCount}`
);
this.#iterationCount = iterationCount;
console.assert(
expectedCacheCommentCount > 0,
`Expected expectedCacheCommentCount to be positive, but got ${expectedCacheCommentCount}`
);
this.#expectedCacheCommentCount = expectedCacheCommentCount;
console.assert(
sourceCodeReuseCount >= 0,
`Expected sourceCodeReuseCount to be non-negative, but got ${sourceCodeReuseCount}`
);
this.#sourceCodeReuseCount = sourceCodeReuseCount;
}
get iterationCount() {
return this.#iterationCount;
}
get sourceCode() {
return this.#sourceCode;
}
get sourceHash() {
return this.#sourceHash;
}
get expectedCacheCommentCount() {
return this.#expectedCacheCommentCount;
}
get sourceCodeReuseCount() {
return this.#sourceCodeReuseCount;
}
get iterationSourceCodes() {
return this.#iterationSourceCodes;
}
async init() {
this.#sourceCode = await JetStream.getString(JetStream.preload.BUNDLE);
const cacheCommentCount = this.sourceCode.match(
CACHE_BUST_COMMENT_RE
).length;
this.#sourceHash = this.quickHash(this.sourceCode);
this.validateSourceCacheComments(cacheCommentCount);
for (let i = 0; i < this.iterationCount; i++)
this.#iterationSourceCodes[i] = this.createIterationSourceCode(i);
this.validateIterationSourceCodes();
}
validateSourceCacheComments(cacheCommentCount) {
console.assert(
cacheCommentCount === this.expectedCacheCommentCount,
`Invalid cache comment count ${cacheCommentCount} expected ${this.expectedCacheCommentCount}.`
);
}
validateIterationSourceCodes() {
if (this.#iterationSourceCodes.some((each) => !each?.length))
throw new Error(`Got invalid iterationSourceCodes`);
let expectedSize = 1;
if (this.sourceCodeReuseCount !== 0)
expectedSize = Math.ceil(this.iterationCount / this.sourceCodeReuseCount);
const uniqueSources = new Set(this.iterationSourceCodes);
if (uniqueSources.size != expectedSize)
throw new Error(
`Expected ${expectedSize} unique sources, but got ${uniqueSources.size}.`
);
}
createIterationSourceCode(iteration) {
// Alter the code per iteration to prevent caching.
const cacheId =
Math.floor(iteration / this.sourceCodeReuseCount) *
this.sourceCodeReuseCount;
// Reuse existing sources if this.codeReuseCount > 1:
if (cacheId < this.iterationSourceCodes.length)
return this.iterationSourceCodes[cacheId];
const sourceCode = this.sourceCode.replaceAll(
CACHE_BUST_COMMENT_RE,
`/*${cacheId}*/`
);
// Warm up quickHash.
this.quickHash(sourceCode);
return sourceCode;
}
quickHash(str) {
let hash = 5381;
let i = str.length;
while (i > 0) {
hash = (hash * 33) ^ (str.charCodeAt(i) | 0);
i -= 919;
}
return hash | 0;
}
}