145 lines
3.6 KiB
Go
145 lines
3.6 KiB
Go
package services
|
|
|
|
import (
|
|
"cmbone/internal/models"
|
|
"database/sql"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
defaultTrendDays = 7
|
|
maxTrendDays = 30
|
|
imageTaskStatusFailed = "failed"
|
|
)
|
|
|
|
type MetricsService struct {
|
|
store *SuiStore
|
|
}
|
|
|
|
func NewMetricsService(store *SuiStore) *MetricsService {
|
|
return &MetricsService{store: store}
|
|
}
|
|
|
|
func (s *MetricsService) GetDashboardMetrics() (models.DashboardMetrics, error) {
|
|
start := localDayStart(time.Now())
|
|
end := start.AddDate(0, 0, 1)
|
|
|
|
taskMetric, err := s.queryTaskMetric(start, end)
|
|
if err != nil {
|
|
return models.DashboardMetrics{}, err
|
|
}
|
|
|
|
auditLogs, err := s.countAuditLogs(start, end)
|
|
if err != nil {
|
|
return models.DashboardMetrics{}, err
|
|
}
|
|
|
|
return models.DashboardMetrics{
|
|
TodayProcessedImages: taskMetric.ProcessedImages,
|
|
SuccessRate: successRate(taskMetric.SucceededTasks, taskMetric.ProcessedImages),
|
|
AverageDurationMs: taskMetric.AverageDuration,
|
|
FailedTasks: taskMetric.FailedTasks,
|
|
TodayAuditLogs: auditLogs,
|
|
}, nil
|
|
}
|
|
|
|
func (s *MetricsService) GetProcessingTrend(days int) ([]models.DailyMetric, error) {
|
|
days = normalizeTrendDays(days)
|
|
today := localDayStart(time.Now())
|
|
start := today.AddDate(0, 0, -days+1)
|
|
|
|
trend := make([]models.DailyMetric, 0, days)
|
|
for i := 0; i < days; i++ {
|
|
dayStart := start.AddDate(0, 0, i)
|
|
dayEnd := dayStart.AddDate(0, 0, 1)
|
|
|
|
taskMetric, err := s.queryTaskMetric(dayStart, dayEnd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
auditLogs, err := s.countAuditLogs(dayStart, dayEnd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
trend = append(trend, models.DailyMetric{
|
|
Date: dayStart.Format("2006-01-02"),
|
|
ProcessedImages: taskMetric.ProcessedImages,
|
|
SucceededTasks: taskMetric.SucceededTasks,
|
|
FailedTasks: taskMetric.FailedTasks,
|
|
AverageDurationMs: taskMetric.AverageDuration,
|
|
AuditLogs: auditLogs,
|
|
})
|
|
}
|
|
return trend, nil
|
|
}
|
|
|
|
type taskMetric struct {
|
|
ProcessedImages int64
|
|
SucceededTasks int64
|
|
FailedTasks int64
|
|
AverageDuration float64
|
|
}
|
|
|
|
func (s *MetricsService) queryTaskMetric(start time.Time, end time.Time) (taskMetric, error) {
|
|
var metric taskMetric
|
|
var average sql.NullFloat64
|
|
err := s.store.DB.QueryRow(`
|
|
SELECT
|
|
COUNT(*),
|
|
COALESCE(SUM(CASE WHEN status = ? THEN 1 ELSE 0 END), 0),
|
|
COALESCE(SUM(CASE WHEN status = ? THEN 1 ELSE 0 END), 0),
|
|
AVG(CASE WHEN duration_ms > 0 THEN duration_ms END)
|
|
FROM image_tasks
|
|
WHERE created_at >= ? AND created_at < ?
|
|
`, imageTaskStatusSucceeded, imageTaskStatusFailed, formatMetricTime(start), formatMetricTime(end)).Scan(
|
|
&metric.ProcessedImages,
|
|
&metric.SucceededTasks,
|
|
&metric.FailedTasks,
|
|
&average,
|
|
)
|
|
if err != nil {
|
|
return taskMetric{}, err
|
|
}
|
|
if average.Valid {
|
|
metric.AverageDuration = average.Float64
|
|
}
|
|
return metric, nil
|
|
}
|
|
|
|
func (s *MetricsService) countAuditLogs(start time.Time, end time.Time) (int64, error) {
|
|
var count int64
|
|
err := s.store.DB.QueryRow(`
|
|
SELECT COUNT(*)
|
|
FROM audit_logs
|
|
WHERE created_at >= ? AND created_at < ?
|
|
`, formatMetricTime(start), formatMetricTime(end)).Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
func normalizeTrendDays(days int) int {
|
|
if days <= 0 {
|
|
return defaultTrendDays
|
|
}
|
|
if days > maxTrendDays {
|
|
return maxTrendDays
|
|
}
|
|
return days
|
|
}
|
|
|
|
func localDayStart(now time.Time) time.Time {
|
|
local := now.In(time.Local)
|
|
return time.Date(local.Year(), local.Month(), local.Day(), 0, 0, 0, 0, local.Location())
|
|
}
|
|
|
|
func formatMetricTime(value time.Time) string {
|
|
return value.UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
func successRate(succeeded int64, total int64) float64 {
|
|
if total <= 0 {
|
|
return 0
|
|
}
|
|
return float64(succeeded) * 100 / float64(total)
|
|
}
|