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

allow specifying field name in graphite template #4178

Merged
merged 10 commits into from
Oct 8, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 39 additions & 3 deletions services/graphite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,39 @@ Additional tags can be added to a metric that don't exist on the received metric
* Template: `.host.resource.measurement* region=us-west,zone=1a`
* Output: _measurement_ = `loadavg.10` _tags_ = `host=localhost resource=cpu region=us-west zone=1a`

### Fields

A field name can be specified by using the keyword _field_. By default if no _field_ keyword is specified then the metric will be written to a field named _value_.

When using the current default engine _BZ1_, it's recommended to use a single field per value for performance reasons.

When using the _TSM1_ engine it's possible to amend measurement metrics with additional fields, e.g:

Input:
```
sensu.metric.net.server0.eth0.rx_packets 461295119435 1444234982
sensu.metric.net.server0.eth0.tx_bytes 1093086493388480 1444234982
sensu.metric.net.server0.eth0.rx_bytes 1015633926034834 1444234982
sensu.metric.net.server0.eth0.tx_errors 0 1444234982
sensu.metric.net.server0.eth0.rx_errors 0 1444234982
sensu.metric.net.server0.eth0.tx_dropped 0 1444234982
sensu.metric.net.server0.eth0.rx_dropped 0 1444234982
```

With template:
```
sensu.metric.* ..measurement.host.interface.field
```

Becomes database entry:
```
> select * from net
name: net
---------
time host interface rx_bytes rx_dropped rx_errors rx_packets tx_bytes tx_dropped tx_errors
1444234982000000000 server0 eth0 1.015633926034834e+15 0 0 4.61295119435e+11 1.09308649338848e+15 0 0
```

## Multiple Templates

