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

     1  package collectors
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"bosun.org/metadata"
    17  	"bosun.org/opentsdb"
    18  	"bosun.org/slog"
    19  	"bosun.org/util"
    20  )
    21  
    22  type ProgramCollector struct {
    23  	Path     string
    24  	Interval time.Duration
    25  
    26  	TagOverride
    27  }
    28  
    29  func InitPrograms(cpath string) {
    30  	cdir, err := os.Open(cpath)
    31  	if err != nil {
    32  		slog.Infoln(err)
    33  		return
    34  	}
    35  	idirs, err := cdir.Readdir(0)
    36  	if err != nil {
    37  		slog.Infoln(err)
    38  		return
    39  	}
    40  	for _, idir := range idirs {
    41  		idirname := idir.Name()
    42  		i, err := strconv.Atoi(idirname)
    43  		if err != nil || i < 0 {
    44  			if idirname != "etc" && idirname != "lib" {
    45  				slog.Infoln("invalid collector folder name:", idirname)
    46  			}
    47  			continue
    48  		}
    49  		interval := time.Second * time.Duration(i)
    50  		dir, err := os.Open(filepath.Join(cdir.Name(), idirname))
    51  		if err != nil {
    52  			slog.Infoln(err)
    53  			continue
    54  		}
    55  		files, err := dir.Readdir(0)
    56  		if err != nil {
    57  			slog.Infoln(err)
    58  			continue
    59  		}
    60  		for _, file := range files {
    61  			if !isExecutable(file) {
    62  				continue
    63  			}
    64  			collectors = append(collectors, &ProgramCollector{
    65  				Path:     filepath.Join(dir.Name(), file.Name()),
    66  				Interval: interval,
    67  			})
    68  		}
    69  	}
    70  }
    71  
    72  func isExecutable(f os.FileInfo) bool {
    73  	switch runtime.GOOS {
    74  	case "windows":
    75  		exts := strings.Split(os.Getenv("PATHEXT"), ";")
    76  		exts = append(exts, ".PS1")
    77  		fileExt := filepath.Ext(strings.ToUpper(f.Name()))
    78  		for _, ext := range exts {
    79  			if filepath.Ext(strings.ToUpper(ext)) == fileExt {
    80  				return true
    81  			}
    82  		}
    83  		return false
    84  	default:
    85  		return f.Mode()&0111 != 0
    86  	}
    87  }
    88  
    89  func (c *ProgramCollector) Run(dpchan chan<- *opentsdb.DataPoint, quit <-chan struct{}) {
    90  	if c.Interval == 0 {
    91  		for {
    92  			next := time.After(DefaultFreq)
    93  			if err := c.runProgram(dpchan); err != nil {
    94  				slog.Infoln(err)
    95  			}
    96  			<-next
    97  			slog.Infoln("restarting", c.Path)
    98  		}
    99  	} else {
   100  		for {
   101  			next := time.After(c.Interval)
   102  			c.runProgram(dpchan)
   103  			select {
   104  			case <-next:
   105  			case <-quit:
   106  				return
   107  			}
   108  
   109  		}
   110  	}
   111  }
   112  
   113  func (c *ProgramCollector) Init() {
   114  }
   115  
   116  var setupExternalCommand = func(cmd *exec.Cmd) {}
   117  
   118  func (c *ProgramCollector) runProgram(dpchan chan<- *opentsdb.DataPoint) (progError error) {
   119  	var cmd *exec.Cmd
   120  	if runtime.GOOS == "windows" && strings.EqualFold(filepath.Ext(c.Path), ".ps1") {
   121  		cmd = exec.Command("powershell", "-NoProfile", "-NoLogo", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", c.Path)
   122  	} else {
   123  		cmd = exec.Command(c.Path)
   124  	}
   125  	setupExternalCommand(cmd)
   126  	pr, pw := io.Pipe()
   127  	s := bufio.NewScanner(pr)
   128  	cmd.Stdout = pw
   129  	er, ew := io.Pipe()
   130  	cmd.Stderr = ew
   131  	if err := cmd.Start(); err != nil {
   132  		return err
   133  	}
   134  	go func() {
   135  		progError = cmd.Wait()
   136  		pw.Close()
   137  		ew.Close()
   138  	}()
   139  	go func() {
   140  		es := bufio.NewScanner(er)
   141  		for es.Scan() {
   142  			line := strings.TrimSpace(es.Text())
   143  			slog.Error(line)
   144  		}
   145  	}()
   146  	for s.Scan() {
   147  		var errs []error
   148  		t := strings.TrimSpace(s.Text())
   149  		if len(t) == 0 {
   150  			continue
   151  		}
   152  		if dp, err := parseTcollectorValue(t); err == nil {
   153  			dpchan <- dp
   154  			continue
   155  		} else {
   156  			errs = append(errs, fmt.Errorf("tcollector: %v", err))
   157  		}
   158  		var dp opentsdb.DataPoint
   159  		if err := json.Unmarshal([]byte(t), &dp); err != nil {
   160  			errs = append(errs, fmt.Errorf("opentsdb.DataPoint: %v", err))
   161  		} else if dp.Valid() {
   162  			if dp.Tags == nil {
   163  				dp.Tags = opentsdb.TagSet{}
   164  			}
   165  			setExternalTags(dp.Tags)
   166  			c.ApplyTagOverrides(dp.Tags)
   167  			dpchan <- &dp
   168  			continue
   169  		} else {
   170  			errs = append(errs, fmt.Errorf("opentsdb.DataPoint: invalid data"))
   171  		}
   172  		var m metadata.Metasend
   173  		if err := json.Unmarshal([]byte(t), &m); err != nil {
   174  			errs = append(errs, fmt.Errorf("metadata.Metasend: %v", err))
   175  		} else {
   176  			if m.Tags == nil {
   177  				m.Tags = opentsdb.TagSet{}
   178  			}
   179  			setExternalTags(m.Tags)
   180  			if m.Value == "" || m.Name == "" || (m.Metric == "" && len(m.Tags) == 0) {
   181  				errs = append(errs, fmt.Errorf("metadata.Metasend: invalid data"))
   182  			} else {
   183  				metadata.AddMeta(m.Metric, m.Tags, m.Name, m.Value, false)
   184  				continue
   185  			}
   186  		}
   187  		slog.Errorf("%s: unparseable line: %s", c.Path, t)
   188  		for _, e := range errs {
   189  			slog.Error(e)
   190  		}
   191  	}
   192  	if err := s.Err(); err != nil {
   193  		return err
   194  	}
   195  	return
   196  }
   197  
   198  // setExternalTags adds and deletes system-level tags to tags. The host
   199  // tag is set to the hostname if unspecified, or removed if present and
   200  // empty. Command line tags (in AddTags) are then added.
   201  func setExternalTags(tags opentsdb.TagSet) {
   202  	if v, ok := tags["host"]; ok && v == "" {
   203  		delete(tags, "host")
   204  	} else if v == "" {
   205  		tags["host"] = util.GetHostManager().GetHostName()
   206  	}
   207  	for k, v := range AddTags {
   208  		if _, ok := tags[k]; !ok {
   209  			tags[k] = v
   210  		}
   211  	}
   212  }
   213  
   214  // parseTcollectorValue parses a tcollector-style line into a data point.
   215  func parseTcollectorValue(line string) (*opentsdb.DataPoint, error) {
   216  	sp := strings.Fields(line)
   217  	if len(sp) < 3 {
   218  		return nil, fmt.Errorf("bad line: %s", line)
   219  	}
   220  	ts, err := strconv.ParseInt(sp[1], 10, 64)
   221  	if err != nil {
   222  		return nil, fmt.Errorf("bad timestamp: %s", sp[1])
   223  	}
   224  	val, err := strconv.ParseFloat(sp[2], 64)
   225  	if err != nil {
   226  		return nil, fmt.Errorf("bad value: %s", sp[2])
   227  	}
   228  	if !opentsdb.ValidTSDBString(sp[0]) {
   229  		return nil, fmt.Errorf("bad metric: %s", sp[0])
   230  	}
   231  	dp := opentsdb.DataPoint{
   232  		Metric:    sp[0],
   233  		Timestamp: ts,
   234  		Value:     val,
   235  	}
   236  	tags := opentsdb.TagSet{}
   237  	for _, tag := range sp[3:] {
   238  		ts, err := opentsdb.ParseTags(tag)
   239  		if err != nil {
   240  			return nil, fmt.Errorf("bad tag, metric %s: %v: %v", sp[0], tag, err)
   241  		}
   242  		tags.Merge(ts)
   243  	}
   244  	setExternalTags(tags)
   245  	dp.Tags = tags
   246  	return &dp, nil
   247  }
   248  
   249  func (c *ProgramCollector) Name() string {
   250  	return c.Path
   251  }