Add Appdynamics output plugin
This commit is contained in:
parent
85dee02a3b
commit
66b589b25d
|
@ -3,6 +3,7 @@ package all
|
|||
import (
|
||||
_ "github.com/influxdata/telegraf/plugins/outputs/amon"
|
||||
_ "github.com/influxdata/telegraf/plugins/outputs/amqp"
|
||||
_ "github.com/influxdata/telegraf/plugins/outputs/appdynamics"
|
||||
_ "github.com/influxdata/telegraf/plugins/outputs/cloudwatch"
|
||||
_ "github.com/influxdata/telegraf/plugins/outputs/datadog"
|
||||
_ "github.com/influxdata/telegraf/plugins/outputs/file"
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# Appdynamics Output Plugin
|
||||
|
||||
This plugin writes to [Appdynamics Machine Agent](http://localhost:8293)
|
||||
via raw TCP.
|
||||
|
||||
## Configuration:
|
||||
|
||||
```toml
|
||||
## controller information tor connect and retrieve tier-id value
|
||||
controllerTierURL = "https://foo.saas.appdynamics.com/controller/rest/applications/bar/tiers/baz?output=JSON"
|
||||
controllerUserName = "apiuser"
|
||||
controllerPassword = "apipass"
|
||||
## Machine agent custom metrics listener url format string
|
||||
## |Component:%d| gets transformed into |Component:id| during initialization - where 'id' is a tier-id for
|
||||
## this controller application/tier combination
|
||||
agentURL = "http://localhost:8293/machineagent/metrics?name=Server|Component:%d|Custom+Metrics|"
|
||||
```
|
|
@ -0,0 +1,127 @@
|
|||
package influxdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/influxdata/telegraf"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"github.com/influxdata/telegraf/plugins/outputs"
|
||||
)
|
||||
|
||||
var sampleConfig = `
|
||||
## controller information tor connect and retrieve tier-id value
|
||||
controllerTierURL = "https://foo.saas.appdynamics.com/controller/rest/applications/bar/tiers/baz?output=JSON"
|
||||
controllerUserName = "apiuser@account.com"
|
||||
controllerPassword = "apipassword"
|
||||
## Machine agent custom metrics listener url format string
|
||||
## |Component:%d| gets transformed into |Component:id| during initialization - where 'id' is a tier-id for
|
||||
## this controller application/tier combination
|
||||
agentURL = "http://localhost:8293/machineagent/metrics?name=Server|Component:%d|Custom+Metrics|"
|
||||
`
|
||||
|
||||
type Appdynamics struct {
|
||||
// Controller values for retrieving tier-id from the controller
|
||||
ControllerTierURL string
|
||||
ControllerUserName string
|
||||
ControllerPassword string
|
||||
|
||||
// Machine agent URL format string
|
||||
AgentURL string
|
||||
|
||||
// Tier id value retrieved from the controller for this application/tier
|
||||
tierId int64
|
||||
}
|
||||
|
||||
// Close - There is nothing to close here, but need to comply with output interface
|
||||
func (a *Appdynamics) Close() error{
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect - initialize appdynamics plugin by retrieving tier-id value from the appdynamics controller
|
||||
// for this application/tier combination and updating (reformatting) agent url string with tier-id value
|
||||
func (a *Appdynamics) Connect() (err error) {
|
||||
a.tierId, err = a.getTierId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Agent Tier ID: %d\n", a.tierId)
|
||||
a.AgentURL = fmt.Sprintf(a.AgentURL, a.tierId)
|
||||
fmt.Printf("Agent URL: %s\n", a.AgentURL)
|
||||
return err
|
||||
}
|
||||
|
||||
// Description - describing what this is
|
||||
func (a *Appdynamics) Description() string {
|
||||
return "Configuration for Appdynamics controller/listener to send metrics to"
|
||||
}
|
||||
|
||||
// getTierId - retrieve tier id value for this application/tier combination from the appdynamics controller
|
||||
func (a *Appdynamics) getTierId() (int64, error) {
|
||||
client := &http.Client{}
|
||||
|
||||
/* Auth */
|
||||
req, err := http.NewRequest("GET", a.ControllerTierURL, nil)
|
||||
req.SetBasicAuth(a.ControllerUserName, a.ControllerPassword)
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var tiers []struct {
|
||||
Id int64 `json:"id"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &tiers)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(tiers) != 1 {
|
||||
fmt.Println("Invalid reply: ", tiers)
|
||||
}
|
||||
|
||||
return tiers[0].Id, nil
|
||||
}
|
||||
|
||||
// Write - post telegraf metrics to appdynamics machine agent listener using
|
||||
// http.Get per https://docs.appdynamics.com/display/PRO40/Standalone+Machine+Agent+HTTP+Listener
|
||||
func (a *Appdynamics) Write(metrics []telegraf.Metric) error {
|
||||
for _, metric := range metrics {
|
||||
if metric.Fields()["value"] == nil {
|
||||
log.Println("WARNING: missing value:", metric)
|
||||
} else {
|
||||
var appdType string
|
||||
switch metric.Tags()["metric_type"] {
|
||||
case "gauge":
|
||||
appdType = "average"
|
||||
default:
|
||||
appdType = "sum"
|
||||
}
|
||||
url := a.AgentURL + metric.Name() + fmt.Sprintf("&value=%v&type=%s", metric.Fields()["value"], appdType)
|
||||
fmt.Printf("Calling %s ...\n", url)
|
||||
_, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Println("ERROR: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Appdynamics) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func init() {
|
||||
outputs.Add("appdynamics", func() telegraf.Output {
|
||||
return &Appdynamics{}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package influxdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestAppdynamicsError - attemp to initialize Appdynamics with invalid controller user name value
|
||||
func TestAppdynamicsError(t *testing.T) {
|
||||
a := Appdynamics{
|
||||
ControllerTierURL: "https://foo.saas.appdynamics.com/controller/rest/applications/bar/tiers/baz?output=JSON",
|
||||
ControllerUserName: "apiuser@foo.bar.com",
|
||||
ControllerPassword: "pass123",
|
||||
AgentURL: "http://localhost:8293/machineagent/metrics?name=Server|Component:%d|Custom+Metrics|",
|
||||
}
|
||||
assert.Error(t, a.Connect())
|
||||
}
|
||||
|
||||
// TestAppdynamicsOK - successfully initialize Appdynamics and process metrics calls
|
||||
func TestAppdynamicsOK(t *testing.T) {
|
||||
// channel to collect received calls
|
||||
ch := make(chan string, 1)
|
||||
|
||||
h := func(w http.ResponseWriter, r *http.Request) {
|
||||
s := r.URL.String()
|
||||
fmt.Fprintf(w, "Hi there, I love %s!", s)
|
||||
ch <- r.URL.RawQuery
|
||||
}
|
||||
http.HandleFunc("/", h)
|
||||
go http.ListenAndServe(":8293", nil)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
|
||||
a := Appdynamics{
|
||||
ControllerTierURL: "https://foo.saas.appdynamics.com/controller/rest/applications/bar/tiers/baz?output=JSON",
|
||||
ControllerUserName: "apiuser@foo.bar",
|
||||
ControllerPassword: "pass123",
|
||||
AgentURL: "http://localhost:8293/machineagent/metrics?name=Server|Component:%d|Custom+Metrics|",
|
||||
}
|
||||
// this error is expected since we are not connecting to actual controller
|
||||
assert.Error(t, a.Connect())
|
||||
// reset agent url value with '123' tier id
|
||||
a.AgentURL = fmt.Sprintf(a.AgentURL, 123)
|
||||
assert.Equal(t, a.AgentURL, "http://localhost:8293/machineagent/metrics?name=Server|Component:123|Custom+Metrics|")
|
||||
|
||||
// counter type - appd-type: sum
|
||||
m, _ := telegraf.NewMetric(
|
||||
"foo",
|
||||
map[string]string{"metrcic_type": "counter"},
|
||||
map[string]interface{}{"value": float64(1.23)},
|
||||
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
)
|
||||
metrics := []telegraf.Metric{m}
|
||||
assert.NoError(t, a.Write(metrics))
|
||||
call := <-ch
|
||||
assert.Equal(t, "name=Server|Component:123|Custom+Metrics|foo&value=1.23&type=sum", call)
|
||||
|
||||
// gauge type - appd-type: average
|
||||
m, _ = telegraf.NewMetric(
|
||||
"foo",
|
||||
map[string]string{"metric_type": "gauge"},
|
||||
map[string]interface{}{"value": float64(4.56)},
|
||||
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
)
|
||||
metrics = []telegraf.Metric{m}
|
||||
assert.NoError(t, a.Write(metrics))
|
||||
call = <-ch
|
||||
assert.Equal(t, "name=Server|Component:123|Custom+Metrics|foo&value=4.56&type=average", call)
|
||||
|
||||
// other type - defaults to appd-type: sum
|
||||
m, _ = telegraf.NewMetric(
|
||||
"foo",
|
||||
map[string]string{"metric_type": "bar"},
|
||||
map[string]interface{}{"value": float64(7.89)},
|
||||
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
)
|
||||
metrics = []telegraf.Metric{m}
|
||||
assert.NoError(t, a.Write(metrics))
|
||||
call = <-ch
|
||||
assert.Equal(t, "name=Server|Component:123|Custom+Metrics|foo&value=7.89&type=sum", call)
|
||||
|
||||
// invalid: missing value
|
||||
m, _ = telegraf.NewMetric(
|
||||
"foo",
|
||||
map[string]string{"metric_type": "bar"},
|
||||
map[string]interface{}{"values": float64(7.89)},
|
||||
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
)
|
||||
metrics = []telegraf.Metric{m}
|
||||
assert.NoError(t, a.Write(metrics))
|
||||
select {
|
||||
case call = <-ch:
|
||||
t.Error("No messages expected, but got: ", call)
|
||||
default:
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue