206 lines
7.2 KiB
Go
206 lines
7.2 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/geo-platform/tenant-api/internal/shared/response"
|
|
)
|
|
|
|
type MonitoringDailyTaskMetricsSnapshot struct {
|
|
RunTotal int64 `json:"run_total"`
|
|
RunCompletedTotal int64 `json:"run_completed_total"`
|
|
RunFailedTotal int64 `json:"run_failed_total"`
|
|
RunPartialFailureTotal int64 `json:"run_partial_failure_total"`
|
|
PlanFailureTotal int64 `json:"plan_failure_total"`
|
|
BrandFailureTotal int64 `json:"brand_failure_total"`
|
|
SkippedPlanTotal int64 `json:"skipped_plan_total"`
|
|
PlannedTaskTotal int64 `json:"planned_task_total"`
|
|
EligibleTaskTotal int64 `json:"eligible_task_total"`
|
|
DueTaskTotal int64 `json:"due_task_total"`
|
|
CreatedTaskTotal int64 `json:"created_task_total"`
|
|
DesktopTaskTotal int64 `json:"desktop_task_total"`
|
|
DeferredTaskTotal int64 `json:"deferred_task_total"`
|
|
LastRunAt *time.Time `json:"last_run_at,omitempty"`
|
|
LastSuccessAt *time.Time `json:"last_success_at,omitempty"`
|
|
LastErrorAt *time.Time `json:"last_error_at,omitempty"`
|
|
LastDurationSeconds float64 `json:"last_duration_seconds"`
|
|
LastBusinessDate string `json:"last_business_date,omitempty"`
|
|
LastTenantCount int64 `json:"last_tenant_count"`
|
|
LastBrandCount int64 `json:"last_brand_count"`
|
|
LastErrorCode string `json:"last_error_code,omitempty"`
|
|
LastErrorMessage string `json:"last_error_message,omitempty"`
|
|
}
|
|
|
|
type monitoringDailyTaskMetricsRecorder struct {
|
|
runTotal atomic.Int64
|
|
runCompletedTotal atomic.Int64
|
|
runFailedTotal atomic.Int64
|
|
runPartialFailureTotal atomic.Int64
|
|
planFailureTotal atomic.Int64
|
|
brandFailureTotal atomic.Int64
|
|
skippedPlanTotal atomic.Int64
|
|
plannedTaskTotal atomic.Int64
|
|
eligibleTaskTotal atomic.Int64
|
|
dueTaskTotal atomic.Int64
|
|
createdTaskTotal atomic.Int64
|
|
desktopTaskTotal atomic.Int64
|
|
deferredTaskTotal atomic.Int64
|
|
lastRunAtUnixNano atomic.Int64
|
|
lastSuccessAtUnixNano atomic.Int64
|
|
lastErrorAtUnixNano atomic.Int64
|
|
lastDurationNanos atomic.Int64
|
|
lastTenantCount atomic.Int64
|
|
lastBrandCount atomic.Int64
|
|
lastBusinessDate atomic.Value
|
|
lastErrorCode atomic.Value
|
|
lastErrorMessage atomic.Value
|
|
}
|
|
|
|
var monitoringDailyTaskMetrics = newMonitoringDailyTaskMetricsRecorder()
|
|
|
|
func newMonitoringDailyTaskMetricsRecorder() *monitoringDailyTaskMetricsRecorder {
|
|
return &monitoringDailyTaskMetricsRecorder{}
|
|
}
|
|
|
|
func recordMonitoringDailyTaskMetrics(summary *MonitoringDailyTaskGenerationSummary, duration time.Duration, err error) {
|
|
monitoringDailyTaskMetrics.record(summary, duration, err)
|
|
}
|
|
|
|
func MonitoringDailyTaskMetricsSnapshotValue() MonitoringDailyTaskMetricsSnapshot {
|
|
return monitoringDailyTaskMetrics.snapshot()
|
|
}
|
|
|
|
func resetMonitoringDailyTaskMetricsForTest() {
|
|
monitoringDailyTaskMetrics = newMonitoringDailyTaskMetricsRecorder()
|
|
}
|
|
|
|
func (r *monitoringDailyTaskMetricsRecorder) record(summary *MonitoringDailyTaskGenerationSummary, duration time.Duration, err error) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
now := time.Now().UTC()
|
|
if duration < 0 {
|
|
duration = 0
|
|
}
|
|
|
|
r.runTotal.Add(1)
|
|
r.lastRunAtUnixNano.Store(now.UnixNano())
|
|
r.lastDurationNanos.Store(duration.Nanoseconds())
|
|
|
|
if summary != nil {
|
|
r.lastBusinessDate.Store(summary.BusinessDate)
|
|
r.lastTenantCount.Store(int64(summary.TenantCount))
|
|
r.lastBrandCount.Store(int64(summary.BrandCount))
|
|
r.planFailureTotal.Add(int64(summary.FailedPlanCount))
|
|
r.brandFailureTotal.Add(int64(summary.FailedBrandCount))
|
|
r.skippedPlanTotal.Add(int64(summary.SkippedPlanCount))
|
|
r.plannedTaskTotal.Add(summary.PlannedTaskCount)
|
|
r.eligibleTaskTotal.Add(summary.EligibleTaskCount)
|
|
r.dueTaskTotal.Add(summary.DueTaskCount)
|
|
r.createdTaskTotal.Add(summary.CreatedTaskCount)
|
|
r.desktopTaskTotal.Add(summary.DesktopTaskCount)
|
|
r.deferredTaskTotal.Add(summary.DeferredTaskCount)
|
|
if summary.FailedPlanCount > 0 || summary.FailedBrandCount > 0 {
|
|
r.runPartialFailureTotal.Add(1)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
code, message := monitoringDailyMetricError(err)
|
|
r.runFailedTotal.Add(1)
|
|
r.lastErrorAtUnixNano.Store(now.UnixNano())
|
|
r.lastErrorCode.Store(code)
|
|
r.lastErrorMessage.Store(message)
|
|
return
|
|
}
|
|
|
|
r.runCompletedTotal.Add(1)
|
|
r.lastSuccessAtUnixNano.Store(now.UnixNano())
|
|
}
|
|
|
|
func (r *monitoringDailyTaskMetricsRecorder) snapshot() MonitoringDailyTaskMetricsSnapshot {
|
|
if r == nil {
|
|
return MonitoringDailyTaskMetricsSnapshot{}
|
|
}
|
|
return MonitoringDailyTaskMetricsSnapshot{
|
|
RunTotal: r.runTotal.Load(),
|
|
RunCompletedTotal: r.runCompletedTotal.Load(),
|
|
RunFailedTotal: r.runFailedTotal.Load(),
|
|
RunPartialFailureTotal: r.runPartialFailureTotal.Load(),
|
|
PlanFailureTotal: r.planFailureTotal.Load(),
|
|
BrandFailureTotal: r.brandFailureTotal.Load(),
|
|
SkippedPlanTotal: r.skippedPlanTotal.Load(),
|
|
PlannedTaskTotal: r.plannedTaskTotal.Load(),
|
|
EligibleTaskTotal: r.eligibleTaskTotal.Load(),
|
|
DueTaskTotal: r.dueTaskTotal.Load(),
|
|
CreatedTaskTotal: r.createdTaskTotal.Load(),
|
|
DesktopTaskTotal: r.desktopTaskTotal.Load(),
|
|
DeferredTaskTotal: r.deferredTaskTotal.Load(),
|
|
LastRunAt: monitoringDailyUnixNanoTime(r.lastRunAtUnixNano.Load()),
|
|
LastSuccessAt: monitoringDailyUnixNanoTime(r.lastSuccessAtUnixNano.Load()),
|
|
LastErrorAt: monitoringDailyUnixNanoTime(r.lastErrorAtUnixNano.Load()),
|
|
LastDurationSeconds: float64(r.lastDurationNanos.Load()) / float64(time.Second),
|
|
LastBusinessDate: monitoringDailyLoadString(&r.lastBusinessDate),
|
|
LastTenantCount: r.lastTenantCount.Load(),
|
|
LastBrandCount: r.lastBrandCount.Load(),
|
|
LastErrorCode: monitoringDailyLoadString(&r.lastErrorCode),
|
|
LastErrorMessage: monitoringDailyLoadString(&r.lastErrorMessage),
|
|
}
|
|
}
|
|
|
|
func monitoringDailyMetricError(err error) (string, string) {
|
|
normalized := normalizeMonitoringWorkerError(err)
|
|
if normalized == nil {
|
|
return "", ""
|
|
}
|
|
switch {
|
|
case errors.Is(normalized, context.Canceled):
|
|
return "context_canceled", normalized.Error()
|
|
case errors.Is(normalized, context.DeadlineExceeded):
|
|
return "context_deadline_exceeded", normalized.Error()
|
|
}
|
|
var appErr *response.AppError
|
|
if errors.As(normalized, &appErr) {
|
|
if strings.TrimSpace(appErr.Message) != "" {
|
|
return appErr.Message, appErr.Error()
|
|
}
|
|
return "app_error", appErr.Error()
|
|
}
|
|
code := strings.TrimLeft(fmt.Sprintf("%T", normalized), "*")
|
|
code = strings.ReplaceAll(code, "/", "_")
|
|
code = strings.ReplaceAll(code, ".", "_")
|
|
if code == "" {
|
|
code = "error"
|
|
}
|
|
return code, normalized.Error()
|
|
}
|
|
|
|
func monitoringDailyUnixNanoTime(value int64) *time.Time {
|
|
if value <= 0 {
|
|
return nil
|
|
}
|
|
t := time.Unix(0, value).UTC()
|
|
return &t
|
|
}
|
|
|
|
func monitoringDailyLoadString(value *atomic.Value) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
raw := value.Load()
|
|
result, _ := raw.(string)
|
|
return result
|
|
}
|
|
|
|
func monitoringDailyTimeSeconds(value *time.Time) float64 {
|
|
if value == nil || value.IsZero() {
|
|
return 0
|
|
}
|
|
return float64(value.UnixNano()) / float64(time.Second)
|
|
}
|