mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-23 12:31:07 +01:00
app/vmselect/graphite: add /tags/<tag_name>
handler for Graphite Tags API
This commit is contained in:
parent
6251762787
commit
d100341394
@ -549,6 +549,7 @@ VictoriaMetrics supports the following handlers from [Graphite Tags API](https:/
|
||||
* [/tags/tagSeries](https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb)
|
||||
* [/tags/tagMultiSeries](https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb)
|
||||
* [/tags](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags)
|
||||
* [/tags/tag_name](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags)
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
20
app/vmselect/graphite/tag_values_response.qtpl
Normal file
20
app/vmselect/graphite/tag_values_response.qtpl
Normal file
@ -0,0 +1,20 @@
|
||||
{% stripspace %}
|
||||
|
||||
Tags generates response for /tags/<tag_name> handler
|
||||
See https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags
|
||||
{% func TagValuesResponse(tagName string, tagValues []string) %}
|
||||
{
|
||||
"tag":{%q= tagName %},
|
||||
"values":[
|
||||
{% for i, value := range tagValues %}
|
||||
{
|
||||
"count":1,
|
||||
"value":{%q= value %}
|
||||
}
|
||||
{% if i+1 < len(tagValues) %},{% endif %}
|
||||
{% endfor %}
|
||||
]
|
||||
}
|
||||
{% endfunc %}
|
||||
|
||||
{% endstripspace %}
|
75
app/vmselect/graphite/tag_values_response.qtpl.go
Normal file
75
app/vmselect/graphite/tag_values_response.qtpl.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Code generated by qtc from "tag_values_response.qtpl". DO NOT EDIT.
|
||||
// See https://github.com/valyala/quicktemplate for details.
|
||||
|
||||
// Tags generates response for /tags/<tag_name> handlerSee https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags
|
||||
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:5
|
||||
package graphite
|
||||
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:5
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:5
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:5
|
||||
func StreamTagValuesResponse(qw422016 *qt422016.Writer, tagName string, tagValues []string) {
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:5
|
||||
qw422016.N().S(`{"tag":`)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:7
|
||||
qw422016.N().Q(tagName)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:7
|
||||
qw422016.N().S(`,"values":[`)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:9
|
||||
for i, value := range tagValues {
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:9
|
||||
qw422016.N().S(`{"count":1,"value":`)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:12
|
||||
qw422016.N().Q(value)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:12
|
||||
qw422016.N().S(`}`)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:14
|
||||
if i+1 < len(tagValues) {
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:14
|
||||
qw422016.N().S(`,`)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:14
|
||||
}
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:15
|
||||
}
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:15
|
||||
qw422016.N().S(`]}`)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
}
|
||||
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
func WriteTagValuesResponse(qq422016 qtio422016.Writer, tagName string, tagValues []string) {
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
StreamTagValuesResponse(qw422016, tagName, tagValues)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
}
|
||||
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
func TagValuesResponse(tagName string, tagValues []string) string {
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
WriteTagValuesResponse(qb422016, tagName, tagValues)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
qs422016 := string(qb422016.B)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
return qs422016
|
||||
//line app/vmselect/graphite/tag_values_response.qtpl:18
|
||||
}
|
@ -13,7 +13,48 @@ import (
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
// TagsHandler implements handler for /tags endpoint.
|
||||
// TagValuesHandler implements /tags/<tag_name> endpoint from Graphite Tags API.
|
||||
//
|
||||
// See https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags
|
||||
func TagValuesHandler(startTime time.Time, tagName string, 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 := 0
|
||||
if limitStr := r.FormValue("limit"); len(limitStr) > 0 {
|
||||
var err error
|
||||
limit, err = strconv.Atoi(limitStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse limit=%q: %w", limit, err)
|
||||
}
|
||||
}
|
||||
tagValues, err := netstorage.GetGraphiteTagValues(tagName, limit, deadline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter := r.FormValue("filter")
|
||||
if len(filter) > 0 {
|
||||
tagValues, err = applyRegexpFilter(filter, tagValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
bw := bufferedwriter.Get(w)
|
||||
defer bufferedwriter.Put(bw)
|
||||
WriteTagValuesResponse(bw, tagName, tagValues)
|
||||
if err := bw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
tagValuesDuration.UpdateDuration(startTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
var tagValuesDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/tags/<tag_name>"}`)
|
||||
|
||||
// TagsHandler implements /tags endpoint from Graphite Tags API.
|
||||
//
|
||||
// See https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags
|
||||
func TagsHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) error {
|
||||
@ -35,20 +76,10 @@ func TagsHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) er
|
||||
}
|
||||
filter := r.FormValue("filter")
|
||||
if len(filter) > 0 {
|
||||
// Anchor filter regexp to the beginning of the string as Graphite does.
|
||||
// See https://github.com/graphite-project/graphite-web/blob/3ad279df5cb90b211953e39161df416e54a84948/webapp/graphite/tags/localdatabase.py#L157
|
||||
filter = "^(?:" + filter + ")"
|
||||
re, err := regexp.Compile(filter)
|
||||
labels, err = applyRegexpFilter(filter, labels)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse regexp filter=%q: %w", filter, err)
|
||||
return err
|
||||
}
|
||||
dst := labels[:0]
|
||||
for _, label := range labels {
|
||||
if re.MatchString(label) {
|
||||
dst = append(dst, label)
|
||||
}
|
||||
}
|
||||
labels = dst
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
@ -63,3 +94,20 @@ func TagsHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) er
|
||||
}
|
||||
|
||||
var tagsDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/tags"}`)
|
||||
|
||||
func applyRegexpFilter(filter string, ss []string) ([]string, error) {
|
||||
// Anchor filter regexp to the beginning of the string as Graphite does.
|
||||
// See https://github.com/graphite-project/graphite-web/blob/3ad279df5cb90b211953e39161df416e54a84948/webapp/graphite/tags/localdatabase.py#L157
|
||||
filter = "^(?:" + filter + ")"
|
||||
re, err := regexp.Compile(filter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse regexp filter=%q: %w", filter, err)
|
||||
}
|
||||
dst := ss[:0]
|
||||
for _, s := range ss {
|
||||
if re.MatchString(s) {
|
||||
dst = append(dst, s)
|
||||
}
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
@ -132,6 +132,16 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(path, "/tags/") {
|
||||
tagName := r.URL.Path[len("/tags/"):]
|
||||
graphiteTagValuesRequests.Inc()
|
||||
if err := graphite.TagValuesHandler(startTime, tagName, w, r); err != nil {
|
||||
graphiteTagValuesErrors.Inc()
|
||||
httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
switch path {
|
||||
case "/api/v1/query":
|
||||
@ -371,6 +381,9 @@ var (
|
||||
graphiteTagsRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags"}`)
|
||||
graphiteTagsErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/tags"}`)
|
||||
|
||||
graphiteTagValuesRequests = metrics.NewCounter(`vm_http_requests_total{path="/tags/<tag_name>"}`)
|
||||
graphiteTagValuesErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/tags/<tag_name>"}`)
|
||||
|
||||
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"}`)
|
||||
|
@ -539,6 +539,31 @@ func GetLabelValuesOnTimeRange(labelName string, tr storage.TimeRange, deadline
|
||||
return labelValues, nil
|
||||
}
|
||||
|
||||
// GetGraphiteTagValues returns tag values for the given tagName until the given deadline.
|
||||
func GetGraphiteTagValues(tagName string, limit int, deadline searchutils.Deadline) ([]string, error) {
|
||||
if deadline.Exceeded() {
|
||||
return nil, fmt.Errorf("timeout exceeded before starting the query processing: %s", deadline.String())
|
||||
}
|
||||
if tagName == "name" {
|
||||
tagName = ""
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = *maxTagValuesPerSearch
|
||||
}
|
||||
if limit > *maxTagValuesPerSearch {
|
||||
return nil, fmt.Errorf("limit=%d exceeds -search.maxTagValues=%d; either reduce limit or increase -search.maxTagValues command-line flag value",
|
||||
limit, *maxTagValuesPerSearch)
|
||||
}
|
||||
// Search for tag values
|
||||
tagValues, err := vmstorage.SearchTagValues([]byte(tagName), limit, deadline.Deadline())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error during tag values search for tagName=%q: %w", tagName, err)
|
||||
}
|
||||
// Sort tagValues like Graphite does
|
||||
sort.Strings(tagValues)
|
||||
return tagValues, nil
|
||||
}
|
||||
|
||||
// GetLabelValues returns label values for the given labelName
|
||||
// until the given deadline.
|
||||
func GetLabelValues(labelName string, deadline searchutils.Deadline) ([]string, error) {
|
||||
|
@ -549,6 +549,7 @@ VictoriaMetrics supports the following handlers from [Graphite Tags API](https:/
|
||||
* [/tags/tagSeries](https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb)
|
||||
* [/tags/tagMultiSeries](https://graphite.readthedocs.io/en/stable/tags.html#adding-series-to-the-tagdb)
|
||||
* [/tags](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags)
|
||||
* [/tags/tag_name](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags)
|
||||
|
||||
|
||||
### How to build from sources
|
||||
|
Loading…
Reference in New Issue
Block a user