mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-16 13:22:41 +00:00
fix: filter totalNodes to active nodes (7-day window), fixes #133
The /api/stats endpoint returned totalNodes from SELECT COUNT(*) FROM nodes, which counts every node ever seen. On long-running instances this climbs to 6800+ for a ~200-400 node mesh. Changes: - totalNodes now counts only nodes with last_seen within the last 7 days - Added totalNodesAllTime field for the full historical count - Role counts (repeaters, rooms, etc.) also filtered to 7-day window - Added countActiveNodes and countActiveNodesByRole prepared statements - Added 6 tests verifying active vs all-time node counting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -346,6 +346,8 @@ const stmts = {
|
||||
getObservers: db.prepare(`SELECT * FROM observers ORDER BY last_seen DESC`),
|
||||
countPackets: db.prepare(`SELECT COUNT(*) as count FROM observations`),
|
||||
countNodes: db.prepare(`SELECT COUNT(*) as count FROM nodes`),
|
||||
countActiveNodes: db.prepare(`SELECT COUNT(*) as count FROM nodes WHERE last_seen > ?`),
|
||||
countActiveNodesByRole: db.prepare(`SELECT COUNT(*) as count FROM nodes WHERE role = ? AND last_seen > ?`),
|
||||
countObservers: db.prepare(`SELECT COUNT(*) as count FROM observers`),
|
||||
countRecentPackets: schemaVersion >= 3
|
||||
? db.prepare(`SELECT COUNT(*) as count FROM observations WHERE timestamp > CAST(strftime('%s', ?) AS INTEGER)`)
|
||||
@@ -579,6 +581,7 @@ function getObservers() {
|
||||
|
||||
function getStats() {
|
||||
const oneHourAgo = new Date(Date.now() - 3600000).toISOString();
|
||||
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 3600000).toISOString();
|
||||
// Try to get transmission count from normalized schema
|
||||
let totalTransmissions = null;
|
||||
try {
|
||||
@@ -588,7 +591,8 @@ function getStats() {
|
||||
totalPackets: totalTransmissions || stmts.countPackets.get().count,
|
||||
totalTransmissions,
|
||||
totalObservations: stmts.countPackets.get().count,
|
||||
totalNodes: stmts.countNodes.get().count,
|
||||
totalNodes: stmts.countActiveNodes.get(sevenDaysAgo).count,
|
||||
totalNodesAllTime: stmts.countNodes.get().count,
|
||||
totalObservers: stmts.countObservers.get().count,
|
||||
packetsLastHour: stmts.countRecentPackets.get(oneHourAgo).count,
|
||||
};
|
||||
|
||||
@@ -876,10 +876,11 @@ app.use(express.json());
|
||||
|
||||
app.get('/api/stats', (req, res) => {
|
||||
const stats = db.getStats();
|
||||
// Get role counts
|
||||
// Get role counts (active nodes only — same 7-day window as totalNodes)
|
||||
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 3600000).toISOString();
|
||||
const counts = {};
|
||||
for (const role of ['repeater', 'room', 'companion', 'sensor']) {
|
||||
const r = db.db.prepare(`SELECT COUNT(*) as count FROM nodes WHERE role = ?`).get(role);
|
||||
const r = db.db.prepare(`SELECT COUNT(*) as count FROM nodes WHERE role = ? AND last_seen > ?`).get(role, sevenDaysAgo);
|
||||
counts[role + 's'] = r.count;
|
||||
}
|
||||
res.json({ ...stats, counts });
|
||||
|
||||
+27
@@ -213,6 +213,33 @@ console.log('\ngetStats:');
|
||||
assert(stats.totalObservers >= 1, 'totalObservers');
|
||||
assert(typeof stats.totalPackets === 'number', 'totalPackets is number');
|
||||
assert(typeof stats.packetsLastHour === 'number', 'packetsLastHour is number');
|
||||
assert(typeof stats.totalNodesAllTime === 'number', 'totalNodesAllTime is number');
|
||||
assert(stats.totalNodesAllTime >= stats.totalNodes, 'totalNodesAllTime >= totalNodes');
|
||||
}
|
||||
|
||||
// --- getStats active node filtering ---
|
||||
console.log('\ngetStats active node filtering:');
|
||||
{
|
||||
// Insert a node with last_seen 30 days ago (should be excluded from totalNodes)
|
||||
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 3600000).toISOString();
|
||||
db.upsertNode({ public_key: 'deadnode0000000000000000deadnode00', name: 'DeadNode', role: 'repeater', last_seen: thirtyDaysAgo, first_seen: thirtyDaysAgo });
|
||||
|
||||
// Insert a node with last_seen now (should be included)
|
||||
db.upsertNode({ public_key: 'livenode0000000000000000livenode00', name: 'LiveNode', role: 'companion', last_seen: new Date().toISOString() });
|
||||
|
||||
const stats = db.getStats();
|
||||
assert(stats.totalNodesAllTime > stats.totalNodes, 'dead node excluded from totalNodes but included in totalNodesAllTime');
|
||||
|
||||
// Verify the dead node is in totalNodesAllTime
|
||||
const allTime = stats.totalNodesAllTime;
|
||||
assert(allTime >= 3, 'totalNodesAllTime includes dead + live nodes');
|
||||
|
||||
// Verify active count doesn't include the 30-day-old node
|
||||
// The dead node's last_seen is 30 days ago, window is 7 days
|
||||
const nodeInDb = db.getNode('deadnode0000000000000000deadnode00');
|
||||
assert(nodeInDb !== null, 'dead node exists in DB');
|
||||
const liveNode = db.getNode('livenode0000000000000000livenode00');
|
||||
assert(liveNode !== null, 'live node exists in DB');
|
||||
}
|
||||
|
||||
// --- getNodeHealth ---
|
||||
|
||||
Reference in New Issue
Block a user