Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
168
data.mjs
Normal file
168
data.mjs
Normal 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
7
package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^5.2.1",
|
||||||
|
"serve-index": "^1.9.1",
|
||||||
|
"ws": "^8.19.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
server.mjs
Normal file
15
server.mjs
Normal 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
9
static/index.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user