mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
app/vmselect/graphite: add /tags/<tag_name>
handler for Graphite Tags API
This commit is contained in:
parent
99cb1a70cf
commit
5889273920
8 changed files with 200 additions and 13 deletions
|
@ -206,6 +206,7 @@ or [an alternative dashboard for VictoriaMetrics cluster](https://grafana.com/gr
|
||||||
- `metrics/expand` - expands Graphite metrics. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-expand).
|
- `metrics/expand` - expands Graphite metrics. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-expand).
|
||||||
- `metrics/index.json` - returns all the metric names. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-index-json).
|
- `metrics/index.json` - returns all the metric names. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-index-json).
|
||||||
- `tags` - returns tag names. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags).
|
- `tags` - returns tag names. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags).
|
||||||
|
- `tags/<tag_name>` - returns tag values for the given `<tag_name>`. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags).
|
||||||
|
|
||||||
* URL for time series deletion: `http://<vmselect>:8481/delete/<accountID>/prometheus/api/v1/admin/tsdb/delete_series?match[]=<timeseries_selector_for_delete>`.
|
* URL for time series deletion: `http://<vmselect>:8481/delete/<accountID>/prometheus/api/v1/admin/tsdb/delete_series?match[]=<timeseries_selector_for_delete>`.
|
||||||
Note that the `delete_series` handler should be used only in exceptional cases such as deletion of accidentally ingested incorrect time series. It shouldn't
|
Note that the `delete_series` handler should be used only in exceptional cases such as deletion of accidentally ingested incorrect time series. It shouldn't
|
||||||
|
|
21
app/vmselect/graphite/tag_values_response.qtpl
Normal file
21
app/vmselect/graphite/tag_values_response.qtpl
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{% stripspace %}
|
||||||
|
|
||||||
|
Tags generates response for /tags/<tag_name> handler
|
||||||
|
See https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags
|
||||||
|
{% func TagValuesResponse(isPartial bool, tagName string, tagValues []string) %}
|
||||||
|
{
|
||||||
|
"tag":{%q= tagName %},
|
||||||
|
{% if isPartial %}"is_partial":true,{% endif %}
|
||||||
|
"values":[
|
||||||
|
{% for i, value := range tagValues %}
|
||||||
|
{
|
||||||
|
"count":1,
|
||||||
|
"value":{%q= value %}
|
||||||
|
}
|
||||||
|
{% if i+1 < len(tagValues) %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% endstripspace %}
|
83
app/vmselect/graphite/tag_values_response.qtpl.go
Normal file
83
app/vmselect/graphite/tag_values_response.qtpl.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// 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, isPartial bool, 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(`,`)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:8
|
||||||
|
if isPartial {
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:8
|
||||||
|
qw422016.N().S(`"is_partial":true,`)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:8
|
||||||
|
}
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:8
|
||||||
|
qw422016.N().S(`"values":[`)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:10
|
||||||
|
for i, value := range tagValues {
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:10
|
||||||
|
qw422016.N().S(`{"count":1,"value":`)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:13
|
||||||
|
qw422016.N().Q(value)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:13
|
||||||
|
qw422016.N().S(`}`)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:15
|
||||||
|
if i+1 < len(tagValues) {
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:15
|
||||||
|
qw422016.N().S(`,`)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:15
|
||||||
|
}
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:16
|
||||||
|
}
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:16
|
||||||
|
qw422016.N().S(`]}`)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
}
|
||||||
|
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
func WriteTagValuesResponse(qq422016 qtio422016.Writer, isPartial bool, tagName string, tagValues []string) {
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
StreamTagValuesResponse(qw422016, isPartial, tagName, tagValues)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
}
|
||||||
|
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
func TagValuesResponse(isPartial bool, tagName string, tagValues []string) string {
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
WriteTagValuesResponse(qb422016, isPartial, tagName, tagValues)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
return qs422016
|
||||||
|
//line app/vmselect/graphite/tag_values_response.qtpl:19
|
||||||
|
}
|
|
@ -14,7 +14,49 @@ import (
|
||||||
"github.com/VictoriaMetrics/metrics"
|
"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, at *auth.Token, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
denyPartialResponse := searchutils.GetDenyPartialResponse(r)
|
||||||
|
tagValues, isPartial, err := netstorage.GetGraphiteTagValues(at, denyPartialResponse, 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, isPartial, 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
|
// See https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags
|
||||||
func TagsHandler(startTime time.Time, at *auth.Token, w http.ResponseWriter, r *http.Request) error {
|
func TagsHandler(startTime time.Time, at *auth.Token, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
@ -37,20 +79,10 @@ func TagsHandler(startTime time.Time, at *auth.Token, w http.ResponseWriter, r *
|
||||||
}
|
}
|
||||||
filter := r.FormValue("filter")
|
filter := r.FormValue("filter")
|
||||||
if len(filter) > 0 {
|
if len(filter) > 0 {
|
||||||
// Anchor filter regexp to the beginning of the string as Graphite does.
|
labels, err = applyRegexpFilter(filter, labels)
|
||||||
// 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 {
|
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")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
@ -65,3 +97,20 @@ func TagsHandler(startTime time.Time, at *auth.Token, w http.ResponseWriter, r *
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagsDuration = metrics.NewSummary(`vm_request_duration_seconds{path="/tags"}`)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -209,6 +209,16 @@ func selectHandler(startTime time.Time, w http.ResponseWriter, r *http.Request,
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(p.Suffix, "graphite/tags/") {
|
||||||
|
tagName := p.Suffix[len("graphite/tags/"):]
|
||||||
|
graphiteTagValuesRequests.Inc()
|
||||||
|
if err := graphite.TagValuesHandler(startTime, at, tagName, w, r); err != nil {
|
||||||
|
graphiteTagValuesErrors.Inc()
|
||||||
|
httpserver.Errorf(w, r, "error in %q: %s", r.URL.Path, err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
switch p.Suffix {
|
switch p.Suffix {
|
||||||
case "prometheus/api/v1/query":
|
case "prometheus/api/v1/query":
|
||||||
|
@ -450,6 +460,9 @@ var (
|
||||||
graphiteTagsRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/graphite/tags"}`)
|
graphiteTagsRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/graphite/tags"}`)
|
||||||
graphiteTagsErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/select/{}/graphite/tags"}`)
|
graphiteTagsErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/select/{}/graphite/tags"}`)
|
||||||
|
|
||||||
|
graphiteTagValuesRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/graphite/tags/<tag_name>"}`)
|
||||||
|
graphiteTagValuesErrors = metrics.NewCounter(`vm_http_request_errors_total{path="/select/{}/graphite/tags/<tag_name>"}`)
|
||||||
|
|
||||||
rulesRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/api/v1/rules"}`)
|
rulesRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/api/v1/rules"}`)
|
||||||
alertsRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/api/v1/alerts"}`)
|
alertsRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/api/v1/alerts"}`)
|
||||||
metadataRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/api/v1/metadata"}`)
|
metadataRequests = metrics.NewCounter(`vm_http_requests_total{path="/select/{}/prometheus/api/v1/metadata"}`)
|
||||||
|
|
|
@ -713,6 +713,24 @@ func GetLabelValuesOnTimeRange(at *auth.Token, denyPartialResponse bool, labelNa
|
||||||
return labelValues, isPartial, nil
|
return labelValues, isPartial, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGraphiteTagValues returns tag values for the given tagName until the given deadline.
|
||||||
|
func GetGraphiteTagValues(at *auth.Token, denyPartialResponse bool, tagName string, limit int, deadline searchutils.Deadline) ([]string, bool, error) {
|
||||||
|
if deadline.Exceeded() {
|
||||||
|
return nil, false, fmt.Errorf("timeout exceeded before starting the query processing: %s", deadline.String())
|
||||||
|
}
|
||||||
|
if tagName == "name" {
|
||||||
|
tagName = ""
|
||||||
|
}
|
||||||
|
tagValues, isPartial, err := GetLabelValues(at, denyPartialResponse, tagName, deadline)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if limit < len(tagValues) {
|
||||||
|
tagValues = tagValues[:limit]
|
||||||
|
}
|
||||||
|
return tagValues, isPartial, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetLabelValues returns label values for the given labelName
|
// GetLabelValues returns label values for the given labelName
|
||||||
// until the given deadline.
|
// until the given deadline.
|
||||||
func GetLabelValues(at *auth.Token, denyPartialResponse bool, labelName string, deadline searchutils.Deadline) ([]string, bool, error) {
|
func GetLabelValues(at *auth.Token, denyPartialResponse bool, labelName string, deadline searchutils.Deadline) ([]string, bool, error) {
|
||||||
|
|
|
@ -206,6 +206,7 @@ or [an alternative dashboard for VictoriaMetrics cluster](https://grafana.com/gr
|
||||||
- `metrics/expand` - expands Graphite metrics. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-expand).
|
- `metrics/expand` - expands Graphite metrics. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-expand).
|
||||||
- `metrics/index.json` - returns all the metric names. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-index-json).
|
- `metrics/index.json` - returns all the metric names. See [these docs](https://graphite-api.readthedocs.io/en/latest/api.html#metrics-index-json).
|
||||||
- `tags` - returns tag names. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags).
|
- `tags` - returns tag names. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags).
|
||||||
|
- `tags/<tag_name>` - returns tag values for the given `<tag_name>`. See [these docs](https://graphite.readthedocs.io/en/stable/tags.html#exploring-tags).
|
||||||
|
|
||||||
* URL for time series deletion: `http://<vmselect>:8481/delete/<accountID>/prometheus/api/v1/admin/tsdb/delete_series?match[]=<timeseries_selector_for_delete>`.
|
* URL for time series deletion: `http://<vmselect>:8481/delete/<accountID>/prometheus/api/v1/admin/tsdb/delete_series?match[]=<timeseries_selector_for_delete>`.
|
||||||
Note that the `delete_series` handler should be used only in exceptional cases such as deletion of accidentally ingested incorrect time series. It shouldn't
|
Note that the `delete_series` handler should be used only in exceptional cases such as deletion of accidentally ingested incorrect time series. It shouldn't
|
||||||
|
|
|
@ -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/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/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](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
|
### How to build from sources
|
||||||
|
|
Loading…
Reference in a new issue