2016-06-06 10:11:46 -05:00
|
|
|
package graphite
|
|
|
|
|
|
|
|
import (
|
2016-10-03 02:38:03 -05:00
|
|
|
"context"
|
2016-06-06 10:11:46 -05:00
|
|
|
"encoding/json"
|
2016-09-01 02:38:43 -05:00
|
|
|
"fmt"
|
2016-06-06 10:11:46 -05:00
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2016-09-01 04:12:35 -05:00
|
|
|
"path"
|
2016-11-02 04:07:08 -05:00
|
|
|
"regexp"
|
2016-06-10 06:26:19 -05:00
|
|
|
"strings"
|
2016-06-06 10:11:46 -05:00
|
|
|
|
2016-10-03 02:38:03 -05:00
|
|
|
"golang.org/x/net/context/ctxhttp"
|
|
|
|
|
2016-06-07 06:47:22 -05:00
|
|
|
"github.com/grafana/grafana/pkg/log"
|
2016-12-07 04:10:42 -06:00
|
|
|
"github.com/grafana/grafana/pkg/models"
|
2016-09-06 13:40:12 -05:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2016-06-06 10:11:46 -05:00
|
|
|
"github.com/grafana/grafana/pkg/tsdb"
|
2017-09-10 15:05:11 -05:00
|
|
|
opentracing "github.com/opentracing/opentracing-go"
|
2016-06-06 10:11:46 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type GraphiteExecutor struct {
|
2016-12-07 04:10:42 -06:00
|
|
|
*models.DataSource
|
|
|
|
HttpClient *http.Client
|
2016-06-06 10:11:46 -05:00
|
|
|
}
|
|
|
|
|
2017-09-21 03:44:25 -05:00
|
|
|
func NewGraphiteExecutor(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
|
2016-12-07 04:10:42 -06:00
|
|
|
httpClient, err := datasource.GetHttpClient()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &GraphiteExecutor{
|
|
|
|
DataSource: datasource,
|
|
|
|
HttpClient: httpClient,
|
|
|
|
}, nil
|
2016-06-06 10:11:46 -05:00
|
|
|
}
|
|
|
|
|
2016-09-08 09:29:31 -05:00
|
|
|
var (
|
2016-12-07 04:10:42 -06:00
|
|
|
glog log.Logger
|
2016-09-08 09:29:31 -05:00
|
|
|
)
|
2016-06-07 06:47:22 -05:00
|
|
|
|
2016-06-06 10:11:46 -05:00
|
|
|
func init() {
|
2016-06-07 06:47:22 -05:00
|
|
|
glog = log.New("tsdb.graphite")
|
2017-09-21 03:44:25 -05:00
|
|
|
tsdb.RegisterTsdbQueryEndpoint("graphite", NewGraphiteExecutor)
|
2016-06-06 10:11:46 -05:00
|
|
|
}
|
|
|
|
|
2017-09-21 03:44:25 -05:00
|
|
|
func (e *GraphiteExecutor) Query(ctx context.Context, context *tsdb.TsdbQuery) *tsdb.BatchResult {
|
2016-06-06 10:11:46 -05:00
|
|
|
result := &tsdb.BatchResult{}
|
|
|
|
|
2017-09-10 15:05:11 -05:00
|
|
|
from := "-" + formatTimeRange(context.TimeRange.From)
|
|
|
|
until := formatTimeRange(context.TimeRange.To)
|
|
|
|
var target string
|
|
|
|
|
2016-09-01 06:03:15 -05:00
|
|
|
formData := url.Values{
|
2017-09-10 15:05:11 -05:00
|
|
|
"from": []string{from},
|
|
|
|
"until": []string{until},
|
2016-06-06 10:11:46 -05:00
|
|
|
"format": []string{"json"},
|
|
|
|
"maxDataPoints": []string{"500"},
|
|
|
|
}
|
|
|
|
|
2017-09-20 11:56:33 -05:00
|
|
|
for _, query := range context.Queries {
|
2016-10-11 05:49:51 -05:00
|
|
|
if fullTarget, err := query.Model.Get("targetFull").String(); err == nil {
|
2017-09-10 15:05:11 -05:00
|
|
|
target = fixIntervalFormat(fullTarget)
|
2016-10-11 05:49:51 -05:00
|
|
|
} else {
|
2017-09-10 15:05:11 -05:00
|
|
|
target = fixIntervalFormat(query.Model.Get("target").MustString())
|
2016-10-11 05:49:51 -05:00
|
|
|
}
|
2016-06-06 10:11:46 -05:00
|
|
|
}
|
|
|
|
|
2017-09-10 15:05:11 -05:00
|
|
|
formData["target"] = []string{target}
|
|
|
|
|
2016-09-06 13:40:12 -05:00
|
|
|
if setting.Env == setting.DEV {
|
|
|
|
glog.Debug("Graphite request", "params", formData)
|
|
|
|
}
|
|
|
|
|
2016-09-01 06:03:15 -05:00
|
|
|
req, err := e.createRequest(formData)
|
2016-09-01 03:09:13 -05:00
|
|
|
if err != nil {
|
2016-09-01 06:03:15 -05:00
|
|
|
result.Error = err
|
2016-09-01 03:09:13 -05:00
|
|
|
return result
|
|
|
|
}
|
2016-10-03 02:38:03 -05:00
|
|
|
|
2017-09-11 13:57:08 -05:00
|
|
|
span, ctx := opentracing.StartSpanFromContext(ctx, "graphite query")
|
2017-09-11 11:48:20 -05:00
|
|
|
span.SetTag("target", target)
|
|
|
|
span.SetTag("from", from)
|
|
|
|
span.SetTag("until", until)
|
2017-09-10 15:05:11 -05:00
|
|
|
defer span.Finish()
|
|
|
|
|
|
|
|
opentracing.GlobalTracer().Inject(
|
|
|
|
span.Context(),
|
|
|
|
opentracing.HTTPHeaders,
|
|
|
|
opentracing.HTTPHeadersCarrier(req.Header))
|
|
|
|
|
2016-12-07 04:10:42 -06:00
|
|
|
res, err := ctxhttp.Do(ctx, e.HttpClient, req)
|
2016-09-01 06:03:15 -05:00
|
|
|
if err != nil {
|
|
|
|
result.Error = err
|
|
|
|
return result
|
2016-08-29 08:49:25 -05:00
|
|
|
}
|
|
|
|
|
2016-09-01 06:03:15 -05:00
|
|
|
data, err := e.parseResponse(res)
|
2016-06-06 10:11:46 -05:00
|
|
|
if err != nil {
|
|
|
|
result.Error = err
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2016-09-01 06:03:15 -05:00
|
|
|
result.QueryResults = make(map[string]*tsdb.QueryResult)
|
2016-09-28 03:37:30 -05:00
|
|
|
queryRes := tsdb.NewQueryResult()
|
2016-09-28 02:15:48 -05:00
|
|
|
|
2016-09-01 06:03:15 -05:00
|
|
|
for _, series := range data {
|
|
|
|
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
|
|
|
|
Name: series.Target,
|
|
|
|
Points: series.DataPoints,
|
|
|
|
})
|
2016-09-06 13:40:12 -05:00
|
|
|
|
|
|
|
if setting.Env == setting.DEV {
|
|
|
|
glog.Debug("Graphite response", "target", series.Target, "datapoints", len(series.DataPoints))
|
|
|
|
}
|
2016-09-01 06:03:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
result.QueryResults["A"] = queryRes
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *GraphiteExecutor) parseResponse(res *http.Response) ([]TargetResponseDTO, error) {
|
2016-06-06 10:11:46 -05:00
|
|
|
body, err := ioutil.ReadAll(res.Body)
|
2016-09-01 03:09:13 -05:00
|
|
|
defer res.Body.Close()
|
2016-06-06 10:11:46 -05:00
|
|
|
if err != nil {
|
2016-09-01 06:03:15 -05:00
|
|
|
return nil, err
|
2016-06-06 10:11:46 -05:00
|
|
|
}
|
|
|
|
|
2016-09-27 04:13:13 -05:00
|
|
|
if res.StatusCode/100 != 2 {
|
2016-09-27 03:09:56 -05:00
|
|
|
glog.Info("Request failed", "status", res.Status, "body", string(body))
|
|
|
|
return nil, fmt.Errorf("Request failed status: %v", res.Status)
|
2016-09-01 02:38:43 -05:00
|
|
|
}
|
|
|
|
|
2016-06-06 10:11:46 -05:00
|
|
|
var data []TargetResponseDTO
|
|
|
|
err = json.Unmarshal(body, &data)
|
|
|
|
if err != nil {
|
2016-09-01 02:38:43 -05:00
|
|
|
glog.Info("Failed to unmarshal graphite response", "error", err, "status", res.Status, "body", string(body))
|
2016-09-01 06:03:15 -05:00
|
|
|
return nil, err
|
2016-06-06 10:11:46 -05:00
|
|
|
}
|
|
|
|
|
2016-09-01 06:03:15 -05:00
|
|
|
return data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *GraphiteExecutor) createRequest(data url.Values) (*http.Request, error) {
|
|
|
|
u, _ := url.Parse(e.Url)
|
|
|
|
u.Path = path.Join(u.Path, "render")
|
|
|
|
|
|
|
|
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(data.Encode()))
|
|
|
|
if err != nil {
|
|
|
|
glog.Info("Failed to create request", "error", err)
|
|
|
|
return nil, fmt.Errorf("Failed to create request. error: %v", err)
|
2016-06-06 10:11:46 -05:00
|
|
|
}
|
|
|
|
|
2016-09-01 06:03:15 -05:00
|
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
|
|
if e.BasicAuth {
|
|
|
|
req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword)
|
|
|
|
}
|
|
|
|
|
|
|
|
return req, err
|
2016-06-06 10:11:46 -05:00
|
|
|
}
|
2016-06-10 06:26:19 -05:00
|
|
|
|
|
|
|
func formatTimeRange(input string) string {
|
2016-07-21 03:29:11 -05:00
|
|
|
if input == "now" {
|
|
|
|
return input
|
|
|
|
}
|
2016-06-10 06:26:19 -05:00
|
|
|
return strings.Replace(strings.Replace(input, "m", "min", -1), "M", "mon", -1)
|
|
|
|
}
|
2016-11-01 19:55:45 -05:00
|
|
|
|
|
|
|
func fixIntervalFormat(target string) string {
|
2016-11-02 04:07:08 -05:00
|
|
|
rMinute := regexp.MustCompile(`'(\d+)m'`)
|
2016-11-01 19:55:45 -05:00
|
|
|
rMin := regexp.MustCompile("m")
|
|
|
|
target = rMinute.ReplaceAllStringFunc(target, func(m string) string {
|
|
|
|
return rMin.ReplaceAllString(m, "min")
|
|
|
|
})
|
2016-11-02 04:07:08 -05:00
|
|
|
rMonth := regexp.MustCompile(`'(\d+)M'`)
|
2016-11-01 19:55:45 -05:00
|
|
|
rMon := regexp.MustCompile("M")
|
|
|
|
target = rMonth.ReplaceAllStringFunc(target, func(M string) string {
|
|
|
|
return rMon.ReplaceAllString(M, "mon")
|
|
|
|
})
|
|
|
|
return target
|
|
|
|
}
|