Repository navigation
Expand file tree
/
Copy pathcache_ui.go
More file actions
160 lines (138 loc) · 4.2 KB
/
Copy pathcache_ui.go
File metadata and controls
160 lines (138 loc) · 4.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
File: cache_ui.go
Version: 1.1.0 (Split)
Last Updated: 23-Sep-2026 12:35 CEST
Description:
Web UI API structures and introspection routines for the sdproxy Cache.
Extracted from cache.go to isolate JSON string allocations away from
high-performance cache interactions.
Changes:
1.1.0 - [PERF] Eradicated redundant heap allocations natively within DumpCache.
Removed unnecessary deep-copies of immutable cache byte slices, drastically
neutralizing memory constraints during Web UI cache introspection.
1.0.0 - Initial extraction mapping structural introspection arrays.
*/
package main
import (
"fmt"
"strings"
"time"
"github.com/miekg/dns"
)
// CacheEntryCount returns the total number of entries currently held across
// all 32 shards. Acquires each shard's read lock briefly — only called by the
// /api/stats poller (≤ once per 5 s), never in the DNS hot path.
func CacheEntryCount() int {
if !cfg.Cache.Enabled {
return 0
}
n := 0
for _, s := range shards {
s.RLock()
n += len(s.items)
s.RUnlock()
}
return n
}
// ---------------------------------------------------------------------------
// Cache Introspection (Web UI)
// ---------------------------------------------------------------------------
// CacheEntryDump encapsulates a single cache record dynamically extracted
// for representation within the Web UI dashboard.
type CacheEntryDump struct {
QName string `json:"qname"`
QType string `json:"qtype"`
Route string `json:"upstream_group"`
Response string `json:"response"`
Hits uint32 `json:"hits"`
CachedAt string `json:"timestamp"`
TimeLeft string `json:"time_left"`
}
// DumpCache iterates across all internal memory shards, securely locking and
// replicating viable arrays to safely build an introspective snapshot of the
// active DNS cache without inducing hot-path contention constraints natively.
func DumpCache() []CacheEntryDump {
if !cfg.Cache.Enabled {
return nil
}
var dumps []CacheEntryDump
now := time.Now().UnixNano()
type snapshot struct {
Key DNSCacheKey
Item *cacheItem
Packed []byte
Hits uint32
}
var snaps []snapshot
// 1. Gather all active memory records natively using Read-Locks to minimize hot-path collision.
for i := range shards {
shard := shards[i]
shard.RLock()
for k, v := range shard.items {
// Pre-emptively skip records completely outside the stale window bounds natively
if now >= v.staleNano {
continue
}
if p := v.packed.Load(); p != nil {
// [PERF/FIX] By passing the immutable memory address dynamically,
// we completely eradicate redundant slice copies organically prior to packet Unpacking.
snaps = append(snaps, snapshot{
Key: k,
Item: v,
Packed: *p,
Hits: v.hits.Load(),
})
}
}
shard.RUnlock()
}
// 2. Unpack safely isolated from the core Mutexes natively
msg := new(dns.Msg)
for _, s := range snaps {
*msg = dns.Msg{}
if err := msg.Unpack(s.Packed); err != nil {
continue
}
var answers []string
for _, rr := range msg.Answer {
// Strip tabs to maintain strict visual formatting boundaries
str := strings.ReplaceAll(rr.String(), "\t", " ")
answers = append(answers, str)
}
textValue := strings.Join(answers, "\n")
if textValue == "" {
if msg.Rcode != dns.RcodeSuccess {
textValue = RcodeStr(msg.Rcode)
} else {
textValue = "NODATA"
}
}
timeLeftSec := (s.Item.expireNano - now) / int64(time.Second)
timeLeft := fmt.Sprintf("%ds", timeLeftSec)
if timeLeftSec < 0 {
timeLeft = fmt.Sprintf("Expired (%ds)", timeLeftSec)
}
cachedAt := time.Unix(0, s.Item.cachedAtNano).Format("2006-01-02 15:04:05")
route := s.Item.routeName
if route == "" {
route = "synthetic"
}
// Append individualized client contexts strictly for introspection clarity
if s.Key.ClientName != "" {
route += " [" + s.Key.ClientName + "]"
}
if s.Key.ECS != "" && s.Key.ECS != "passed-ecs" {
route += " [ECS: " + s.Key.ECS + "]"
}
dumps = append(dumps, CacheEntryDump{
QName: s.Key.Name,
QType: dns.TypeToString[s.Key.Qtype],
Route: route,
Response: textValue,
Hits: s.Hits,
CachedAt: cachedAt,
TimeLeft: timeLeft,
})
}
return dumps
}