Skip to content

Commit

Permalink
feat(cache): adds option to compress the cache file
Browse files Browse the repository at this point in the history
  • Loading branch information
sverweij committed Nov 4, 2023
1 parent 3fc50a2 commit 1c52a98
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 24 deletions.
3 changes: 2 additions & 1 deletion .dependency-cruiser.json
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,8 @@
},
"progress": { "type": "performance-log", "maximumLevel": 60 },
"cache": {
"strategy": "metadata"
"strategy": "metadata",
"compress": false
}
}
}
9 changes: 7 additions & 2 deletions doc/options-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1644,7 +1644,12 @@ The long form:
// background) or 'content' (which will look at file content (hashes),
// is slower than 'metadata' and is a bleeding edge feature as of
// version 12.5.0)
strategy: "metadata"
strategy: "metadata",
// whether or not to compress the cache file. Switching this to true
// will make dependency-cruiser a few milliseconds slower over all.
// The resulting cache file will be 80-90% smaller though.
// Defaults to false (don't compress)
compress: false
}
// ...
}
Expand All @@ -1668,7 +1673,7 @@ will interpret that as the cache folder.
}
```
If you don't want to use caching you cah leave the cache option out altogether or
If you don't want to use caching you can leave the cache option out altogether or
use `cache: false`.
As with most settings the command line option of the same name takes
Expand Down
64 changes: 51 additions & 13 deletions src/cache/cache.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// @ts-check
import { readFile, mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import {
brotliCompressSync,
brotliDecompressSync,
constants as zlibConstants,
} from "node:zlib";
import { optionsAreCompatible } from "./options-compatible.mjs";
import MetadataStrategy from "./metadata-strategy.mjs";
import ContentStrategy from "./content-strategy.mjs";
Expand All @@ -14,13 +18,15 @@ const CACHE_FILE_NAME = "cache.json";
export default class Cache {
/**
* @param {import("../../types/cache-options.js").cacheStrategyType=} pCacheStrategy
* @param {import("../../types/cache-options.js").cacheCompressionType=} pCompress
*/
constructor(pCacheStrategy) {
constructor(pCacheStrategy, pCompress) {
this.revisionData = null;
this.cacheStrategy =
pCacheStrategy === "content"
? new ContentStrategy()
: new MetadataStrategy();
this.compress = pCompress ?? false;
}

/**
Expand Down Expand Up @@ -61,9 +67,17 @@ export default class Cache {
*/
async read(pCacheFolder) {
try {
return JSON.parse(
await readFile(join(pCacheFolder, CACHE_FILE_NAME), "utf8"),
);
let lPayload = "";
if (this.compress === true) {
const lCompressedPayload = await readFile(
join(pCacheFolder, CACHE_FILE_NAME),
);
const lPayloadAsBuffer = brotliDecompressSync(lCompressedPayload);
lPayload = lPayloadAsBuffer.toString("utf8");
} else {
lPayload = await readFile(join(pCacheFolder, CACHE_FILE_NAME), "utf8");
}
return JSON.parse(lPayload);
} catch (pError) {
return { modules: [], summary: {} };
}
Expand All @@ -78,15 +92,39 @@ export default class Cache {
const lRevisionData = pRevisionData ?? this.revisionData;

await mkdir(pCacheFolder, { recursive: true });
await writeFile(
join(pCacheFolder, CACHE_FILE_NAME),
JSON.stringify(
this.cacheStrategy.prepareRevisionDataForSaving(
pCruiseResult,
lRevisionData,
),
const lUncompressedPayload = JSON.stringify(
this.cacheStrategy.prepareRevisionDataForSaving(
pCruiseResult,
lRevisionData,
),
"utf8",
);
let lPayload = lUncompressedPayload;
if (this.compress === true) {
/**
* we landed on brotli with BROTLI_MIN_QUALITY because:
* - even with BROTLI_MIN_QUALITY it compresses
* better than gzip (regardless of the compression level)
* - at BROTLI_MIN_QUALITY it's faster than gzip (/ deflate)
* - BROTLI_MAX_QUALITY gives a bit better compression
* but is _much_ slower than even gzip (on compressing)
*
* In our situation the sync version is significantly
* faster than the async version. As sync or async doesn't _really_
* matter for the cli, we're using the sync version here.
*
* (also zlib functions need to promisified first before they can be
* used in promises, which will add the to the execution time)
*/
lPayload = brotliCompressSync(lPayload, {
params: {
[zlibConstants.BROTLI_PARAM_QUALITY]:
zlibConstants.BROTLI_MIN_QUALITY,
},
});
}

// relying on writeFile defaults to 'do the right thing' (i.e. utf8
// when the payload is a string, raw buffer otherwise)
await writeFile(join(pCacheFolder, CACHE_FILE_NAME), lPayload);
}
}
5 changes: 4 additions & 1 deletion src/main/cruise.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ export default async function cruise(
);

const { default: Cache } = await import("#cache/cache.mjs");
lCache = new Cache(lCruiseOptions.cache.strategy);
lCache = new Cache(
lCruiseOptions.cache.strategy,
lCruiseOptions.cache.compress,
);
const lCachedResults = await lCache.read(lCruiseOptions.cache.folder);

if (await lCache.canServeFromCache(lCruiseOptions, lCachedResults)) {
Expand Down
7 changes: 6 additions & 1 deletion src/schema/configuration.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1c52a98

Please sign in to comment.