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 }