Skip to content

Commit 837e9e4

Browse files
author
Vladimir Smirnov
committed
Functions: make aliasSub graphite-compatible
Previously it was operating only on metric names and that's why produced incorrect results in some cases. This commit changes this behavior and aliasSub now operates on full metric name, as graphite-web do Fixes #290
1 parent a9ba2ea commit 837e9e4

File tree

12 files changed

+187
-85
lines changed

12 files changed

+187
-85
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ CHANGELOG
1717
- [Fix] Fix graphiteWeb proxy function (thx. to @sylvain-beugin)
1818
- [Fix] Prometheus Backend: correctly handle groups, fixes #405
1919
- [Fix] Prometheus Backend: convert target that doesn't contain seriesByTags in a same way that's used for /metrics/find
20+
- [Fix] change behavior of aliasSub to match graphite-web (fixes #290)
2021
- [Improvement] Prometheus backend: Allow to specify "start" parameter (via backendOptions)
2122
**0.12.2**
2223
- [Fix] Fix stacked cairo-based graphs (doesn't affect json or grafana rendering)

cmd/mockbackend/aliasByTags.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
pathExpression: "seriesByTag('__name__=base.metric1')"
2-
data:
3-
- metricName: "base.metric1;foo=bar;baz=bam"
4-
values: [1.0, 2.0, 3.0, 4.0, 5.0]
1+
expressions:
2+
"seriesByTag('__name__=base.metric1')":
3+
pathExpression: "seriesByTag('__name__=base.metric1')"
4+
data:
5+
- metricName: "base.metric1;foo=bar;baz=bam"
6+
values: [1.0, 2.0, 3.0, 4.0, 5.0]
57

cmd/mockbackend/aliasSub.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
expressions:
2+
"dns.snake.sql_updated":
3+
pathExpression: "dns.snake.sql_updated"
4+
data:
5+
- metricName: "dns.snake.sql_updated"
6+
values: [1.0, .NaN, 2.0, 3.0, 4.0, 5.0]
7+
"dns.snake.zone_updated":
8+
pathExpression: "dns.snake.zone_updated"
9+
data:
10+
- metricName: "dns.snake.zone_updated"
11+
values: [2.0, .NaN, 3.0, .NaN, 5.0, 6.0]
12+

cmd/mockbackend/average.yaml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
pathExpression: "metric[123]"
2-
data:
3-
- metricName: "metric1"
4-
values: [1.0, .NaN, 2.0, 3.0, 4.0, 5.0]
5-
- metricName: "metric2"
6-
values: [2.0, .NaN, 3.0, .NaN, 5.0, 6.0]
7-
- metricName: "metric3"
8-
values: [3.0, .NaN, 4.0, 5.0, 6.0, .NaN]
1+
expressions:
2+
"metric[123]":
3+
pathExpression: "metric[123]"
4+
data:
5+
- metricName: "metric1"
6+
values: [1.0, .NaN, 2.0, 3.0, 4.0, 5.0]
7+
- metricName: "metric2"
8+
values: [2.0, .NaN, 3.0, .NaN, 5.0, 6.0]
9+
- metricName: "metric3"
10+
values: [3.0, .NaN, 4.0, 5.0, 6.0, .NaN]
911

cmd/mockbackend/empty-404.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
emptyBody: true
2+
httpCode: 404

cmd/mockbackend/empty-500.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
emptyBody: true
2+
httpCode: 500

cmd/mockbackend/empty.yaml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
11
emptyBody: true
22
httpCode: 200
3-
pathExpression: "metric[123]"
4-
data:
5-
- metricName: "metric1"
6-
values: [1.0, .NaN, 2.0, 3.0, 4.0, 5.0]
7-
- metricName: "metric2"
8-
values: [2.0, .NaN, 3.0, .NaN, 5.0, 6.0]
9-
- metricName: "metric3"
10-
values: [3.0, .NaN, 4.0, 5.0, 6.0, .NaN]
113

cmd/mockbackend/i18n.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
pathExpression: "test.metric.株式.rate"
2-
data:
3-
- metricName: "test.metric.株式.rate"
4-
values: [1.0, .NaN, 2.0, 3.0, 4.0, 5.0]
1+
expressions:
2+
"test.metric.株式.rate":
3+
pathExpression: "test.metric.株式.rate"
4+
data:
5+
- metricName: "test.metric.株式.rate"
6+
values: [1.0, .NaN, 2.0, 3.0, 4.0, 5.0]

cmd/mockbackend/main.go

Lines changed: 123 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/go-graphite/carbonapi/zipper/httpHeaders"
1414
protov2 "github.com/go-graphite/protocol/carbonapi_v2_pb"
15+
protov3 "github.com/go-graphite/protocol/carbonapi_v3_pb"
1516
pickle "github.com/lomik/og-rek"
1617
"gopkg.in/yaml.v2"
1718
)
@@ -35,14 +36,64 @@ const (
3536
jsonFormat responseFormat = iota
3637
pickleFormat
3738
protoV2Format
39+
protoV3Format
3840
)
3941

42+
type Metric struct {
43+
MetricName string `yaml:"metricName"`
44+
Values []float64 `yaml:"values"`
45+
}
46+
47+
type Response struct {
48+
PathExpression string `yaml:"pathExpression"`
49+
Data []Metric `yaml:"data"`
50+
}
51+
52+
type Config struct {
53+
Code int `yaml:"httpCode"`
54+
EmptyBody bool `yaml:"emptyBody"`
55+
Expressions map[string]Response `yaml:"expressions"`
56+
}
57+
58+
func copyResponse(src Response) Response {
59+
dst := Response{
60+
PathExpression: src.PathExpression,
61+
Data: make([]Metric, len(src.Data)),
62+
}
63+
64+
for i := range src.Data {
65+
dst.Data[i] = Metric{
66+
MetricName: src.Data[i].MetricName,
67+
Values: make([]float64, len(src.Data[i].Values)),
68+
}
69+
70+
for j := range src.Data[i].Values {
71+
dst.Data[i].Values[j] = src.Data[i].Values[j]
72+
}
73+
}
74+
75+
return dst
76+
}
77+
78+
func copy(src map[string]Response) map[string]Response {
79+
dst := make(map[string]Response)
80+
81+
for k, v := range src {
82+
dst[k] = copyResponse(v)
83+
}
84+
85+
return dst
86+
}
87+
88+
var cfg = Config{}
89+
4090
var knownFormats = map[string]responseFormat{
4191
"json": jsonFormat,
4292
"pickle": pickleFormat,
4393
"protobuf": protoV2Format,
4494
"protobuf3": protoV2Format,
4595
"carbonapi_v2_pb": protoV2Format,
96+
"carbonapi_v3_pb": protoV3Format,
4697
}
4798

4899
func getFormat(req *http.Request) (responseFormat, error) {
@@ -64,6 +115,7 @@ func renderHandler(wr http.ResponseWriter, req *http.Request) {
64115
wr.WriteHeader(cfg.Code)
65116
return
66117
}
118+
67119
format, err := getFormat(req)
68120
if err != nil {
69121
wr.WriteHeader(http.StatusBadRequest)
@@ -78,27 +130,61 @@ func renderHandler(wr http.ResponseWriter, req *http.Request) {
78130
Metrics: []protov2.FetchResponse{},
79131
}
80132

81-
newCfg := copy(&cfg)
133+
multiv3 := protov3.MultiFetchResponse{
134+
Metrics: []protov3.FetchResponse{},
135+
}
82136

83-
for _, m := range newCfg.Data {
84-
isAbsent := make([]bool, 0, len(m.Values))
85-
for i := range m.Values {
86-
if math.IsNaN(m.Values[i]) {
87-
isAbsent = append(isAbsent, true)
88-
m.Values[i] = 0.0
89-
} else {
90-
isAbsent = append(isAbsent, false)
91-
}
137+
newCfg := Config{
138+
Code: cfg.Code,
139+
EmptyBody: cfg.EmptyBody,
140+
Expressions: copy(cfg.Expressions),
141+
}
142+
143+
for _, target := range targets{
144+
response, ok := newCfg.Expressions[target]
145+
if !ok {
146+
wr.WriteHeader(http.StatusNotFound)
147+
wr.Write([]byte(err.Error()))
148+
return
92149
}
93-
fr := protov2.FetchResponse{
94-
Name: m.MetricName,
95-
StartTime: 1,
96-
StopTime: int32(1 + len(m.Values)),
97-
StepTime: 1,
98-
Values: m.Values,
99-
IsAbsent: isAbsent,
150+
for _, m := range response.Data {
151+
isAbsent := make([]bool, 0, len(m.Values))
152+
protov2Values := make([]float64, 0, len(m.Values))
153+
for i := range m.Values {
154+
if math.IsNaN(m.Values[i]) {
155+
isAbsent = append(isAbsent, true)
156+
protov2Values = append(protov2Values, 0.0)
157+
} else {
158+
isAbsent = append(isAbsent, false)
159+
protov2Values = append(protov2Values, m.Values[i])
160+
}
161+
}
162+
fr2 := protov2.FetchResponse{
163+
Name: m.MetricName,
164+
StartTime: 1,
165+
StopTime: int32(1 + len(protov2Values)),
166+
StepTime: 1,
167+
Values: protov2Values,
168+
IsAbsent: isAbsent,
169+
}
170+
171+
fr3 := protov3.FetchResponse{
172+
Name: m.MetricName,
173+
PathExpression: target,
174+
ConsolidationFunc: "avg",
175+
StartTime: 1,
176+
StopTime: int64(1 + len(m.Values)),
177+
StepTime: 1,
178+
XFilesFactor: 0,
179+
HighPrecisionTimestamps: false,
180+
Values: m.Values,
181+
RequestStartTime: 1,
182+
RequestStopTime: int64(1 + len(m.Values)),
183+
}
184+
185+
multiv2.Metrics = append(multiv2.Metrics, fr2)
186+
multiv3.Metrics = append(multiv3.Metrics, fr3)
100187
}
101-
multiv2.Metrics = append(multiv2.Metrics, fr)
102188
}
103189

104190
var d []byte
@@ -111,21 +197,21 @@ func renderHandler(wr http.ResponseWriter, req *http.Request) {
111197
}
112198
var response []map[string]interface{}
113199

114-
for _, metric := range multiv2.GetMetrics() {
200+
for _, metric := range multiv3.GetMetrics() {
115201
var m map[string]interface{}
116202

117203
m = make(map[string]interface{})
118204
m["start"] = metric.StartTime
119205
m["step"] = metric.StepTime
120206
m["end"] = metric.StopTime
121207
m["name"] = metric.Name
122-
m["pathExpression"] = cfg.PathExpression
208+
m["pathExpression"] = metric.PathExpression
123209
m["xFilesFactor"] = 0.5
124210
m["consolidationFunc"] = "avg"
125211

126212
mv := make([]interface{}, len(metric.Values))
127213
for i, p := range metric.Values {
128-
if metric.IsAbsent[i] {
214+
if math.IsNaN(p) {
129215
mv[i] = nil
130216
} else {
131217
mv[i] = p
@@ -154,6 +240,18 @@ func renderHandler(wr http.ResponseWriter, req *http.Request) {
154240
wr.Write([]byte(err.Error()))
155241
return
156242
}
243+
case protoV3Format:
244+
contentType = httpHeaders.ContentTypeCarbonAPIv3PB
245+
if cfg.EmptyBody {
246+
break
247+
}
248+
log.Printf("request will be served. format=protov3, data=%+v\n", multiv3)
249+
d, err = multiv3.Marshal()
250+
if err != nil {
251+
wr.WriteHeader(http.StatusBadGateway)
252+
wr.Write([]byte(err.Error()))
253+
return
254+
}
157255
case jsonFormat:
158256
contentType = "application/json"
159257
if cfg.EmptyBody {
@@ -171,40 +269,6 @@ func renderHandler(wr http.ResponseWriter, req *http.Request) {
171269
wr.Write(d)
172270
}
173271

174-
type Metric struct {
175-
MetricName string `yaml:"metricName"`
176-
Values []float64 `yaml:"values"`
177-
}
178-
179-
type Response struct {
180-
Code int `yaml:"httpCode"`
181-
EmptyBody bool `yaml:"emptyBody"`
182-
PathExpression string `yaml:"pathExpression"`
183-
Data []Metric `yaml:"data"`
184-
}
185-
186-
func copy(src *Response) *Response {
187-
dst := &Response{
188-
PathExpression: src.PathExpression,
189-
Data: make([]Metric, len(src.Data)),
190-
}
191-
192-
for i := range src.Data {
193-
dst.Data[i] = Metric{
194-
MetricName: src.Data[i].MetricName,
195-
Values: make([]float64, len(src.Data[i].Values)),
196-
}
197-
198-
for j := range src.Data[i].Values {
199-
dst.Data[i].Values[j] = src.Data[i].Values[j]
200-
}
201-
}
202-
203-
return dst
204-
}
205-
206-
var cfg = Response{}
207-
208272
func main() {
209273
config := flag.String("config", "average.yaml", "yaml where it would be possible to get data")
210274
address := flag.String("address", ":9070", "address to bind")
@@ -227,9 +291,10 @@ func main() {
227291
return
228292
}
229293

230-
if cfg.Code == 0 {
231-
cfg.Code = http.StatusOK
232-
}
294+
if cfg.Code == 0 {
295+
cfg.Code = http.StatusOK
296+
}
297+
233298

234299
log.Printf("started. config=%v\n", cfg)
235300

cmd/mockbackend/maxSeries.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
expressions:
2+
"m":
3+
pathExpression: "m"
4+
data:
5+
- metricName: "m"
6+
values: [2693,1953,1642,1747,1775]

0 commit comments

Comments
 (0)