6215add877
Change-Id: I2fe6e6a634a406f4c0e5c491b4ff6286ae888175
Reviewed-on: https://chromium-review.googlesource.com/c/external/github.com/WebKit/JetStream/+/7124399
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
307 lines
8.9 KiB
JavaScript
307 lines
8.9 KiB
JavaScript
/*
|
|
* Copyright (C) 2018-2023 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
function computeIsLittleEndian() {
|
|
let buf = new ArrayBuffer(4);
|
|
let dv = new DataView(buf);
|
|
dv.setUint32(0, 0x11223344, true);
|
|
let view = new Uint8Array(buf);
|
|
return view[0] === 0x44;
|
|
}
|
|
|
|
const isLittleEndian = computeIsLittleEndian();
|
|
|
|
async function *randomFileContents() {
|
|
let counter = 1;
|
|
while(true) {
|
|
const numBytes = ((counter * 1192.18851371) % 2056);
|
|
counter++;
|
|
let result = new ArrayBuffer(numBytes);
|
|
let view = new Uint8Array(result);
|
|
for (let i = 0; i < numBytes; ++i)
|
|
view[i] = (i + counter) % 255;
|
|
yield new DataView(result);
|
|
}
|
|
}
|
|
|
|
|
|
class File {
|
|
constructor(dataView, permissions) {
|
|
this._data = dataView;
|
|
}
|
|
|
|
get data() { return this._data; }
|
|
|
|
set data(dataView) { this._data = dataView; }
|
|
|
|
get byteLength() { return this._data.byteLength; }
|
|
|
|
swapByteOrder() {
|
|
let hash = 0x1a2b3c4d;
|
|
for (let i = 0; i < Math.floor(this.data.byteLength / 8) * 8; i += 8) {
|
|
const data = this.data.getFloat64(i, isLittleEndian);
|
|
this.data.setFloat64(i, data, !isLittleEndian);
|
|
hash ^= data | 0;
|
|
}
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
class Directory {
|
|
constructor() {
|
|
this.structure = new Map;
|
|
}
|
|
|
|
async addFile(name, file) {
|
|
let entry = this.structure.get(name);
|
|
if (entry !== undefined) {
|
|
if (entry instanceof File)
|
|
throw new Error("Can't replace file with file.");
|
|
if (entry instanceof Directory)
|
|
throw new Error("Can't replace a file with a new directory.");
|
|
throw new Error("Should not reach this code");
|
|
}
|
|
|
|
this.structure.set(name, file);
|
|
return file;
|
|
}
|
|
|
|
async addDirectory(name, directory = new Directory) {
|
|
let entry = this.structure.get(name);
|
|
if (entry !== undefined) {
|
|
if (entry instanceof File)
|
|
throw new Error("Can't replace file with directory.");
|
|
if (entry instanceof Directory)
|
|
throw new Error("Can't replace directory with new directory.");
|
|
throw new Error("Should not reach this code");
|
|
}
|
|
|
|
this.structure.set(name, directory);
|
|
return directory;
|
|
}
|
|
|
|
async* ls() {
|
|
for (let [name, entry] of this.structure)
|
|
yield { name, entry, isDirectory: entry instanceof Directory };
|
|
}
|
|
|
|
async* forEachFile() {
|
|
for await (let item of this.ls()) {
|
|
if (!item.isDirectory)
|
|
yield item;
|
|
}
|
|
}
|
|
|
|
async* forEachFileRecursively() {
|
|
for await (let item of this.ls()) {
|
|
if (item.isDirectory) {
|
|
for await (let file of item.entry.forEachFileRecursively())
|
|
yield file;
|
|
} else {
|
|
yield item;
|
|
}
|
|
}
|
|
}
|
|
|
|
async* forEachDirectoryRecursively() {
|
|
for await (let item of this.ls()) {
|
|
if (!item.isDirectory)
|
|
continue;
|
|
|
|
for await (let dirItem of item.entry.forEachDirectoryRecursively())
|
|
yield dirItem;
|
|
|
|
yield item;
|
|
}
|
|
}
|
|
|
|
async fileCount() {
|
|
let count = 0;
|
|
for await (let item of this.ls()) {
|
|
if (!item.isDirectory)
|
|
++count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
async totalFileSize() {
|
|
let size = 0;
|
|
for await (const {entry: file} of this.forEachFileRecursively()) {
|
|
size += file.byteLength;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
async rm(name) {
|
|
return this.structure.delete(name);
|
|
}
|
|
|
|
async visit(visitor) {
|
|
visitor.visitDir(undefined, this);
|
|
for await (const {name, entry, isDirectory} of this.ls()) {
|
|
if (isDirectory)
|
|
await entry.visit(visitor);
|
|
else
|
|
visitor.visitFile(name, entry);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
const MAX_DIR_COUNT = 2500;
|
|
const MAX_FILE_COUNT = 800;
|
|
|
|
async function setupDirectory() {
|
|
const fs = new Directory;
|
|
let dirs = [fs];
|
|
let counter = 0;
|
|
for (let dir of dirs) {
|
|
for (let i = 0; i < 15; ++i) {
|
|
if (dirs.length <= MAX_DIR_COUNT) {
|
|
dirs.push(await dir.addDirectory(`dir-${i}`));
|
|
}
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
let fileCounter = 0;
|
|
for await (const fileContents of randomFileContents()) {
|
|
const dirIndex = fileCounter * 107;
|
|
const dir = dirs[dirIndex % dirs.length];
|
|
await dir.addFile(`file-${fileCounter}`, new File(fileContents));
|
|
fileCounter++
|
|
if (fileCounter >= MAX_FILE_COUNT)
|
|
break;
|
|
}
|
|
|
|
return fs;
|
|
}
|
|
|
|
class FSVisitor {
|
|
visitFile(name, file) {
|
|
}
|
|
|
|
visitDir(name, dir) {
|
|
}
|
|
}
|
|
|
|
class CountVisitor extends FSVisitor {
|
|
fileCount = 0;
|
|
dirCount = 0;
|
|
|
|
visitFile() {
|
|
this.fileCount++;
|
|
}
|
|
|
|
visitDir() {
|
|
this.dirCount++;
|
|
}
|
|
}
|
|
|
|
class CountDracula extends FSVisitor {
|
|
bytes = 0;
|
|
visitFile(name, file) {
|
|
this.bytes += file.byteLength;
|
|
}
|
|
}
|
|
|
|
|
|
class Benchmark {
|
|
EXPECTED_FILE_COUNT = 739;
|
|
|
|
totalFileCount = 0;
|
|
totalDirCount = 0;
|
|
lastFileHash = undefined;
|
|
fs;
|
|
|
|
async prepareForNextIteration() {
|
|
this.fs = await setupDirectory();
|
|
}
|
|
|
|
async runIteration() {
|
|
for await (let { entry: file } of this.fs.forEachFileRecursively()) {
|
|
this.lastFileHash = file.swapByteOrder();
|
|
}
|
|
|
|
let bytesDeleted = 0;
|
|
let counter = 0;
|
|
for await (const { name, entry: dir } of this.fs.forEachDirectoryRecursively()) {
|
|
const oldTotalSize = await dir.totalFileSize();
|
|
if (await dir.fileCount() === 0)
|
|
continue;
|
|
counter++;
|
|
if (counter % 13 !== 0)
|
|
continue;
|
|
for await (const { name } of dir.forEachFile()) {
|
|
const result = await dir.rm(name);
|
|
if (!result)
|
|
throw new Error("rm should have returned true");
|
|
}
|
|
const totalReducedSize = oldTotalSize - dir.totalFileSize();
|
|
bytesDeleted += totalReducedSize;
|
|
}
|
|
if (bytesDeleted === 0)
|
|
throw new Error("Did not delete any files");
|
|
|
|
const countVisitor = new CountVisitor();
|
|
await this.fs.visit(countVisitor);
|
|
|
|
const countDracula = new CountDracula();
|
|
await this.fs.visit(countDracula);
|
|
|
|
let fileCount = 0;
|
|
let fileBytes = 0;
|
|
for await (const {entry: file} of this.fs.forEachFileRecursively()) {
|
|
fileCount++
|
|
fileBytes += file.byteLength;
|
|
}
|
|
this.totalFileCount += fileCount;
|
|
|
|
let dirCount = 1;
|
|
for await (let _ of this.fs.forEachDirectoryRecursively()) {
|
|
dirCount++;
|
|
}
|
|
this.totalDirCount += dirCount;
|
|
|
|
if (countVisitor.fileCount !== fileCount)
|
|
throw new Error(`Invalid total file count ${countVisitor.fileCount}, expected ${fileCount}.`);
|
|
if (countDracula.bytes !== fileBytes)
|
|
throw new Error(`Invalid total file bytes ${countDracula.bytes}, expected ${fileBytes}.`);
|
|
if (countVisitor.dirCount !== dirCount)
|
|
throw new Error(`Invalid total dir count ${countVisitor.dirCount}, expected ${dirCount}.`);
|
|
|
|
}
|
|
|
|
validate(iterations) {
|
|
const expectedFileCount = this.EXPECTED_FILE_COUNT * iterations;
|
|
if (this.totalFileCount != expectedFileCount)
|
|
throw new Error(`Invalid total file count ${this.totalFileCount}, expected ${expectedFileCount}.`);
|
|
if (this.lastFileHash === undefined)
|
|
throw new Error(`Invalid file hash: ${this.lastFileHash}`);
|
|
}
|
|
}
|