Skip to content
This repository has been archived by the owner on Aug 23, 2023. It is now read-only.

tags/findSeries - add lastts-json format #1580

Merged
merged 11 commits into from
Jan 14, 2020
74 changes: 66 additions & 8 deletions api/graphite.go
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,12 @@ func (s *Server) executePlan(ctx context.Context, orgId uint32, plan expr.Plan)
return nil, meta, err
}

series, err = s.clusterFindByTag(ctx, orgId, exprs, int64(r.From), maxSeriesPerReq-len(reqs))
remainingSeriesLimit := maxSeriesPerReq - len(reqs)
if remainingSeriesLimit <= 0 && maxSeriesPerReq > 0 {
// Use 1 to enable series count checking.
Dieterbe marked this conversation as resolved.
Show resolved Hide resolved
remainingSeriesLimit = 1
}
series, err = s.clusterFindByTag(ctx, orgId, exprs, int64(r.From), remainingSeriesLimit, false)
} else {
series, err = s.findSeries(ctx, orgId, []string{r.Query}, int64(r.From))
}
Expand Down Expand Up @@ -1012,7 +1017,17 @@ func (s *Server) graphiteTagFindSeries(ctx *middleware.Context, request models.G
return
}

series, err := s.clusterFindByTag(reqCtx, ctx.OrgId, expressions, request.From, maxSeriesPerReq)
// If limit is specified and less than the global `maxSeriesPerReq` (or `maxSeriesPerReq` is disabled),
// then this is a "soft" limit, meaning we stop and don't return an error. If global `maxSeriesPerReq`
// exists, then respect that
isSoftLimit := true
limit := request.Limit
if maxSeriesPerReq > 0 && (limit == 0 || limit > maxSeriesPerReq) {
limit = maxSeriesPerReq
isSoftLimit = false
}

