Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add datadog #915

Merged
merged 38 commits into from
Feb 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e35e515
Add datadog / statsd integration
ivoreis Jan 10, 2019
1ff30ed
fixup! Add datadog / statsd integration
mstoykov Jan 31, 2019
2f3a932
statsd/datadog: combine addr and port together
mstoykov Jan 31, 2019
03e262a
statsd/datadog: refactoring and making tag whitelisting faster
mstoykov Jan 31, 2019
b9fadaf
statsd/datadog: add namespace for statsd as well
mstoykov Jan 31, 2019
6aa196d
statsd/datadog: set namespace by default to 'k6.'
mstoykov Jan 31, 2019
b1bd6ef
Fix using old name of Threshold.LastFailed
mstoykov Jan 31, 2019
ce3e7e6
statsd/datadog: Remove unused threshold
mstoykov Jan 31, 2019
d796a0d
statsd/datadog: use string concat instead of Sprintf
mstoykov Jan 31, 2019
3b19b49
statsd/datadog: shrink and simplify internal sample struct
mstoykov Jan 31, 2019
493022a
Add release note for the new datadog and statsd collectors
mstoykov Jan 31, 2019
b6fb058
statsd/datadog: update year in copyright
mstoykov Feb 1, 2019
4eb763c
statsd/datadog: refactor sample struct to be smaller
mstoykov Feb 1, 2019
cb3a384
statsd/datadog: remove summary sending
mstoykov Feb 1, 2019
7730884
statsd/datadog: move client making to the initalization of the collector
mstoykov Feb 1, 2019
47750bb
stasd/datadog: Remove unneeded types and some connection making refactor
mstoykov Feb 1, 2019
30b28fa
statsd/datadog: Make push interval configurable
mstoykov Feb 1, 2019
a746bff
statsd/datadog: Add/Fix error logging
mstoykov Feb 1, 2019
abf2381
statsd/datadog: More refactoring - fixing visibility and refactor err…
mstoykov Feb 1, 2019
1cd780b
statsd/datadog: Fix comments and better naming
mstoykov Feb 1, 2019
8af8dc3
statsd/datadog: stop using envconfig for setting default values
mstoykov Feb 1, 2019
884ac1e
Unmarshall TagSet from a list of tags fix #768
mstoykov Feb 1, 2019
338ec03
statsd/datadog: update the release notes
mstoykov Feb 1, 2019
d3db1e9
statsd/datadog: change json names to camelCase
mstoykov Feb 1, 2019
3082d67
statsd/datadog: fix not being able to distinguish between the default…
mstoykov Feb 1, 2019
5d644b5
statsd/datadog: update the release notes
mstoykov Feb 1, 2019
268e594
statsd/datadog: fix TagWhitelist default value ... quotation marks ...
mstoykov Feb 1, 2019
e38b216
Add test for TagSet's UnmarhalText
mstoykov Feb 4, 2019
96801f3
statsd/datadog: print less by default when metrics coulnd't be send
mstoykov Feb 4, 2019
84e7813
statsd/datadog: fix error starting with capital letter
mstoykov Feb 4, 2019
2828701
statsd/datadog: fix merging base statsd config in case of datadog
mstoykov Feb 6, 2019
c0a3b1f
statsd/datadog: add base datadog test
mstoykov Feb 6, 2019
6cfe4d8
statsd/datadog: add test for checks
mstoykov Feb 7, 2019
d4cce2e
stasd/datadog: add stupid tests
mstoykov Feb 7, 2019
5244715
statsd/datadog: refactor tests
mstoykov Feb 7, 2019
3d9b426
Remove vim swap file
mstoykov Feb 12, 2019
4c5e049
statsd/datadog: Change datadog's whitelist to blacklist and don't bla…
mstoykov Feb 13, 2019
1c93908
Merge remote-tracking branch 'origin/master' into feature/addDatadog
mstoykov Feb 13, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions cmd/collectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ import (
"github.com/kelseyhightower/envconfig"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/stats/cloud"
"github.com/loadimpact/k6/stats/datadog"
"github.com/loadimpact/k6/stats/influxdb"
jsonc "github.com/loadimpact/k6/stats/json"
"github.com/loadimpact/k6/stats/kafka"
"github.com/loadimpact/k6/stats/statsd"
"github.com/loadimpact/k6/stats/statsd/common"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
Expand All @@ -41,6 +44,8 @@ const (
collectorJSON = "json"
collectorKafka = "kafka"
collectorCloud = "cloud"
collectorStatsD = "statsd"
collectorDatadog = "datadog"
mstoykov marked this conversation as resolved.
Show resolved Hide resolved
)

func parseCollector(s string) (t, arg string) {
Expand Down Expand Up @@ -93,6 +98,18 @@ func newCollector(collectorName, arg string, src *lib.SourceData, conf Config) (
config = config.Apply(cmdConfig)
}
return kafka.New(config)
case collectorStatsD:
config := common.NewConfig().Apply(conf.Collectors.StatsD)
if err := envconfig.Process("k6_statsd", &config); err != nil {
return nil, err
}
return statsd.New(config)
case collectorDatadog:
config := datadog.NewConfig().Apply(conf.Collectors.Datadog)
if err := envconfig.Process("k6_datadog", &config); err != nil {
return nil, err
}
return datadog.New(config)
default:
return nil, errors.Errorf("unknown output type: %s", collectorName)
}
Expand Down
6 changes: 6 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import (
"github.com/kelseyhightower/envconfig"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/stats/cloud"
"github.com/loadimpact/k6/stats/datadog"
"github.com/loadimpact/k6/stats/influxdb"
"github.com/loadimpact/k6/stats/kafka"
"github.com/loadimpact/k6/stats/statsd/common"
"github.com/shibukawa/configdir"
"github.com/spf13/afero"
"github.com/spf13/pflag"
Expand Down Expand Up @@ -74,6 +76,8 @@ type Config struct {
InfluxDB influxdb.Config `json:"influxdb"`
Kafka kafka.Config `json:"kafka"`
Cloud cloud.Config `json:"cloud"`
StatsD common.Config `json:"statsd"`
Datadog datadog.Config `json:"datadog"`
} `json:"collectors"`
}

Expand All @@ -97,6 +101,8 @@ func (c Config) Apply(cfg Config) Config {
c.Collectors.InfluxDB = c.Collectors.InfluxDB.Apply(cfg.Collectors.InfluxDB)
c.Collectors.Cloud = c.Collectors.Cloud.Apply(cfg.Collectors.Cloud)
c.Collectors.Kafka = c.Collectors.Kafka.Apply(cfg.Collectors.Kafka)
c.Collectors.StatsD = c.Collectors.StatsD.Apply(cfg.Collectors.StatsD)
c.Collectors.Datadog = c.Collectors.Datadog.Apply(cfg.Collectors.Datadog)
return c
}

Expand Down
16 changes: 16 additions & 0 deletions lib/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
package lib

import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"reflect"
"strings"

"github.com/loadimpact/k6/lib/types"
"github.com/loadimpact/k6/stats"
Expand Down Expand Up @@ -73,6 +75,20 @@ func (t *TagSet) UnmarshalJSON(data []byte) error {
return nil
}

// UnmarshalText converts the tag list to tagset.
func (t *TagSet) UnmarshalText(data []byte) error {
var list = bytes.Split(data, []byte(","))
*t = make(map[string]bool, len(list))
for _, key := range list {
key := strings.TrimSpace(string(key))
if key == "" {
continue
}
(*t)[key] = true
}
return nil
}

// Describes a TLS version. Serialised to/from JSON as a string, eg. "tls1.2".
type TLSVersion int

Expand Down
23 changes: 22 additions & 1 deletion lib/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import (
"github.com/loadimpact/k6/lib/types"
"github.com/loadimpact/k6/stats"
"github.com/stretchr/testify/assert"
"gopkg.in/guregu/null.v3"
"github.com/stretchr/testify/require"
null "gopkg.in/guregu/null.v3"
)

func TestOptions(t *testing.T) {
Expand Down Expand Up @@ -480,3 +481,23 @@ func TestOptionsEnv(t *testing.T) {
})
}
}

