@@ -35,6 +35,8 @@ type MonitoringUIConfig struct {
3535 PrometheusPath string `toml:"prometheus_path"` // Path for Prometheus metrics endpoint (default: /metrics)
3636}
3737
38+ const maxTopDomains = 1000
39+
3840// MetricsCollector - Collects and stores metrics for the monitoring UI
3941type MetricsCollector struct {
4042 // Split locks for better concurrency
@@ -361,6 +363,9 @@ func (ui *MonitoringUI) UpdateMetrics(pluginsState PluginsState, msg *dns.Msg) {
361363 // Store domain name directly - no sanitization needed for internal metrics
362364 domainName := pluginsState .qName
363365 mc .domainMutex .Lock ()
366+ if _ , found := mc .topDomains [domainName ]; ! found && len (mc .topDomains ) >= maxTopDomains {
367+ mc .pruneTopDomainsLocked ()
368+ }
364369 mc .topDomains [domainName ]++
365370 mc .domainMutex .Unlock ()
366371 }
@@ -449,6 +454,26 @@ func (ui *MonitoringUI) UpdateMetrics(pluginsState PluginsState, msg *dns.Msg) {
449454 ui .scheduleBroadcast ()
450455}
451456
457+ func (mc * MetricsCollector ) pruneTopDomainsLocked () {
458+ type domainCount struct {
459+ domain string
460+ count uint64
461+ }
462+ counts := make ([]domainCount , 0 , len (mc .topDomains ))
463+ for domain , hits := range mc .topDomains {
464+ counts = append (counts , domainCount {domain , hits })
465+ }
466+ sort .Slice (counts , func (i , j int ) bool {
467+ if counts [i ].count != counts [j ].count {
468+ return counts [i ].count > counts [j ].count
469+ }
470+ return counts [i ].domain < counts [j ].domain
471+ })
472+ for _ , dc := range counts [maxTopDomains / 2 :] {
473+ delete (mc .topDomains , dc .domain )
474+ }
475+ }
476+
452477// generatePrometheusMetrics - Generates Prometheus-formatted metrics
453478func (mc * MetricsCollector ) generatePrometheusMetrics () string {
454479 if ! mc .prometheusEnabled {
0 commit comments