series, err := s.clusterFindByTag(reqCtx, ctx.OrgId, expressions, request.From, limit, isSoftLimit)
if err != nil {
response.Write(ctx, response.WrapError(err))
return
Expand All @@ -1025,14 +1040,45 @@ func (s *Server) graphiteTagFindSeries(ctx *middleware.Context, request models.G
return
default:
}
seriesNames := make([]string, 0, len(series))
for _, serie := range series {
seriesNames = append(seriesNames, serie.Pattern)

var warnings []string
if len(series) == limit {
warnings = append(warnings, "Result set truncated due to limit")
Dieterbe marked this conversation as resolved.
Show resolved Hide resolved
}

switch request.Format {
case "lastts-json":
retval := models.GraphiteTagFindSeriesLastTsResp{Warnings: warnings}
retval.Series = make([]models.SeriesLastTs, 0, len(series))
for _, serie := range series {
var lastUpdate int64
for _, node := range serie.Series {
for _, ndef := range node.Defs {
if ndef.LastUpdate > lastUpdate {
lastUpdate = ndef.LastUpdate
}
}
}
retval.Series = append(retval.Series, models.SeriesLastTs{Series: serie.Pattern, Ts: lastUpdate})
}

response.Write(ctx, response.NewJson(200, retval, ""))
case "series-json":
seriesNames := make([]string, 0, len(series))
for _, serie := range series {
seriesNames = append(seriesNames, serie.Pattern)
}

if request.Meta == true {
retval := models.GraphiteTagFindSeriesMetaResp{Series: seriesNames, Warnings: warnings}
response.Write(ctx, response.NewJson(200, retval, ""))
} else {
response.Write(ctx, response.NewJson(200, seriesNames, ""))
}
}
response.Write(ctx, response.NewJson(200, seriesNames, ""))
}

func (s *Server) clusterFindByTag(ctx context.Context, orgId uint32, expressions tagquery.Expressions, from int64, maxSeries int) ([]Series, error) {
func (s *Server) clusterFindByTag(ctx context.Context, orgId uint32, expressions tagquery.Expressions, from int64, maxSeries int, softLimit bool) ([]Series, error) {
data := models.IndexFindByTag{OrgId: orgId, Expr: expressions.Strings(), From: from}
newCtx, cancel := context.WithCancel(ctx)
defer cancel()
Expand All @@ -1048,7 +1094,19 @@ func (s *Server) clusterFindByTag(ctx context.Context, orgId uint32, expressions
}

// 0 disables the check, so only check if maxSeriesPerReq > 0
if maxSeriesPerReq > 0 && len(resp.Metrics)+len(allSeries) > maxSeries {
if maxSeries > 0 && len(resp.Metrics)+len(allSeries) > maxSeries {
if softLimit {
remainingSpace := maxSeries - len(allSeries)
// Fill in up to maxSeries
for _, series := range resp.Metrics[:remainingSpace] {
allSeries = append(allSeries, Series{
Pattern: series.Path,
Node: r.peer,
Series: []idx.Node{series},
})
}
return allSeries, nil
}
return nil,
response.NewError(
http.StatusRequestEntityTooLarge,
Expand Down
25 changes: 23 additions & 2 deletions api/models/graphite.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ import (
//msgp:ignore GraphiteTagDetailsValueResp
//msgp:ignore GraphiteTagFindSeries
//msgp:ignore GraphiteTagFindSeriesResp
//msgp:ignore GraphiteTagFindSeriesLastTsResp
//msgp:ignore GraphiteTagFindSeriesMetaResp
//msgp:ignore GraphiteTagResp
//msgp:ignore GraphiteTags
//msgp:ignore GraphiteTagsResp
//msgp:ignore MetricNames
//msgp:ignore MetricsDelete
//msgp:ignore SeriesCompleter
//msgp:ignore SeriesCompleterItem
//msgp:ignore SeriesLastTs
//msgp:ignore SeriesTree
//msgp:ignore SeriesTreeItem

Expand Down Expand Up @@ -117,14 +120,32 @@ type GraphiteTagDetailsValueResp struct {
}

type GraphiteTagFindSeries struct {
Expr []string `json:"expr" form:"expr"`
From int64 `json:"from" form:"from"`
Expr []string `json:"expr" form:"expr"`
From int64 `json:"from" form:"from"`
Format string `json:"format" form:"format" binding:"In(,series-json,lastts-json);Default(series-json)"`
Limit int `json:"limit" binding:"Default(0)"`
Meta bool `json:"meta" binding:"Default(false)"`
}

type GraphiteTagFindSeriesResp struct {
Series []string `json:"series"`
}

type SeriesLastTs struct {
Series string `json:"val"`
Ts int64 `json:"lastTs"`
}

type GraphiteTagFindSeriesLastTsResp struct {
Series []SeriesLastTs `json:"series"`
Warnings []string `json:"warnings,omitempty"`
}

type GraphiteTagFindSeriesMetaResp struct {
Series []string `json:"series"`
Warnings []string `json:"warnings,omitempty"`
}

type GraphiteTagDelSeries struct {
Paths []string `json:"path" form:"path"`
Propagate bool `json:"propagate" form:"propagate" binding:"Default(true)"`
Expand Down
2 changes: 1 addition & 1 deletion api/prometheus_querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (q *querier) Select(matchers ...*labels.Matcher) (storage.SeriesSet, error)
return nil, err
}

series, err := q.clusterFindByTag(q.ctx, q.OrgID, parsedExpressions, 0, maxSeriesPerReq)
series, err := q.clusterFindByTag(q.ctx, q.OrgID, parsedExpressions, 0, maxSeriesPerReq, false)
if err != nil {
return nil, err
}
Expand Down
16 changes: 16 additions & 0 deletions docs/cloud/http-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ that duplicate entries will be returned.

* expr (required): a list of [tag expressions](#tag-expressions)
* from: Graphite [from time specification](#fromto) (optional. defaults to now-24hours)
* format: series-json, lastts-json. (defaults to series-json)
* limit: max number to return, 0 to defer to the instance configured max (default: 0). Note: if limit is 0 or greater than the instance configured max and the result set is greater than the instance configured max, an error is returned. Otherwise, the result set is truncated at the limit
* meta: If false and format is `series-json` then return series names as array (graphite compatibility). If true, include meta information like warnings. (defaults to false)

##### Example

Expand All @@ -290,6 +293,19 @@ curl -H "Authorization: Bearer $key" "$out/tags/findSeries?expr=datacenter=dc1&e
]
```

```sh
curl -H "Authorization: Bearer $key" "$out/tags/findSeries?expr=datacenter=dc1&expr=server=web01&format=lastts-json"

{
"series": [
{
"lastTs": 1576683990,
"val": "disk.used;datacenter=dc1;rack=a1;server=web01"
}
]
}
```

### Render `/render` (return data for a given query)

Graphite-web-like api. It can return JSON, pickle or messagepack output
Expand Down
43 changes: 42 additions & 1 deletion docs/http-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,48 @@ json and treejson are the same.
curl -H "X-Org-Id: 12345" "http://localhost:6060/metrics/find?query=statsd.fakesite.counters.session_start.*.count"
```

## Find tagged metrics

```
GET /tags/findSeries
POST /tags/findSeries
```

Returns metrics which match tag queries and have received an update since `from`.
Note: the returned results are not deduplicated and in certain cases it is possible
that duplicate entries will be returned.

##### Parameters

* expr (required): a list of [tag expressions](#tag-expressions)
* from: Graphite [from time specification](#fromto) (optional. defaults to now-24hours)
* format: series-json, lastts-json. (defaults to series-json)
* limit: max number to return, 0 to defer to `http.max-series-per-req` (default: 0). Note: if limit is 0 or greater than `http.max-series-per-req` and the result set is greater than `http.max-series-per-req`, an error is returned. Otherwise, the result set is truncated at the limit
* meta: If false and format is `series-json` then return series names as array (graphite compatibility). If true, include meta information like warnings. (defaults to false)

##### Example

```sh
curl "http://localhost:6060/tags/findSeries?expr=datacenter=dc1&expr=server=web01"

[
"disk.used;datacenter=dc1;rack=a1;server=web01"
]
```

```sh
curl "http://localhost:6060/tags/findSeries?expr=datacenter=dc1&expr=server=web01&format=lastts-json"

{
"series": [
{
"lastTs": 1576683990,
"val": "disk.used;datacenter=dc1;rack=a1;server=web01"
}
]
}
```

## Deleting metrics

This will delete any metrics (technically metricdefinitions) matching the query from the index.
Expand Down Expand Up @@ -475,4 +517,3 @@ The time specification is used throughout the http api and it can be any of thes
- `y`, `year`, `years`

* datetime in any of the following formats: `15:04 20060102`, `20060102`, `01/02/06`