One template may not match all metrics. For example, using multiple plugins with diamond will produce metrics in different formats. If you need to use multiple templates, you'll need to define a prefix filter that must match before the template can be applied.
Expand Down Expand Up @@ -119,13 +152,16 @@ If you need to add the same set of tags to all metrics, you can define them glob
separator = "_"
tags = ["region=us-east", "zone=1c"]
templates = [
# filter + template
"*.app env.service.resource.measurement",
# filter + template
"*.app env.service.resource.measurement",

# filter + template + extra tag
"stats.* .host.measurement* region=us-west,agent=sensu",

# default template. Ignore the first graphite component "servers"
# filter + template with field name
"stats.* .host.measurement.field",

# default template. Ignore the first graphite component "servers"
".measurement*",
]
```
27 changes: 21 additions & 6 deletions services/graphite/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ func (p *Parser) Parse(line string) (models.Point, error) {

// decode the name and tags
template := p.matcher.Match(fields[0])
measurement, tags := template.Apply(fields[0])
measurement, tags, field, err := template.Apply(fields[0])
if err != nil {
return nil, err
}

// Could not extract measurement, use the raw value
if measurement == "" {
Expand All @@ -113,7 +116,12 @@ func (p *Parser) Parse(line string) (models.Point, error) {
return nil, fmt.Errorf(`field "%s" value: %s`, fields[0], err)
}

fieldValues := map[string]interface{}{"value": v}
fieldValues := map[string]interface{}{}
if field != "" {
fieldValues[field] = v
} else {
fieldValues["value"] = v
}

// If no 3rd field, use now as timestamp
timestamp := time.Now().UTC()
Expand Down Expand Up @@ -149,11 +157,11 @@ func (p *Parser) Parse(line string) (models.Point, error) {

// Apply extracts the template fields form the given line and returns the
// measurement name and tags
func (p *Parser) ApplyTemplate(line string) (string, map[string]string) {
func (p *Parser) ApplyTemplate(line string) (string, map[string]string, string, error) {
// Break line into fields (name, value, timestamp), only name is used
fields := strings.Fields(line)
if len(fields) == 0 {
return "", make(map[string]string)
return "", make(map[string]string), "", nil
}
// decode the name and tags
template := p.matcher.Match(fields[0])
Expand Down Expand Up @@ -191,11 +199,12 @@ func NewTemplate(pattern string, defaultTags models.Tags, separator string) (*te

// Apply extracts the template fields form the given line and returns the measurement
// name and tags
func (t *template) Apply(line string) (string, map[string]string) {
func (t *template) Apply(line string) (string, map[string]string, string, error) {
fields := strings.Split(line, ".")
var (
measurement []string
tags = make(map[string]string)
field string
)

// Set any default tags
Expand All @@ -210,6 +219,12 @@ func (t *template) Apply(line string) (string, map[string]string) {

if tag == "measurement" {
measurement = append(measurement, fields[i])
} else if tag == "field" {
if len(field) != 0 {
return "", nil, "", fmt.Errorf("'field' can only be used once in each template: %q", line)
} else {
field = fields[i]
}
} else if tag == "measurement*" {
measurement = append(measurement, fields[i:]...)
break
Expand All @@ -218,7 +233,7 @@ func (t *template) Apply(line string) (string, map[string]string) {
}
}

return strings.Join(measurement, t.separator), tags
return strings.Join(measurement, t.separator), tags, field, nil
}

// matcher determines which template should be applied to a given metric
Expand Down
50 changes: 45 additions & 5 deletions services/graphite/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func TestTemplateApply(t *testing.T) {
continue
}

measurement, tags := tmpl.Apply(test.input)
measurement, tags, _, _ := tmpl.Apply(test.input)
if measurement != test.measurement {
t.Fatalf("name parse failer. expected %v, got %v", test.measurement, measurement)
}
Expand Down Expand Up @@ -558,7 +558,7 @@ func TestApplyTemplate(t *testing.T) {
t.Fatalf("unexpected error creating parser, got %v", err)
}

measurement, _ := p.ApplyTemplate("current.users")
measurement, _, _, _ := p.ApplyTemplate("current.users")
if measurement != "current_users" {
t.Errorf("Parser.ApplyTemplate unexpected result. got %s, exp %s",
measurement, "current_users")
Expand All @@ -576,7 +576,7 @@ func TestApplyTemplateNoMatch(t *testing.T) {
t.Fatalf("unexpected error creating parser, got %v", err)
}

measurement, _ := p.ApplyTemplate("current.users")
measurement, _, _, _ := p.ApplyTemplate("current.users")
if measurement != "current.users" {
t.Errorf("Parser.ApplyTemplate unexpected result. got %s, exp %s",
measurement, "current.users")
Expand All @@ -597,7 +597,7 @@ func TestApplyTemplateSpecific(t *testing.T) {
t.Fatalf("unexpected error creating parser, got %v", err)
}

measurement, tags := p.ApplyTemplate("current.users.facebook")
measurement, tags, _, _ := p.ApplyTemplate("current.users.facebook")
if measurement != "current_users" {
t.Errorf("Parser.ApplyTemplate unexpected result. got %s, exp %s",
measurement, "current_users")
Expand All @@ -621,7 +621,7 @@ func TestApplyTemplateTags(t *testing.T) {
t.Fatalf("unexpected error creating parser, got %v", err)
}

measurement, tags := p.ApplyTemplate("current.users")
measurement, tags, _, _ := p.ApplyTemplate("current.users")
if measurement != "current_users" {
t.Errorf("Parser.ApplyTemplate unexpected result. got %s, exp %s",
measurement, "current_users")
Expand All @@ -635,3 +635,43 @@ func TestApplyTemplateTags(t *testing.T) {
t.Errorf("Expected region='us-west' tag, got region='%s'", region)
}
}

func TestApplyTemplateField(t *testing.T) {
o := graphite.Options{
Separator: "_",
Templates: []string{"current.* measurement.measurement.field"},
}
p, err := graphite.NewParserWithOptions(o)
if err != nil {
t.Fatalf("unexpected error creating parser, got %v", err)
}

measurement, _, field, err := p.ApplyTemplate("current.users.logged_in")

if measurement != "current_users" {
t.Errorf("Parser.ApplyTemplate unexpected result. got %s, exp %s",
measurement, "current_users")
}

if field != "logged_in" {
t.Errorf("Parser.ApplyTemplate unexpected result. got %s, exp %s",
field, "logged_in")
}
}

func TestApplyTemplateFieldError(t *testing.T) {
o := graphite.Options{
Separator: "_",
Templates: []string{"current.* measurement.field.field"},
}
p, err := graphite.NewParserWithOptions(o)
if err != nil {
t.Fatalf("unexpected error creating parser, got %v", err)
}

_, _, _, err = p.ApplyTemplate("current.users.logged_in")
if err == nil {
t.Errorf("Parser.ApplyTemplate unexpected result. got %s, exp %s", err,
"'field' can only be used once in each template: current.users.logged_in")
}
}