@@ -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+
4090var 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
4899func 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-
208272func 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
0 commit comments