app/vmselect/graphite: add /tags/autoComplete/values handler from Graphite Tags API

This commit is contained in:
Aliaksandr Valialkin 2020-11-16 15:22:36 +02:00
parent 86f99c6b55
commit 2c67232565
8 changed files with 214 additions and 113 deletions

View File

@ -552,6 +552,7 @@ VictoriaMetrics supports the following handlers from [Graphite Tags API](https:/
* [/tags/tag_name](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags)
* [/tags/findSeries](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags)
* [/tags/autoComplete/tags](https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support)
* [/tags/autoComplete/values](https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support)
### How to build from sources

View File

@ -16,6 +16,91 @@ import (
"github.com/VictoriaMetrics/metrics"
)
// TagsAutoCompleteValuesHandler implements /tags/autoComplete/values endpoint from Graphite Tags API.
//
// See https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support
func TagsAutoCompleteValuesHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) error {
deadline := searchutils.GetDeadlineForQuery(r, startTime)
if err := r.ParseForm(); err != nil {
return fmt.Errorf("cannot parse form values: %w", err)
}
limit, err := getInt(r, "limit")
if err != nil {
return err
}
if limit <= 0 {
// Use limit=100 by default. See https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support
limit = 100
}
tag := r.FormValue("tag")
if len(tag) == 0 {
return fmt.Errorf("missing `tag` query arg")
}
valuePrefix := r.FormValue("valuePrefix")
exprs := r.Form["expr"]
var tagValues []string
if len(exprs) == 0 {
// Fast path: there are no `expr` filters, so use netstorage.GetGraphiteTagValues.
// Escape special chars in tagPrefix as Graphite does.
// See https://github.com/graphite-project/graphite-web/blob/3ad279df5cb90b211953e39161df416e54a84948/webapp/graphite/tags/base.py#L228
filter := regexp.QuoteMeta(valuePrefix)
tagValues, err = netstorage.GetGraphiteTagValues(tag, filter, limit, deadline)
if err != nil {
return err
}
} else {
// Slow path: use netstorage.SearchMetricNames for applying `expr` filters.
sq, err := getSearchQueryForExprs(exprs)
if err != nil {
return err
}
mns, err := netstorage.SearchMetricNames(sq, deadline)
if err != nil {
return fmt.Errorf("cannot fetch metric names for %q: %w", sq, err)
}
m := make(map[string]struct{})
if tag == "name" {
tag = "__name__"
}
for _, mn := range mns {
tagValue := mn.GetTagValue(tag)
if len(tagValue) == 0 {
continue
}
m[string(tagValue)] = struct{}{}
}
if len(valuePrefix) > 0 {
for tagValue := range m {
if !strings.HasPrefix(tagValue, valuePrefix) {
delete(m, tagValue)
}
}
}
tagValues = make([]string, 0, len(m))
for tagValue := range m {
tagValues = append(tagValues, tagValue)
}
sort.Strings(tagValues)
if limit > 0 && limit < len(tagValues) {
tagValues = tagValues[:limit]
}
}
jsonp := r.FormValue("jsonp")
contentType := getContentType(jsonp)
w.Header().Set("Content-Type", contentType)
bw := bufferedwriter.Get(w)
defer bufferedwriter.Put(bw)
WriteTagsAutoCompleteResponse(bw, tagValues, jsonp)
if err := bw.Flush(); err != nil {
return err
}
tagsAutoCompleteValuesDuration.UpdateDuration(startTime)
return nil
}
var tagsAutoCompleteValuesDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/tags/autoComplete/values"}`)
// TagsAutoCompleteTagsHandler implements /tags/autoComplete/tags endpoint from Graphite Tags API.
//
// See https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support
@ -36,7 +121,7 @@ func TagsAutoCompleteTagsHandler(startTime time.Time, w http.ResponseWriter, r *
exprs := r.Form["expr"]
var labels []string
if len(exprs) == 0 {
// Fast path: there are no `expr` filters.
// Fast path: there are no `expr` filters, so use netstorage.GetGraphiteTags.
// Escape special chars in tagPrefix as Graphite does.
// See https://github.com/graphite-project/graphite-web/blob/3ad279df5cb90b211953e39161df416e54a84948/webapp/graphite/tags/base.py#L181
@ -47,16 +132,10 @@ func TagsAutoCompleteTagsHandler(startTime time.Time, w http.ResponseWriter, r *
}
} else {
// Slow path: use netstorage.SearchMetricNames for applying `expr` filters.
tfs, err := exprsToTagFilters(exprs)
sq, err := getSearchQueryForExprs(exprs)
if err != nil {
return err
}
ct := time.Now().UnixNano() / 1e6
sq := &storage.SearchQuery{
MinTimestamp: 0,
MaxTimestamp: ct,
TagFilterss: [][]storage.TagFilter{tfs},
}
mns, err := netstorage.SearchMetricNames(sq, deadline)
if err != nil {
return fmt.Errorf("cannot fetch metric names for %q: %w", sq, err)
@ -90,7 +169,7 @@ func TagsAutoCompleteTagsHandler(startTime time.Time, w http.ResponseWriter, r *
w.Header().Set("Content-Type", contentType)
bw := bufferedwriter.Get(w)
defer bufferedwriter.Put(bw)
WriteTagsAutoCompleteTagsResponse(bw, labels, jsonp)
WriteTagsAutoCompleteResponse(bw, labels, jsonp)
if err := bw.Flush(); err != nil {
return err
}
@ -116,16 +195,10 @@ func TagsFindSeriesHandler(startTime time.Time, w http.ResponseWriter, r *http.R
if len(exprs) == 0 {
return fmt.Errorf("expecting at least one `expr` query arg")
}
tfs, err := exprsToTagFilters(exprs)
sq, err := getSearchQueryForExprs(exprs)
if err != nil {
return err
}
ct := time.Now().UnixNano() / 1e6
sq := &storage.SearchQuery{
MinTimestamp: 0,
MaxTimestamp: ct,
TagFilterss: [][]storage.TagFilter{tfs},
}
mns, err := netstorage.SearchMetricNames(sq, deadline)
if err != nil {
return fmt.Errorf("cannot fetch metric names for %q: %w", sq, err)
@ -244,6 +317,20 @@ func getInt(r *http.Request, argName string) (int, error) {
return n, nil
}
func getSearchQueryForExprs(exprs []string) (*storage.SearchQuery, error) {
tfs, err := exprsToTagFilters(exprs)
if err != nil {
return nil, err
}
ct := time.Now().UnixNano() / 1e6
sq := &storage.SearchQuery{
MinTimestamp: 0,
MaxTimestamp: ct,
TagFilterss: [][]storage.TagFilter{tfs},
}
return sq, nil
}
func exprsToTagFilters(exprs []string) ([]storage.TagFilter, error) {
tfs := make([]storage.TagFilter, 0, len(exprs))
for _, expr := range exprs {

View File

@ -0,0 +1,16 @@
{% stripspace %}
TagsAutoCompleteResponse generates responses for /tags/autoComplete/{tags,values} handlers in Graphite Tags API.
See https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support
{% func TagsAutoCompleteResponse(ss []string, jsonp string) %}
{% if jsonp != "" %}{%s= jsonp %}({% endif %}
[
{% for i, s := range ss %}
{%q= s %}
{% if i+1 < len(ss) %},{% endif %}
{% endfor %}
]
{% if jsonp != "" %}){% endif %}
{% endfunc %}
{% endstripspace %}

View File

@ -0,0 +1,81 @@
// Code generated by qtc from "tags_autocomplete_response.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
// TagsAutoCompleteResponse generates responses for /tags/autoComplete/{tags,values} handlers in Graphite Tags API.See https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:5
package graphite
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:5
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:5
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:5
func StreamTagsAutoCompleteResponse(qw422016 *qt422016.Writer, ss []string, jsonp string) {
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:6
if jsonp != "" {
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:6
qw422016.N().S(jsonp)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:6
qw422016.N().S(`(`)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:6
}
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:6
qw422016.N().S(`[`)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:8
for i, s := range ss {
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:9
qw422016.N().Q(s)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:10
if i+1 < len(ss) {
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:10
qw422016.N().S(`,`)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:10
}
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:11
}
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:11
qw422016.N().S(`]`)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:13
if jsonp != "" {
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:13
qw422016.N().S(`)`)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:13
}
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
}
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
func WriteTagsAutoCompleteResponse(qq422016 qtio422016.Writer, ss []string, jsonp string) {
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
StreamTagsAutoCompleteResponse(qw422016, ss, jsonp)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
qt422016.ReleaseWriter(qw422016)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
}
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
func TagsAutoCompleteResponse(ss []string, jsonp string) string {
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
WriteTagsAutoCompleteResponse(qb422016, ss, jsonp)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
qs422016 := string(qb422016.B)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
return qs422016
//line app/vmselect/graphite/tags_autocomplete_response.qtpl:14
}

View File

@ -1,16 +0,0 @@
{% stripspace %}
TagsAutoCompleteTagsResponse generates response for /tags/autoComplete/tags handler in Graphite Tags API.
See https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support
{% func TagsAutoCompleteTagsResponse(labels []string, jsonp string) %}
{% if jsonp != "" %}{%s= jsonp %}({% endif %}
[
{% for i, label := range labels %}
{%q= label %}
{% if i+1 < len(labels) %},{% endif %}
{% endfor %}
]
{% if jsonp != "" %}){% endif %}
{% endfunc %}
{% endstripspace %}

View File

@ -1,81 +0,0 @@
// Code generated by qtc from "tags_autocomplete_tags_response.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
// TagsAutoCompleteTagsResponse generates response for /tags/autoComplete/tags handler in Graphite Tags API.See https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:5
package graphite
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:5
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:5
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:5
func StreamTagsAutoCompleteTagsResponse(qw422016 *qt422016.Writer, labels []string, jsonp string) {
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:6
if jsonp != "" {
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:6
qw422016.N().S(jsonp)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:6
qw422016.N().S(`(`)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:6
}
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:6
qw422016.N().S(`[`)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:8
for i, label := range labels {
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:9
qw422016.N().Q(label)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:10
if i+1 < len(labels) {
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:10
qw422016.N().S(`,`)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:10
}
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:11
}
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:11
qw422016.N().S(`]`)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:13
if jsonp != "" {
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:13
qw422016.N().S(`)`)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:13
}
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
}
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
func WriteTagsAutoCompleteTagsResponse(qq422016 qtio422016.Writer, labels []string, jsonp string) {
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
StreamTagsAutoCompleteTagsResponse(qw422016, labels, jsonp)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
qt422016.ReleaseWriter(qw422016)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
}
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
func TagsAutoCompleteTagsResponse(labels []string, jsonp string) string {
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
WriteTagsAutoCompleteTagsResponse(qb422016, labels, jsonp)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
qs422016 := string(qb422016.B)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
return qs422016
//line app/vmselect/graphite/tags_autocomplete_tags_response.qtpl:14
}

View File

@ -294,6 +294,15 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
return true
}
return true
case "/tags/autoComplete/values":
graphiteTagsAutoCompleteValuesRequests.Inc()
httpserver.EnableCORS(w, r)
if err := graphite.TagsAutoCompleteValuesHandler(startTime, w, r); err != nil {
graphiteTagsAutoCompleteValuesErrors.Inc()
httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
return true
}
return true
case "/api/v1/rules":
// Return dumb placeholder
rulesRequests.Inc()
@ -419,6 +428,9 @@ var (
graphiteTagsAutoCompleteTagsRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags/autoComplete/tags"}`)
graphiteTagsAutoCompleteTagsErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/tags/autoComplete/tags"}`)
graphiteTagsAutoCompleteValuesRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags/autoComplete/values"}`)
graphiteTagsAutoCompleteValuesErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/tags/autoComplete/values"}`)
rulesRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/rules"}`)
alertsRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/alerts"}`)
metadataRequests = metrics.NewCounter(`vm_http_requests_total{path="/api/v1/metadata"}`)

View File

@ -552,6 +552,7 @@ VictoriaMetrics supports the following handlers from [Graphite Tags API](https:/
* [/tags/tag_name](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags)
* [/tags/findSeries](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags)
* [/tags/autoComplete/tags](https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support)
* [/tags/autoComplete/values](https://graphite.readthedocs.io/en/stable/tags.html#auto-complete-support)
### How to build from sources