Use dynatrace-metric-utils (#9295)

This commit is contained in:
Daniel Dyla 2021-06-08 17:27:39 -04:00 committed by GitHub
parent d6ac4abfb8
commit 298670ae18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 315 additions and 235 deletions

View File

@ -63,6 +63,7 @@ following works:
- github.com/docker/docker [Apache License 2.0](https://github.com/docker/docker/blob/master/LICENSE)
- github.com/docker/go-connections [Apache License 2.0](https://github.com/docker/go-connections/blob/master/LICENSE)
- github.com/docker/go-units [Apache License 2.0](https://github.com/docker/go-units/blob/master/LICENSE)
- github.com/dynatrace-oss/dynatrace-metric-utils-go [Apache License 2.0](https://github.com/dynatrace-oss/dynatrace-metric-utils-go/blob/master/LICENSE)
- github.com/eapache/go-resiliency [MIT License](https://github.com/eapache/go-resiliency/blob/master/LICENSE)
- github.com/eapache/go-xerial-snappy [MIT License](https://github.com/eapache/go-xerial-snappy/blob/master/LICENSE)
- github.com/eapache/queue [MIT License](https://github.com/eapache/queue/blob/master/LICENSE)

1
go.mod
View File

@ -45,6 +45,7 @@ require (
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1
github.com/dimchansky/utfbom v1.1.1
github.com/docker/docker v20.10.5+incompatible
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.1.0
github.com/eclipse/paho.mqtt.golang v1.3.0
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
github.com/go-logfmt/logfmt v0.5.0

20
go.sum
View File

@ -239,7 +239,6 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
@ -401,6 +400,8 @@ github.com/dropbox/godropbox v0.0.0-20180512210157-31879d3884b9/go.mod h1:glr97h
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.1.0 h1:ldKn47mFgWCoiJRXA32psdEACPKffX9O1Msh1K8M+f0=
github.com/dynatrace-oss/dynatrace-metric-utils-go v0.1.0/go.mod h1:qw0E9EJ0PnSlhWawDNuqE0zhc1hqOBUCFIAj3dd9DNw=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@ -463,7 +464,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@ -620,7 +620,6 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -629,7 +628,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1 h1:jAbXjIeW2ZSW2AwFxlGTDoc2CjI2XujLkV3ArsZFCvc=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
@ -1114,7 +1112,6 @@ github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go
github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
@ -1265,9 +1262,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@ -1287,7 +1281,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@ -1602,8 +1595,6 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1690,10 +1681,6 @@ golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190906203814-12febf440ab1/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -1729,7 +1716,6 @@ golang.org/x/tools v0.0.0-20200822203824-307de81be3f4/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1800,7 +1786,6 @@ google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@ -1836,7 +1821,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=

View File

@ -1,49 +1,135 @@
# Dynatrace Output Plugin
This plugin is sending telegraf metrics to [Dynatrace](https://www.dynatrace.com). It has two operational modes.
This plugin sends Telegraf metrics to [Dynatrace](https://www.dynatrace.com) via the [Dynatrace Metrics API V2](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/metric-v2/). It may be run alongside the Dynatrace OneAgent for automatic authentication or it may be run standalone on a host without a OneAgent by specifying a URL and API Token.
More information on the plugin can be found in the [Dynatrace documentation](https://www.dynatrace.com/support/help/how-to-use-dynatrace/metrics/metric-ingestion/ingestion-methods/telegraf/).
Telegraf minimum version: Telegraf 1.16
Plugin minimum tested version: 1.16
## Requirements
## Running alongside Dynatrace OneAgent
You will either need a Dynatrace OneAgent (version 1.201 or higher) installed on the same host as Telegraf; or a Dynatrace environment with version 1.202 or higher. Monotonic counters (e.g. `diskio.reads`, `system.uptime`) require Dynatrace 208 or later.
if you run the Telegraf agent on a host or VM that is monitored by the Dynatrace OneAgent then you only need to enable the plugin but need no further configuration. The Dynatrace telegraf output plugin will send all metrics to the OneAgent which will use its secure and load balanced connection to send the metrics to your Dynatrace SaaS or Managed environment.
- Telegraf minimum version: Telegraf 1.16
## Getting Started
Setting up Telegraf is explained in the [Telegraf Documentation](https://docs.influxdata.com/telegraf/latest/introduction/getting-started/).
The Dynatrace exporter may be enabled by adding an `[[outputs.dynatrace]]` section to your `telegraf.conf` config file.
All configurations are optional, but if a `url` other than the OneAgent metric ingestion endpoint is specified then an `api_token` is required.
To see all available options, see [Configuration](#configuration) below.
### Running alongside Dynatrace OneAgent
If you run the Telegraf agent on a host or VM that is monitored by the Dynatrace OneAgent then you only need to enable the plugin, but need no further configuration. The Dynatrace Telegraf output plugin will send all metrics to the OneAgent which will use its secure and load balanced connection to send the metrics to your Dynatrace SaaS or Managed environment.
Depending on your environment, you might have to enable metrics ingestion on the OneAgent first as described in the [Dynatrace documentation](https://www.dynatrace.com/support/help/how-to-use-dynatrace/metrics/metric-ingestion/ingestion-methods/telegraf/).
Note: The name and identifier of the host running Telegraf will be added as a dimension to every metric. If this is undesirable, then the output plugin may be used in standalone mode using the directions below.
```toml
[[outputs.dynatrace]]
## No options are required. By default, metrics will be exported via the OneAgent on the local host.
```
## Running standalone
If you run the Telegraf agent on a host or VM without a OneAgent you will need to configure the environment API endpoint to send the metrics to and an API token for security.
The endpoint for the Dynatrace Metrics API is
You will also need to configure an API token for secure access. Find out how to create a token in the [Dynatrace documentation](https://www.dynatrace.com/support/help/dynatrace-api/basics/dynatrace-api-authentication/) or simply navigate to **Settings > Integration > Dynatrace API** in your Dynatrace environment and create a token with Dynatrace API and create a new token with
'Ingest metrics' (`metrics.ingest`) scope enabled. It is recommended to limit Token scope to only this permission.
* Managed https://{your-domain}/e/{your-environment-id}/api/v2/metrics/ingest
* SaaS https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest
The endpoint for the Dynatrace Metrics API v2 is
You can learn more about how to use the Dynatrace API [here](https://www.dynatrace.com/support/help/dynatrace-api/)
* on Dynatrace Managed: `https://{your-domain}/e/{your-environment-id}/api/v2/metrics/ingest`
* on Dynatrace SaaS: `https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest`
You will also need to configure an API token for secure access. Find out how to create a token [here](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/tokens/) or simply navigate to **Settings > Integration > Dynatrace API** in your Dynatrace environment and create a token with Dynatrace API and create a new token with
'Ingest metrics data points' access scope enabled.
```toml
[[outputs.dynatrace]]
## If no OneAgent is running on the host, url and api_token need to be set
## Dynatrace Metrics Ingest v2 endpoint to receive metrics
url = "https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest"
## API token is required if a URL is specified and should be restricted to the 'Ingest metrics' scope
api_token = "your API token here" // hard-coded for illustration only, should be read from environment
```
You can learn more about how to use the Dynatrace API [here](https://www.dynatrace.com/support/help/dynatrace-api/).
## Configuration
### `url`
*required*: `false`
*default*: Local OneAgent endpoint
Set your Dynatrace environment URL (e.g.: `https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest`) if you do not use a OneAgent or wish to export metrics directly to a Dynatrace metrics v2 endpoint. If a URL is set to anything other than the local OneAgent endpoint, then an API token is required.
```toml
[[outputs.dynatrace]]
## Leave empty or use the local ingest endpoint of your OneAgent monitored host (e.g.: http://127.0.0.1:14499/metrics/ingest).
## Set Dynatrace environment URL (e.g.: https://YOUR_DOMAIN/api/v2/metrics/ingest) if you do not use a OneAgent
url = ""
api_token = ""
## Optional prefix for metric names (e.g.: "telegraf.")
prefix = "telegraf."
## Optional prefix for metric names (e.g.: "telegraf")
prefix = "telegraf"
## Flag for skipping the tls certificate check, just for testing purposes, should be false by default
insecure_skip_verify = false
## If you want to convert values represented as gauges to counters, add the metric names here
additional_counters = [ ]
url = "https://{your-environment-id}.live.dynatrace.com/api/v2/metrics/ingest"
```
## Requirements
### `api_token`
You will either need a Dynatrace OneAgent (version 1.201 or higher) installed on the same host as Telegraf; or a Dynatrace environment with version 1.202 or higher. Monotonic counters (e.g. diskio.reads, system.uptime) require release 208 or later.
You will either need a Dynatrace OneAgent (version 1.201 or higher) installed on the same host as Telegraf; or a Dynatrace environment with version 1.202 or higher
*required*: `false` unless `url` is specified
API token is required if a URL other than the OneAgent endpoint is specified and it should be restricted to the 'Ingest metrics' scope.
```toml
api_token = "your API token here"
```
### `prefix`
*required*: `false`
Optional prefix to be prepended to all metric names (will be separated with a `.`).
```toml
prefix = "telegraf"
```
### `insecure_skip_verify`
*required*: `false`
Setting this option to true skips TLS verification for testing or when using self-signed certificates.
```toml
insecure_skip_verify = false
```
### `additional_counters`
*required*: `false`
If you want to convert values represented as gauges to counters, add the metric names here.
```toml
additional_counters = [ ]
```
### `default_dimensions`
*required*: `false`
Default dimensions that will be added to every exported metric.
```toml
default_dimensions = {
key = "value"
}
```
## Limitations
Telegraf measurements which can't be converted to a float64 are skipped.
Telegraf measurements which can't be converted to a number are skipped.

View File

@ -4,11 +4,7 @@ import (
"bytes"
"fmt"
"io/ioutil"
"math"
"net/http"
"regexp"
"sort"
"strconv"
"strings"
"time"
@ -16,17 +12,10 @@ import (
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/common/tls"
"github.com/influxdata/telegraf/plugins/outputs"
)
const (
oneAgentMetricsURL = "http://127.0.0.1:14499/metrics/ingest"
dtIngestAPILineLimit = 1000
)
var (
reNameAllowedCharList = regexp.MustCompile("[^A-Za-z0-9.-]+")
maxDimKeyLen = 100
maxMetricKeyLen = 250
dtMetric "github.com/dynatrace-oss/dynatrace-metric-utils-go/metric"
"github.com/dynatrace-oss/dynatrace-metric-utils-go/metric/apiconstants"
"github.com/dynatrace-oss/dynatrace-metric-utils-go/metric/dimensions"
)
// Dynatrace Configuration for the Dynatrace output plugin
@ -37,12 +26,12 @@ type Dynatrace struct {
Log telegraf.Logger `toml:"-"`
Timeout config.Duration `toml:"timeout"`
AddCounterMetrics []string `toml:"additional_counters"`
State map[string]string
SendCounter int
tls.ClientConfig
client *http.Client
loggedMetrics map[string]bool // New empty set
}
const sampleConfig = `
@ -61,8 +50,8 @@ const sampleConfig = `
## The API token needs data ingest scope permission. When using OneAgent, no API token is required.
api_token = ""
## Optional prefix for metric names (e.g.: "telegraf.")
prefix = "telegraf."
## Optional prefix for metric names (e.g.: "telegraf")
prefix = "telegraf"
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
@ -101,163 +90,97 @@ func (d *Dynatrace) Description() string {
return "Send telegraf metrics to a Dynatrace environment"
}
// Normalizes a metric keys or metric dimension identifiers
// according to Dynatrace format.
func (d *Dynatrace) normalize(s string, max int) (string, error) {
s = reNameAllowedCharList.ReplaceAllString(s, "_")
// Strip Digits and underscores if they are at the beginning of the string
normalizedString := strings.TrimLeft(s, "_0123456789")
for strings.HasPrefix(normalizedString, "_") {
normalizedString = normalizedString[1:]
}
if len(normalizedString) > max {
normalizedString = normalizedString[:max]
}
for strings.HasSuffix(normalizedString, "_") {
normalizedString = normalizedString[:len(normalizedString)-1]
}
normalizedString = strings.ReplaceAll(normalizedString, "..", "_")
if len(normalizedString) == 0 {
return "", fmt.Errorf("error normalizing the string: %s", s)
}
return normalizedString, nil
}
func (d *Dynatrace) escape(v string) string {
return strconv.Quote(v)
}
func (d *Dynatrace) Write(metrics []telegraf.Metric) error {
var buf bytes.Buffer
metricCounter := 1
var tagb bytes.Buffer
if len(metrics) == 0 {
return nil
}
for _, metric := range metrics {
// first write the tags into a buffer
tagb.Reset()
if len(metric.Tags()) > 0 {
keys := make([]string, 0, len(metric.Tags()))
for k := range metric.Tags() {
keys = append(keys, k)
}
// sort tag keys to expect the same order in ech run
sort.Strings(keys)
lines := []string{}
for _, k := range keys {
tagKey, err := d.normalize(k, maxDimKeyLen)
if err != nil {
for _, tm := range metrics {
dims := []dimensions.Dimension{}
for _, tag := range tm.TagList() {
// Ignore special tags for histogram and summary types.
switch tm.Type() {
case telegraf.Histogram:
if tag.Key == "le" || tag.Key == "gt" {
continue
}
if len(metric.Tags()[k]) > 0 {
fmt.Fprintf(&tagb, ",%s=%s", strings.ToLower(tagKey), d.escape(metric.Tags()[k]))
case telegraf.Summary:
if tag.Key == "quantile" {
continue
}
}
dims = append(dims, dimensions.NewDimension(tag.Key, tag.Value))
}
if len(metric.Fields()) > 0 {
for k, v := range metric.Fields() {
var value string
switch v := v.(type) {
case string:
continue
case float64:
if !math.IsNaN(v) && !math.IsInf(v, 0) {
value = fmt.Sprintf("%f", v)
} else {
continue
}
case uint64:
value = strconv.FormatUint(v, 10)
case int64:
value = strconv.FormatInt(v, 10)
case bool:
if v {
value = "1"
} else {
value = "0"
}
default:
d.Log.Debugf("Dynatrace type not supported! %s", v)
continue
metricType := tm.Type()
for _, field := range tm.FieldList() {
metricName := tm.Name() + "." + field.Key
for _, i := range d.AddCounterMetrics {
if metricName == i {
metricType = telegraf.Counter
}
}
// metric name
metricKey, err := d.normalize(k, maxMetricKeyLen)
if err != nil {
continue
typeOpt := getTypeOption(metricType, field)
if typeOpt == nil {
// Unsupported type. Log only once per unsupported metric name
if !d.loggedMetrics[metricName] {
d.Log.Warnf("Unsupported type for %s", metricName)
d.loggedMetrics[metricName] = true
}
continue
}
metricID, err := d.normalize(d.Prefix+metric.Name()+"."+metricKey, maxMetricKeyLen)
// write metric name combined with its field
if err != nil {
continue
}
// write metric id,tags and value
name := tm.Name() + "." + field.Key
dm, err := dtMetric.NewMetric(
name,
dtMetric.WithPrefix(d.Prefix),
dtMetric.WithDimensions(
dimensions.MergeLists(
// dimensions.NewNormalizedDimensionList(e.opts.DefaultDimensions...),
dimensions.NewNormalizedDimensionList(dims...),
),
),
typeOpt,
)
metricType := metric.Type()
for _, i := range d.AddCounterMetrics {
if metric.Name()+"."+metricKey == i {
metricType = telegraf.Counter
}
}
if err != nil {
d.Log.Warn(fmt.Sprintf("failed to normalize metric: %s - %s", name, err.Error()))
continue
}
switch metricType {
case telegraf.Counter:
var delta float64
line, err := dm.Serialize()
// Check if LastValue exists
if lastvalue, ok := d.State[metricID+tagb.String()]; ok {
// Convert Strings to Floats
floatLastValue, err := strconv.ParseFloat(lastvalue, 32)
if err != nil {
d.Log.Debugf("Could not parse last value: %s", lastvalue)
}
floatCurrentValue, err := strconv.ParseFloat(value, 32)
if err != nil {
d.Log.Debugf("Could not parse current value: %s", value)
}
if floatCurrentValue >= floatLastValue {
delta = floatCurrentValue - floatLastValue
fmt.Fprintf(&buf, "%s%s count,delta=%f\n", metricID, tagb.String(), delta)
}
}
d.State[metricID+tagb.String()] = value
if err != nil {
d.Log.Warn(fmt.Sprintf("failed to serialize metric: %s - %s", name, err.Error()))
continue
}
default:
fmt.Fprintf(&buf, "%s%s %v\n", metricID, tagb.String(), value)
}
lines = append(lines, line)
}
}
if metricCounter%dtIngestAPILineLimit == 0 {
err = d.send(buf.Bytes())
if err != nil {
return err
}
buf.Reset()
}
metricCounter++
limit := apiconstants.GetPayloadLinesLimit()
for i := 0; i < len(lines); i += limit {
batch := lines[i:min(i+limit, len(lines))]
output := strings.Join(batch, "\n")
if output != "" {
if err := d.send(output); err != nil {
return fmt.Errorf("error processing data:, %s", err.Error())
}
}
}
d.SendCounter++
// in typical interval of 10s, we will clean the counter state once in 24h which is 8640 iterations
if d.SendCounter%8640 == 0 {
d.State = make(map[string]string)
}
return d.send(buf.Bytes())
return nil
}
func (d *Dynatrace) send(msg []byte) error {
func (d *Dynatrace) send(msg string) error {
var err error
req, err := http.NewRequest("POST", d.URL, bytes.NewBuffer(msg))
req, err := http.NewRequest("POST", d.URL, bytes.NewBufferString(msg))
if err != nil {
d.Log.Errorf("Dynatrace error: %s", err.Error())
return fmt.Errorf("error while creating HTTP request:, %s", err.Error())
@ -292,12 +215,11 @@ func (d *Dynatrace) send(msg []byte) error {
}
func (d *Dynatrace) Init() error {
d.State = make(map[string]string)
if len(d.URL) == 0 {
d.Log.Infof("Dynatrace URL is empty, defaulting to OneAgent metrics interface")
d.URL = oneAgentMetricsURL
d.URL = apiconstants.GetDefaultOneAgentEndpoint()
}
if d.URL != oneAgentMetricsURL && len(d.APIToken) == 0 {
if d.URL != apiconstants.GetDefaultOneAgentEndpoint() && len(d.APIToken) == 0 {
d.Log.Errorf("Dynatrace api_token is a required field for Dynatrace output")
return fmt.Errorf("api_token is a required field for Dynatrace output")
}
@ -320,8 +242,45 @@ func (d *Dynatrace) Init() error {
func init() {
outputs.Add("dynatrace", func() telegraf.Output {
return &Dynatrace{
Timeout: config.Duration(time.Second * 5),
SendCounter: 0,
Timeout: config.Duration(time.Second * 5),
}
})
}
func getTypeOption(metricType telegraf.ValueType, field *telegraf.Field) dtMetric.MetricOption {
if metricType == telegraf.Counter {
switch v := field.Value.(type) {
case float64:
return dtMetric.WithFloatCounterValueTotal(v)
case uint64:
return dtMetric.WithIntCounterValueTotal(int64(v))
case int64:
return dtMetric.WithIntCounterValueTotal(v)
default:
return nil
}
}
switch v := field.Value.(type) {
case float64:
return dtMetric.WithFloatGaugeValue(v)
case uint64:
return dtMetric.WithIntGaugeValue(int64(v))
case int64:
return dtMetric.WithIntGaugeValue(32)
case bool:
if v {
return dtMetric.WithIntGaugeValue(1)
}
return dtMetric.WithIntGaugeValue(0)
}
return nil
}
func min(a, b int) int {
if a <= b {
return a
}
return b
}

View File

@ -5,9 +5,11 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"regexp"
"testing"
"time"
"github.com/dynatrace-oss/dynatrace-metric-utils-go/metric/apiconstants"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/metric"
@ -18,7 +20,8 @@ import (
func TestNilMetrics(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(`{"linesOk":10,"linesInvalid":0,"error":null}`)
err := json.NewEncoder(w).Encode(`{"linesOk":10,"linesInvalid":0,"error":null}`)
require.NoError(t, err)
}))
defer ts.Close()
@ -42,7 +45,8 @@ func TestNilMetrics(t *testing.T) {
func TestEmptyMetricsSlice(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(`{"linesOk":10,"linesInvalid":0,"error":null}`)
err := json.NewEncoder(w).Encode(`{"linesOk":10,"linesInvalid":0,"error":null}`)
require.NoError(t, err)
}))
defer ts.Close()
@ -65,7 +69,8 @@ func TestEmptyMetricsSlice(t *testing.T) {
func TestMockURL(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(`{"linesOk":10,"linesInvalid":0,"error":null}`)
err := json.NewEncoder(w).Encode(`{"linesOk":10,"linesInvalid":0,"error":null}`)
require.NoError(t, err)
}))
defer ts.Close()
@ -88,9 +93,10 @@ func TestMissingURL(t *testing.T) {
d.Log = testutil.Logger{}
err := d.Init()
require.Equal(t, oneAgentMetricsURL, d.URL)
require.NoError(t, err)
require.Equal(t, apiconstants.GetDefaultOneAgentEndpoint(), d.URL)
err = d.Connect()
require.Equal(t, oneAgentMetricsURL, d.URL)
require.Equal(t, apiconstants.GetDefaultOneAgentEndpoint(), d.URL)
require.NoError(t, err)
}
@ -99,9 +105,10 @@ func TestMissingAPITokenMissingURL(t *testing.T) {
d.Log = testutil.Logger{}
err := d.Init()
require.Equal(t, oneAgentMetricsURL, d.URL)
require.NoError(t, err)
require.Equal(t, apiconstants.GetDefaultOneAgentEndpoint(), d.URL)
err = d.Connect()
require.Equal(t, oneAgentMetricsURL, d.URL)
require.Equal(t, apiconstants.GetDefaultOneAgentEndpoint(), d.URL)
require.NoError(t, err)
}
@ -118,16 +125,15 @@ func TestSendMetric(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// check the encoded result
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
require.NoError(t, err)
}
require.NoError(t, err)
bodyString := string(bodyBytes)
expected := "mymeasurement.myfield,host=\"192.168.0.1\",nix=\"nix\" 3.140000\nmymeasurement.value,host=\"192.168.0.1\" 3.140000\n"
expected := "mymeasurement.myfield,host=192.168.0.1 gauge,3.14\nmymeasurement.value,host=192.168.0.2 count,3.14"
if bodyString != expected {
t.Errorf("Metric encoding failed. expected: %s but got: %s", expected, bodyString)
t.Errorf("Metric encoding failed. expected: %#v but got: %#v", expected, bodyString)
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(`{"linesOk":10,"linesInvalid":0,"error":null}`)
err = json.NewEncoder(w).Encode(`{"linesOk":10,"linesInvalid":0,"error":null}`)
require.NoError(t, err)
}))
defer ts.Close()
@ -145,16 +151,17 @@ func TestSendMetric(t *testing.T) {
m1 := metric.New(
"mymeasurement",
map[string]string{"host": "192.168.0.1", "nix": "nix"},
map[string]string{"host": "192.168.0.1"},
map[string]interface{}{"myfield": float64(3.14)},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)
m2 := metric.New(
"mymeasurement",
map[string]string{"host": "192.168.0.1"},
map[string]string{"host": "192.168.0.2"},
map[string]interface{}{"value": float64(3.14)},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
telegraf.Counter,
)
metrics := []telegraf.Metric{m1, m2}
@ -167,16 +174,16 @@ func TestSendSingleMetricWithUnorderedTags(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// check the encoded result
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
require.NoError(t, err)
}
require.NoError(t, err)
bodyString := string(bodyBytes)
expected := "mymeasurement.myfield,a=\"test\",b=\"test\",c=\"test\" 3.140000\n"
if bodyString != expected {
t.Errorf("Metric encoding failed. expected: %s but got: %s", expected, bodyString)
}
require.Regexp(t, regexp.MustCompile(`^mymeasurement\.myfield`), bodyString)
require.Regexp(t, regexp.MustCompile(`a=test`), bodyString)
require.Regexp(t, regexp.MustCompile(`b=test`), bodyString)
require.Regexp(t, regexp.MustCompile(`c=test`), bodyString)
require.Regexp(t, regexp.MustCompile(`gauge,3.14$`), bodyString)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
require.NoError(t, err)
}))
defer ts.Close()
@ -210,15 +217,14 @@ func TestSendMetricWithoutTags(t *testing.T) {
w.WriteHeader(http.StatusOK)
// check the encoded result
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
require.NoError(t, err)
}
require.NoError(t, err)
bodyString := string(bodyBytes)
expected := "mymeasurement.myfield 3.140000\n"
expected := "mymeasurement.myfield gauge,3.14"
if bodyString != expected {
t.Errorf("Metric encoding failed. expected: %s but got: %s", expected, bodyString)
t.Errorf("Metric encoding failed. expected: %#v but got: %#v", expected, bodyString)
}
json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
require.NoError(t, err)
}))
defer ts.Close()
@ -252,15 +258,19 @@ func TestSendMetricWithUpperCaseTagKeys(t *testing.T) {
w.WriteHeader(http.StatusOK)
// check the encoded result
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
require.NoError(t, err)
}
require.NoError(t, err)
bodyString := string(bodyBytes)
expected := "mymeasurement.myfield,aaa=\"test\",b_b=\"test\",ccc=\"test\" 3.140000\n"
if bodyString != expected {
t.Errorf("Metric encoding failed. expected: %s but got: %s", expected, bodyString)
}
json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
// expected := "mymeasurement.myfield,b_b=test,ccc=test,aaa=test gauge,3.14"
// use regex because dimension order isn't guaranteed
require.Regexp(t, regexp.MustCompile(`^mymeasurement\.myfield`), bodyString)
require.Regexp(t, regexp.MustCompile(`aaa=test`), bodyString)
require.Regexp(t, regexp.MustCompile(`b_b=test`), bodyString)
require.Regexp(t, regexp.MustCompile(`ccc=test`), bodyString)
require.Regexp(t, regexp.MustCompile(`gauge,3.14$`), bodyString)
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
require.NoError(t, err)
}))
defer ts.Close()
@ -294,15 +304,13 @@ func TestSendBooleanMetricWithoutTags(t *testing.T) {
w.WriteHeader(http.StatusOK)
// check the encoded result
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
require.NoError(t, err)
}
require.NoError(t, err)
bodyString := string(bodyBytes)
expected := "mymeasurement.myfield 1\n"
if bodyString != expected {
t.Errorf("Metric encoding failed. expected: %s but got: %s", expected, bodyString)
}
json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
// use regex because field order isn't guaranteed
require.Contains(t, bodyString, "mymeasurement.yes gauge,1")
require.Contains(t, bodyString, "mymeasurement.no gauge,0")
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
require.NoError(t, err)
}))
defer ts.Close()
@ -321,7 +329,48 @@ func TestSendBooleanMetricWithoutTags(t *testing.T) {
m1 := metric.New(
"mymeasurement",
map[string]string{},
map[string]interface{}{"myfield": bool(true)},
map[string]interface{}{"yes": true, "no": false},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)
metrics := []telegraf.Metric{m1}
err = d.Write(metrics)
require.NoError(t, err)
}
func TestSendCounterMetricWithoutTags(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
// check the encoded result
bodyBytes, err := ioutil.ReadAll(r.Body)
require.NoError(t, err)
bodyString := string(bodyBytes)
expected := "mymeasurement.value gauge,32"
if bodyString != expected {
t.Errorf("Metric encoding failed. expected: %#v but got: %#v", expected, bodyString)
}
err = json.NewEncoder(w).Encode(`{"linesOk":1,"linesInvalid":0,"error":null}`)
require.NoError(t, err)
}))
defer ts.Close()
d := &Dynatrace{}
d.URL = ts.URL
d.APIToken = "123"
d.Log = testutil.Logger{}
err := d.Init()
require.NoError(t, err)
err = d.Connect()
require.NoError(t, err)
// Init metrics
m1 := metric.New(
"mymeasurement",
map[string]string{},
map[string]interface{}{"value": 32},
time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC),
)