func TestTagSetTextUnmarshal(t *testing.T) {

var testMatrix = map[string]map[string]bool{
"": {},
"test": {"test": true},
"test1,test2": {"test1": true, "test2": true},
" test1 , test2 ": {"test1": true, "test2": true},
" test1 , , test2 ": {"test1": true, "test2": true},
" test1 ,, test2 ,,": {"test1": true, "test2": true},
}

for input, expected := range testMatrix {
var set = new(TagSet)
err := set.UnmarshalText([]byte(input))
require.NoError(t, err)

require.Equal(t, (map[string]bool)(*set), expected)
}
}
13 changes: 13 additions & 0 deletions release notes/upcoming.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ You can now specify a file for all things logged by `console.log` to get written

Thanks to @cheesedosa for both proposing and implementing this!

### New result outputs: StatsD and Datadog (#915)

You can now output any metrics k6 collects to StatsD or Datadog by running `k6 run --out statsd script.js` or `k6 run --out datadog script.js` respectively. Both are very similar, but Datadog has a concept of metric tags, the key-value metadata pairs that will allow you to distinguish between requests for different URLs, response statuses, different groups, etc.

Some details:
- By default both outputs send metrics to a local agent listening on `localhost:8125` (currently only UDP is supported as a transport). You can change this address via the `K6_DATADOG_ADDR` or `K6_STATSD_ADDR` environment variables, by setting their values in the format of `address:port`.
- The new outputs also support adding a `namespace` - a prefix before all the metric names. You can set it via the `K6_DATADOG_NAMESPACE` or `K6_STATSD_NAMESPACE` environment variables respectively. Its default value is `k6.` - notice the dot at the end.
- You can configure how often data batches are sent via the `K6_STATSD_PUSH_INTERVAL` / `K6_DATADOG_PUSH_INTEVAL` environment variables. The default value is `1s`.
- Another performance tweak can be done by changing the default buffer size of 20 through `K6_STATSD_BUFFER_SIZE` / `K6_DATADOG_BUFFER_SIZE`.
- In the case of Datadog, there is an additional configuration `K6_DATADOG_TAG_BLACKLIST`, which by default is equal to `` (nothing). This is a comma separated list of tags that should *NOT* be sent to Datadog. All other metric tags that k6 emits will be sent.

