Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
135
plugins/serializers/nowmetric/README.md
Normal file
135
plugins/serializers/nowmetric/README.md
Normal file
|
@ -0,0 +1,135 @@
|
|||
# ServiceNow Metrics serializer
|
||||
|
||||
The ServiceNow Metrics serializer outputs metrics in the
|
||||
[ServiceNow Operational Intelligence format][ServiceNow-format] or optionally
|
||||
with the [ServiceNow JSONv2 format][ServiceNow-jsonv2]
|
||||
|
||||
It can be used to write to a file using the file output, or for sending metrics
|
||||
to a MID Server with Enable REST endpoint activated using the standard telegraf
|
||||
HTTP output. If you are using the HTTP output, this serializer knows how to
|
||||
batch the metrics so you do not end up with an HTTP POST per metric.
|
||||
|
||||
[ServiceNow-format]: https://docs.servicenow.com/bundle/london-it-operations-management/page/product/event-management/reference/mid-POST-metrics.html
|
||||
[ServiceNow-jsonv2]: https://docs.servicenow.com/bundle/tokyo-application-development/page/integrate/inbound-other-web-services/concept/c_JSONv2WebService.html
|
||||
|
||||
An example Operational Intelligence format event looks like:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"metric_type": "Disk C: % Free Space",
|
||||
"resource": "C:\\",
|
||||
"node": "lnux100",
|
||||
"value": 50,
|
||||
"timestamp": 1473183012000,
|
||||
"ci2metric_id": {
|
||||
"node": "lnux100"
|
||||
},
|
||||
"source": "Telegraf"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
An example of the JSONv2 format even looks like:
|
||||
|
||||
```json
|
||||
{
|
||||
"records": [
|
||||
{
|
||||
"metric_type": "Disk C: % Free Space",
|
||||
"resource": "C:\\",
|
||||
"node": "lnux100",
|
||||
"value": 50,
|
||||
"timestamp": 1473183012000,
|
||||
"ci2metric_id": {
|
||||
"node": "lnux100"
|
||||
},
|
||||
"source": "Telegraf"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Using with the HTTP output
|
||||
|
||||
To send this data to a ServiceNow MID Server with Web Server extension activated, you can use the HTTP output, there are some custom headers that you need to add to manage the MID Web Server authorization, here's a sample config for an HTTP output:
|
||||
|
||||
```toml
|
||||
[[outputs.http]]
|
||||
## URL is the address to send metrics to
|
||||
url = "http://<mid server fqdn or ip address>:9082/api/mid/sa/metrics"
|
||||
|
||||
## Timeout for HTTP message
|
||||
# timeout = "5s"
|
||||
|
||||
## HTTP method, one of: "POST" or "PUT"
|
||||
method = "POST"
|
||||
|
||||
## HTTP Basic Auth credentials
|
||||
username = 'evt.integration'
|
||||
password = 'P@$$w0rd!'
|
||||
|
||||
## Optional TLS Config
|
||||
# tls_ca = "/etc/telegraf/ca.pem"
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
|
||||
## Data format to output.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
data_format = "nowmetric"
|
||||
|
||||
## Format Type
|
||||
## By default, the serializer returns an array of metrics matching the
|
||||
## Now Metric Operational Intelligence format or with the option set to 'oi'.
|
||||
## Optionally, if set to 'jsonv2' the output format will involve the newer
|
||||
## JSON object based format.
|
||||
# nowmetric_format = "oi"
|
||||
|
||||
## Additional HTTP headers
|
||||
[outputs.http.headers]
|
||||
# # Should be set manually to "application/json" for json data_format
|
||||
Content-Type = "application/json"
|
||||
Accept = "application/json"
|
||||
```
|
||||
|
||||
Starting with the [London release](https://docs.servicenow.com/bundle/london-it-operations-management/page/product/event-management/task/event-rule-bind-metrics-to-host.html
|
||||
),
|
||||
you also need to explicitly create event rule to allow binding of metric events to host CIs.
|
||||
|
||||
## Metric Format
|
||||
|
||||
The following describes the two options of the `nowmetric_format` option:
|
||||
|
||||
The Operational Intelligence format is used along with the
|
||||
`/api/mid/sa/metrics` API endpoint. The payload is requires a JSON array full
|
||||
of metrics. This is the default settings and used when set to `oi`. See the
|
||||
[ServiceNow KB0853084][KB0853084] for more details on this format.
|
||||
|
||||
Another option is the use of the [JSONv2 web service][jsonv2]. This service
|
||||
requires a different format that is [JSON object based][jsonv2_format]. This
|
||||
option is used when set to `jsonv2`.
|
||||
|
||||
[KB0853084]: https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0853084
|
||||
[jsonv2]: https://docs.servicenow.com/bundle/tokyo-application-development/page/integrate/inbound-other-web-services/concept/c_JSONv2WebService.html
|
||||
[jsonv2_format]: https://docs.servicenow.com/bundle/tokyo-application-development/page/integrate/inbound-other-web-services/concept/c_JSONObjectFormat.html
|
||||
|
||||
## Using with the File output
|
||||
|
||||
You can use the file output to output the payload in a file.
|
||||
In this case, just add the following section to your telegraf config file
|
||||
|
||||
```toml
|
||||
[[outputs.file]]
|
||||
## Files to write to, "stdout" is a specially handled file.
|
||||
files = ["C:/Telegraf/metrics.out"]
|
||||
|
||||
## Data format to output.
|
||||
## Each data format has its own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
data_format = "nowmetric"
|
||||
```
|
137
plugins/serializers/nowmetric/nowmetric.go
Normal file
137
plugins/serializers/nowmetric/nowmetric.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package nowmetric
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/plugins/serializers"
|
||||
)
|
||||
|
||||
type Serializer struct {
|
||||
Format string `toml:"nowmetric_format"`
|
||||
}
|
||||
|
||||
type OIMetric struct {
|
||||
Metric string `json:"metric_type"`
|
||||
Resource string `json:"resource"`
|
||||
Node string `json:"node"`
|
||||
Value interface{} `json:"value"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
CiMapping map[string]string `json:"ci2metric_id"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
type OIMetrics []OIMetric
|
||||
type OIMetricsObj struct {
|
||||
Records []OIMetric `json:"records"`
|
||||
}
|
||||
|
||||
func (s *Serializer) Init() error {
|
||||
switch s.Format {
|
||||
case "":
|
||||
s.Format = "oi"
|
||||
case "oi", "jsonv2":
|
||||
default:
|
||||
return fmt.Errorf("invalid format %q", s.Format)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Serializer) Serialize(metric telegraf.Metric) (out []byte, err error) {
|
||||
m := createObject(metric)
|
||||
|
||||
if s.Format == "jsonv2" {
|
||||
obj := OIMetricsObj{Records: m}
|
||||
return json.Marshal(obj)
|
||||
}
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
func (s *Serializer) SerializeBatch(metrics []telegraf.Metric) (out []byte, err error) {
|
||||
objects := make([]OIMetric, 0)
|
||||
for _, metric := range metrics {
|
||||
objects = append(objects, createObject(metric)...)
|
||||
}
|
||||
|
||||
if s.Format == "jsonv2" {
|
||||
obj := OIMetricsObj{Records: objects}
|
||||
return json.Marshal(obj)
|
||||
}
|
||||
|
||||
return json.Marshal(objects)
|
||||
}
|
||||
|
||||
func createObject(metric telegraf.Metric) OIMetrics {
|
||||
/* ServiceNow Operational Intelligence supports an array of JSON objects.
|
||||
** Following elements accepted in the request body:
|
||||
** metric_type: The name of the metric
|
||||
** resource: Information about the resource for which metric data is being collected.
|
||||
In the example below, C:\ is the resource for which metric data is collected
|
||||
** node: IP, FQDN, name of the CI, or host
|
||||
** value: Value of the metric
|
||||
** timestamp: Epoch timestamp of the metric in milliseconds
|
||||
** ci2metric_id: List of key-value pairs to identify the CI.
|
||||
** source: Data source monitoring the metric type
|
||||
*/
|
||||
var allmetrics OIMetrics //nolint:prealloc // Pre-allocating may change format of marshaled JSON
|
||||
var oimetric OIMetric
|
||||
oimetric.Source = "Telegraf"
|
||||
|
||||
// Process Tags to extract node & resource name info
|
||||
for _, tag := range metric.TagList() {
|
||||
if tag.Key == "" || tag.Value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if tag.Key == "objectname" {
|
||||
oimetric.Resource = tag.Value
|
||||
}
|
||||
|
||||
if tag.Key == "host" {
|
||||
oimetric.Node = tag.Value
|
||||
}
|
||||
}
|
||||
|
||||
// Format timestamp to UNIX epoch
|
||||
oimetric.Timestamp = metric.Time().UnixNano() / int64(time.Millisecond)
|
||||
|
||||
// Loop of fields value pair and build datapoint for each of them
|
||||
for _, field := range metric.FieldList() {
|
||||
if !verifyValue(field.Value) {
|
||||
// Ignore String
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Key == "" {
|
||||
// Ignore Empty Key
|
||||
continue
|
||||
}
|
||||
|
||||
oimetric.Metric = field.Key
|
||||
oimetric.Value = field.Value
|
||||
|
||||
if oimetric.Node != "" {
|
||||
oimetric.CiMapping = map[string]string{"node": oimetric.Node}
|
||||
}
|
||||
|
||||
allmetrics = append(allmetrics, oimetric)
|
||||
}
|
||||
|
||||
return allmetrics
|
||||
}
|
||||
|
||||
func verifyValue(v interface{}) bool {
|
||||
_, ok := v.(string)
|
||||
return !ok
|
||||
}
|
||||
|
||||
func init() {
|
||||
serializers.Add("nowmetric",
|
||||
func() telegraf.Serializer {
|
||||
return &Serializer{}
|
||||
},
|
||||
)
|
||||
}
|
270
plugins/serializers/nowmetric/nowmetric_test.go
Normal file
270
plugins/serializers/nowmetric/nowmetric_test.go
Normal file
|
@ -0,0 +1,270 @@
|
|||
package nowmetric
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
"github.com/influxdata/telegraf/plugins/serializers"
|
||||
)
|
||||
|
||||
func TestSerializeMetricFloat(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
"cpu": "cpu0",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": float64(91.5),
|
||||
}
|
||||
m := metric.New("cpu", tags, fields, now)
|
||||
|
||||
s := &Serializer{}
|
||||
var buf []byte
|
||||
buf, err := s.Serialize(m)
|
||||
require.NoError(t, err)
|
||||
expS := []byte(
|
||||
fmt.Sprintf(
|
||||
`[{"metric_type":"usage_idle","resource":"","node":"","value":91.5,"timestamp":%d,"ci2metric_id":null,"source":"Telegraf"}]`,
|
||||
now.UnixNano()/int64(time.Millisecond),
|
||||
),
|
||||
)
|
||||
require.Equal(t, string(expS), string(buf))
|
||||
}
|
||||
|
||||
func TestSerialize_TimestampUnits(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
timestampUnits time.Duration
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "1ms",
|
||||
timestampUnits: 1 * time.Millisecond,
|
||||
expected: `[{"metric_type":"value","resource":"","node":"","value":42,"timestamp":1525478795123,"ci2metric_id":null,"source":"Telegraf"}]`,
|
||||
},
|
||||
{
|
||||
name: "10ms",
|
||||
timestampUnits: 10 * time.Millisecond,
|
||||
expected: `[{"metric_type":"value","resource":"","node":"","value":42,"timestamp":1525478795123,"ci2metric_id":null,"source":"Telegraf"}]`,
|
||||
},
|
||||
{
|
||||
name: "15ms is reduced to 10ms",
|
||||
timestampUnits: 15 * time.Millisecond,
|
||||
expected: `[{"metric_type":"value","resource":"","node":"","value":42,"timestamp":1525478795123,"ci2metric_id":null,"source":"Telegraf"}]`,
|
||||
},
|
||||
{
|
||||
name: "65ms is reduced to 10ms",
|
||||
timestampUnits: 65 * time.Millisecond,
|
||||
expected: `[{"metric_type":"value","resource":"","node":"","value":42,"timestamp":1525478795123,"ci2metric_id":null,"source":"Telegraf"}]`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := metric.New(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"value": 42.0,
|
||||
},
|
||||
time.Unix(1525478795, 123456789),
|
||||
)
|
||||
s := &Serializer{}
|
||||
actual, err := s.Serialize(m)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expected, string(actual))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSerializeMetricInt(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
"cpu": "cpu0",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": int64(90),
|
||||
}
|
||||
m := metric.New("cpu", tags, fields, now)
|
||||
|
||||
s := &Serializer{}
|
||||
var buf []byte
|
||||
buf, err := s.Serialize(m)
|
||||
require.NoError(t, err)
|
||||
|
||||
expS := []byte(
|
||||
fmt.Sprintf(
|
||||
`[{"metric_type":"usage_idle","resource":"","node":"","value":90,"timestamp":%d,"ci2metric_id":null,"source":"Telegraf"}]`,
|
||||
now.UnixNano()/int64(time.Millisecond),
|
||||
),
|
||||
)
|
||||
require.Equal(t, string(expS), string(buf))
|
||||
}
|
||||
|
||||
func TestSerializeMetricString(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
"cpu": "cpu0",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": "foobar",
|
||||
}
|
||||
m := metric.New("cpu", tags, fields, now)
|
||||
|
||||
s := &Serializer{}
|
||||
var buf []byte
|
||||
buf, err := s.Serialize(m)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "null", string(buf))
|
||||
}
|
||||
|
||||
func TestSerializeMultiFields(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
"cpu": "cpu0",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"usage_idle": int64(90),
|
||||
"usage_total": 8559615,
|
||||
}
|
||||
m := metric.New("cpu", tags, fields, now)
|
||||
|
||||
// Sort for predictable field order
|
||||
sort.Slice(m.FieldList(), func(i, j int) bool {
|
||||
return m.FieldList()[i].Key < m.FieldList()[j].Key
|
||||
})
|
||||
|
||||
s := &Serializer{}
|
||||
var buf []byte
|
||||
buf, err := s.Serialize(m)
|
||||
require.NoError(t, err)
|
||||
|
||||
expS := []byte(
|
||||
fmt.Sprintf(
|
||||
`[{"metric_type":"usage_idle","resource":"","node":"","value":90,"timestamp":%d,"ci2metric_id":null,"source":"Telegraf"},`+
|
||||
`{"metric_type":"usage_total","resource":"","node":"","value":8559615,"timestamp":%d,"ci2metric_id":null,"source":"Telegraf"}]`,
|
||||
now.UnixNano()/int64(time.Millisecond),
|
||||
now.UnixNano()/int64(time.Millisecond),
|
||||
),
|
||||
)
|
||||
require.Equal(t, string(expS), string(buf))
|
||||
}
|
||||
|
||||
func TestSerializeMetricWithEscapes(t *testing.T) {
|
||||
now := time.Now()
|
||||
tags := map[string]string{
|
||||
"cpu tag": "cpu0",
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"U,age=Idle": int64(90),
|
||||
}
|
||||
m := metric.New("My CPU", tags, fields, now)
|
||||
|
||||
s := &Serializer{}
|
||||
buf, err := s.Serialize(m)
|
||||
require.NoError(t, err)
|
||||
|
||||
expS := []byte(
|
||||
fmt.Sprintf(
|
||||
`[{"metric_type":"U,age=Idle","resource":"","node":"","value":90,"timestamp":%d,"ci2metric_id":null,"source":"Telegraf"}]`,
|
||||
now.UnixNano()/int64(time.Millisecond),
|
||||
),
|
||||
)
|
||||
require.Equal(t, string(expS), string(buf))
|
||||
}
|
||||
|
||||
func TestSerializeBatch(t *testing.T) {
|
||||
m := metric.New(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"value": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
)
|
||||
metrics := []telegraf.Metric{m, m}
|
||||
s := &Serializer{}
|
||||
buf, err := s.SerializeBatch(metrics)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(
|
||||
t,
|
||||
`[{"metric_type":"value","resource":"","node":"","value":42,"timestamp":0,"ci2metric_id":null,"source":"Telegraf"},`+
|
||||
`{"metric_type":"value","resource":"","node":"","value":42,"timestamp":0,"ci2metric_id":null,"source":"Telegraf"}]`,
|
||||
string(buf),
|
||||
)
|
||||
}
|
||||
|
||||
func TestSerializeJSONv2Format(t *testing.T) {
|
||||
m := metric.New(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"value": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
)
|
||||
s := &Serializer{Format: "jsonv2"}
|
||||
buf, err := s.Serialize(m)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(
|
||||
t,
|
||||
`{"records":[{"metric_type":"value","resource":"","node":"","value":42,"timestamp":0,"ci2metric_id":null,"source":"Telegraf"}]}`,
|
||||
string(buf),
|
||||
)
|
||||
}
|
||||
|
||||
func TestSerializeJSONv2FormatBatch(t *testing.T) {
|
||||
m := metric.New(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"value": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
)
|
||||
s := &Serializer{Format: "jsonv2"}
|
||||
metrics := []telegraf.Metric{m, m}
|
||||
buf, err := s.SerializeBatch(metrics)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(
|
||||
t,
|
||||
`{"records":[`+
|
||||
`{"metric_type":"value","resource":"","node":"","value":42,"timestamp":0,"ci2metric_id":null,"source":"Telegraf"},`+
|
||||
`{"metric_type":"value","resource":"","node":"","value":42,"timestamp":0,"ci2metric_id":null,"source":"Telegraf"}`+
|
||||
`]}`,
|
||||
string(buf),
|
||||
)
|
||||
}
|
||||
|
||||
func TestSerializeInvalidFormat(t *testing.T) {
|
||||
s := &Serializer{Format: "foo"}
|
||||
require.Error(t, s.Init())
|
||||
}
|
||||
|
||||
func BenchmarkSerialize(b *testing.B) {
|
||||
s := &Serializer{}
|
||||
require.NoError(b, s.Init())
|
||||
metrics := serializers.BenchmarkMetrics(b)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := s.Serialize(metrics[i%len(metrics)])
|
||||
require.NoError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSerializeBatch(b *testing.B) {
|
||||
s := &Serializer{}
|
||||
require.NoError(b, s.Init())
|
||||
m := serializers.BenchmarkMetrics(b)
|
||||
metrics := m[:]
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := s.SerializeBatch(metrics)
|
||||
require.NoError(b, err)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue