Merge fade65d044
into f05d89ed72
This commit is contained in:
commit
bb72d46ebf
|
@ -173,6 +173,7 @@ Telegraf currently has support for collecting metrics from:
|
|||
* exec (generic JSON-emitting executable plugin)
|
||||
* haproxy
|
||||
* httpjson (generic JSON-emitting http service plugin)
|
||||
* jolokia (remote JMX with JSON over HTTP)
|
||||
* kafka_consumer
|
||||
* leofs
|
||||
* lustre2
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
_ "github.com/influxdb/telegraf/plugins/exec"
|
||||
_ "github.com/influxdb/telegraf/plugins/haproxy"
|
||||
_ "github.com/influxdb/telegraf/plugins/httpjson"
|
||||
_ "github.com/influxdb/telegraf/plugins/jolokia"
|
||||
_ "github.com/influxdb/telegraf/plugins/kafka_consumer"
|
||||
_ "github.com/influxdb/telegraf/plugins/leofs"
|
||||
_ "github.com/influxdb/telegraf/plugins/lustre2"
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# Telegraf plugin: Jolokia
|
||||
|
||||
#### Plugin arguments:
|
||||
- **context** string: Context root used of jolokia url
|
||||
- **servers** []Server: List of servers
|
||||
+ **name** string: Server's logical name
|
||||
+ **host** string: Server's ip address or hostname
|
||||
+ **port** string: Server's listening port
|
||||
- **metrics** []Metric
|
||||
+ **name** string: Name of the measure
|
||||
+ **jmx** string: Jmx path that identifies mbeans attributes
|
||||
+ **pass** []string: Attributes to retain when collecting values
|
||||
+ **drop** []string: Attributes to drop when collecting values
|
||||
|
||||
#### Description
|
||||
|
||||
The Jolokia plugin collects JVM metrics exposed as MBean's attributes through jolokia REST endpoint. All metrics
|
||||
are collected for each server configured.
|
||||
|
||||
See: https://jolokia.org/
|
||||
|
||||
# Measurements:
|
||||
Jolokia plugin produces one measure for each metric configured, adding Server's `name`, `host` and `port` as tags.
|
||||
|
||||
Given a configuration like:
|
||||
|
||||
```ini
|
||||
[jolokia]
|
||||
|
||||
[[jolokia.servers]]
|
||||
name = "as-service-1"
|
||||
host = "127.0.0.1"
|
||||
port = "8080"
|
||||
|
||||
[[jolokia.servers]]
|
||||
name = "as-service-2"
|
||||
host = "127.0.0.1"
|
||||
port = "8180"
|
||||
|
||||
[[jolokia.metrics]]
|
||||
name = "heap_memory_usage"
|
||||
jmx = "/java.lang:type=Memory/HeapMemoryUsage"
|
||||
pass = ["used", "max"]
|
||||
```
|
||||
|
||||
The collected metrics will be:
|
||||
|
||||
```
|
||||
jolokia_heap_memory_usage name=as-service-1,host=127.0.0.1,port=8080 used=xxx,max=yyy
|
||||
jolokia_heap_memory_usage name=as-service-2,host=127.0.0.1,port=8180 used=vvv,max=zzz
|
||||
```
|
|
@ -0,0 +1,223 @@
|
|||
package jolokia
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdb/telegraf/plugins"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Name string
|
||||
Host string
|
||||
Port string
|
||||
}
|
||||
|
||||
type Metric struct {
|
||||
Name string
|
||||
Jmx string
|
||||
Pass []string
|
||||
Drop []string
|
||||
}
|
||||
|
||||
type JolokiaClient interface {
|
||||
MakeRequest(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type JolokiaClientImpl struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (c JolokiaClientImpl) MakeRequest(req *http.Request) (*http.Response, error) {
|
||||
return c.client.Do(req)
|
||||
}
|
||||
|
||||
type Jolokia struct {
|
||||
jClient JolokiaClient
|
||||
Context string
|
||||
Servers []Server
|
||||
Metrics []Metric
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
func (j *Jolokia) SampleConfig() string {
|
||||
return `
|
||||
# This is the context root used to compose the jolokia url
|
||||
context = "/jolokia/read"
|
||||
|
||||
# Tags added to each measurements
|
||||
[jolokia.tags]
|
||||
group = "as"
|
||||
|
||||
# List of servers exposing jolokia read service
|
||||
[[jolokia.servers]]
|
||||
name = "stable"
|
||||
host = "192.168.103.2"
|
||||
port = "8180"
|
||||
|
||||
# List of metrics collected on above servers
|
||||
# Each metric consists in a name, a jmx path and either a pass or drop slice attributes
|
||||
# This collect all heap memory usage metrics
|
||||
[[jolokia.metrics]]
|
||||
name = "heap_memory_usage"
|
||||
jmx = "/java.lang:type=Memory/HeapMemoryUsage"
|
||||
|
||||
|
||||
# This drops the 'committed' value from Eden space measurement
|
||||
[[jolokia.metrics]]
|
||||
name = "memory_eden"
|
||||
jmx = "/java.lang:type=MemoryPool,name=PS Eden Space/Usage"
|
||||
drop = [ "committed" ]
|
||||
|
||||
|
||||
# This passes only DaemonThreadCount and ThreadCount
|
||||
[[jolokia.metrics]]
|
||||
name = "heap_threads"
|
||||
jmx = "/java.lang:type=Threading"
|
||||
pass = [
|
||||
"DaemonThreadCount",
|
||||
"ThreadCount"
|
||||
]
|
||||
`
|
||||
}
|
||||
|
||||
func (j *Jolokia) Description() string {
|
||||
return "Read JMX metrics through Jolokia"
|
||||
}
|
||||
|
||||
func (j *Jolokia) getAttr(requestUrl *url.URL) (map[string]interface{}, error) {
|
||||
// Create + send request
|
||||
req, err := http.NewRequest("GET", requestUrl.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := j.jClient.MakeRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Process response
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("Response from url \"%s\" has status code %d (%s), expected %d (%s)",
|
||||
requestUrl,
|
||||
resp.StatusCode,
|
||||
http.StatusText(resp.StatusCode),
|
||||
http.StatusOK,
|
||||
http.StatusText(http.StatusOK))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// read body
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unmarshal json
|
||||
var jsonOut map[string]interface{}
|
||||
if err = json.Unmarshal([]byte(body), &jsonOut); err != nil {
|
||||
return nil, errors.New("Error decoding JSON response")
|
||||
}
|
||||
|
||||
return jsonOut, nil
|
||||
}
|
||||
|
||||
func (m *Metric) shouldPass(field string) bool {
|
||||
|
||||
if m.Pass != nil {
|
||||
|
||||
for _, pass := range m.Pass {
|
||||
if strings.HasPrefix(field, pass) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if m.Drop != nil {
|
||||
|
||||
for _, drop := range m.Drop {
|
||||
if strings.HasPrefix(field, drop) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *Metric) filterFields(fields map[string]interface{}) map[string]interface{} {
|
||||
|
||||
for field, _ := range fields {
|
||||
if !m.shouldPass(field) {
|
||||
delete(fields, field)
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func (j *Jolokia) Gather(acc plugins.Accumulator) error {
|
||||
|
||||
context := j.Context //"/jolokia/read"
|
||||
servers := j.Servers
|
||||
metrics := j.Metrics
|
||||
tags := j.Tags
|
||||
|
||||
if tags == nil {
|
||||
tags = map[string]string{}
|
||||
}
|
||||
|
||||
for _, server := range servers {
|
||||
for _, metric := range metrics {
|
||||
|
||||
measurement := metric.Name
|
||||
jmxPath := metric.Jmx
|
||||
|
||||
tags["server"] = server.Name
|
||||
tags["port"] = server.Port
|
||||
tags["host"] = server.Host
|
||||
|
||||
// Prepare URL
|
||||
requestUrl, err := url.Parse("http://" + server.Host + ":" + server.Port + context + jmxPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, _ := j.getAttr(requestUrl)
|
||||
|
||||
if values, ok := out["value"]; ok {
|
||||
switch values.(type) {
|
||||
case map[string]interface{}:
|
||||
acc.AddFields(measurement, metric.filterFields(values.(map[string]interface{})), tags)
|
||||
case interface{}:
|
||||
acc.Add(measurement, values.(interface{}), tags)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Missing key 'value' in '%s' output response\n", requestUrl.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
plugins.Add("jolokia", func() plugins.Plugin {
|
||||
return &Jolokia{jClient: &JolokiaClientImpl{client: &http.Client{}}}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package jolokia
|
||||
|
||||
import (
|
||||
_ "fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdb/telegraf/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
_ "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const validMultiValueJSON = `
|
||||
{
|
||||
"request":{
|
||||
"mbean":"java.lang:type=Memory",
|
||||
"attribute":"HeapMemoryUsage",
|
||||
"type":"read"
|
||||
},
|
||||
"value":{
|
||||
"init":67108864,
|
||||
"committed":456130560,
|
||||
"max":477626368,
|
||||
"used":203288528
|
||||
},
|
||||
"timestamp":1446129191,
|
||||
"status":200
|
||||
}`
|
||||
|
||||
const validSingleValueJSON = `
|
||||
{
|
||||
"request":{
|
||||
"path":"used",
|
||||
"mbean":"java.lang:type=Memory",
|
||||
"attribute":"HeapMemoryUsage",
|
||||
"type":"read"
|
||||
},
|
||||
"value":209274376,
|
||||
"timestamp":1446129256,
|
||||
"status":200
|
||||
}`
|
||||
|
||||
const invalidJSON = "I don't think this is JSON"
|
||||
|
||||
const empty = ""
|
||||
|
||||
var Servers = []Server{Server{Name: "as1", Host: "127.0.0.1", Port: "8080"}}
|
||||
var HeapMetric = Metric{Name: "heap_memory_usage", Jmx: "/java.lang:type=Memory/HeapMemoryUsage"}
|
||||
var UsedHeapMetric = Metric{Name: "heap_memory_usage", Jmx: "/java.lang:type=Memory/HeapMemoryUsage", Pass: []string{"used"}}
|
||||
|
||||
type jolokiaClientStub struct {
|
||||
responseBody string
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (c jolokiaClientStub) MakeRequest(req *http.Request) (*http.Response, error) {
|
||||
resp := http.Response{}
|
||||
resp.StatusCode = c.statusCode
|
||||
resp.Body = ioutil.NopCloser(strings.NewReader(c.responseBody))
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// Generates a pointer to an HttpJson object that uses a mock HTTP client.
|
||||
// Parameters:
|
||||
// response : Body of the response that the mock HTTP client should return
|
||||
// statusCode: HTTP status code the mock HTTP client should return
|
||||
//
|
||||
// Returns:
|
||||
// *HttpJson: Pointer to an HttpJson object that uses the generated mock HTTP client
|
||||
func genJolokiaClientStub(response string, statusCode int, servers []Server, metrics []Metric) *Jolokia {
|
||||
return &Jolokia{
|
||||
jClient: jolokiaClientStub{responseBody: response, statusCode: statusCode},
|
||||
Servers: servers,
|
||||
Metrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the proper values are ignored or collected
|
||||
func TestHttpJsonMultiValue(t *testing.T) {
|
||||
|
||||
jolokia := genJolokiaClientStub(validMultiValueJSON, 200, Servers, []Metric{HeapMetric})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := jolokia.Gather(&acc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(acc.Points))
|
||||
|
||||
assert.True(t, acc.CheckFieldsValue("heap_memory_usage", map[string]interface{}{"init": 67108864.0,
|
||||
"committed": 456130560.0,
|
||||
"max": 477626368.0,
|
||||
"used": 203288528.0}))
|
||||
}
|
||||
|
||||
// Test that the proper values are ignored or collected
|
||||
func TestHttpJsonMultiValueWithPass(t *testing.T) {
|
||||
|
||||
jolokia := genJolokiaClientStub(validMultiValueJSON, 200, Servers, []Metric{UsedHeapMetric})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := jolokia.Gather(&acc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(acc.Points))
|
||||
|
||||
assert.True(t, acc.CheckFieldsValue("heap_memory_usage", map[string]interface{}{"used": 203288528.0}))
|
||||
}
|
||||
|
||||
// Test that the proper values are ignored or collected
|
||||
func TestHttpJsonMultiValueTags(t *testing.T) {
|
||||
|
||||
jolokia := genJolokiaClientStub(validMultiValueJSON, 200, Servers, []Metric{UsedHeapMetric})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := jolokia.Gather(&acc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(acc.Points))
|
||||
assert.NoError(t, acc.ValidateTaggedFieldsValue("heap_memory_usage", map[string]interface{}{"used": 203288528.0}, map[string]string{"host": "127.0.0.1", "port": "8080", "server": "as1"}))
|
||||
}
|
||||
|
||||
// Test that the proper values are ignored or collected
|
||||
func TestHttpJsonSingleValueTags(t *testing.T) {
|
||||
|
||||
jolokia := genJolokiaClientStub(validSingleValueJSON, 200, Servers, []Metric{UsedHeapMetric})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := jolokia.Gather(&acc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(acc.Points))
|
||||
assert.NoError(t, acc.ValidateTaggedFieldsValue("heap_memory_usage", map[string]interface{}{"value": 209274376.0}, map[string]string{"host": "127.0.0.1", "port": "8080", "server": "as1"}))
|
||||
}
|
||||
|
||||
// Test that the proper values are ignored or collected
|
||||
func TestHttpJsonOn404(t *testing.T) {
|
||||
|
||||
jolokia := genJolokiaClientStub(validMultiValueJSON, 404, Servers, []Metric{UsedHeapMetric})
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := jolokia.Gather(&acc)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(acc.Points))
|
||||
}
|
|
@ -106,15 +106,20 @@ func (a *Accumulator) Get(measurement string) (*Point, bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
// CheckValue calls CheckFieldsValue passing a single-value map as fields
|
||||
func (a *Accumulator) CheckValue(measurement string, val interface{}) bool {
|
||||
return a.CheckFieldsValue(measurement, map[string]interface{}{"value": val})
|
||||
}
|
||||
|
||||
// CheckValue checks that the accumulators point for the given measurement
|
||||
// is the same as the given value.
|
||||
func (a *Accumulator) CheckValue(measurement string, val interface{}) bool {
|
||||
func (a *Accumulator) CheckFieldsValue(measurement string, fields map[string]interface{}) bool {
|
||||
for _, p := range a.Points {
|
||||
if p.Measurement == measurement {
|
||||
return p.Values["value"] == val
|
||||
return reflect.DeepEqual(fields, p.Values)
|
||||
}
|
||||
}
|
||||
fmt.Printf("CheckValue failed, measurement %s, value %s", measurement, val)
|
||||
fmt.Printf("CheckFieldsValue failed, measurement %s, fields %s", measurement, fields)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -127,12 +132,35 @@ func (a *Accumulator) CheckTaggedValue(
|
|||
return a.ValidateTaggedValue(measurement, val, tags) == nil
|
||||
}
|
||||
|
||||
// ValidateTaggedValue validates that the given measurement and value exist
|
||||
// in the accumulator and with the given tags.
|
||||
// ValidateTaggedValue calls ValidateTaggedFieldsValue passing a single-value map as fields
|
||||
func (a *Accumulator) ValidateTaggedValue(
|
||||
measurement string,
|
||||
val interface{},
|
||||
tags map[string]string,
|
||||
) error {
|
||||
return a.ValidateTaggedFieldsValue(measurement, map[string]interface{}{"value": val}, tags)
|
||||
}
|
||||
|
||||
// ValidateValue calls ValidateTaggedValue
|
||||
func (a *Accumulator) ValidateValue(measurement string, val interface{}) error {
|
||||
return a.ValidateTaggedValue(measurement, val, nil)
|
||||
}
|
||||
|
||||
// CheckTaggedFieldsValue calls ValidateTaggedFieldsValue
|
||||
func (a *Accumulator) CheckTaggedFieldsValue(
|
||||
measurement string,
|
||||
fields map[string]interface{},
|
||||
tags map[string]string,
|
||||
) bool {
|
||||
return a.ValidateTaggedFieldsValue(measurement, fields, tags) == nil
|
||||
}
|
||||
|
||||
// ValidateTaggedValue validates that the given measurement and value exist
|
||||
// in the accumulator and with the given tags.
|
||||
func (a *Accumulator) ValidateTaggedFieldsValue(
|
||||
measurement string,
|
||||
fields map[string]interface{},
|
||||
tags map[string]string,
|
||||
) error {
|
||||
if tags == nil {
|
||||
tags = map[string]string{}
|
||||
|
@ -143,9 +171,8 @@ func (a *Accumulator) ValidateTaggedValue(
|
|||
}
|
||||
|
||||
if p.Measurement == measurement {
|
||||
if p.Values["value"] != val {
|
||||
return fmt.Errorf("%v (%T) != %v (%T)",
|
||||
p.Values["value"], p.Values["value"], val, val)
|
||||
if !reflect.DeepEqual(fields, p.Values) {
|
||||
return fmt.Errorf("%v != %v ", fields, p.Values)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -154,9 +181,12 @@ func (a *Accumulator) ValidateTaggedValue(
|
|||
return fmt.Errorf("unknown measurement %s with tags %v", measurement, tags)
|
||||
}
|
||||
|
||||
// ValidateValue calls ValidateTaggedValue
|
||||
func (a *Accumulator) ValidateValue(measurement string, val interface{}) error {
|
||||
return a.ValidateTaggedValue(measurement, val, nil)
|
||||
// ValidateFieldsValue calls ValidateTaggedFieldsValue
|
||||
func (a *Accumulator) ValidateFieldsValue(
|
||||
measurement string,
|
||||
fields map[string]interface{},
|
||||
) error {
|
||||
return a.ValidateTaggedValue(measurement, fields, nil)
|
||||
}
|
||||
|
||||
func (a *Accumulator) ValidateTaggedFields(
|
||||
|
|
Loading…
Reference in New Issue