mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-25 12:05:06 +02:00
* fix(autopilots): make webhook admission durable Co-authored-by: multica-agent <github@multica.ai> * fix(autopilots): address webhook delivery review Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai>
234 lines
8.2 KiB
Go
234 lines
8.2 KiB
Go
package metrics
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/multica-ai/multica/server/pkg/taskfailure"
|
|
)
|
|
|
|
const (
|
|
labelSource = "source"
|
|
labelRuntimeMode = "runtime_mode"
|
|
labelProvider = "provider"
|
|
labelTerminalStatus = "terminal_status"
|
|
labelFailureReason = "failure_reason"
|
|
labelTokenType = "token_type"
|
|
labelModel = "model"
|
|
labelModelAlias = "model_alias"
|
|
|
|
// PR3 labels (funnel / community / commercial).
|
|
labelSignupSource = "signup_source"
|
|
labelPlatform = "platform"
|
|
labelPath = "path"
|
|
labelCadence = "cadence"
|
|
labelTriggerKind = "trigger_kind"
|
|
labelReason = "reason"
|
|
labelRecoverable = "recoverable"
|
|
labelKind = "kind"
|
|
labelStatus = "status"
|
|
labelEventKind = "event_kind"
|
|
labelAction = "action"
|
|
labelResult = "result"
|
|
labelOp = "op"
|
|
labelGate = "gate"
|
|
)
|
|
|
|
var businessMetricLabels = map[string][]string{
|
|
"multica_agent_task_enqueued_total": {labelSource, labelRuntimeMode},
|
|
"multica_agent_task_dispatched_total": {labelSource, labelRuntimeMode},
|
|
"multica_agent_task_started_total": {labelSource, labelRuntimeMode, labelProvider},
|
|
"multica_agent_task_terminal_total": {labelSource, labelRuntimeMode, labelTerminalStatus},
|
|
"multica_agent_task_failed_total": {labelSource, labelRuntimeMode, labelFailureReason},
|
|
"multica_agent_task_queue_wait_seconds": {labelSource, labelRuntimeMode},
|
|
"multica_agent_task_run_seconds": {labelSource, labelRuntimeMode, labelTerminalStatus},
|
|
"multica_agent_task_total_seconds": {labelSource, labelRuntimeMode, labelTerminalStatus},
|
|
"multica_agent_task_in_progress": {labelSource, labelRuntimeMode},
|
|
"multica_agent_task_iteration_count": {labelSource, labelTerminalStatus},
|
|
"multica_llm_tokens_total": {labelProvider, labelModel, labelTokenType, labelRuntimeMode, labelSource},
|
|
"multica_llm_cost_usd_total": {labelProvider, labelModel, labelTokenType, labelRuntimeMode, labelSource},
|
|
"multica_llm_unpriced_tokens_total": {labelProvider, labelModelAlias, labelTokenType},
|
|
"multica_llm_request_total": {labelProvider, labelModel, labelRuntimeMode},
|
|
"multica_task_queued_expired_total": {labelSource, labelRuntimeMode},
|
|
"multica_task_lease_expired_total": {labelSource},
|
|
|
|
// PR3 funnel / community / commercial.
|
|
"multica_signup_total": {labelSignupSource},
|
|
"multica_workspace_created_total": {labelSource},
|
|
"multica_team_invite_sent_total": {},
|
|
"multica_team_invite_accepted_total": {},
|
|
"multica_onboarding_started_total": {labelPlatform},
|
|
"multica_onboarding_questionnaire_submitted_total": {},
|
|
"multica_onboarding_completed_total": {labelPath},
|
|
"multica_cloud_waitlist_joined_total": {},
|
|
"multica_issue_created_total": {labelSource, labelPlatform},
|
|
"multica_chat_message_sent_total": {labelPlatform},
|
|
"multica_agent_created_total": {labelRuntimeMode, labelSource},
|
|
"multica_squad_created_total": {},
|
|
"multica_autopilot_created_total": {labelCadence},
|
|
"multica_issue_executed_total": {labelSource},
|
|
"multica_runtime_registered_total": {labelRuntimeMode, labelProvider},
|
|
"multica_runtime_ready_total": {labelRuntimeMode, labelProvider},
|
|
"multica_runtime_ready_seconds": {labelRuntimeMode, labelProvider},
|
|
"multica_runtime_failed_total": {labelRuntimeMode, labelProvider, labelFailureReason, labelRecoverable},
|
|
"multica_runtime_offline_total": {labelRuntimeMode, labelProvider},
|
|
"multica_daemon_ws_message_received_total": {labelKind},
|
|
"multica_autopilot_run_started_total": {labelCadence, labelTriggerKind},
|
|
"multica_autopilot_run_terminal_total": {labelCadence, labelTriggerKind, labelTerminalStatus},
|
|
"multica_autopilot_run_skipped_total": {labelCadence, labelReason},
|
|
"multica_webhook_delivery_total": {labelProvider, labelStatus},
|
|
"multica_webhook_rate_limited_total": {labelGate},
|
|
"multica_github_event_received_total": {labelEventKind, labelAction},
|
|
"multica_github_pr_review_total": {labelResult},
|
|
"multica_cloudruntime_request_total": {labelOp, labelStatus},
|
|
"multica_cloudruntime_request_duration_seconds": {labelOp},
|
|
"multica_feedback_submitted_total": {labelKind, labelPlatform},
|
|
"multica_contact_sales_submitted_total": {labelSource},
|
|
}
|
|
|
|
var forbiddenMetricLabels = map[string]struct{}{
|
|
"workspace_id": {},
|
|
"user_id": {},
|
|
"agent_id": {},
|
|
"task_id": {},
|
|
"issue_id": {},
|
|
"runtime_id": {},
|
|
"session_id": {},
|
|
"ip": {},
|
|
}
|
|
|
|
var (
|
|
knownSources = map[string]string{
|
|
"issue": "issue",
|
|
"chat": "chat",
|
|
"autopilot": "autopilot",
|
|
"autopilot_issue": "autopilot_issue",
|
|
"quick_create": "quick_create",
|
|
"manual": "manual",
|
|
"api": "api",
|
|
"other": "other",
|
|
}
|
|
knownRuntimeModes = map[string]string{
|
|
"local": "local",
|
|
"cloud": "cloud",
|
|
"unknown": "unknown",
|
|
}
|
|
knownRuntimeProviders = map[string]string{
|
|
"antigravity": "antigravity",
|
|
"claude": "claude",
|
|
"codebuddy": "codebuddy",
|
|
"codex": "codex",
|
|
"copilot": "copilot",
|
|
"cursor": "cursor",
|
|
"gemini": "gemini",
|
|
"hermes": "hermes",
|
|
"kiro": "kiro",
|
|
"kimi": "kimi",
|
|
"multica_agent": "multica_agent",
|
|
"openclaw": "openclaw",
|
|
"opencode": "opencode",
|
|
"deveco": "deveco",
|
|
"pi": "pi",
|
|
"other": "other",
|
|
}
|
|
knownTerminalStatuses = map[string]string{
|
|
"completed": "completed",
|
|
"failed": "failed",
|
|
"cancelled": "cancelled",
|
|
"blocked": "blocked",
|
|
"other": "other",
|
|
}
|
|
knownTokenTypes = map[string]string{
|
|
"input": "input",
|
|
"output": "output",
|
|
"cache_read": "cache_read",
|
|
"cache_write": "cache_write",
|
|
}
|
|
knownFailureReasons = map[string]string{}
|
|
modelAliasUnsafeRe = regexp.MustCompile(`[^a-z0-9._:/+-]+`)
|
|
)
|
|
|
|
func init() {
|
|
for _, reason := range taskfailure.AllReasons() {
|
|
knownFailureReasons[reason.String()] = reason.String()
|
|
}
|
|
}
|
|
|
|
func validateBusinessMetricLabels() {
|
|
for metric, labels := range businessMetricLabels {
|
|
for _, label := range labels {
|
|
if _, forbidden := forbiddenMetricLabels[label]; forbidden {
|
|
panic("forbidden high-cardinality label " + label + " on " + metric)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func metricLabels(metric string) []string {
|
|
labels, ok := businessMetricLabels[metric]
|
|
if !ok {
|
|
panic("missing business metric label definition for " + metric)
|
|
}
|
|
return labels
|
|
}
|
|
|
|
func NormalizeTaskSource(value string) string {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
if normalized, ok := knownSources[value]; ok {
|
|
return normalized
|
|
}
|
|
return "other"
|
|
}
|
|
|
|
func NormalizeRuntimeMode(value string) string {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
if normalized, ok := knownRuntimeModes[value]; ok {
|
|
return normalized
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
func NormalizeRuntimeProvider(value string) string {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
if normalized, ok := knownRuntimeProviders[value]; ok {
|
|
return normalized
|
|
}
|
|
return "other"
|
|
}
|
|
|
|
func NormalizeTerminalStatus(value string) string {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
if normalized, ok := knownTerminalStatuses[value]; ok {
|
|
return normalized
|
|
}
|
|
return "other"
|
|
}
|
|
|
|
func NormalizeFailureReason(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if normalized, ok := knownFailureReasons[value]; ok {
|
|
return normalized
|
|
}
|
|
return taskfailure.Classify(value).String()
|
|
}
|
|
|
|
func NormalizeTokenType(value string) string {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
if normalized, ok := knownTokenTypes[value]; ok {
|
|
return normalized
|
|
}
|
|
return "input"
|
|
}
|
|
|
|
func NormalizeModelAlias(value string) string {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
if value == "" {
|
|
return "unknown"
|
|
}
|
|
value = modelAliasUnsafeRe.ReplaceAllString(value, "_")
|
|
if len(value) > 128 {
|
|
return value[:128]
|
|
}
|
|
return value
|
|
}
|