package metrics import ( "github.com/prism/proxy/internal/mihomoapi" "github.com/prism/proxy/internal/store" ) type TrafficItem struct { Key string `json:"key"` Label string `json:"label"` Node string `json:"node,omitempty"` NodeKey string `json:"node_key,omitempty"` Upload int64 `json:"upload"` Download int64 `json:"download"` Total int64 `json:"total"` Connections int `json:"connections"` } func AggregateConnections(conns []map[string]any, policyGroups map[string]bool) (domains, nodes []TrafficItem) { domainMap := map[string]*TrafficItem{} nodeMap := map[string]*TrafficItem{} for _, conn := range conns { host := connHost(conn) nodeKey := connLeafNode(conn, policyGroups) up := toInt64(conn["upload"]) down := toInt64(conn["download"]) if host != "" { key := host + "\x00" + nodeKey acc(domainMap, key, up, down) item := domainMap[key] item.Label = host item.NodeKey = nodeKey } if nodeKey != "" { acc(nodeMap, nodeKey, up, down) } } return sortTrafficItems(domainMap), sortTrafficItems(nodeMap) } func acc(m map[string]*TrafficItem, key string, up, down int64) { item, ok := m[key] if !ok { item = &TrafficItem{Key: key, Label: key} m[key] = item } item.Upload += up item.Download += down item.Total += up + down item.Connections++ } func sortTrafficItems(m map[string]*TrafficItem) []TrafficItem { out := make([]TrafficItem, 0, len(m)) for _, v := range m { out = append(out, *v) } for i := 0; i < len(out); i++ { for j := i + 1; j < len(out); j++ { if out[j].Total > out[i].Total { out[i], out[j] = out[j], out[i] } } } return out } func connHost(m map[string]any) string { meta, _ := m["metadata"].(map[string]any) if meta == nil { return "" } if h, ok := meta["host"].(string); ok && h != "" { return h } if h, ok := meta["sniffHost"].(string); ok && h != "" { return h } if h, ok := meta["remoteDestination"].(string); ok && h != "" { return h } if ip, ok := meta["destinationIP"].(string); ok && ip != "" { port, _ := meta["destinationPort"].(string) if port != "" { return ip + ":" + port } return ip } return "" } func connLeafNode(m map[string]any, policyGroups map[string]bool) string { return mihomoapi.ResolveLeafOutbound(mihomoapi.ChainStringsFromAny(m["chains"]), policyGroups) } func toInt64(v any) int64 { switch n := v.(type) { case float64: return int64(n) case int64: return n case int: return int64(n) default: return 0 } } func ApplyTrafficLabels(items []TrafficItem, names map[string]string) []TrafficItem { out := make([]TrafficItem, len(items)) for i, item := range items { out[i] = item if label, ok := names[item.Key]; ok && label != "" { out[i].Label = label } } return out } func ApplyDomainLabels(items []TrafficItem, names map[string]string) []TrafficItem { out := make([]TrafficItem, len(items)) for i, item := range items { out[i] = item if label, ok := names[item.NodeKey]; ok && label != "" { out[i].Node = label } else if item.NodeKey != "" { out[i].Node = item.NodeKey } } return out } func TrafficAggToItems(aggs []store.TrafficAgg, names map[string]string) []TrafficItem { out := make([]TrafficItem, 0, len(aggs)) for _, a := range aggs { label := a.Key if n, ok := names[a.Key]; ok && n != "" { label = n } out = append(out, TrafficItem{ Key: a.Key, Label: label, Upload: a.Upload, Download: a.Download, Total: a.Upload + a.Download, Connections: a.Connections, }) } return out } func TrafficHostNodeAggToItems(aggs []store.TrafficHostNodeAgg, names map[string]string) []TrafficItem { out := make([]TrafficItem, 0, len(aggs)) for _, a := range aggs { nodeLabel := a.NodeKey if n, ok := names[a.NodeKey]; ok && n != "" { nodeLabel = n } out = append(out, TrafficItem{ Key: a.Host + "\x00" + a.NodeKey, Label: a.Host, Node: nodeLabel, NodeKey: a.NodeKey, Upload: a.Upload, Download: a.Download, Total: a.Upload + a.Download, Connections: a.Connections, }) } return out }