Initial commit

This commit is contained in:
2026-02-19 00:18:50 +01:00
commit f27f003a75
5 changed files with 201 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules/
package-lock.json

168
data.mjs Normal file
View File

@@ -0,0 +1,168 @@
import fs from 'node:fs';
export function load_ndjson(filePath, show_errors=true) {
return fs.readFileSync(filePath, 'utf8').split(/\n/).filter(Boolean).map(line => {
try {
return JSON.parse(line);
} catch (e) {
if (show_errors) {
console.warn({ error: e, line });
}
}
}).filter(Boolean);
}
const data = load_ndjson('/srv/project-fs.log', false);
class Data_Range {
constructor(data) {
const min = data.at(0);
const max = data.at(-1);
const median = data.at(Math.floor(data.length / 2));
Object.assign(this, { data, min, max, median });
}
get length() {
return this.data.length;
}
median_split() {
const first_half = [];
const second_half = [];
const median = this.median[0];
for (const entry of this.data) {
const position = entry[0];
if (position <= median) {
first_half.push(entry);
} else {
second_half.push(entry);
}
}
return [
new Data_Range(first_half),
new Data_Range(second_half),
];
}
}
function predicate_split(range, should_split) {
if (should_split(range)) {
const [left, right] = range.median_split();
if ((left.length > 0) && (right.length > 0)) {
return [...predicate_split(left, should_split), ...predicate_split(right, should_split)];
}
}
return [range];
}
const all_data = new Data_Range(data.map(e => [e.ts[0], e]));
export const ranges = predicate_split(new Data_Range(data.map(e => [e.ts[0], e])), r => r.length > 10_000);
export function* entries_after(ranges, query_position) {
const range_iter = ranges[Symbol.iterator]();
for (const range of range_iter) {
if (range.max[0] >= query_position) {
for (const entry of range.data) {
const pos = entry[0];
if (pos >= query_position) {
yield entry;
}
}
for (const range of range_iter) {
yield* range.data;
}
return;
}
}
}
const point_in_time = Math.floor(Date.now() / 1000) - 30 * 86400; // 30 days ago
const events = new Data_Range([...entries_after(ranges, point_in_time)]);
class Counter extends Map {
increase(key, delta = 1) {
this.set(key, (this.get(key) ?? 0) + delta);
}
}
class Counter_Bin_Set extends Map {
increase(slot, value, delta = 1) {
const existing = this.get(slot);
if (existing) {
existing.increase(value, delta);
} else {
const new_counter = new Counter();
new_counter.increase(value, delta);
this.set(slot, new_counter);
}
}
}
const activity = new Counter_Bin_Set();
function pos_to_slot(pos) {
return Math.floor(pos / 86400) - Math.floor(events.min[0] / 86400); // Per day
}
function slot_to_pos(slot) {
return (slot + Math.floor(events.min[0] / 86400)) * 86400; // Per day
}
const ignore_patterns = [
/\/\.git(?:\/.*)?/g,
/\.tmp$/ig,
];
for (const [pos, value] of events.data) {
for (const name of [value.name, value.old, value.new]) {
if (name) {
let ignore = false;
for (const pattern of ignore_patterns) {
if (name.match(pattern)) {
ignore = true;
break;
}
}
if (!ignore) {
activity.increase(pos_to_slot(pos), name);
}
}
}
}
function epoch_s_to_date_time(epoch) {
return new Date(epoch*1000).toISOString().slice(0,16).replace('T',' ');
}
function epoch_s_to_date(epoch) {
return new Date(epoch*1000).toISOString().split('T')[0];
}
for (const [slot, entry] of activity.entries()) {
console.log(epoch_s_to_date(slot_to_pos(slot)), entry.size);
}

7
package.json Normal file
View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"express": "^5.2.1",
"serve-index": "^1.9.1",
"ws": "^8.19.0"
}
}

15
server.mjs Normal file
View File

@@ -0,0 +1,15 @@
import express from 'express';
//import { WebSocketServer } from 'ws';
import http from 'node:http';
import serveIndex from 'serve-index';
const app = express();
const server = http.createServer(app);
//const wss = new WebSocketServer({ noServer: true });
app.use('/', express.static('static', { index: [] }), serveIndex('static', { icons: true }));
server.listen(3535);

9
static/index.html Normal file
View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
</body>
</html>