mirror of
https://github.com/silvanocerza/github-gitless-sync.git
synced 2026-07-22 12:10:28 +00:00
232 lines
6.8 KiB
TypeScript
232 lines
6.8 KiB
TypeScript
import * as fs from "fs";
|
|
import * as proxyquire from "proxyquire";
|
|
import * as obsidianMocks from "./mock-obsidian";
|
|
import * as os from "os";
|
|
import * as path from "path";
|
|
import * as crypto from "crypto";
|
|
const proxyquireNonStrict = proxyquire.noCallThru();
|
|
|
|
const LoggerModule = proxyquireNonStrict("./src/logger", {
|
|
obsidian: obsidianMocks,
|
|
});
|
|
|
|
const GithubClientModule = proxyquireNonStrict("./src/github/client", {
|
|
obsidian: obsidianMocks,
|
|
});
|
|
|
|
const MetadataStoreModule = proxyquireNonStrict("./src/metadata-store", {
|
|
obsidian: obsidianMocks,
|
|
});
|
|
|
|
const EventsListenerModule = proxyquireNonStrict("./src/events-listener", {
|
|
obsidian: obsidianMocks,
|
|
"./metadata-store": MetadataStoreModule,
|
|
});
|
|
|
|
const UtilsModule = proxyquireNonStrict("./src/utils", {
|
|
obsidian: obsidianMocks,
|
|
});
|
|
|
|
const SyncManagerModule = proxyquireNonStrict("./src/sync-manager", {
|
|
obsidian: obsidianMocks,
|
|
"./metadata-store": MetadataStoreModule,
|
|
"./events-listener": EventsListenerModule,
|
|
"./github/client": GithubClientModule,
|
|
"./utils": UtilsModule,
|
|
});
|
|
|
|
async function runBenchmark(vaultRootDir: string) {
|
|
const vault = new obsidianMocks.Vault(vaultRootDir);
|
|
console.log("Vault config dir:", vault.configDir);
|
|
|
|
// Create a real logger with our mock vault
|
|
const logger = new LoggerModule.default(vault, false);
|
|
|
|
// Settings for the sync manager
|
|
const settings = {
|
|
githubToken: process.env.GITHUB_TOKEN,
|
|
githubOwner: process.env.REPO_OWNER,
|
|
githubRepo: process.env.REPO_NAME,
|
|
githubBranch: process.env.REPO_BRANCH,
|
|
syncConfigDir: false,
|
|
};
|
|
|
|
// We're not going to get any conflicts, this is useless
|
|
const onConflicts = async () => {
|
|
return [];
|
|
};
|
|
|
|
// Create the sync manager
|
|
const SyncManager = SyncManagerModule.default;
|
|
const syncManager = new SyncManager(vault, settings, onConflicts, logger);
|
|
await syncManager.loadMetadata();
|
|
|
|
const startTime = performance.now();
|
|
await syncManager.firstSync();
|
|
return performance.now() - startTime;
|
|
}
|
|
|
|
const generateRandomFiles = (
|
|
rootPath: string,
|
|
numFiles: number,
|
|
maxDepth: number,
|
|
maxTotalSize: number,
|
|
) => {
|
|
// Create root directory if it doesn't exist
|
|
if (!fs.existsSync(rootPath)) {
|
|
fs.mkdirSync(rootPath, { recursive: true });
|
|
}
|
|
|
|
// Generate folder structure first
|
|
const allFolderPaths = [rootPath];
|
|
|
|
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
const numFoldersAtThisDepth = Math.floor(Math.random() * 3) + 1; // 1-3 folders per level
|
|
|
|
for (let i = 0; i < numFoldersAtThisDepth; i++) {
|
|
const parentPath =
|
|
allFolderPaths[Math.floor(Math.random() * allFolderPaths.length)];
|
|
const currentDepthOfParent =
|
|
parentPath.split(path.sep).length - rootPath.split(path.sep).length;
|
|
|
|
// Only create subfolders if we haven't reached max depth for this path
|
|
if (currentDepthOfParent < maxDepth) {
|
|
const folderName = crypto.randomBytes(5).toString("hex");
|
|
const newFolderPath = path.join(parentPath, folderName);
|
|
|
|
fs.mkdirSync(newFolderPath, { recursive: true });
|
|
allFolderPaths.push(newFolderPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now generate files
|
|
let totalSize = 0;
|
|
// Calculate approximate size per file to ensure we create all files
|
|
const targetSizePerFile = Math.floor(maxTotalSize / numFiles);
|
|
|
|
for (let i = 0; i < numFiles; i++) {
|
|
// Pick a random folder to place the file in
|
|
const targetFolder =
|
|
allFolderPaths[Math.floor(Math.random() * allFolderPaths.length)];
|
|
|
|
// Generate random file name
|
|
const fileName = crypto.randomBytes(8).toString("hex") + ".md";
|
|
const filePath = path.join(targetFolder, fileName);
|
|
|
|
// Calculate remaining size and files
|
|
const remainingSize = maxTotalSize - totalSize;
|
|
const remainingFiles = numFiles - i;
|
|
|
|
if (remainingSize <= 0) {
|
|
// If we've hit the max size but still need files, create empty files
|
|
fs.writeFileSync(filePath, "");
|
|
} else {
|
|
// Calculate appropriate file size to ensure we can create all files
|
|
const maxPossibleSize = Math.floor(remainingSize / remainingFiles);
|
|
const contentSize =
|
|
Math.floor(
|
|
Math.random() * Math.min(maxPossibleSize, targetSizePerFile),
|
|
) + 1;
|
|
|
|
// Generate random content
|
|
const content = crypto.randomBytes(contentSize).toString("hex");
|
|
|
|
// Write file
|
|
fs.writeFileSync(filePath, content);
|
|
totalSize += contentSize;
|
|
}
|
|
}
|
|
|
|
return totalSize;
|
|
};
|
|
|
|
const cleanupRemote = async () => {
|
|
const owner = process.env.REPO_OWNER;
|
|
const repo = process.env.REPO_NAME;
|
|
const branch = process.env.REPO_BRANCH;
|
|
const headers = {
|
|
Accept: "application/vnd.github+json",
|
|
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
|
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
};
|
|
try {
|
|
// Get the manifest SHA
|
|
const contentResponse = await obsidianMocks.requestUrl({
|
|
url: `https://api.github.com/repos/${owner}/${repo}/contents/.obsidian/github-sync-metadata.json?ref=${branch}`,
|
|
headers: headers,
|
|
});
|
|
|
|
// Delete manifest
|
|
await obsidianMocks.requestUrl({
|
|
url: `https://api.github.com/repos/${owner}/${repo}/contents/.obsidian/github-sync-metadata.json`,
|
|
method: "DELETE",
|
|
headers: headers,
|
|
body: JSON.stringify({
|
|
message: "Cleanup",
|
|
sha: contentResponse.json.sha,
|
|
branch: branch,
|
|
}),
|
|
});
|
|
console.log("Remote repository cleaned up successfully");
|
|
} catch (error) {
|
|
console.error("Failed to clean up remote repository:", error);
|
|
}
|
|
};
|
|
|
|
const BENCHMARK_DATA = [
|
|
{
|
|
files: 1,
|
|
maxDepth: 1,
|
|
maxSize: 7000,
|
|
},
|
|
];
|
|
|
|
(async () => {
|
|
const tmp = os.tmpdir();
|
|
const benchmarkRootDir = path.join(tmp, "github-gitless-sync-benchmark");
|
|
try {
|
|
const results = [];
|
|
for (const data of BENCHMARK_DATA) {
|
|
const vaultRootDir = path.join(
|
|
benchmarkRootDir,
|
|
`${data.files}-${data.maxDepth}-${data.maxSize}`,
|
|
);
|
|
// Generates random files
|
|
generateRandomFiles(
|
|
vaultRootDir,
|
|
data.files,
|
|
data.maxDepth,
|
|
data.maxSize,
|
|
);
|
|
|
|
// Run first sync by uploading all local files
|
|
const uploadTime = await runBenchmark(vaultRootDir);
|
|
|
|
// Cleanup vault dir completely
|
|
fs.rmSync(vaultRootDir, { recursive: true, force: true });
|
|
|
|
// Run first sync again, this time we download the files we just uploaded
|
|
const downloadTime = await runBenchmark(vaultRootDir);
|
|
|
|
// Cleanup the remote repo so it's ready for another benchmark
|
|
await cleanupRemote();
|
|
|
|
results.push({
|
|
data,
|
|
uploadTime,
|
|
downloadTime,
|
|
});
|
|
}
|
|
fs.writeFileSync("benchmark_result.json", JSON.stringify(results), {
|
|
flag: "w",
|
|
});
|
|
} catch (error) {
|
|
console.error("Benchmark failed:", error);
|
|
}
|
|
fs.rm(benchmarkRootDir, { recursive: true, force: true }, (err) => {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
});
|
|
})();
|