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  }