Files
fs-event-based-activity-tra…/data.mjs
2026-02-19 00:18:50 +01:00

169 lines
3.3 KiB
JavaScript

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);
}