Created
March 23, 2023 15:13
-
-
Save sempostma/b6599b1e63591a5cb03b0df349f3a770 to your computer and use it in GitHub Desktop.
Create a treemap of largest files in a folder recursively
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { exec } = require('child_process') | |
const fs = require('fs') | |
const path = require('path') | |
let lastDir = '' | |
let currDir | |
const recurse = async (dir, filenames, writeStream) => { | |
currDir = dir | |
const stats = filenames.map(async filename => { | |
const filepath = path.join(dir, filename) | |
try { | |
const stat = await fs.promises.stat(filepath) | |
stat.filename = filename | |
if (stat.isDirectory()) { | |
stat.filenames = await fs.promises.readdir(filepath) | |
} | |
return stat | |
} catch(err) { | |
return { | |
error: err.toString() | |
} | |
} | |
}) | |
for (let index = 0; index < stats.length; index++) { | |
const stat = await stats[index]; | |
let status | |
if (index !== 0) { | |
writeStream.write(',') | |
} | |
if (stat.error) { | |
status = writeStream.write(`{ | |
name: ${JSON.stringify(stat.filename + ' (ERROR)')}, | |
value: 1 | |
}`) | |
} | |
else if (stat.isFile()) { | |
status = writeStream.write(`{ | |
name: ${JSON.stringify(stat.filename)}, | |
value: ${stat.size} | |
}`) | |
} else if (stat.isDirectory()) { | |
status = writeStream.write(`{ | |
name: ${JSON.stringify(stat.filename)}, | |
children: [`) | |
await recurse(path.join(dir, stat.filename), stat.filenames, writeStream) | |
status = writeStream.write(`] | |
}`) | |
} else if (stat.isSymbolicLink()) { | |
status = writeStream.write(`{ | |
name: ${JSON.stringify(stat.filename + ' (LINK)')}, | |
value: 1 | |
}`) | |
} else if (stat.isFIFO()) { | |
status = writeStream.write(`{ | |
name: ${JSON.stringify(stat.filename + ' (FIFO)')}, | |
value: 1 | |
}`) | |
} else if (stat.isSocket) { | |
status = writeStream.write(`{ | |
name: ${JSON.stringify(stat.filename + ' (SOCKET)')}, | |
value: 1 | |
}`) | |
} else { | |
status = writeStream.write(`{ | |
name: ${JSON.stringify(stat.filename + ' (UNKNOWN)')}, | |
value: 1 | |
}`) | |
} | |
if (status === false && writeStream.writableNeedDrain) { | |
await new Promise(r => writeStream.once('drain', r)) | |
} | |
} | |
} | |
const analyze = async (dir) => { | |
console.log('Run with admin privileges!') | |
const outputFile = path.join(__dirname, 'index.html') | |
const writeStream = fs.createWriteStream(outputFile) | |
if (fs.existsSync(outputFile)) { | |
fs.unlinkSync(outputFile) | |
} | |
writeStream.write(` | |
<html> | |
<head> | |
<style> | |
.treemap-viz .tooltip { | |
max-width: none!important; | |
} | |
body, html { | |
margin: 0; | |
overflow: hidden; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="root"></div> | |
<script src="https://unpkg.com/treemap-chart"></script> | |
<script> | |
function humanFileSize(bytes, si=false, dp=1) { | |
const thresh = si ? 1000 : 1024; | |
if (Math.abs(bytes) < thresh) { | |
return bytes + ' B'; | |
} | |
const units = si | |
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] | |
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; | |
let u = -1; | |
const r = 10**dp; | |
do { | |
bytes /= thresh; | |
++u; | |
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); | |
return bytes.toFixed(dp) + ' ' + units[u]; | |
} | |
function getNodeStack(d) { | |
const stack = []; | |
let curNode = d; | |
while (curNode) { | |
stack.unshift(curNode); | |
curNode = curNode.parent; | |
} | |
return stack; | |
} | |
function genColor(ratio) { | |
var color = Math.round(ratio * 255); | |
var red = color.toString(16); | |
var green = (255 - color).toString(16); | |
// pad any colors shorter than 6 characters with leading 0s | |
while(red.length < 2) { | |
red = '0' + red; | |
} | |
while(green.length < 2) { | |
green = '0' + green; | |
} | |
color = '#' + red + green + '00' | |
console.log(color) | |
return color; | |
} | |
async function run() { | |
const myChart = Treemap(); | |
const mb50 = 1024 * 1024 * 50 | |
myChart | |
.minBlockArea(50 * 50) | |
.label(d => d.name + ' ' + humanFileSize(d.__dataNode.value)) | |
.color(d => d.__dataNode.children ? 'lightgrey' : genColor(d.__dataNode.value / Math.max(d.__dataNode.parent.value, mb50))) | |
.tooltipTitle(d => getNodeStack(d.__dataNode.parent) | |
.map(d => d.data.name) | |
.join('\\\\')) | |
.data( | |
{ | |
name: ${JSON.stringify(dir)}, | |
children: [`) | |
const filenames = await fs.promises.readdir(dir) | |
currDir = dir | |
lastDir = dir | |
const interval = setInterval(function() { | |
let size = Math.floor(writeStream.bytesWritten / 1048576) | |
if (size === 0) size = writeStream.bytesWritten + ' bytes' | |
else size += ' MB' | |
const currDirSections = currDir.split(path.sep) | |
const lastDirSections = lastDir.split(path.sep) | |
let matchingParents = currDirSections.findIndex((v, i) => v !== lastDirSections[i]) | |
if (matchingParents === -1) matchingParents = currDirSections.length + 1 | |
else matchingParents++ | |
const displayDir = currDirSections.slice(0, matchingParents).join(path.sep) | |
console.log('Treemap size: ' + size + ' [current_dir=' + displayDir + ']') | |
lastDir = displayDir | |
}, 5000) | |
await recurse(dir, filenames, writeStream) | |
clearInterval(interval) | |
let size = Math.floor(writeStream.bytesWritten / 1048576) | |
if (size === 0) size = writeStream.bytesWritten + ' bytes' | |
else size += ' MB' | |
console.log('Treemap size: ' + size) | |
writeStream.end(`] | |
} | |
) | |
(document.getElementById('root')); | |
} | |
run(); | |
</script> | |
</body> | |
</html> | |
`) | |
} | |
const run = async () => { | |
const root = process.argv[2] | |
console.log('root: ' + root) | |
await analyze(root) | |
const htmlFile = path.join(__dirname, 'index.html') | |
exec(`start file://${htmlFile}`) | |
} | |
run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment