Skip to content

Commit febed48

Browse files
committedSep 16, 2017
Implement remote read server in Prometheus.
1 parent 84211bd commit febed48

File tree

4 files changed

+281
-80
lines changed

4 files changed

+281
-80
lines changed
 

‎storage/remote/client.go

Lines changed: 8 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -76,29 +76,7 @@ type recoverableError struct {
7676

7777
// Store sends a batch of samples to the HTTP endpoint.
7878
func (c *Client) Store(samples model.Samples) error {
79-
req := &WriteRequest{
80-
Timeseries: make([]*TimeSeries, 0, len(samples)),
81-
}
82-
for _, s := range samples {
83-
ts := &TimeSeries{
84-
Labels: make([]*LabelPair, 0, len(s.Metric)),
85-
}
86-
for k, v := range s.Metric {
87-
ts.Labels = append(ts.Labels,
88-
&LabelPair{
89-
Name: string(k),
90-
Value: string(v),
91-
})
92-
}
93-
ts.Samples = []*Sample{
94-
{
95-
Value: float64(s.Value),
96-
TimestampMs: int64(s.Timestamp),
97-
},
98-
}
99-
req.Timeseries = append(req.Timeseries, ts)
100-
}
101-
79+
req := ToWriteRequest(samples)
10280
data, err := proto.Marshal(req)
10381
if err != nil {
10482
return err
@@ -147,14 +125,15 @@ func (c Client) Name() string {
147125

148126
// Read reads from a remote endpoint.
149127
func (c *Client) Read(ctx context.Context, from, through model.Time, matchers metric.LabelMatchers) (model.Matrix, error) {
128+
query, err := ToQuery(from, through, matchers)
129+
if err != nil {
130+
return nil, err
131+
}
132+
150133
req := &ReadRequest{
151134
// TODO: Support batching multiple queries into one read request,
152135
// as the protobuf interface allows for it.
153-
Queries: []*Query{{
154-
StartTimestampMs: int64(from),
155-
EndTimestampMs: int64(through),
156-
Matchers: labelMatchersToProto(matchers),
157-
}},
136+
Queries: []*Query{query},
158137
}
159138

160139
data, err := proto.Marshal(req)
@@ -203,56 +182,5 @@ func (c *Client) Read(ctx context.Context, from, through model.Time, matchers me
203182
return nil, fmt.Errorf("responses: want %d, got %d", len(req.Queries), len(resp.Results))
204183
}
205184

206-
return matrixFromProto(resp.Results[0].Timeseries), nil
207-
}
208-
209-
func labelMatchersToProto(matchers metric.LabelMatchers) []*LabelMatcher {
210-
pbMatchers := make([]*LabelMatcher, 0, len(matchers))
211-
for _, m := range matchers {
212-
var mType MatchType
213-
switch m.Type {
214-
case metric.Equal:
215-
mType = MatchType_EQUAL
216-
case metric.NotEqual:
217-
mType = MatchType_NOT_EQUAL
218-
case metric.RegexMatch:
219-
mType = MatchType_REGEX_MATCH
220-
case metric.RegexNoMatch:
221-
mType = MatchType_REGEX_NO_MATCH
222-
default:
223-
panic("invalid matcher type")
224-
}
225-
pbMatchers = append(pbMatchers, &LabelMatcher{
226-
Type: mType,
227-
Name: string(m.Name),
228-
Value: string(m.Value),
229-
})
230-
}
231-
return pbMatchers
232-
}
233-
234-
func matrixFromProto(seriesSet []*TimeSeries) model.Matrix {
235-
m := make(model.Matrix, 0, len(seriesSet))
236-
for _, ts := range seriesSet {
237-
var ss model.SampleStream
238-
ss.Metric = labelPairsToMetric(ts.Labels)
239-
ss.Values = make([]model.SamplePair, 0, len(ts.Samples))
240-
for _, s := range ts.Samples {
241-
ss.Values = append(ss.Values, model.SamplePair{
242-
Value: model.SampleValue(s.Value),
243-
Timestamp: model.Time(s.TimestampMs),
244-
})
245-
}
246-
m = append(m, &ss)
247-
}
248-
249-
return m
250-
}
251-
252-
func labelPairsToMetric(labelPairs []*LabelPair) model.Metric {
253-
metric := make(model.Metric, len(labelPairs))
254-
for _, l := range labelPairs {
255-
metric[model.LabelName(l.Name)] = model.LabelValue(l.Value)
256-
}
257-
return metric
185+
return FromQueryResult(resp.Results[0]), nil
258186
}

‎storage/remote/codec.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Copyright 2016 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package remote
15+
16+
import (
17+
"fmt"
18+
"io/ioutil"
19+
"net/http"
20+
21+
"github.com/gogo/protobuf/proto"
22+
"github.com/golang/snappy"
23+
"github.com/prometheus/common/model"
24+
25+
"github.com/prometheus/prometheus/storage/metric"
26+
)
27+
28+
// DecodeReadRequest reads a remote.Request from a http.Request.
29+
func DecodeReadRequest(r *http.Request) (*ReadRequest, error) {
30+
compressed, err := ioutil.ReadAll(r.Body)
31+
if err != nil {
32+
return nil, err
33+
}
34+
35+
reqBuf, err := snappy.Decode(nil, compressed)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
var req ReadRequest
41+
if err := proto.Unmarshal(reqBuf, &req); err != nil {
42+
return nil, err
43+
}
44+
45+
return &req, nil
46+
}
47+
48+
// EncodReadResponse writes a remote.Response to a http.ResponseWriter.
49+
func EncodReadResponse(resp *ReadResponse, w http.ResponseWriter) error {
50+
data, err := proto.Marshal(resp)
51+
if err != nil {
52+
return err
53+
}
54+
55+
w.Header().Set("Content-Type", "application/x-protobuf")
56+
w.Header().Set("Content-Encoding", "snappy")
57+
58+
compressed := snappy.Encode(nil, data)
59+
_, err = w.Write(compressed)
60+
return err
61+
}
62+
63+
// ToWriteRequest converts an array of samples into a WriteRequest proto.
64+
func ToWriteRequest(samples []*model.Sample) *WriteRequest {
65+
req := &WriteRequest{
66+
Timeseries: make([]*TimeSeries, 0, len(samples)),
67+
}
68+
69+
for _, s := range samples {
70+
ts := TimeSeries{
71+
Labels: ToLabelPairs(s.Metric),
72+
Samples: []*Sample{
73+
{
74+
Value: float64(s.Value),
75+
TimestampMs: int64(s.Timestamp),
76+
},
77+
},
78+
}
79+
req.Timeseries = append(req.Timeseries, &ts)
80+
}
81+
82+
return req
83+
}
84+
85+
// ToQuery builds a Query proto.
86+
func ToQuery(from, to model.Time, matchers []*metric.LabelMatcher) (*Query, error) {
87+
ms, err := toLabelMatchers(matchers)
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
return &Query{
93+
StartTimestampMs: int64(from),
94+
EndTimestampMs: int64(to),
95+
Matchers: ms,
96+
}, nil
97+
}
98+
99+
// FromQuery unpacks a Query proto.
100+
func FromQuery(req *Query) (model.Time, model.Time, []*metric.LabelMatcher, error) {
101+
matchers, err := fromLabelMatchers(req.Matchers)
102+
if err != nil {
103+
return 0, 0, nil, err
104+
}
105+
from := model.Time(req.StartTimestampMs)
106+
to := model.Time(req.EndTimestampMs)
107+
return from, to, matchers, nil
108+
}
109+
110+
// ToQueryResult builds a QueryResult proto.
111+
func ToQueryResult(matrix model.Matrix) *QueryResult {
112+
resp := &QueryResult{}
113+
for _, ss := range matrix {
114+
ts := TimeSeries{
115+
Labels: ToLabelPairs(ss.Metric),
116+
Samples: make([]*Sample, 0, len(ss.Values)),
117+
}
118+
for _, s := range ss.Values {
119+
ts.Samples = append(ts.Samples, &Sample{
120+
Value: float64(s.Value),
121+
TimestampMs: int64(s.Timestamp),
122+
})
123+
}
124+
resp.Timeseries = append(resp.Timeseries, &ts)
125+
}
126+
return resp
127+
}
128+
129+
// FromQueryResult unpacks a QueryResult proto.
130+
func FromQueryResult(resp *QueryResult) model.Matrix {
131+
m := make(model.Matrix, 0, len(resp.Timeseries))
132+
for _, ts := range resp.Timeseries {
133+
var ss model.SampleStream
134+
ss.Metric = FromLabelPairs(ts.Labels)
135+
ss.Values = make([]model.SamplePair, 0, len(ts.Samples))
136+
for _, s := range ts.Samples {
137+
ss.Values = append(ss.Values, model.SamplePair{
138+
Value: model.SampleValue(s.Value),
139+
Timestamp: model.Time(s.TimestampMs),
140+
})
141+
}
142+
m = append(m, &ss)
143+
}
144+
145+
return m
146+
}
147+
148+
func toLabelMatchers(matchers []*metric.LabelMatcher) ([]*LabelMatcher, error) {
149+
result := make([]*LabelMatcher, 0, len(matchers))
150+
for _, matcher := range matchers {
151+
var mType MatchType
152+
switch matcher.Type {
153+
case metric.Equal:
154+
mType = MatchType_EQUAL
155+
case metric.NotEqual:
156+
mType = MatchType_NOT_EQUAL
157+
case metric.RegexMatch:
158+
mType = MatchType_REGEX_MATCH
159+
case metric.RegexNoMatch:
160+
mType = MatchType_REGEX_NO_MATCH
161+
default:
162+
return nil, fmt.Errorf("invalid matcher type")
163+
}
164+
result = append(result, &LabelMatcher{
165+
Type: mType,
166+
Name: string(matcher.Name),
167+
Value: string(matcher.Value),
168+
})
169+
}
170+
return result, nil
171+
}
172+
173+
func fromLabelMatchers(matchers []*LabelMatcher) ([]*metric.LabelMatcher, error) {
174+
result := make(metric.LabelMatchers, 0, len(matchers))
175+
for _, matcher := range matchers {
176+
var mtype metric.MatchType
177+
switch matcher.Type {
178+
case MatchType_EQUAL:
179+
mtype = metric.Equal
180+
case MatchType_NOT_EQUAL:
181+
mtype = metric.NotEqual
182+
case MatchType_REGEX_MATCH:
183+
mtype = metric.RegexMatch
184+
case MatchType_REGEX_NO_MATCH:
185+
mtype = metric.RegexNoMatch
186+
default:
187+
return nil, fmt.Errorf("invalid matcher type")
188+
}
189+
matcher, err := metric.NewLabelMatcher(mtype, model.LabelName(matcher.Name), model.LabelValue(matcher.Value))
190+
if err != nil {
191+
return nil, err
192+
}
193+
result = append(result, matcher)
194+
}
195+
return result, nil
196+
}
197+
198+
// ToLabelPairs builds a []LabelPair from a model.Metric
199+
func ToLabelPairs(metric model.Metric) []*LabelPair {
200+
labelPairs := make([]*LabelPair, 0, len(metric))
201+
for k, v := range metric {
202+
labelPairs = append(labelPairs, &LabelPair{
203+
Name: string(k),
204+
Value: string(v),
205+
})
206+
}
207+
return labelPairs
208+
}
209+
210+
// FromLabelPairs unpack a []LabelPair to a model.Metric
211+
func FromLabelPairs(labelPairs []*LabelPair) model.Metric {
212+
metric := make(model.Metric, len(labelPairs))
213+
for _, l := range labelPairs {
214+
metric[model.LabelName(l.Name)] = model.LabelValue(l.Value)
215+
}
216+
return metric
217+
}

‎storage/remote/iterator.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"sort"
1818

1919
"github.com/prometheus/common/model"
20+
"github.com/prometheus/prometheus/storage/local"
2021
"github.com/prometheus/prometheus/storage/metric"
2122
)
2223

@@ -62,3 +63,15 @@ func (it sampleStreamIterator) RangeValues(in metric.Interval) []model.SamplePai
6263
}
6364

6465
func (it sampleStreamIterator) Close() {}
66+
67+
// IteratorsToMatrix converts a list of iterators into a model.Matrix.
68+
func IteratorsToMatrix(iters []local.SeriesIterator, interval metric.Interval) model.Matrix {
69+
result := make(model.Matrix, 0, len(iters))
70+
for _, iter := range iters {
71+
result = append(result, &model.SampleStream{
72+
Metric: iter.Metric().Metric,
73+
Values: iter.RangeValues(interval),
74+
})
75+
}
76+
return result
77+
}

‎web/api/v1/api.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"github.com/prometheus/prometheus/retrieval"
3535
"github.com/prometheus/prometheus/storage/local"
3636
"github.com/prometheus/prometheus/storage/metric"
37+
"github.com/prometheus/prometheus/storage/remote"
3738
"github.com/prometheus/prometheus/util/httputil"
3839
)
3940

@@ -152,6 +153,7 @@ func (api *API) Register(r *route.Router) {
152153
r.Get("/alertmanagers", instr("alertmanagers", api.alertmanagers))
153154

154155
r.Get("/status/config", instr("config", api.serveConfig))
156+
r.Post("/read", prometheus.InstrumentHandler("read", http.HandlerFunc(api.remoteRead)))
155157
}
156158

157159
type queryData struct {
@@ -452,6 +454,47 @@ func (api *API) serveConfig(r *http.Request) (interface{}, *apiError) {
452454
return cfg, nil
453455
}
454456

457+
func (api *API) remoteRead(w http.ResponseWriter, r *http.Request) {
458+
req, err := remote.DecodeReadRequest(r)
459+
if err != nil {
460+
http.Error(w, err.Error(), http.StatusBadRequest)
461+
return
462+
}
463+
464+
resp := remote.ReadResponse{
465+
Results: make([]*remote.QueryResult, len(req.Queries)),
466+
}
467+
for i, query := range req.Queries {
468+
querier, err := api.Storage.Querier()
469+
if err != nil {
470+
http.Error(w, err.Error(), http.StatusInternalServerError)
471+
return
472+
}
473+
defer querier.Close()
474+
475+
from, through, matchers, err := remote.FromQuery(query)
476+
if err != nil {
477+
http.Error(w, err.Error(), http.StatusBadRequest)
478+
return
479+
}
480+
iters, err := querier.QueryRange(r.Context(), from, through, matchers...)
481+
if err != nil {
482+
http.Error(w, err.Error(), http.StatusInternalServerError)
483+
return
484+
}
485+
486+
resp.Results[i] = remote.ToQueryResult(remote.IteratorsToMatrix(iters, metric.Interval{
487+
OldestInclusive: from,
488+
NewestInclusive: through,
489+
}))
490+
}
491+
492+
if err := remote.EncodReadResponse(&resp, w); err != nil {
493+
http.Error(w, err.Error(), http.StatusInternalServerError)
494+
return
495+
}
496+
}
497+
455498
func respond(w http.ResponseWriter, data interface{}) {
456499
w.Header().Set("Content-Type", "application/json")
457500
w.WriteHeader(http.StatusOK)

0 commit comments

Comments
 (0)
Please sign in to comment.