github.com/netdata/go.d.plugin@v0.58.1/modules/bind/bind.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package bind 4 5 import ( 6 _ "embed" 7 "fmt" 8 "strings" 9 "time" 10 11 "github.com/netdata/go.d.plugin/pkg/matcher" 12 "github.com/netdata/go.d.plugin/pkg/web" 13 14 "github.com/netdata/go.d.plugin/agent/module" 15 ) 16 17 //go:embed "config_schema.json" 18 var configSchema string 19 20 func init() { 21 module.Register("bind", module.Creator{ 22 JobConfigSchema: configSchema, 23 Create: func() module.Module { return New() }, 24 }) 25 } 26 27 const ( 28 defaultURL = "http://127.0.0.1:8653/json/v1" 29 defaultHTTPTimeout = time.Second * 2 30 ) 31 32 // New creates Bind with default values. 33 func New() *Bind { 34 config := Config{ 35 HTTP: web.HTTP{ 36 Request: web.Request{ 37 URL: defaultURL, 38 }, 39 Client: web.Client{ 40 Timeout: web.Duration{Duration: defaultHTTPTimeout}, 41 }, 42 }, 43 } 44 45 return &Bind{ 46 Config: config, 47 charts: &Charts{}, 48 } 49 } 50 51 type bindAPIClient interface { 52 serverStats() (*serverStats, error) 53 } 54 55 // Config is the Bind module configuration. 56 type Config struct { 57 web.HTTP `yaml:",inline"` 58 PermitView string `yaml:"permit_view"` 59 } 60 61 // Bind Bind module. 62 type Bind struct { 63 module.Base 64 Config `yaml:",inline"` 65 66 bindAPIClient 67 permitView matcher.Matcher 68 charts *Charts 69 } 70 71 // Cleanup makes cleanup. 72 func (Bind) Cleanup() {} 73 74 // Init makes initialization. 75 func (b *Bind) Init() bool { 76 if b.URL == "" { 77 b.Error("URL not set") 78 return false 79 } 80 81 client, err := web.NewHTTPClient(b.Client) 82 if err != nil { 83 b.Errorf("error on creating http client : %v", err) 84 return false 85 } 86 87 switch { 88 case strings.HasSuffix(b.URL, "/xml/v3"): // BIND 9.9+ 89 b.bindAPIClient = newXML3Client(client, b.Request) 90 case strings.HasSuffix(b.URL, "/json/v1"): // BIND 9.10+ 91 b.bindAPIClient = newJSONClient(client, b.Request) 92 default: 93 b.Errorf("URL %s is wrong, supported endpoints: `/xml/v3`, `/json/v1`", b.URL) 94 return false 95 } 96 97 if b.PermitView != "" { 98 m, err := matcher.NewSimplePatternsMatcher(b.PermitView) 99 if err != nil { 100 b.Errorf("error on creating permitView matcher : %v", err) 101 return false 102 } 103 b.permitView = matcher.WithCache(m) 104 } 105 106 return true 107 } 108 109 // Check makes check. 110 func (b *Bind) Check() bool { 111 return len(b.Collect()) > 0 112 } 113 114 // Charts creates Charts. 115 func (b Bind) Charts() *Charts { 116 return b.charts 117 } 118 119 // Collect collects metrics. 120 func (b *Bind) Collect() map[string]int64 { 121 metrics := make(map[string]int64) 122 123 s, err := b.serverStats() 124 if err != nil { 125 b.Error(err) 126 return nil 127 } 128 b.collectServerStats(metrics, s) 129 130 return metrics 131 } 132 133 func (b *Bind) collectServerStats(metrics map[string]int64, stats *serverStats) { 134 var chart *Chart 135 136 for k, v := range stats.NSStats { 137 var ( 138 algo = module.Incremental 139 dimName = k 140 chartID string 141 ) 142 switch { 143 default: 144 continue 145 case k == "RecursClients": 146 dimName = "clients" 147 chartID = keyRecursiveClients 148 algo = module.Absolute 149 case k == "Requestv4": 150 dimName = "IPv4" 151 chartID = keyReceivedRequests 152 case k == "Requestv6": 153 dimName = "IPv6" 154 chartID = keyReceivedRequests 155 case k == "QryFailure": 156 dimName = "failures" 157 chartID = keyQueryFailures 158 case k == "QryUDP": 159 dimName = "UDP" 160 chartID = keyProtocolsQueries 161 case k == "QryTCP": 162 dimName = "TCP" 163 chartID = keyProtocolsQueries 164 case k == "QrySuccess": 165 dimName = "queries" 166 chartID = keyQueriesSuccess 167 case strings.HasSuffix(k, "QryRej"): 168 chartID = keyQueryFailuresDetail 169 case strings.HasPrefix(k, "Qry"): 170 chartID = keyQueriesAnalysis 171 case strings.HasPrefix(k, "Update"): 172 chartID = keyReceivedUpdates 173 } 174 175 if !b.charts.Has(chartID) { 176 _ = b.charts.Add(charts[chartID].Copy()) 177 } 178 179 chart = b.charts.Get(chartID) 180 181 if !chart.HasDim(k) { 182 _ = chart.AddDim(&Dim{ID: k, Name: dimName, Algo: algo}) 183 chart.MarkNotCreated() 184 } 185 186 delete(stats.NSStats, k) 187 metrics[k] = v 188 } 189 190 for _, v := range []struct { 191 item map[string]int64 192 chartID string 193 }{ 194 {item: stats.NSStats, chartID: keyNSStats}, 195 {item: stats.OpCodes, chartID: keyInOpCodes}, 196 {item: stats.QTypes, chartID: keyInQTypes}, 197 {item: stats.SockStats, chartID: keyInSockStats}, 198 } { 199 if len(v.item) == 0 { 200 continue 201 } 202 203 if !b.charts.Has(v.chartID) { 204 _ = b.charts.Add(charts[v.chartID].Copy()) 205 } 206 207 chart = b.charts.Get(v.chartID) 208 209 for key, val := range v.item { 210 if !chart.HasDim(key) { 211 _ = chart.AddDim(&Dim{ID: key, Algo: module.Incremental}) 212 chart.MarkNotCreated() 213 } 214 215 metrics[key] = val 216 } 217 } 218 219 if !(b.permitView != nil && len(stats.Views) > 0) { 220 return 221 } 222 223 for name, view := range stats.Views { 224 if !b.permitView.MatchString(name) { 225 continue 226 } 227 r := view.Resolver 228 229 delete(r.Stats, "BucketSize") 230 231 for key, val := range r.Stats { 232 var ( 233 algo = module.Incremental 234 dimName = key 235 chartKey string 236 ) 237 238 switch { 239 default: 240 chartKey = keyResolverStats 241 case key == "NumFetch": 242 chartKey = keyResolverNumFetch 243 dimName = "queries" 244 algo = module.Absolute 245 case strings.HasPrefix(key, "QryRTT"): 246 // TODO: not ordered 247 chartKey = keyResolverRTT 248 } 249 250 chartID := fmt.Sprintf(chartKey, name) 251 252 if !b.charts.Has(chartID) { 253 chart = charts[chartKey].Copy() 254 chart.ID = chartID 255 chart.Fam = fmt.Sprintf(chart.Fam, name) 256 _ = b.charts.Add(chart) 257 } 258 259 chart = b.charts.Get(chartID) 260 dimID := fmt.Sprintf("%s_%s", name, key) 261 262 if !chart.HasDim(dimID) { 263 _ = chart.AddDim(&Dim{ID: dimID, Name: dimName, Algo: algo}) 264 chart.MarkNotCreated() 265 } 266 267 metrics[dimID] = val 268 } 269 270 if len(r.QTypes) > 0 { 271 chartID := fmt.Sprintf(keyResolverInQTypes, name) 272 273 if !b.charts.Has(chartID) { 274 chart = charts[keyResolverInQTypes].Copy() 275 chart.ID = chartID 276 chart.Fam = fmt.Sprintf(chart.Fam, name) 277 _ = b.charts.Add(chart) 278 } 279 280 chart = b.charts.Get(chartID) 281 282 for key, val := range r.QTypes { 283 dimID := fmt.Sprintf("%s_%s", name, key) 284 if !chart.HasDim(dimID) { 285 _ = chart.AddDim(&Dim{ID: dimID, Name: key, Algo: module.Incremental}) 286 chart.MarkNotCreated() 287 } 288 metrics[dimID] = val 289 } 290 } 291 292 if len(r.CacheStats) > 0 { 293 chartID := fmt.Sprintf(keyResolverCacheHits, name) 294 295 if !b.charts.Has(chartID) { 296 chart = charts[keyResolverCacheHits].Copy() 297 chart.ID = chartID 298 chart.Fam = fmt.Sprintf(chart.Fam, name) 299 _ = b.charts.Add(chart) 300 for _, dim := range chart.Dims { 301 dim.ID = fmt.Sprintf(dim.ID, name) 302 } 303 } 304 305 metrics[name+"_CacheHits"] = r.CacheStats["CacheHits"] 306 metrics[name+"_CacheMisses"] = r.CacheStats["CacheMisses"] 307 } 308 } 309 }