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 }