mirror of
https://github.com/grafana/grafana.git
synced 2024-11-28 11:44:26 -06:00
Merge pull request #13496 from grafana/stackdriver-filter-wildcards
Stackdriver filter wildcards
This commit is contained in:
commit
98071ccd17
@ -74,7 +74,17 @@ Click on the links above and click the `Enable` button:
|
||||
|
||||
Choose a metric from the `Metric` dropdown.
|
||||
|
||||
To add a filter, click the plus icon and choose a field to filter by and enter a filter value e.g. `instance_name = grafana-1`
|
||||
### Filter
|
||||
|
||||
To add a filter, click the plus icon and choose a field to filter by and enter a filter value e.g. `instance_name = grafana-1`. You can remove the filter by clicking on the filter name and select `--remove filter--`.
|
||||
|
||||
#### Simple wildcards
|
||||
|
||||
When the operator is set to `=` or `!=` it is possible to add wildcards to the filter value field. E.g `us-*` will capture all values that starts with "us-" and `*central-a` will capture all values that ends with "central-a". `*-central-*` captures all values that has the substring of -central-. Simple wildcards are less expensive than regular expressions.
|
||||
|
||||
#### Regular expressions
|
||||
|
||||
When the operator is set to `=~` or `!=~` it is possible to add regular expressions to the filter value field. E.g `us-central[1-3]-[af]` would match all values that starts with "us-central", is followed by a number in the range of 1 to 3, a dash and then either an "a" or an "f". Leading and trailing slashes are not needed when creating regular expressions.
|
||||
|
||||
### Aggregation
|
||||
|
||||
@ -105,20 +115,20 @@ The Alias By field allows you to control the format of the legend keys. The defa
|
||||
|
||||
#### Metric Type Patterns
|
||||
|
||||
Alias Pattern | Description | Example Result
|
||||
----------------- | ---------------------------- | -------------
|
||||
`{{metric.type}}` | returns the full Metric Type | `compute.googleapis.com/instance/cpu/utilization`
|
||||
`{{metric.name}}` | returns the metric name part | `instance/cpu/utilization`
|
||||
`{{metric.service}}` | returns the service part | `compute`
|
||||
| Alias Pattern | Description | Example Result |
|
||||
| -------------------- | ---------------------------- | ------------------------------------------------- |
|
||||
| `{{metric.type}}` | returns the full Metric Type | `compute.googleapis.com/instance/cpu/utilization` |
|
||||
| `{{metric.name}}` | returns the metric name part | `instance/cpu/utilization` |
|
||||
| `{{metric.service}}` | returns the service part | `compute` |
|
||||
|
||||
#### Label Patterns
|
||||
|
||||
In the Group By dropdown, you can see a list of metric and resource labels for a metric. These can be included in the legend key using alias patterns.
|
||||
|
||||
Alias Pattern Format | Description | Alias Pattern Example | Example Result
|
||||
---------------------- | ---------------------------------- | ---------------------------- | -------------
|
||||
`{{metric.label.xxx}}` | returns the metric label value | `{{metric.label.instance_name}}` | `grafana-1-prod`
|
||||
`{{resource.label.xxx}}` | returns the resource label value | `{{resource.label.zone}}` | `us-east1-b`
|
||||
| Alias Pattern Format | Description | Alias Pattern Example | Example Result |
|
||||
| ------------------------ | -------------------------------- | -------------------------------- | ---------------- |
|
||||
| `{{metric.label.xxx}}` | returns the metric label value | `{{metric.label.instance_name}}` | `grafana-1-prod` |
|
||||
| `{{resource.label.xxx}}` | returns the resource label value | `{{resource.label.zone}}` | `us-east1-b` |
|
||||
|
||||
Example Alias By: `{{metric.type}} - {{metric.labels.instance_name}}`
|
||||
|
||||
|
@ -159,6 +159,39 @@ func (e *StackdriverExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Stackd
|
||||
return stackdriverQueries, nil
|
||||
}
|
||||
|
||||
func reverse(s string) string {
|
||||
chars := []rune(s)
|
||||
for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 {
|
||||
chars[i], chars[j] = chars[j], chars[i]
|
||||
}
|
||||
return string(chars)
|
||||
}
|
||||
|
||||
func interpolateFilterWildcards(value string) string {
|
||||
re := regexp.MustCompile("[*]")
|
||||
matches := len(re.FindAllStringIndex(value, -1))
|
||||
if matches == 2 && strings.HasSuffix(value, "*") && strings.HasPrefix(value, "*") {
|
||||
value = strings.Replace(value, "*", "", -1)
|
||||
value = fmt.Sprintf(`has_substring("%s")`, value)
|
||||
} else if matches == 1 && strings.HasPrefix(value, "*") {
|
||||
value = strings.Replace(value, "*", "", 1)
|
||||
value = fmt.Sprintf(`ends_with("%s")`, value)
|
||||
} else if matches == 1 && strings.HasSuffix(value, "*") {
|
||||
value = reverse(strings.Replace(reverse(value), "*", "", 1))
|
||||
value = fmt.Sprintf(`starts_with("%s")`, value)
|
||||
} else if matches != 0 {
|
||||
re := regexp.MustCompile(`[-\/^$+?.()|[\]{}]`)
|
||||
value = string(re.ReplaceAllFunc([]byte(value), func(in []byte) []byte {
|
||||
return []byte(strings.Replace(string(in), string(in), `\\`+string(in), 1))
|
||||
}))
|
||||
value = strings.Replace(value, "*", ".*", -1)
|
||||
value = strings.Replace(value, `"`, `\\"`, -1)
|
||||
value = fmt.Sprintf(`monitoring.regex.full_match("^%s$")`, value)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func buildFilterString(metricType string, filterParts []interface{}) string {
|
||||
filterString := ""
|
||||
for i, part := range filterParts {
|
||||
@ -166,7 +199,15 @@ func buildFilterString(metricType string, filterParts []interface{}) string {
|
||||
if part == "AND" {
|
||||
filterString += " "
|
||||
} else if mod == 2 {
|
||||
filterString += fmt.Sprintf(`"%s"`, part)
|
||||
operator := filterParts[i-1]
|
||||
if operator == "=~" || operator == "!=~" {
|
||||
filterString = reverse(strings.Replace(reverse(filterString), "~", "", 1))
|
||||
filterString += fmt.Sprintf(`monitoring.regex.full_match("%s")`, part)
|
||||
} else if strings.Contains(part.(string), "*") {
|
||||
filterString += interpolateFilterWildcards(part.(string))
|
||||
} else {
|
||||
filterString += fmt.Sprintf(`"%s"`, part)
|
||||
}
|
||||
} else {
|
||||
filterString += part.(string)
|
||||
}
|
||||
|
@ -342,6 +342,97 @@ func TestStackdriver(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("when interpolating filter wildcards", func() {
|
||||
Convey("and wildcard is used in the beginning and the end of the word", func() {
|
||||
Convey("and theres no wildcard in the middle of the word", func() {
|
||||
value := interpolateFilterWildcards("*-central1*")
|
||||
So(value, ShouldEqual, `has_substring("-central1")`)
|
||||
})
|
||||
Convey("and there is a wildcard in the middle of the word", func() {
|
||||
value := interpolateFilterWildcards("*-cent*ral1*")
|
||||
So(value, ShouldNotStartWith, `has_substring`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and wildcard is used in the beginning of the word", func() {
|
||||
Convey("and there is not a wildcard elsewhere in the word", func() {
|
||||
value := interpolateFilterWildcards("*-central1")
|
||||
So(value, ShouldEqual, `ends_with("-central1")`)
|
||||
})
|
||||
Convey("and there is a wildcard elsewhere in the word", func() {
|
||||
value := interpolateFilterWildcards("*-cent*al1")
|
||||
So(value, ShouldNotStartWith, `ends_with`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and wildcard is used at the end of the word", func() {
|
||||
Convey("and there is not a wildcard elsewhere in the word", func() {
|
||||
value := interpolateFilterWildcards("us-central*")
|
||||
So(value, ShouldEqual, `starts_with("us-central")`)
|
||||
})
|
||||
Convey("and there is a wildcard elsewhere in the word", func() {
|
||||
value := interpolateFilterWildcards("*us-central*")
|
||||
So(value, ShouldNotStartWith, `starts_with`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and wildcard is used in the middle of the word", func() {
|
||||
Convey("and there is only one wildcard", func() {
|
||||
value := interpolateFilterWildcards("us-ce*tral1-b")
|
||||
So(value, ShouldEqual, `monitoring.regex.full_match("^us\\-ce.*tral1\\-b$")`)
|
||||
})
|
||||
|
||||
Convey("and there is more than one wildcard", func() {
|
||||
value := interpolateFilterWildcards("us-ce*tra*1-b")
|
||||
So(value, ShouldEqual, `monitoring.regex.full_match("^us\\-ce.*tra.*1\\-b$")`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and wildcard is used in the middle of the word and in the beginning of the word", func() {
|
||||
value := interpolateFilterWildcards("*s-ce*tral1-b")
|
||||
So(value, ShouldEqual, `monitoring.regex.full_match("^.*s\\-ce.*tral1\\-b$")`)
|
||||
})
|
||||
|
||||
Convey("and wildcard is used in the middle of the word and in the ending of the word", func() {
|
||||
value := interpolateFilterWildcards("us-ce*tral1-*")
|
||||
So(value, ShouldEqual, `monitoring.regex.full_match("^us\\-ce.*tral1\\-.*$")`)
|
||||
})
|
||||
|
||||
Convey("and no wildcard is used", func() {
|
||||
value := interpolateFilterWildcards("us-central1-a}")
|
||||
So(value, ShouldEqual, `us-central1-a}`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("when building filter string", func() {
|
||||
Convey("and theres no regex operator", func() {
|
||||
Convey("and there are wildcards in a filter value", func() {
|
||||
filterParts := []interface{}{"zone", "=", "*-central1*"}
|
||||
value := buildFilterString("somemetrictype", filterParts)
|
||||
So(value, ShouldEqual, `metric.type="somemetrictype" zone=has_substring("-central1")`)
|
||||
})
|
||||
|
||||
Convey("and there are no wildcards in any filter value", func() {
|
||||
filterParts := []interface{}{"zone", "!=", "us-central1-a"}
|
||||
value := buildFilterString("somemetrictype", filterParts)
|
||||
So(value, ShouldEqual, `metric.type="somemetrictype" zone!="us-central1-a"`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and there is a regex operator", func() {
|
||||
filterParts := []interface{}{"zone", "=~", "us-central1-a~"}
|
||||
value := buildFilterString("somemetrictype", filterParts)
|
||||
Convey("it should remove the ~ character from the operator that belongs to the value", func() {
|
||||
So(value, ShouldNotContainSubstring, `=~`)
|
||||
So(value, ShouldContainSubstring, `zone=`)
|
||||
})
|
||||
|
||||
Convey("it should insert monitoring.regex.full_match before filter value", func() {
|
||||
So(value, ShouldContainSubstring, `zone=monitoring.regex.full_match("us-central1-a~")`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user