bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/scollector/collectors/collectors.go (about)

     1  package collectors // import "bosun.org/cmd/scollector/collectors"
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"runtime"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  	"unicode"
    17  	"unicode/utf8"
    18  
    19  	"bosun.org/cmd/scollector/conf"
    20  	"bosun.org/metadata"
    21  	"bosun.org/opentsdb"
    22  	"bosun.org/slog"
    23  	"bosun.org/util"
    24  )
    25  
    26  var collectors []Collector
    27  
    28  type Collector interface {
    29  	Run(chan<- *opentsdb.DataPoint, <-chan struct{})
    30  	Name() string
    31  	Init()
    32  	AddTagOverrides(map[string]string, opentsdb.TagSet) error
    33  	ApplyTagOverrides(opentsdb.TagSet)
    34  }
    35  
    36  //These should be in alphabetical order to help prevent merge conflicts
    37  const (
    38  	osCPU              = "os.cpu"
    39  	osCPUClock         = "os.cpu.clock"
    40  	osDiskFree         = "os.disk.fs.space_free"
    41  	osDiskPctFree      = "os.disk.fs.percent_free"
    42  	osDiskTotal        = "os.disk.fs.space_total"
    43  	osDiskUsed         = "os.disk.fs.space_used"
    44  	osMemFree          = "os.mem.free"
    45  	osMemPctFree       = "os.mem.percent_free"
    46  	osMemTotal         = "os.mem.total"
    47  	osMemUsed          = "os.mem.used"
    48  	osNetAdminStatus   = "os.net.admin_status"
    49  	osNetBondBroadcast = "os.net.bond.packets_broadcast"
    50  	osNetBondBytes     = "os.net.bond.bytes"
    51  	osNetBondDropped   = "os.net.bond.dropped"
    52  	osNetBondErrors    = "os.net.bond.errs"
    53  	osNetBondIfSpeed   = "os.net.bond.ifspeed"
    54  	osNetBondMulticast = "os.net.bond.packets_multicast"
    55  	osNetBondPackets   = "os.net.bond.packets"
    56  	osNetBondUnicast   = "os.net.bond.packets_unicast"
    57  	osNetBroadcast     = "os.net.packets_broadcast"
    58  	osNetBytes         = "os.net.bytes"
    59  	osNetDropped       = "os.net.dropped"
    60  	osNetErrors        = "os.net.errs"
    61  	osNetIfSpeed       = "os.net.ifspeed"
    62  	osNetMTU           = "os.net.mtu"
    63  	osNetMulticast     = "os.net.packets_multicast"
    64  	osNetOperStatus    = "os.net.oper_status"
    65  	osNetPackets       = "os.net.packets"
    66  	osNetPauseFrames   = "os.net.pause_frames"
    67  	osNetUnicast       = "os.net.packets_unicast"
    68  	osProcCount        = "os.proc.count"
    69  	osProcCPU          = "os.proc.cpu"
    70  	osProcMemReal      = "os.proc.mem.real"
    71  	osProcMemVirtual   = "os.proc.mem.virtual"
    72  	osServiceRunning   = "os.service.running"
    73  	osSystemUptime     = "os.system.uptime"
    74  )
    75  
    76  const (
    77  	osCPUClockDesc       = "The current speed of the processor in MHz."
    78  	osDiskFreeDesc       = "The space_free property indicates in bytes how much free space is available on the disk."
    79  	osDiskPctFreeDesc    = "The percent_free property indicates what percentage of the disk is available."
    80  	osDiskTotalDesc      = "The space_total property indicates in bytes how much total space is on the disk."
    81  	osDiskUsedDesc       = "The space_used property indicates in bytes how much space is used on the disk."
    82  	osMemFreeDesc        = "The number of bytes of physical memory currently unused and available. In Linux this metric considers buffers, cache, and slab to be free memory."
    83  	osMemPctFreeDesc     = "The percent of free memory. In Linux this metric considers buffers, cache, and slab to be free memory."
    84  	osMemTotalDesc       = "The total amount of physical memory available in bytes to the operating system."
    85  	osMemUsedDesc        = "The amount of used memory. In Linux this metric excludes buffers, cache, from used memory."
    86  	osNetAdminStatusDesc = "The desired state of the interface. The testing(3) state indicates that no operational packets can be passed. When a managed system initializes, all interfaces start with ifAdminStatus in the down(2) state. As a result of either explicit management action or per configuration information retained by the managed system, ifAdminStatus is then changed to either the up(1) or testing(3) states (or remains in the down(2) state)."
    87  	osNetBroadcastDesc   = "The rate at which broadcast packets are sent or received on the network interface."
    88  	osNetBytesDesc       = "The rate at which bytes are sent or received over the network interface."
    89  	osNetDroppedDesc     = "The number of packets that were chosen to be discarded even though no errors had been detected to prevent transmission."
    90  	osNetErrorsDesc      = "The number of packets that could not be transmitted because of errors."
    91  	osNetIfSpeedDesc     = "The total link speed of the network interface in Megabits per second."
    92  	osNetMTUDesc         = "The maximum transmission unit for the ethernet frame."
    93  	osNetMulticastDesc   = "The rate at which multicast packets are sent or received on the network interface."
    94  	osNetOperStatusDesc  = "The current operational state of the interface. The testing(3) state indicates that no operational packets can be passed. If ifAdminStatus is down(2) then ifOperStatus should be down(2). If ifAdminStatus is changed to up(1) then ifOperStatus should change to up(1) if the interface is ready to transmit and receive network traffic; it should change to dormant(5) if the interface is waiting for external actions (such as a serial line waiting for an incoming connection); it should remain in the down(2) state if and only if there is a fault that prevents it from going to the up(1) state; it should remain in the notPresent(6) state if the interface has missing (typically, hardware) components."
    95  	osNetPacketsDesc     = "The rate at which packets are sent or received on the network interface."
    96  	osNetPauseFrameDesc  = "The rate of pause frames sent or recieved on the network interface. An overwhelmed network element can send a pause frame, which halts the transmission of the sender for a specified period of time."
    97  	osNetUnicastDesc     = "The rate at which unicast packets are sent or received on the network interface."
    98  	osProcCountDesc      = "The number of processes running with this name."
    99  	osProcCPUDesc        = "The summed percentage of CPU time used by processes with this name (0-100)."
   100  	osProcMemRealDesc    = "The total amount of real memory used by the processes with this name. For Linux this is RSS and in Windows it is the private working set."
   101  	osProcMemVirtualDesc = "The total amount of virtual memory used by the processes with this name."
   102  	osProcPID            = "The PID of the process being tracked by a given ID tag. As this metric value represents the actual PID, it is not suitable for any form of aggregation."
   103  	osServiceRunningDesc = "1: active, 0: inactive"
   104  	osSystemUptimeDesc   = "Seconds since last reboot."
   105  )
   106  
   107  var (
   108  	// DefaultFreq is the duration between collection intervals if none is
   109  	// specified.
   110  	DefaultFreq = time.Second * 15
   111  
   112  	timestamp = time.Now().Unix()
   113  	tlock     sync.Mutex
   114  	AddTags   opentsdb.TagSet
   115  
   116  	metricFilters = make([]*regexp.Regexp, 0)
   117  
   118  	AddProcessDotNetConfig = func(params conf.ProcessDotNet) error {
   119  		return fmt.Errorf("process_dotnet watching not implemented on this platform")
   120  	}
   121  	WatchProcessesDotNet = func() {}
   122  
   123  	KeepalivedCommunity = ""
   124  
   125  	//TotalScollectorMemory stores the total memory used by Scollector (including CGO and WMI)
   126  	TotalScollectorMemoryMB uint64
   127  
   128  	MetricPrefix = ""
   129  )
   130  
   131  func init() {
   132  	go func() {
   133  		for t := range time.Tick(time.Second) {
   134  			tlock.Lock()
   135  			timestamp = t.Unix()
   136  			tlock.Unlock()
   137  		}
   138  	}()
   139  }
   140  
   141  func now() (t int64) {
   142  	tlock.Lock()
   143  	t = timestamp
   144  	tlock.Unlock()
   145  	return
   146  }
   147  
   148  func matchPattern(s string, patterns []string) bool {
   149  	for _, p := range patterns {
   150  		if !strings.HasPrefix(p, "-") {
   151  			if strings.Contains(s, p) {
   152  				return true
   153  			}
   154  		}
   155  	}
   156  	return false
   157  }
   158  
   159  func matchInvertPattern(s string, patterns []string) bool {
   160  	for _, p := range patterns {
   161  		if strings.HasPrefix(p, "-") {
   162  			var np = p[1:]
   163  			if strings.Contains(s, np) {
   164  				return true
   165  			}
   166  		}
   167  	}
   168  	return false
   169  }
   170  
   171  // Search returns all collectors matching the pattern s.
   172  func Search(s []string) []Collector {
   173  	if len(s) == 0 {
   174  		return collectors
   175  	}
   176  	var r []Collector
   177  	sort.Strings(s)
   178  	i := sort.SearchStrings(s, "*")
   179  	IncludeAll := i < len(s) && s[i] == "*"
   180  	for _, c := range collectors {
   181  		if matchInvertPattern(c.Name(), s) {
   182  			continue
   183  		} else if IncludeAll || matchPattern(c.Name(), s) {
   184  			r = append(r, c)
   185  		}
   186  	}
   187  	return r
   188  }
   189  
   190  // Adds configured tag overrides to all matching collectors
   191  func AddTagOverrides(s []Collector, tagOverride []conf.TagOverride) error {
   192  	for _, to := range tagOverride {
   193  		re := regexp.MustCompile(to.CollectorExpr)
   194  		for _, c := range s {
   195  			if re.MatchString(c.Name()) {
   196  				err := c.AddTagOverrides(to.MatchedTags, to.Tags)
   197  				if err != nil {
   198  					return err
   199  				}
   200  			}
   201  		}
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  // Run runs specified collectors. Use nil for all collectors.
   208  func Run(cs []Collector) (chan *opentsdb.DataPoint, chan struct{}) {
   209  	if cs == nil {
   210  		cs = collectors
   211  	}
   212  	ch := make(chan *opentsdb.DataPoint)
   213  	quit := make(chan struct{})
   214  	for _, c := range cs {
   215  		go c.Run(ch, quit)
   216  	}
   217  	return ch, quit
   218  }
   219  
   220  type initFunc func(*conf.Conf)
   221  
   222  var inits = []initFunc{}
   223  
   224  func registerInit(i initFunc) {
   225  	inits = append(inits, i)
   226  }
   227  
   228  func Init(c *conf.Conf) {
   229  	if c.MetricPrefix != "" {
   230  		MetricPrefix = c.MetricPrefix
   231  	}
   232  	for _, f := range inits {
   233  		f(c)
   234  	}
   235  }
   236  
   237  type MetricMeta struct {
   238  	Metric   string
   239  	TagSet   opentsdb.TagSet
   240  	RateType metadata.RateType
   241  	Unit     metadata.Unit
   242  	Desc     string
   243  }
   244  
   245  // AddTS is the same as Add but lets you specify the timestamp
   246  func AddTS(md *opentsdb.MultiDataPoint, name string, ts int64, value interface{}, t opentsdb.TagSet, rate metadata.RateType, unit metadata.Unit, desc string) {
   247  	// Check if we really want that metric
   248  	if skipMetric(name) {
   249  		return
   250  	}
   251  	// Add Prefix
   252  	if MetricPrefix != "" {
   253  		name = MetricPrefix + "." + name
   254  	}
   255  
   256  	tags := t.Copy()
   257  	if host, present := tags["host"]; !present {
   258  		tags["host"] = util.GetHostManager().GetHostName()
   259  	} else if host == "" {
   260  		delete(tags, "host")
   261  	}
   262  	// if tags are not cleanable, log a message and skip it
   263  	if err := tags.Clean(); err != nil {
   264  		line := ""
   265  		//attempt to log where Add was called from
   266  		if _, filename, l, ok := runtime.Caller(1); ok {
   267  			if filepath.Base(filename) == "collectors.go" {
   268  				_, filename, l, ok = runtime.Caller(2)
   269  			}
   270  			if ok {
   271  				line = fmt.Sprintf("%s:%d", filepath.Base(filename), l)
   272  			}
   273  		}
   274  		slog.Errorf("Invalid tagset discovered: %s. Skipping datapoint. Added from: %s", tags.String(), line)
   275  		return
   276  	}
   277  	if rate != metadata.Unknown {
   278  		metadata.AddMeta(name, nil, "rate", rate, false)
   279  	}
   280  	if unit != metadata.None {
   281  		metadata.AddMeta(name, nil, "unit", unit, false)
   282  	}
   283  	if desc != "" {
   284  		metadata.AddMeta(name, tags, "desc", desc, false)
   285  	}
   286  	tags = AddTags.Copy().Merge(tags)
   287  	if b, ok := value.(bool); ok {
   288  		if b {
   289  			value = 1
   290  		} else {
   291  			value = 0
   292  		}
   293  	}
   294  	d := opentsdb.DataPoint{
   295  		Metric:    name,
   296  		Timestamp: ts,
   297  		Value:     value,
   298  		Tags:      tags,
   299  	}
   300  	*md = append(*md, &d)
   301  }
   302  
   303  // Add appends a new data point with given metric name, value, and tags. Tags
   304  // may be nil. If tags is nil or does not contain a host key, it will be
   305  // automatically added. If the value of the host key is the empty string, it
   306  // will be removed (use this to prevent the normal auto-adding of the host tag).
   307  func Add(md *opentsdb.MultiDataPoint, name string, value interface{}, t opentsdb.TagSet, rate metadata.RateType, unit metadata.Unit, desc string) {
   308  	AddTS(md, name, now(), value, t, rate, unit, desc)
   309  }
   310  
   311  func readLine(fname string, line func(string) error) error {
   312  	f, err := os.Open(fname)
   313  	if err != nil {
   314  		return err
   315  	}
   316  	defer f.Close()
   317  	scanner := bufio.NewScanner(f)
   318  	for scanner.Scan() {
   319  		if err := line(scanner.Text()); err != nil {
   320  			return err
   321  		}
   322  	}
   323  	return scanner.Err()
   324  }
   325  
   326  // IsDigit returns true if s consists of decimal digits.
   327  func IsDigit(s string) bool {
   328  	r := strings.NewReader(s)
   329  	for {
   330  		ch, _, err := r.ReadRune()
   331  		if ch == 0 || err != nil {
   332  			break
   333  		} else if ch == utf8.RuneError {
   334  			return false
   335  		} else if !unicode.IsDigit(ch) {
   336  			return false
   337  		}
   338  	}
   339  	return true
   340  }
   341  
   342  // IsAlNum returns true if s is alphanumeric.
   343  func IsAlNum(s string) bool {
   344  	r := strings.NewReader(s)
   345  	for {
   346  		ch, _, err := r.ReadRune()
   347  		if ch == 0 || err != nil {
   348  			break
   349  		} else if ch == utf8.RuneError {
   350  			return false
   351  		} else if !unicode.IsDigit(ch) && !unicode.IsLetter(ch) {
   352  			return false
   353  		}
   354  	}
   355  	return true
   356  }
   357  
   358  func TSys100NStoEpoch(nsec uint64) int64 {
   359  	nsec -= 116444736000000000
   360  	seconds := nsec / 1e7
   361  	return int64(seconds)
   362  }
   363  
   364  func metaIfaces(f func(iface net.Interface, tags opentsdb.TagSet)) {
   365  	ifaces, _ := net.Interfaces()
   366  	for _, iface := range ifaces {
   367  		if strings.HasPrefix(iface.Name, "lo") {
   368  			continue
   369  		}
   370  		tags := opentsdb.TagSet{"iface": iface.Name}
   371  		metadata.AddMeta("", tags, "name", iface.Name, true)
   372  		if mac := strings.ToUpper(strings.Replace(iface.HardwareAddr.String(), ":", "", -1)); mac != "" {
   373  			metadata.AddMeta("", tags, "mac", mac, true)
   374  		}
   375  		rawAds, _ := iface.Addrs()
   376  		addrs := make([]string, len(rawAds))
   377  		for i, rAd := range rawAds {
   378  			addrs[i] = rAd.String()
   379  		}
   380  		sort.Strings(addrs)
   381  		j, _ := json.Marshal(addrs)
   382  		metadata.AddMeta("", tags, "addresses", string(j), true)
   383  		if f != nil {
   384  			f(iface, tags)
   385  		}
   386  	}
   387  }
   388  
   389  // AddMetricFilters adds metric filters provided by the conf
   390  func AddMetricFilters(s string) error {
   391  	re, err := regexp.Compile(s)
   392  	if err != nil {
   393  		return err
   394  	}
   395  	metricFilters = append(metricFilters, re)
   396  	return nil
   397  }
   398  
   399  // skipMetric will return true if we need to skip this metric
   400  func skipMetric(index string) bool {
   401  	// If no filters provided, we skip nothing
   402  	if len(metricFilters) == 0 {
   403  		return false
   404  	}
   405  	for _, re := range metricFilters {
   406  		if re.MatchString(index) {
   407  			return false
   408  		}
   409  	}
   410  	return true
   411  }
   412  
   413  type tsIntegrator func(int64, float64) float64
   414  
   415  func getTsIntegrator() tsIntegrator {
   416  	var total float64
   417  	var lastTimestamp int64
   418  	return func(timestamp int64, v float64) float64 {
   419  		if lastTimestamp > 0 {
   420  			total += v * float64(timestamp-lastTimestamp)
   421  		}
   422  		lastTimestamp = timestamp
   423  		return total
   424  	}
   425  }