diff --git a/etc/telegraf.conf b/etc/telegraf.conf index 98138eef4..c9011536a 100644 --- a/etc/telegraf.conf +++ b/etc/telegraf.conf @@ -526,6 +526,19 @@ # socket_suffix = "asok" +# # Read specific statistics per cgroup +# [[inputs.cgroup]] +# ## Directories in which to look for files, globs are supported. +# # paths = [ +# # "/cgroup/memory", +# # "/cgroup/memory/child1", +# # "/cgroup/memory/child2/*", +# # ] +# ## cgroup stat fields, as file names, globs are supported. +# ## these file names are appended to each path from above. +# # files = ["memory.*usage*", "memory.limit_in_bytes"] + + # # Pull Metric Statistics from Amazon CloudWatch # [[inputs.cloudwatch]] # ## Amazon Region diff --git a/plugins/inputs/cgroup/README.md b/plugins/inputs/cgroup/README.md index 283b17634..ab06342bf 100644 --- a/plugins/inputs/cgroup/README.md +++ b/plugins/inputs/cgroup/README.md @@ -46,7 +46,7 @@ All measurements have the following tags: # "/cgroup/memory/child1", # container cgroup # "/cgroup/memory/child2/*", # all children cgroups under child2, but not child2 itself # ] - # fields = ["memory.*usage*", "memory.limit_in_bytes"] + # files = ["memory.*usage*", "memory.limit_in_bytes"] # [[inputs.cgroup]] # paths = [ @@ -54,5 +54,5 @@ All measurements have the following tags: # "/cgroup/cpu/*", # all container cgroups # "/cgroup/cpu/*/*", # all children cgroups under each container cgroup # ] - # fields = ["cpuacct.usage", "cpu.cfs_period_us", "cpu.cfs_quota_us"] + # files = ["cpuacct.usage", "cpu.cfs_period_us", "cpu.cfs_quota_us"] ``` diff --git a/plugins/inputs/cgroup/cgroup.go b/plugins/inputs/cgroup/cgroup.go index 341ada5a1..e38b6a4c1 100644 --- a/plugins/inputs/cgroup/cgroup.go +++ b/plugins/inputs/cgroup/cgroup.go @@ -1,22 +1,10 @@ -// +build linux - package cgroup import ( - "fmt" - "io/ioutil" - "os" - "path" - "path/filepath" - "regexp" - "strconv" - "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/plugins/inputs" ) -const metricName = "cgroup" - type CGroup struct { Paths []string `toml:"paths"` Files []string `toml:"files"` @@ -42,233 +30,6 @@ func (g *CGroup) Description() string { return "Read specific statistics per 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 { - return dir.err - } - if err := g.gatherDir(dir.path, acc); err != nil { - return 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) { - 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} - } - } - } - close(list) -} - -func (g *CGroup) generateFiles(dir string, list chan<- pathInfo) { - 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} - } - } - } - close(list) -} - -// ====================================================================== - -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 - fileFormat{ - 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 - // ... - fileFormat{ - 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 - fileFormat{ - 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 - // ... - fileFormat{ - 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.Atoi(s) - 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 -} - func init() { inputs.Add("cgroup", func() telegraf.Input { return &CGroup{} }) } diff --git a/plugins/inputs/cgroup/cgroup_linux.go b/plugins/inputs/cgroup/cgroup_linux.go new file mode 100644 index 000000000..e8ba6f881 --- /dev/null +++ b/plugins/inputs/cgroup/cgroup_linux.go @@ -0,0 +1,244 @@ +// +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 { + return dir.err + } + if err := g.gatherDir(dir.path, acc); err != nil { + return 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) { + 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} + } + } + } + close(list) +} + +func (g *CGroup) generateFiles(dir string, list chan<- pathInfo) { + 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} + } + } + } + close(list) +} + +// ====================================================================== + +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 + fileFormat{ + 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 + // ... + fileFormat{ + 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 + fileFormat{ + 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 + // ... + fileFormat{ + 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.Atoi(s) + 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 +} diff --git a/plugins/inputs/cgroup/cgroup_notlinux.go b/plugins/inputs/cgroup/cgroup_notlinux.go index 661f99f5c..2bc227410 100644 --- a/plugins/inputs/cgroup/cgroup_notlinux.go +++ b/plugins/inputs/cgroup/cgroup_notlinux.go @@ -1,3 +1,11 @@ // +build !linux package cgroup + +import ( + "github.com/influxdata/telegraf" +) + +func (g *CGroup) Gather(acc telegraf.Accumulator) error { + return nil +}