Skip to content

Commit b384a30

Browse files
committed
perf(@angular/cli): optimize cache size calculation in ng cache info command
This commit significantly improves the performance and reliability of the cache size calculation for the `ng cache info` command. The previous implementation performed sequential `fs.stat` calls, which was inefficient for directories with many files. The new implementation introduces several key improvements: - **Parallel I/O:** `fs.stat` calls are now executed in parallel to dramatically speed up the calculation. - **Batching:** To prevent file descriptor exhaustion (`EMFILE` errors) on large caches, the parallel operations are processed in controlled batches. - **Type Safety:** The directory traversal loop was refactored to remove a non-null assertion, making the code safer and more robust. - **Error Handling:** Unreadable directories are now handled gracefully with a warning instead of failing silently.
1 parent 5063671 commit b384a30

1 file changed

Lines changed: 26 additions & 11 deletions

File tree

  • packages/angular/cli/src/commands/cache/info

packages/angular/cli/src/commands/cache/info/cli.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,27 +68,42 @@ export class CacheInfoCommandModule extends CommandModule implements CommandModu
6868
}
6969

7070
private async getSizeOfDirectory(path: string): Promise<string> {
71+
const CONCURRENCY_LIMIT = 100;
7172
const directoriesStack = [path];
7273
let size = 0;
7374

74-
while (directoriesStack.length) {
75-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
76-
const dirPath = directoriesStack.pop()!;
77-
let entries: string[] = [];
75+
let dirPath: string | undefined;
76+
while ((dirPath = directoriesStack.pop()) !== undefined) {
77+
let entries: import('node:fs').Dirent[];
7878

7979
try {
80-
entries = await fs.readdir(dirPath);
81-
} catch {}
80+
entries = await fs.readdir(dirPath, { withFileTypes: true });
81+
} catch {
82+
this.context.logger.warn(
83+
`Warning: Could not read directory ${dirPath}. Size calculation may be incomplete.`,
84+
);
85+
continue;
86+
}
8287

88+
const filesToStat: string[] = [];
8389
for (const entry of entries) {
84-
const entryPath = join(dirPath, entry);
85-
const stats = await fs.stat(entryPath);
86-
87-
if (stats.isDirectory()) {
90+
const entryPath = join(dirPath, entry.name);
91+
if (entry.isDirectory()) {
8892
directoriesStack.push(entryPath);
93+
} else if (entry.isFile()) {
94+
filesToStat.push(entryPath);
8995
}
96+
}
9097

91-
size += stats.size;
98+
for (let i = 0; i < filesToStat.length; i += CONCURRENCY_LIMIT) {
99+
const batch = filesToStat.slice(i, i + CONCURRENCY_LIMIT);
100+
const batchPromises = batch.map((file) =>
101+
fs.stat(file).then(
102+
(stats) => stats.size,
103+
() => 0, // Ignore errors for individual files
104+
),
105+
);
106+
size += (await Promise.all(batchPromises)).reduce((total, current) => total + current, 0);
92107
}
93108
}
94109

0 commit comments

Comments
 (0)