hourly stats and daily rollup

This commit is contained in:
Alex Holliday committed 2025-12-17 19:20:03 +00:00
1 parent e1db8ea513
commit 59e85f7613
10 files changed
+1132 -19

No files matched your search

+5
View File
@@ -59,3 +59,8 @@ export type { IIncident, ResolutionType } from "./incidents/Incident.js";
export { SystemSettings } from "./system/SystemSettings.js";
export type { ISystemSettings } from "./system/SystemSettings.js";
export { StatsHourly } from "./monitors/StatsHourly.js";
export type { IStatsHourly } from "./monitors/StatsHourly.js";
export { StatsDaily } from "./monitors/StatsDaily.js";
export type { IStatsDaily } from "./monitors/StatsDaily.js";
+11 -1
View File
@@ -1,5 +1,11 @@
import mongoose, { Schema, Document, Types } from "mongoose";
import { Check, Incident, MonitorStats } from "@/db/models/index.js";
import {
Check,
Incident,
MonitorStats,
StatsDaily,
StatsHourly,
} from "@/db/models/index.js";
export const MonitorTypes = [
"http",
@@ -120,6 +126,8 @@ MonitorSchema.pre(
const monitorId = this._id;
await Check.deleteMany({ "metadata.monitorId": monitorId });
await MonitorStats.deleteMany({ monitorId });
await StatsHourly.deleteMany({ monitorId });
await StatsDaily.deleteMany({ monitorId });
await Incident.deleteMany({ monitorId });
next();
} catch (error: any) {
@@ -139,6 +147,8 @@ MonitorSchema.pre(
if (monitorIds.length > 0) {
await Check.deleteMany({ "metadata.monitorId": { $in: monitorIds } });
await MonitorStats.deleteMany({ monitorId: { $in: monitorIds } });
await StatsHourly.deleteMany({ monitorId: { $in: monitorIds } });
await StatsDaily.deleteMany({ monitorId: { $in: monitorIds } });
await Incident.deleteMany({ monitorId: { $in: monitorIds } });
}
next();
+132
View File
@@ -0,0 +1,132 @@
import mongoose, { Schema, Types } from "mongoose";
import type { MonitorType } from "@/db/models/monitors/Monitor.js";
import type {
IInfraCpu,
IInfraDiskEntry,
IInfraHost,
IInfraMemory,
IInfraNetEntry,
IPageSpeedMetrics,
IStatsBase,
} from "./StatsHourly.js";
export interface IStatsDaily
extends mongoose.Document,
IStatsBase,
IPageSpeedMetrics {
cpu?: IInfraCpu;
memory?: IInfraMemory;
disk?: IInfraDiskEntry[];
host?: IInfraHost;
net?: IInfraNetEntry[];
}
const InfraCpuSchema = new Schema<IInfraCpu>(
{
physical_core: { type: Number },
logical_core: { type: Number },
frequency: { type: Number },
current_frequency: { type: Number },
temperature: { type: [Number], default: undefined },
free_percent: { type: Number },
used_percent: { type: Number },
},
{ _id: false }
);
const InfraMemorySchema = new Schema<IInfraMemory>(
{
total_bytes: { type: Number },
available_bytes: { type: Number },
used_bytes: { type: Number },
usage_percent: { type: Number },
},
{ _id: false }
);
const InfraDiskEntrySchema = new Schema<IInfraDiskEntry>(
{
device: { type: String },
total_bytes: { type: Number },
free_bytes: { type: Number },
used_bytes: { type: Number },
usage_percent: { type: Number },
total_inodes: { type: Number },
free_inodes: { type: Number },
used_inodes: { type: Number },
inodes_usage_percent: { type: Number },
read_bytes: { type: Number },
write_bytes: { type: Number },
read_time: { type: Number },
write_time: { type: Number },
},
{ _id: false }
);
const InfraHostSchema = new Schema<IInfraHost>(
{
os: { type: String },
platform: { type: String },
kernel_version: { type: String },
pretty_name: { type: String },
},
{ _id: false }
);
const InfraNetEntrySchema = new Schema<IInfraNetEntry>(
{
name: { type: String },
bytes_sent: { type: Number },
bytes_recv: { type: Number },
packets_sent: { type: Number },
packets_recv: { type: Number },
err_in: { type: Number },
err_out: { type: Number },
drop_in: { type: Number },
drop_out: { type: Number },
fifo_in: { type: Number },
fifo_out: { type: Number },
},
{ _id: false }
);
const StatsDailySchema = new Schema<IStatsDaily>(
{
monitorId: { type: Schema.Types.ObjectId, ref: "Monitor", index: true, required: true },
teamId: { type: Schema.Types.ObjectId, ref: "Team", index: true, required: true },
type: { type: String, required: true },
windowStart: { type: Date, required: true, index: true },
windowEnd: { type: Date, required: true },
finalized: { type: Boolean, default: false },
count: { type: Number, required: true, default: 0 },
avgResponseTime: { type: Number, required: true, default: 0 },
// Pagespeed
accessibility: { type: Number },
bestPractices: { type: Number },
seo: { type: Number },
performance: { type: Number },
cls: { type: Number },
si: { type: Number },
fcp: { type: Number },
lcp: { type: Number },
tbt: { type: Number },
// Infrastructure
cpu: { type: InfraCpuSchema, required: false },
memory: { type: InfraMemorySchema, required: false },
disk: { type: [InfraDiskEntrySchema], required: false, default: undefined },
host: { type: InfraHostSchema, required: false },
net: { type: [InfraNetEntrySchema], required: false, default: undefined },
},
{ timestamps: true, collection: "stats_daily" }
);
// One document per (monitorId, windowStart)
StatsDailySchema.index({ monitorId: 1, windowStart: 1 }, { unique: true });
export const StatsDaily = mongoose.model<IStatsDaily>(
"StatsDaily",
StatsDailySchema
);
@@ -0,0 +1,202 @@
import mongoose, { Schema, Types } from "mongoose";
import type { MonitorType } from "@/db/models/monitors/Monitor.js";
// Common/base metrics across monitor types
export interface IStatsBase {
monitorId: Types.ObjectId;
teamId: Types.ObjectId;
type: MonitorType;
windowStart: Date; // inclusive
windowEnd: Date; // exclusive
finalized: boolean;
count: number; // number of checks in window
avgResponseTime: number; // ms
createdAt: Date;
updatedAt: Date;
}
// Pagespeed rollups (averages per window)
export interface IPageSpeedMetrics {
accessibility?: number;
bestPractices?: number;
seo?: number;
performance?: number;
cls?: number;
si?: number;
fcp?: number;
lcp?: number;
tbt?: number;
}
// Infrastructure rollups
export interface IInfraCpu {
physical_core?: number;
logical_core?: number;
frequency?: number;
current_frequency?: number;
temperature?: number[]; // averaged per sensor index
free_percent?: number;
used_percent?: number;
}
export interface IInfraMemory {
total_bytes?: number;
available_bytes?: number;
used_bytes?: number;
usage_percent?: number;
}
export interface IInfraDiskEntry {
device?: string;
total_bytes?: number;
free_bytes?: number;
used_bytes?: number;
usage_percent?: number;
total_inodes?: number;
free_inodes?: number;
used_inodes?: number;
inodes_usage_percent?: number;
read_bytes?: number;
write_bytes?: number;
read_time?: number;
write_time?: number;
}
export interface IInfraHost {
os?: string;
platform?: string;
kernel_version?: string;
pretty_name?: string;
}
export interface IInfraNetEntry {
name?: string;
bytes_sent?: number;
bytes_recv?: number;
packets_sent?: number;
packets_recv?: number;
err_in?: number;
err_out?: number;
drop_in?: number;
drop_out?: number;
fifo_in?: number;
fifo_out?: number;
}
export interface IStatsHourly extends mongoose.Document, IStatsBase, IPageSpeedMetrics {
cpu?: IInfraCpu;
memory?: IInfraMemory;
disk?: IInfraDiskEntry[];
host?: IInfraHost;
net?: IInfraNetEntry[];
}
const InfraCpuSchema = new Schema<IInfraCpu>(
{
physical_core: { type: Number },
logical_core: { type: Number },
frequency: { type: Number },
current_frequency: { type: Number },
temperature: { type: [Number], default: undefined },
free_percent: { type: Number },
used_percent: { type: Number },
},
{ _id: false }
);
const InfraMemorySchema = new Schema<IInfraMemory>(
{
total_bytes: { type: Number },
available_bytes: { type: Number },
used_bytes: { type: Number },
usage_percent: { type: Number },
},
{ _id: false }
);
const InfraDiskEntrySchema = new Schema<IInfraDiskEntry>(
{
device: { type: String },
total_bytes: { type: Number },
free_bytes: { type: Number },
used_bytes: { type: Number },
usage_percent: { type: Number },
total_inodes: { type: Number },
free_inodes: { type: Number },
used_inodes: { type: Number },
inodes_usage_percent: { type: Number },
read_bytes: { type: Number },
write_bytes: { type: Number },
read_time: { type: Number },
write_time: { type: Number },
},
{ _id: false }
);
const InfraHostSchema = new Schema<IInfraHost>(
{
os: { type: String },
platform: { type: String },
kernel_version: { type: String },
pretty_name: { type: String },
},
{ _id: false }
);
const InfraNetEntrySchema = new Schema<IInfraNetEntry>(
{
name: { type: String },
bytes_sent: { type: Number },
bytes_recv: { type: Number },
packets_sent: { type: Number },
packets_recv: { type: Number },
err_in: { type: Number },
err_out: { type: Number },
drop_in: { type: Number },
drop_out: { type: Number },
fifo_in: { type: Number },
fifo_out: { type: Number },
},
{ _id: false }
);
const StatsHourlySchema = new Schema<IStatsHourly>(
{
monitorId: { type: Schema.Types.ObjectId, ref: "Monitor", index: true, required: true },
teamId: { type: Schema.Types.ObjectId, ref: "Team", index: true, required: true },
type: { type: String, required: true },
windowStart: { type: Date, required: true, index: true },
windowEnd: { type: Date, required: true },
finalized: { type: Boolean, default: false },
count: { type: Number, required: true, default: 0 },
avgResponseTime: { type: Number, required: true, default: 0 },
// Pagespeed
accessibility: { type: Number },
bestPractices: { type: Number },
seo: { type: Number },
performance: { type: Number },
cls: { type: Number },
si: { type: Number },
fcp: { type: Number },
lcp: { type: Number },
tbt: { type: Number },
// Infrastructure
cpu: { type: InfraCpuSchema, required: false },
memory: { type: InfraMemorySchema, required: false },
disk: { type: [InfraDiskEntrySchema], required: false, default: undefined },
host: { type: InfraHostSchema, required: false },
net: { type: [InfraNetEntrySchema], required: false, default: undefined },
},
{ timestamps: true, collection: "stats_hourly" }
);
// One document per (monitorId, windowStart)
StatsHourlySchema.index({ monitorId: 1, windowStart: 1 }, { unique: true });
export const StatsHourly = mongoose.model<IStatsHourly>(
"StatsHourly",
StatsHourlySchema
);