Thanks to @ivoreis for their work on this!

### k6/crypto: Random bytes method (#922)
This feature adds a method to return an array with a number of cryptographically random bytes. It will either return exactly the amount of bytes requested or will throw an exception if something went wrong.

Expand Down
74 changes: 74 additions & 0 deletions stats/datadog/collector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2019 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package datadog

import (
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/stats/statsd/common"
)

type tagHandler lib.TagSet

func (t tagHandler) processTags(tags map[string]string) []string {
var res []string

for key, value := range tags {
if value != "" && !t[key] {
res = append(res, key+":"+value)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not certain how well this will hold up if I have a : in my tag name (after all, I can define custom tags) or value...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm ... maybe we should restrict tags as we have restricted metrics ? https://github.com/loadimpact/k6/blob/master/js/modules/k6/metrics/metrics.go#L35 . We could escape the ':' somehow but in my opinion this will not help much ... maybe just leave it and document that having : in the tags will produce not desired tags in datadog ? after all it won't break just if you have tag with name something:else and value myvalye you will get tag with key something and value ``else:mayvalue` in datadog

}
}
return res
}

// Config defines the datadog configuration
type Config struct {
common.Config

TagBlacklist lib.TagSet `json:"tagBlacklist,omitempty" envconfig:"TAG_BLACKLIST"`
}

// Apply saves config non-zero config values from the passed config in the receiver.
func (c Config) Apply(cfg Config) Config {
c.Config = c.Config.Apply(cfg.Config)

if cfg.TagBlacklist != nil {
c.TagBlacklist = cfg.TagBlacklist
}

return c
}

// NewConfig creates a new Config instance with default values for some fields.
func NewConfig() Config {
return Config{
Config: common.NewConfig(),
TagBlacklist: lib.GetTagSet(),
}
}

// New creates a new statsd connector client
func New(conf Config) (*common.Collector, error) {
return &common.Collector{
Config: conf.Config,
Type: "datadog",
ProcessTags: tagHandler(conf.TagBlacklist).processTags,
}, nil
}
45 changes: 45 additions & 0 deletions stats/datadog/collector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package datadog

import (
"strings"
"testing"

"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/stats"
"github.com/loadimpact/k6/stats/statsd/common"
"github.com/loadimpact/k6/stats/statsd/common/testutil"
"github.com/stretchr/testify/require"
)

func TestCollector(t *testing.T) {
var tagSet = lib.GetTagSet("tag1", "tag2")
var handler = tagHandler(tagSet)
testutil.BaseTest(t, func(config common.Config) (*common.Collector, error) {
return New(NewConfig().Apply(Config{
TagBlacklist: tagSet,
Config: config,
}))
}, func(t *testing.T, containers []stats.SampleContainer, expectedOutput, output string) {
var outputLines = strings.Split(output, "\n")
var expectedOutputLines = strings.Split(expectedOutput, "\n")
for i, container := range containers {
for j, sample := range container.GetSamples() {
var (
expectedTagList = handler.processTags(sample.GetTags().CloneTags())
expectedOutputLine = expectedOutputLines[i*j+i]
outputLine = outputLines[i*j+i]
outputWithoutTags = outputLine
outputTagList = []string{}
tagSplit = strings.LastIndex(outputLine, "|#")
)

if tagSplit != -1 {
outputWithoutTags = outputLine[:tagSplit]
outputTagList = strings.Split(outputLine[tagSplit+len("|#"):], ",")
}
require.Equal(t, expectedOutputLine, outputWithoutTags)
require.ElementsMatch(t, expectedTagList, outputTagList)
}
}
})
}
33 changes: 33 additions & 0 deletions stats/statsd/collector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2019 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package statsd

import (
"github.com/loadimpact/k6/stats/statsd/common"
)

// New creates a new statsd connector client
func New(conf common.Config) (*common.Collector, error) {
return &common.Collector{
Config: conf,
Type: "statsd",
}, nil
}
16 changes: 16 additions & 0 deletions stats/statsd/collector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package statsd

import (
"testing"

"github.com/loadimpact/k6/stats"
"github.com/loadimpact/k6/stats/statsd/common/testutil"
"github.com/stretchr/testify/require"
)

func TestCollector(t *testing.T) {
testutil.BaseTest(t, New,
func(t *testing.T, _ []stats.SampleContainer, expectedOutput, output string) {
require.Equal(t, expectedOutput, output)
})
}
Loading