188 lines
5.5 KiB
JavaScript
188 lines
5.5 KiB
JavaScript
import { z } from "zod";
|
|
import prisma from "~/server/utils/prisma";
|
|
|
|
// Query parameter validation schema
|
|
const querySchema = z.object({
|
|
period: z.enum(["hour", "day", "week", "month"]).default("day"),
|
|
metric: z.enum(["throughput", "error_rate", "response_time"]).default("throughput"),
|
|
});
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
// Parse and validate query parameters
|
|
const query = getQuery(event);
|
|
const { period, metric } = querySchema.parse(query);
|
|
|
|
// Set time range based on period
|
|
const now = new Date();
|
|
let startDate;
|
|
let intervalMinutes;
|
|
let numPoints;
|
|
|
|
switch (period) {
|
|
case "hour":
|
|
startDate = new Date(now.getTime() - 60 * 60 * 1000); // 1 hour ago
|
|
intervalMinutes = 1; // 1-minute intervals
|
|
numPoints = 60;
|
|
break;
|
|
case "day":
|
|
startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 24 hours ago
|
|
intervalMinutes = 60; // 1-hour intervals
|
|
numPoints = 24;
|
|
break;
|
|
case "week":
|
|
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 7 days ago
|
|
intervalMinutes = 24 * 60; // 1-day intervals
|
|
numPoints = 7;
|
|
break;
|
|
case "month":
|
|
startDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago
|
|
intervalMinutes = 24 * 60; // 1-day intervals
|
|
numPoints = 30;
|
|
break;
|
|
default:
|
|
startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
intervalMinutes = 60;
|
|
numPoints = 24;
|
|
}
|
|
|
|
const dataPoints = [];
|
|
|
|
// Generate time buckets
|
|
for (let i = 0; i < numPoints; i++) {
|
|
const bucketStart = new Date(startDate.getTime() + i * intervalMinutes * 60 * 1000);
|
|
const bucketEnd = new Date(bucketStart.getTime() + intervalMinutes * 60 * 1000);
|
|
|
|
let value = 0;
|
|
|
|
switch (metric) {
|
|
case "throughput": {
|
|
// Count completed jobs in this time bucket
|
|
const count = await prisma.notification_queue.count({
|
|
where: {
|
|
status: "completed",
|
|
updated_at: {
|
|
gte: bucketStart,
|
|
lt: bucketEnd,
|
|
},
|
|
},
|
|
});
|
|
value = count;
|
|
break;
|
|
}
|
|
|
|
case "error_rate": {
|
|
// Calculate error rate in this time bucket
|
|
const [totalJobs, failedJobs] = await Promise.all([
|
|
prisma.notification_queue.count({
|
|
where: {
|
|
OR: [{ status: "completed" }, { status: "failed" }],
|
|
updated_at: {
|
|
gte: bucketStart,
|
|
lt: bucketEnd,
|
|
},
|
|
},
|
|
}),
|
|
prisma.notification_queue.count({
|
|
where: {
|
|
status: "failed",
|
|
updated_at: {
|
|
gte: bucketStart,
|
|
lt: bucketEnd,
|
|
},
|
|
},
|
|
}),
|
|
]);
|
|
value = totalJobs > 0 ? Math.round((failedJobs / totalJobs) * 100) : 0;
|
|
break;
|
|
}
|
|
|
|
case "response_time": {
|
|
// Calculate average response time in this time bucket
|
|
const jobs = await prisma.notification_queue.findMany({
|
|
where: {
|
|
status: "completed",
|
|
updated_at: {
|
|
gte: bucketStart,
|
|
lt: bucketEnd,
|
|
},
|
|
last_attempt_at: { not: null },
|
|
},
|
|
select: {
|
|
created_at: true,
|
|
last_attempt_at: true,
|
|
},
|
|
take: 50, // Sample size
|
|
});
|
|
|
|
if (jobs.length > 0) {
|
|
const responseTimes = jobs.map(job => {
|
|
const diff = new Date(job.last_attempt_at).getTime() - new Date(job.created_at).getTime();
|
|
return Math.abs(diff);
|
|
});
|
|
value = Math.round(responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length);
|
|
} else {
|
|
value = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
dataPoints.push({
|
|
timestamp: bucketStart.toISOString(),
|
|
value,
|
|
});
|
|
}
|
|
|
|
// Calculate summary statistics
|
|
const values = dataPoints.map(p => p.value);
|
|
const min = Math.min(...values);
|
|
const max = Math.max(...values);
|
|
const avg = Math.round(values.reduce((sum, val) => sum + val, 0) / values.length);
|
|
|
|
// Calculate trend (compare first half vs second half)
|
|
const midpoint = Math.floor(values.length / 2);
|
|
const firstHalfAvg = values.slice(0, midpoint).reduce((sum, val) => sum + val, 0) / midpoint;
|
|
const secondHalfAvg = values.slice(midpoint).reduce((sum, val) => sum + val, 0) / (values.length - midpoint);
|
|
const trend = secondHalfAvg > firstHalfAvg * 1.1 ? "increasing"
|
|
: secondHalfAvg < firstHalfAvg * 0.9 ? "decreasing"
|
|
: "stable";
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
metric,
|
|
period,
|
|
dataPoints,
|
|
summary: {
|
|
min,
|
|
max,
|
|
avg,
|
|
trend,
|
|
},
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error("Error fetching queue history:", error);
|
|
|
|
if (error.name === "ZodError") {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: "Invalid query parameters",
|
|
data: {
|
|
errors: error.errors,
|
|
},
|
|
});
|
|
}
|
|
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: "Failed to fetch queue history",
|
|
data: {
|
|
error: error.message,
|
|
},
|
|
});
|
|
} finally {
|
|
}
|
|
});
|