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
|
||||
*~
|
||||
*#
|
||||
.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)
|
||||
* [prometheus](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/prometheus_client)
|
||||
* [riemann](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/riemann)
|
||||
* [newrelic](https://github.com/influxdata/telegraf/tree/master/plugins/outputs/riemann)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
_ "github.com/influxdata/telegraf/plugins/outputs/kinesis"
|
||||
_ "github.com/influxdata/telegraf/plugins/outputs/librato"
|
||||
_ "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/opentsdb"
|
||||
_ "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