246 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
| // +build linux
 | |
| 
 | |
| package cgroup
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"github.com/influxdata/telegraf"
 | |
| )
 | |
| 
 | |
| const metricName = "cgroup"
 | |
| 
 | |
| func (g *CGroup) Gather(acc telegraf.Accumulator) error {
 | |
| 	list := make(chan pathInfo)
 | |
| 	go g.generateDirs(list)
 | |
| 
 | |
| 	for dir := range list {
 | |
| 		if dir.err != nil {
 | |
| 			acc.AddError(dir.err)
 | |
| 			continue
 | |
| 		}
 | |
| 		if err := g.gatherDir(dir.path, acc); err != nil {
 | |
| 			acc.AddError(err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (g *CGroup) gatherDir(dir string, acc telegraf.Accumulator) error {
 | |
| 	fields := make(map[string]interface{})
 | |
| 
 | |
| 	list := make(chan pathInfo)
 | |
| 	go g.generateFiles(dir, list)
 | |
| 
 | |
| 	for file := range list {
 | |
| 		if file.err != nil {
 | |
| 			return file.err
 | |
| 		}
 | |
| 
 | |
| 		raw, err := ioutil.ReadFile(file.path)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if len(raw) == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		fd := fileData{data: raw, path: file.path}
 | |
| 		if err := fd.parse(fields); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	tags := map[string]string{"path": dir}
 | |
| 
 | |
| 	acc.AddFields(metricName, fields, tags)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // ======================================================================
 | |
| 
 | |
| type pathInfo struct {
 | |
| 	path string
 | |
| 	err  error
 | |
| }
 | |
| 
 | |
| func isDir(path string) (bool, error) {
 | |
| 	result, err := os.Stat(path)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	return result.IsDir(), nil
 | |
| }
 | |
| 
 | |
| func (g *CGroup) generateDirs(list chan<- pathInfo) {
 | |
| 	defer close(list)
 | |
| 	for _, dir := range g.Paths {
 | |
| 		// getting all dirs that match the pattern 'dir'
 | |
| 		items, err := filepath.Glob(dir)
 | |
| 		if err != nil {
 | |
| 			list <- pathInfo{err: err}
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		for _, item := range items {
 | |
| 			ok, err := isDir(item)
 | |
| 			if err != nil {
 | |
| 				list <- pathInfo{err: err}
 | |
| 				return
 | |
| 			}
 | |
| 			// supply only dirs
 | |
| 			if ok {
 | |
| 				list <- pathInfo{path: item}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (g *CGroup) generateFiles(dir string, list chan<- pathInfo) {
 | |
| 	defer close(list)
 | |
| 	for _, file := range g.Files {
 | |
| 		// getting all file paths that match the pattern 'dir + file'
 | |
| 		// path.Base make sure that file variable does not contains part of path
 | |
| 		items, err := filepath.Glob(path.Join(dir, path.Base(file)))
 | |
| 		if err != nil {
 | |
| 			list <- pathInfo{err: err}
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		for _, item := range items {
 | |
| 			ok, err := isDir(item)
 | |
| 			if err != nil {
 | |
| 				list <- pathInfo{err: err}
 | |
| 				return
 | |
| 			}
 | |
| 			// supply only files not dirs
 | |
| 			if !ok {
 | |
| 				list <- pathInfo{path: item}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ======================================================================
 | |
| 
 | |
| type fileData struct {
 | |
| 	data []byte
 | |
| 	path string
 | |
| }
 | |
| 
 | |
| func (fd *fileData) format() (*fileFormat, error) {
 | |
| 	for _, ff := range fileFormats {
 | |
| 		ok, err := ff.match(fd.data)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		if ok {
 | |
| 			return &ff, nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil, fmt.Errorf("%v: unknown file format", fd.path)
 | |
| }
 | |
| 
 | |
| func (fd *fileData) parse(fields map[string]interface{}) error {
 | |
| 	format, err := fd.format()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	format.parser(filepath.Base(fd.path), fields, fd.data)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // ======================================================================
 | |
| 
 | |
| type fileFormat struct {
 | |
| 	name    string
 | |
| 	pattern string
 | |
| 	parser  func(measurement string, fields map[string]interface{}, b []byte)
 | |
| }
 | |
| 
 | |
| const keyPattern = "[[:alpha:]_]+"
 | |
| const valuePattern = "[\\d-]+"
 | |
| 
 | |
| var fileFormats = [...]fileFormat{
 | |
| 	// 	VAL\n
 | |
| 	{
 | |
| 		name:    "Single value",
 | |
| 		pattern: "^" + valuePattern + "\n$",
 | |
| 		parser: func(measurement string, fields map[string]interface{}, b []byte) {
 | |
| 			re := regexp.MustCompile("^(" + valuePattern + ")\n$")
 | |
| 			matches := re.FindAllStringSubmatch(string(b), -1)
 | |
| 			fields[measurement] = numberOrString(matches[0][1])
 | |
| 		},
 | |
| 	},
 | |
| 	// 	VAL0\n
 | |
| 	// 	VAL1\n
 | |
| 	// 	...
 | |
| 	{
 | |
| 		name:    "New line separated values",
 | |
| 		pattern: "^(" + valuePattern + "\n){2,}$",
 | |
| 		parser: func(measurement string, fields map[string]interface{}, b []byte) {
 | |
| 			re := regexp.MustCompile("(" + valuePattern + ")\n")
 | |
| 			matches := re.FindAllStringSubmatch(string(b), -1)
 | |
| 			for i, v := range matches {
 | |
| 				fields[measurement+"."+strconv.Itoa(i)] = numberOrString(v[1])
 | |
| 			}
 | |
| 		},
 | |
| 	},
 | |
| 	// 	VAL0 VAL1 ...\n
 | |
| 	{
 | |
| 		name:    "Space separated values",
 | |
| 		pattern: "^(" + valuePattern + " )+\n$",
 | |
| 		parser: func(measurement string, fields map[string]interface{}, b []byte) {
 | |
| 			re := regexp.MustCompile("(" + valuePattern + ") ")
 | |
| 			matches := re.FindAllStringSubmatch(string(b), -1)
 | |
| 			for i, v := range matches {
 | |
| 				fields[measurement+"."+strconv.Itoa(i)] = numberOrString(v[1])
 | |
| 			}
 | |
| 		},
 | |
| 	},
 | |
| 	// 	KEY0 VAL0\n
 | |
| 	// 	KEY1 VAL1\n
 | |
| 	// 	...
 | |
| 	{
 | |
| 		name:    "New line separated key-space-value's",
 | |
| 		pattern: "^(" + keyPattern + " " + valuePattern + "\n)+$",
 | |
| 		parser: func(measurement string, fields map[string]interface{}, b []byte) {
 | |
| 			re := regexp.MustCompile("(" + keyPattern + ") (" + valuePattern + ")\n")
 | |
| 			matches := re.FindAllStringSubmatch(string(b), -1)
 | |
| 			for _, v := range matches {
 | |
| 				fields[measurement+"."+v[1]] = numberOrString(v[2])
 | |
| 			}
 | |
| 		},
 | |
| 	},
 | |
| }
 | |
| 
 | |
| func numberOrString(s string) interface{} {
 | |
| 	i, err := strconv.ParseInt(s, 10, 64)
 | |
| 	if err == nil {
 | |
| 		return i
 | |
| 	}
 | |
| 
 | |
| 	return s
 | |
| }
 | |
| 
 | |
| func (f fileFormat) match(b []byte) (bool, error) {
 | |
| 	ok, err := regexp.Match(f.pattern, b)
 | |
| 	if err != nil {
 | |
| 		return false, err
 | |
| 	}
 | |
| 	if ok {
 | |
| 		return true, nil
 | |
| 	}
 | |
| 	return false, nil
 | |
| }
 |