diff --git a/Godeps b/Godeps index 6a0a17df1..ecb035772 100644 --- a/Godeps +++ b/Godeps @@ -51,6 +51,7 @@ github.com/shirou/gopsutil 1516eb9ddc5e61ba58874047a98f8b44b5e585e8 github.com/soniah/gosnmp 3fe3beb30fa9700988893c56a63b1df8e1b68c26 github.com/streadway/amqp b4f3ceab0337f013208d31348b578d83c0064744 github.com/stretchr/testify 1f4a1643a57e798696635ea4c126e9127adb7d3c +github.com/tokuhirom/go-hsperfdata 63efb7d3b4adbb23528abb6edcb7361a0c7ac22b github.com/vjeantet/grok 83bfdfdfd1a8146795b28e547a8e3c8b28a466c2 github.com/wvanbergen/kafka 46f9a1cf3f670edec492029fadded9c2d9e18866 github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8 diff --git a/README.md b/README.md index 92ebf19ee..9ae131d72 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ configuration options. * [filestat](./plugins/inputs/filestat) * [haproxy](./plugins/inputs/haproxy) * [hddtemp](./plugins/inputs/hddtemp) +* [hsperfdata](./plugins/inputs/hsperfdata) (Hostpot JVMs) * [http_response](./plugins/inputs/http_response) * [httpjson](./plugins/inputs/httpjson) (generic JSON-emitting http service plugin) * [influxdb](./plugins/inputs/influxdb) diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 67b85905e..c359d03dd 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -32,6 +32,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/jolokia" _ "github.com/influxdata/telegraf/plugins/inputs/kafka_consumer" _ "github.com/influxdata/telegraf/plugins/inputs/kubernetes" + _ "github.com/influxdata/telegraf/plugins/inputs/hsperfdata" _ "github.com/influxdata/telegraf/plugins/inputs/leofs" _ "github.com/influxdata/telegraf/plugins/inputs/logparser" _ "github.com/influxdata/telegraf/plugins/inputs/lustre2" diff --git a/plugins/inputs/hsperfdata/README.md b/plugins/inputs/hsperfdata/README.md new file mode 100644 index 000000000..e9c9c52c2 --- /dev/null +++ b/plugins/inputs/hsperfdata/README.md @@ -0,0 +1,39 @@ +# hsperfdata Plugin + +The plugin gathers data from Hotspot JVMs via the hsperfdata files they expose. This plugin won't work if you've disabled their creation using `-XX:-UsePerfData` or `-XX:+PerfDisableSharedMem`! + +### Configuration: + +```toml +[[inputs.hsperfdata]] + # Optional: gather data from processes belonging to a different user. By + # default, the username in the USER environment variable is used to generate + # the hsperfdata directory name (usually "/tmp/hsperfdata_username") + user: "root" + + # use the named keys in the hsperfdata file as tags, not fields. By default, + # every key is exposed as a field. This example shows how to tag by JVM major + # version: + tags: ["java.property.java.vm.specification.version"] +``` + +### Measurements & Fields: + +All metrics are gathered as the "java" measurement. + +All keys in the hsperfdata file are exposed as fields; there's no comprehensive list as they vary by Hotspot version. + +### Tags: + +- All measurements have the following tags: + - pid (the process id of the monitored process) + - procname (the class name containing the `main` function being run) + +### Example Output: + +Most fields abbreviated; there's usually 200-300 of them: + +``` +$ ./telegraf -config telegraf.conf -input-filter example -test +java,host=nwhite91-mac,pid=17427,procname=com.sun.javaws.Main java.ci.totalTime="49874911809",...,sun.zip.zipFiles="28" 1479466710000000000 +``` diff --git a/plugins/inputs/hsperfdata/hsperfdata.go b/plugins/inputs/hsperfdata/hsperfdata.go new file mode 100644 index 000000000..a438f0f22 --- /dev/null +++ b/plugins/inputs/hsperfdata/hsperfdata.go @@ -0,0 +1,89 @@ +package hsperfdata + +import ( + "fmt" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" + "github.com/tokuhirom/go-hsperfdata/hsperfdata" +) + +type Hsperfdata struct { + User string + Tags []string +} + +var sampleConfig = ` + ## Use the hsperfdata directory belonging to a different user. + # user = "root" + # + ## Use the value for these keys in the hsperfdata as tags, not fields. By + ## default everything is a field. + # tags = ["sun.rt.jvmVersion"] +` + +func (n *Hsperfdata) SampleConfig() string { + return sampleConfig +} + +func (n *Hsperfdata) Repo() (*hsperfdata.Repository, error) { + if n.User == "" { + return hsperfdata.New() + } else { + return hsperfdata.NewUser(n.User) + } +} + +func (n *Hsperfdata) Gather(acc telegraf.Accumulator) error { + repo, err := n.Repo() + if err != nil { + return err + } + + files, err := repo.GetFiles() + if err != nil { + // the directory doesn't exist - so there aren't any Java processes running + return nil + } + + for _, file := range files { + result, err := file.Read() + if err != nil { + return err + } + + tags := map[string]string{"pid": file.GetPid()} + fields := result.GetMap() + + procname := result.GetProcName() + if procname != "" { + tags["procname"] = procname + } + + for _, tag := range n.Tags { + // don't tag metrics with "nil", just skip the tag if it's not there + if value, ok := fields[tag]; ok { + if valuestr, ok := value.(string); ok { + tags[tag] = valuestr + } else { + tags[tag] = fmt.Sprintf("%#v", fields[tag]) + } + delete(fields, tag) + } + } + + acc.AddFields("java", fields, tags) + } + + return nil +} + +func (n *Hsperfdata) Description() string { + return "Read performance data from running hotspot JVMs from shared memory" +} + +func init() { + inputs.Add("hsperfdata", func() telegraf.Input { + return &Hsperfdata{} + }) +} diff --git a/plugins/inputs/hsperfdata/hsperfdata_test.go b/plugins/inputs/hsperfdata/hsperfdata_test.go new file mode 100644 index 000000000..ff7541740 --- /dev/null +++ b/plugins/inputs/hsperfdata/hsperfdata_test.go @@ -0,0 +1,180 @@ +package hsperfdata + +import ( + "fmt" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" + + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const datadir = "hsperfdata_tokuhirom" + +func TestGatherNoTags(t *testing.T) { + setup() + defer teardown() + + hs := &Hsperfdata{User: "tokuhirom"} + + acc := testutil.Accumulator{} + require.NoError(t, hs.Gather(&acc)) + + assert.New(t).Equal(acc.NMetrics(), uint64(2)) + + acc.Lock() + defer acc.Unlock() + for _, p := range acc.Metrics { + if reflect.DeepEqual( + map[string]string{ + "pid": "13223", + }, + p.Tags) { + + assert.Equal( + t, + 212, + len(p.Fields)) + + // verify some of the fields (there's quite a lot!) + assert.Equal( + t, + "367001600", + p.Fields["sun.gc.generation.2.space.0.capacity"]) + assert.Equal( + t, + "/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Libraries", + p.Fields["sun.property.sun.boot.library.path"]) + + } else if reflect.DeepEqual(map[string]string{ + "pid": "21916", + "procname": "org.jetbrains.jps.cmdline.Launcher", + }, p.Tags) { + + assert.Equal( + t, + 253, + len(p.Fields), + fmt.Sprintf("wrong number of fields in %v", p)) + + assert.Equal( + t, + "3313990237", + p.Fields["java.ci.totalTime"]) + assert.Equal( + t, + "/Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/lib", + p.Fields["sun.property.sun.boot.library.path"]) + + } else { + msg := fmt.Sprintf("unknown with tags %v", p.Tags) + assert.Fail(t, msg) + } + } +} + +func TestGatherWithTags(t *testing.T) { + setup() + defer teardown() + + hs := &Hsperfdata{ + User: "tokuhirom", + Tags: []string{"java.property.java.vm.specification.vendor", "sun.gc.policy.minorCollectionSlope"}} + + acc := testutil.Accumulator{} + require.NoError(t, hs.Gather(&acc)) + + assert.New(t).Equal(acc.NMetrics(), uint64(2)) + + acc.Lock() + defer acc.Unlock() + for _, p := range acc.Metrics { + if reflect.DeepEqual( + map[string]string{ + "pid": "13223", + "java.property.java.vm.specification.vendor": "Sun Microsystems Inc.", + }, + p.Tags) { + + assert.Equal( + t, + 211, + len(p.Fields)) + + assert.NotContains( + t, + p.Fields, + "java.property.java.vm.specification.vendor", + "value promoted to tag") + + // verify some of the fields (there's quite a lot!) + assert.Equal( + t, + "367001600", + p.Fields["sun.gc.generation.2.space.0.capacity"]) + assert.Equal( + t, + "/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Libraries", + p.Fields["sun.property.sun.boot.library.path"]) + + } else if reflect.DeepEqual(map[string]string{ + "pid": "21916", + "procname": "org.jetbrains.jps.cmdline.Launcher", + "java.property.java.vm.specification.vendor": "Oracle Corporation", + "sun.gc.policy.minorCollectionSlope": "0", + }, p.Tags) { + + assert.Equal( + t, + 251, + len(p.Fields)) + + assert.NotContains( + t, + p.Fields, + "java.property.java.vm.specification.vendor", + "value promoted to tag") + + assert.Equal( + t, + "3313990237", + p.Fields["java.ci.totalTime"]) + assert.Equal( + t, + "/Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/lib", + p.Fields["sun.property.sun.boot.library.path"]) + + } else { + msg := fmt.Sprintf("unknown with tags %v", p.Tags) + assert.Fail(t, msg) + } + } +} + +func TestNoDirectoryNoMeasurements(t *testing.T) { + hs := &Hsperfdata{User: "tokuhirom"} + + acc := testutil.Accumulator{} + require.NoError(t, hs.Gather(&acc)) + assert.New(t).Equal(acc.NMetrics(), uint64(0)) +} + +func setup() { + _, filename, _, _ := runtime.Caller(1) + src := filepath.Join( + strings.Replace(filename, "hsperfdata_test.go", "testdata", 1), + datadir) + dest := filepath.Join( + os.TempDir(), + datadir) + os.Symlink(src, dest) +} + +func teardown() { + os.Remove(filepath.Join(os.TempDir(), datadir)) +} diff --git a/plugins/inputs/hsperfdata/testdata/hsperfdata_tokuhirom/13223 b/plugins/inputs/hsperfdata/testdata/hsperfdata_tokuhirom/13223 new file mode 100644 index 000000000..53e73d2df Binary files /dev/null and b/plugins/inputs/hsperfdata/testdata/hsperfdata_tokuhirom/13223 differ diff --git a/plugins/inputs/hsperfdata/testdata/hsperfdata_tokuhirom/21916 b/plugins/inputs/hsperfdata/testdata/hsperfdata_tokuhirom/21916 new file mode 100644 index 000000000..dd2b9b80a Binary files /dev/null and b/plugins/inputs/hsperfdata/testdata/hsperfdata_tokuhirom/21916 differ