Newrelic Plugin
Adds a plugin to send new relic metric data. This is generally functional, although not all kinks are worked out. We currently use a "demo" uid for testing purposes.
This commit is contained in:
parent
c046232425
commit
a627c348a4
|
@ -5,3 +5,4 @@ tivan
|
||||||
.idea
|
.idea
|
||||||
*~
|
*~
|
||||||
*#
|
*#
|
||||||
|
.tags
|
||||||
|
|
|
@ -246,6 +246,7 @@ want to add support for another service or third-party API.
|
||||||
* [opentsdb](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/opentsdb)
|
* [opentsdb](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/opentsdb)
|
||||||
* [prometheus](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/prometheus_client)
|
* [prometheus](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/prometheus_client)
|
||||||
* [riemann](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/riemann)
|
* [riemann](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/riemann)
|
||||||
|
* [newrelic](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/riemann)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/kinesis"
|
_ "github.com/influxdata/telegraf/plugins/outputs/kinesis"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/librato"
|
_ "github.com/influxdata/telegraf/plugins/outputs/librato"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/mqtt"
|
_ "github.com/influxdata/telegraf/plugins/outputs/mqtt"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/outputs/newrelic"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/nsq"
|
_ "github.com/influxdata/telegraf/plugins/outputs/nsq"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/opentsdb"
|
_ "github.com/influxdata/telegraf/plugins/outputs/opentsdb"
|
||||||
_ "github.com/influxdata/telegraf/plugins/outputs/prometheus_client"
|
_ "github.com/influxdata/telegraf/plugins/outputs/prometheus_client"
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
package newrelic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"net/http"
|
||||||
|
// "io"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/outputs"
|
||||||
|
"github.com/influxdata/telegraf/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewRelic struct {
|
||||||
|
ApiKey string
|
||||||
|
GuidBase string
|
||||||
|
Timeout internal.Duration
|
||||||
|
|
||||||
|
LastWrite time.Time
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var sampleConfig = `
|
||||||
|
## Your NewRelic Api Key
|
||||||
|
api_key = "XXXX" # required
|
||||||
|
|
||||||
|
## Guid base
|
||||||
|
## This allows you have unique GUIDs for your installation. This will generate
|
||||||
|
## a separate "plugin" GUID for each of the inputs that you use.
|
||||||
|
##
|
||||||
|
## see https://docs.newrelic.com/docs/plugins/plugin-developer-resources/planning-your-plugin/parts-plugin#guid
|
||||||
|
##
|
||||||
|
## This setting will allow you to "fork" your "plugins", and have your own
|
||||||
|
## dashboards and settings for them.
|
||||||
|
## The default behaviour is that the original author of the plugin sets up
|
||||||
|
## all the dashboards; other users cannot modify them.
|
||||||
|
## As it is very hard to provide useful defaults for all possible setup, we
|
||||||
|
## instead allow you to make your "own plugin" and modify the dashboards.
|
||||||
|
##
|
||||||
|
## The drawback is that your GUID must be unique, and that you must setup
|
||||||
|
## your own dashboards for everything.
|
||||||
|
##
|
||||||
|
## TODO: The default for this should be
|
||||||
|
## a "proper" GUID that is maintained to have reasonable default
|
||||||
|
# guid_base = 'my.domain.something.something' # TODO must still be implemented
|
||||||
|
|
||||||
|
## Metric Type TODO - Not yet implemented
|
||||||
|
##
|
||||||
|
## Can either be "Component" or "Custom"
|
||||||
|
##
|
||||||
|
## Component metrics are the default for plugins. They make the metrics
|
||||||
|
## available even to free accounts, but with the restrictions mentioned above.
|
||||||
|
##
|
||||||
|
## Custom metrics don't show up as plugins. They are freely usable in custom
|
||||||
|
## dashboards, but you need to have a paid subscription to see the data.
|
||||||
|
##
|
||||||
|
## Default is "Component"
|
||||||
|
# metric_type = "Custom"
|
||||||
|
`
|
||||||
|
func (nr *NewRelic) Connect() error {
|
||||||
|
if nr.ApiKey == "" {
|
||||||
|
return fmt.Errorf("apikey is a required field for newrelic output")
|
||||||
|
}
|
||||||
|
|
||||||
|
if nr.GuidBase == "" {
|
||||||
|
nr.GuidBase = "com.influxdata.demo-newrelic-agent"
|
||||||
|
}
|
||||||
|
nr.client = &http.Client{
|
||||||
|
Timeout: nr.Timeout.Duration,
|
||||||
|
}
|
||||||
|
nr.LastWrite = time.Now()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nr *NewRelic) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nr *NewRelic) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nr *NewRelic) Description() string {
|
||||||
|
return "Send telegraf metrics to NewRelic"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nr *NewRelic) PostPluginData(jsonData []byte) error {
|
||||||
|
req, reqErr := http.NewRequest("POST", "https://platform-api.newrelic.com/platform/v1/metrics", bytes.NewBuffer(jsonData))
|
||||||
|
if reqErr != nil { return reqErr }
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
req.Header.Add("X-License-Key", nr.ApiKey)
|
||||||
|
|
||||||
|
resp, respErr := nr.client.Do(req)
|
||||||
|
if respErr != nil { return respErr }
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode > 209 {
|
||||||
|
return fmt.Errorf("received bad status code, %d\n", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nr *NewRelic) SendDataPage(dataPage interface{}) error {
|
||||||
|
cmpJson, err := json.Marshal(dataPage)
|
||||||
|
if err != nil { return err }
|
||||||
|
fmt.Println("Sending " + string(cmpJson) + " <")
|
||||||
|
return nr.PostPluginData(cmpJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nr *NewRelic) Write(metrics []telegraf.Metric) error {
|
||||||
|
data := NewRelicData{LastWrite: nr.LastWrite, Hosts: make(map[string][]NewRelicComponent)}
|
||||||
|
data.AddMetrics(metrics)
|
||||||
|
|
||||||
|
for _, dataPage := range(data.DataSets()) {
|
||||||
|
nr.SendDataPage(dataPage)
|
||||||
|
}
|
||||||
|
nr.LastWrite = time.Now()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
outputs.Add("newrelic", func() telegraf.Output { return &NewRelic{} })
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package newrelic
|
||||||
|
|
||||||
|
import(
|
||||||
|
"fmt"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"bytes"
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewRelicComponent struct {
|
||||||
|
Duration int
|
||||||
|
TMetric telegraf.Metric
|
||||||
|
GuidBase string
|
||||||
|
tags *NewRelicTags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrc NewRelicComponent) Tags() *NewRelicTags {
|
||||||
|
if nrc.tags == nil {
|
||||||
|
nrc.tags = &NewRelicTags{};
|
||||||
|
nrc.tags.Fill(nrc.TMetric.Tags())
|
||||||
|
}
|
||||||
|
return nrc.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrc NewRelicComponent) Name() string {
|
||||||
|
return nrc.TMetric.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func metricValue(value interface{}) int {
|
||||||
|
result := 0
|
||||||
|
switch value.(type) {
|
||||||
|
case int32:
|
||||||
|
result = int(value.(int32))
|
||||||
|
case int64:
|
||||||
|
result = int(value.(int64))
|
||||||
|
case float32:
|
||||||
|
result = int(value.(float32))
|
||||||
|
case float64:
|
||||||
|
result = int(value.(float64))
|
||||||
|
default:
|
||||||
|
result = 0
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrc* NewRelicComponent) MetricName(originalName string) string {
|
||||||
|
var nameBuffer bytes.Buffer
|
||||||
|
nameBuffer.WriteString("Component/")
|
||||||
|
nameBuffer.WriteString(strings.Title(nrc.TMetric.Name()))
|
||||||
|
nameBuffer.WriteString("/")
|
||||||
|
nameBuffer.WriteString(strings.Title(originalName))
|
||||||
|
tags := nrc.Tags()
|
||||||
|
for _, key := range tags.SortedKeys {
|
||||||
|
nameBuffer.WriteString(fmt.Sprintf("/%s-%s", key, tags.GetTag(key)))
|
||||||
|
}
|
||||||
|
nameBuffer.WriteString("[Units]")
|
||||||
|
return nameBuffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrc *NewRelicComponent) Metrics() map[string]int {
|
||||||
|
result := make(map[string]int)
|
||||||
|
for k,v := range(nrc.TMetric.Fields()) {
|
||||||
|
result[nrc.MetricName(k)] = metricValue(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrc NewRelicComponent) Hostname() string {
|
||||||
|
result := nrc.Tags().Hostname
|
||||||
|
if result == "" {
|
||||||
|
osname, err := os.Hostname()
|
||||||
|
if err == nil { result = "unknown" } else { result = osname }
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrc *NewRelicComponent) Guid() string {
|
||||||
|
return fmt.Sprintf("%s-%s", nrc.GuidBase, strings.ToLower(nrc.TMetric.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrc NewRelicComponent) MarshalJSON() ([]byte, error) {
|
||||||
|
myData := map[string]interface{} {
|
||||||
|
"name": nrc.Hostname(),
|
||||||
|
"guid": nrc.Guid(),
|
||||||
|
"duration": nrc.Duration,
|
||||||
|
"metrics": nrc.Metrics(),
|
||||||
|
}
|
||||||
|
return json.Marshal(myData)
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package newrelic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
// "github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/outputs/newrelic"
|
||||||
|
// "github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestName(t *testing.T) {
|
||||||
|
dm := newrelic.DemoMetric{MyName: "Foo", TagList: newrelic.DemoTagList()}
|
||||||
|
component := newrelic.NewRelicComponent{TMetric: dm}
|
||||||
|
require.EqualValues(t, component.Name(), "Foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGuid(t *testing.T) {
|
||||||
|
dm := newrelic.DemoMetric{MyName: "Lulu", TagList: newrelic.DemoTagList()}
|
||||||
|
component := newrelic.NewRelicComponent{TMetric: dm, GuidBase: "org.betterplace.telegraf-agent"}
|
||||||
|
require.EqualValues(t, component.Guid(), "org.betterplace.telegraf-agent-lulu")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTags(t *testing.T) {
|
||||||
|
dm := newrelic.DemoMetric{MyName: "Lulu", TagList: newrelic.DemoTagList()}
|
||||||
|
component := newrelic.NewRelicComponent{TMetric: dm}
|
||||||
|
component.Tags()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostname(t *testing.T) {
|
||||||
|
tagList := newrelic.DemoTagList()
|
||||||
|
tagList["host"] = "baba"
|
||||||
|
dm := newrelic.DemoMetric{MyName: "Lulu", TagList: tagList}
|
||||||
|
component := newrelic.NewRelicComponent{TMetric: dm}
|
||||||
|
require.EqualValues(t, component.Hostname(), "baba")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetricName(t *testing.T) {
|
||||||
|
tagList := newrelic.DemoTagList()
|
||||||
|
dm := newrelic.DemoMetric{MyName: "Lulu", TagList: tagList}
|
||||||
|
component := newrelic.NewRelicComponent{TMetric: dm}
|
||||||
|
require.EqualValues(t, component.MetricName("fnord"), "Component/Lulu/Fnord/Fluff-naa/Hoof-bar/Zoo-goo[Units]")
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package newrelic
|
||||||
|
|
||||||
|
import(
|
||||||
|
"time"
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewRelicData struct {
|
||||||
|
LastWrite time.Time
|
||||||
|
Hosts map[string][]NewRelicComponent
|
||||||
|
GuidBase string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrd *NewRelicData) AddMetric(metric telegraf.Metric) {
|
||||||
|
component := NewRelicComponent{
|
||||||
|
Duration: int(time.Since(nrd.LastWrite).Seconds()),
|
||||||
|
TMetric: metric,
|
||||||
|
GuidBase: nrd.GuidBase}
|
||||||
|
host := component.Hostname()
|
||||||
|
nrd.Hosts[host] = append(nrd.Hosts[host],component)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrd *NewRelicData) AddMetrics(metrics []telegraf.Metric) {
|
||||||
|
for _, metric := range(metrics) {
|
||||||
|
nrd.AddMetric(metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrd *NewRelicData) DataSets() []interface{} {
|
||||||
|
result := make([]interface{}, 0)
|
||||||
|
for host, components := range(nrd.Hosts) {
|
||||||
|
result = append(result, map[string]interface{} { "agent": map[string]string { "host": host, "version": "0.0.1" }, "components": components })
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package newrelic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/outputs/newrelic"
|
||||||
|
// "github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddMetrics(t *testing.T) {
|
||||||
|
dm := []telegraf.Metric{newrelic.DemoMetric{MyName: "Foo", TagList: newrelic.DemoTagList()}}
|
||||||
|
data := newrelic.NewRelicData{
|
||||||
|
LastWrite: time.Now(),
|
||||||
|
Hosts: make(map[string][]newrelic.NewRelicComponent),
|
||||||
|
GuidBase: "org.betterplace.test-foo"}
|
||||||
|
data.AddMetrics(dm)
|
||||||
|
require.EqualValues(t, len(data.Hosts), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDataSets(t *testing.T) {
|
||||||
|
dm := []telegraf.Metric{newrelic.DemoMetric{MyName: "Foo", TagList: newrelic.DemoTagList()}}
|
||||||
|
data := newrelic.NewRelicData{
|
||||||
|
LastWrite: time.Now(),
|
||||||
|
Hosts: make(map[string][]newrelic.NewRelicComponent),
|
||||||
|
GuidBase: "org.betterplace.test-foo"}
|
||||||
|
data.AddMetrics(dm)
|
||||||
|
sets := data.DataSets()
|
||||||
|
require.EqualValues(t, len(sets), 1)
|
||||||
|
set, _ := sets[0].(map[string]interface{})
|
||||||
|
agent := set["agent"].(map[string]string)
|
||||||
|
require.EqualValues(t, agent["host"], "Hulu")
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package newrelic
|
||||||
|
|
||||||
|
import(
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewRelicTags struct {
|
||||||
|
Tags *map[string]string
|
||||||
|
SortedKeys []string
|
||||||
|
Hostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TagValue(tagValue string) string {
|
||||||
|
tagValueParts := strings.Split(tagValue, "/")
|
||||||
|
var clean_parts []string
|
||||||
|
for _, part := range(tagValueParts) {
|
||||||
|
if part != "" { clean_parts = append(clean_parts, part) }
|
||||||
|
}
|
||||||
|
if len(clean_parts) > 0 {
|
||||||
|
tagValue = strings.ToLower(strings.Join(clean_parts, "-"))
|
||||||
|
} else {
|
||||||
|
tagValue = "root"
|
||||||
|
}
|
||||||
|
return tagValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrt *NewRelicTags) Fill(originalTags map[string]string) {
|
||||||
|
nrt.SortedKeys = make([]string, 0, len(originalTags))
|
||||||
|
tags := make(map[string]string)
|
||||||
|
nrt.Tags = &tags
|
||||||
|
for key, value := range originalTags {
|
||||||
|
if key != "host" {
|
||||||
|
nrt.SortedKeys = append(nrt.SortedKeys, key)
|
||||||
|
(*nrt.Tags)[key] = TagValue(value)
|
||||||
|
} else {
|
||||||
|
nrt.Hostname = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(nrt.SortedKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nrt *NewRelicTags) GetTag(tagKey string) string {
|
||||||
|
return (*nrt.Tags)[tagKey]
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package newrelic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"github.com/influxdata/telegraf/plugins/outputs/newrelic"
|
||||||
|
// "github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTagValue(t *testing.T) {
|
||||||
|
require.EqualValues(t, newrelic.TagValue("Hello/World"), "hello-world")
|
||||||
|
require.EqualValues(t, newrelic.TagValue("/Hello/World"), "hello-world")
|
||||||
|
require.EqualValues(t, newrelic.TagValue(""), "root")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFillSimple(t *testing.T) {
|
||||||
|
tags := &newrelic.NewRelicTags{}
|
||||||
|
tags.Fill(newrelic.DemoTagList())
|
||||||
|
require.EqualValues(t, tags.SortedKeys, []string{"Fluff", "Hoof", "Zoo"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFillWithHost(t *testing.T) {
|
||||||
|
tags := &newrelic.NewRelicTags{}
|
||||||
|
demoList := newrelic.DemoTagList()
|
||||||
|
demoList["host"] = "hulu"
|
||||||
|
tags.Fill(demoList)
|
||||||
|
require.EqualValues(t, tags.SortedKeys, []string{"Fluff", "Hoof", "Zoo"})
|
||||||
|
require.EqualValues(t, tags.Hostname, "hulu")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFillAndGetTag(t *testing.T) {
|
||||||
|
tags := &newrelic.NewRelicTags{}
|
||||||
|
tags.Fill(newrelic.DemoTagList())
|
||||||
|
require.EqualValues(t, tags.GetTag("Zoo"), "goo")
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package newrelic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"github.com/influxdata/influxdb/client/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DemoTagList() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"Fluff": "Naa",
|
||||||
|
"Hoof": "Bar",
|
||||||
|
"Zoo": "Goo",
|
||||||
|
"host": "Hulu",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DemoMetric struct {
|
||||||
|
MyName string
|
||||||
|
TagList map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dm DemoMetric) Name() string { return dm.MyName }
|
||||||
|
func (dm DemoMetric) Tags() map[string]string { return dm.TagList }
|
||||||
|
func (dm DemoMetric) Time() time.Time { return time.Now() }
|
||||||
|
func (dm DemoMetric) UnixNano() int64 { return 0 }
|
||||||
|
func (dm DemoMetric) Fields() map[string]interface{} { return nil }
|
||||||
|
func (dm DemoMetric) String() string { return "StringRepresenation" }
|
||||||
|
func (dm DemoMetric) PrecisionString(precison string) string { return "PrecisionString" }
|
||||||
|
func (dm DemoMetric) Point() *client.Point { return nil }
|
Loading…
Reference in New Issue