From 43d8bd5a254733582baf3c1bc209a9b2db3e0866 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Sep 2016 07:01:53 +0200 Subject: [PATCH 01/12] feat(prometheus): initial support for prometheus --- pkg/services/alerting/conditions/query.go | 47 +- pkg/services/alerting/init/init.go | 1 + pkg/tsdb/batch.go | 2 +- pkg/tsdb/models.go | 12 +- pkg/tsdb/prometheus/prometheus.go | 96 +++++ pkg/tsdb/prometheus/types.go | 1 + pkg/tsdb/query.go | 12 - pkg/tsdb/time_range.go | 77 ++++ pkg/tsdb/time_range_test.go | 78 ++++ .../app/features/alerting/alert_tab_ctrl.ts | 4 +- .../prometheus/client_golang/LICENSE | 201 +++++++++ .../prometheus/client_golang/NOTICE | 23 + .../client_golang/api/prometheus/api.go | 348 +++++++++++++++ vendor/github.com/prometheus/common/LICENSE | 201 +++++++++ vendor/github.com/prometheus/common/NOTICE | 5 + .../prometheus/common/model/alert.go | 136 ++++++ .../prometheus/common/model/fingerprinting.go | 105 +++++ .../github.com/prometheus/common/model/fnv.go | 42 ++ .../prometheus/common/model/labels.go | 206 +++++++++ .../prometheus/common/model/labelset.go | 169 ++++++++ .../prometheus/common/model/metric.go | 98 +++++ .../prometheus/common/model/model.go | 16 + .../prometheus/common/model/signature.go | 144 +++++++ .../prometheus/common/model/silence.go | 106 +++++ .../prometheus/common/model/time.go | 249 +++++++++++ .../prometheus/common/model/value.go | 403 ++++++++++++++++++ vendor/golang.org/x/net/LICENSE | 27 ++ vendor/golang.org/x/net/PATENTS | 22 + .../x/net/context/ctxhttp/ctxhttp.go | 74 ++++ .../x/net/context/ctxhttp/ctxhttp_pre17.go | 147 +++++++ vendor/vendor.json | 21 +- 31 files changed, 3046 insertions(+), 27 deletions(-) create mode 100644 pkg/tsdb/prometheus/prometheus.go create mode 100644 pkg/tsdb/prometheus/types.go delete mode 100644 pkg/tsdb/query.go create mode 100644 pkg/tsdb/time_range.go create mode 100644 pkg/tsdb/time_range_test.go create mode 100644 vendor/github.com/prometheus/client_golang/LICENSE create mode 100644 vendor/github.com/prometheus/client_golang/NOTICE create mode 100644 vendor/github.com/prometheus/client_golang/api/prometheus/api.go create mode 100644 vendor/github.com/prometheus/common/LICENSE create mode 100644 vendor/github.com/prometheus/common/NOTICE create mode 100644 vendor/github.com/prometheus/common/model/alert.go create mode 100644 vendor/github.com/prometheus/common/model/fingerprinting.go create mode 100644 vendor/github.com/prometheus/common/model/fnv.go create mode 100644 vendor/github.com/prometheus/common/model/labels.go create mode 100644 vendor/github.com/prometheus/common/model/labelset.go create mode 100644 vendor/github.com/prometheus/common/model/metric.go create mode 100644 vendor/github.com/prometheus/common/model/model.go create mode 100644 vendor/github.com/prometheus/common/model/signature.go create mode 100644 vendor/github.com/prometheus/common/model/silence.go create mode 100644 vendor/github.com/prometheus/common/model/time.go create mode 100644 vendor/github.com/prometheus/common/model/value.go create mode 100644 vendor/golang.org/x/net/LICENSE create mode 100644 vendor/golang.org/x/net/PATENTS create mode 100644 vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go create mode 100644 vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index f966985f132..208527287f3 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -2,6 +2,8 @@ package conditions import ( "fmt" + "strings" + "time" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" @@ -32,7 +34,8 @@ type AlertQuery struct { } func (c *QueryCondition) Eval(context *alerting.EvalContext) { - seriesList, err := c.executeQuery(context) + timerange := tsdb.NewTimerange(c.Query.From, c.Query.To) + seriesList, err := c.executeQuery(context, timerange) if err != nil { context.Error = err return @@ -66,7 +69,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) { context.Firing = len(context.EvalMatches) > 0 } -func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeSeriesSlice, error) { +func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timerange tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) { getDsInfo := &m.GetDataSourceByIdQuery{ Id: c.Query.DatasourceId, OrgId: context.Rule.OrgId, @@ -76,7 +79,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeS return nil, fmt.Errorf("Could not find datasource") } - req := c.getRequestForAlertRule(getDsInfo.Result) + req := c.getRequestForAlertRule(getDsInfo.Result, timerange) result := make(tsdb.TimeSeriesSlice, 0) resp, err := c.HandleRequest(req) @@ -102,12 +105,9 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeS return result, nil } -func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource) *tsdb.Request { +func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource, timerange tsdb.TimeRange) *tsdb.Request { req := &tsdb.Request{ - TimeRange: tsdb.TimeRange{ - From: c.Query.From, - To: c.Query.To, - }, + TimeRange: timerange, Queries: []*tsdb.Query{ { RefId: "A", @@ -141,6 +141,15 @@ func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, erro condition.Query.Model = queryJson.Get("model") condition.Query.From = queryJson.Get("params").MustArray()[1].(string) condition.Query.To = queryJson.Get("params").MustArray()[2].(string) + + if err := validateFromValue(condition.Query.From); err != nil { + return nil, err + } + + if err := validateToValue(condition.Query.To); err != nil { + return nil, err + } + condition.Query.DatasourceId = queryJson.Get("datasourceId").MustInt64() reducerJson := model.Get("reducer") @@ -155,3 +164,25 @@ func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, erro condition.Evaluator = evaluator return &condition, nil } + +func validateFromValue(from string) error { + fromRaw := strings.Replace(from, "now-", "", 1) + + _, err := time.ParseDuration("-" + fromRaw) + return err +} + +func validateToValue(to string) error { + if to == "now" { + return nil + } else if strings.HasPrefix(to, "now-") { + withoutNow := strings.Replace(to, "now-", "", 1) + + _, err := time.ParseDuration("-" + withoutNow) + if err == nil { + return nil + } + } + + return fmt.Errorf("cannot parse to value %s", to) +} diff --git a/pkg/services/alerting/init/init.go b/pkg/services/alerting/init/init.go index b6627a359e6..b9cba2fd353 100644 --- a/pkg/services/alerting/init/init.go +++ b/pkg/services/alerting/init/init.go @@ -6,6 +6,7 @@ import ( _ "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" _ "github.com/grafana/grafana/pkg/tsdb/graphite" + _ "github.com/grafana/grafana/pkg/tsdb/prometheus" ) var engine *alerting.Engine diff --git a/pkg/tsdb/batch.go b/pkg/tsdb/batch.go index bc16ed1e75a..4dee7b31c86 100644 --- a/pkg/tsdb/batch.go +++ b/pkg/tsdb/batch.go @@ -26,7 +26,7 @@ func (bg *Batch) process(context *QueryContext) { if executor == nil { bg.Done = true result := &BatchResult{ - Error: errors.New("Could not find executor for data source type " + bg.Queries[0].DataSource.PluginId), + Error: errors.New("Could not find executor for data source type: " + bg.Queries[0].DataSource.PluginId), QueryResults: make(map[string]*QueryResult), } for _, query := range bg.Queries { diff --git a/pkg/tsdb/models.go b/pkg/tsdb/models.go index 05a8b13ef84..117b00efe3a 100644 --- a/pkg/tsdb/models.go +++ b/pkg/tsdb/models.go @@ -1,10 +1,16 @@ package tsdb -type TimeRange struct { - From string - To string +type Query struct { + RefId string + Query string + Depends []string + DataSource *DataSourceInfo + Results []*TimeSeries + Exclude bool } +type QuerySlice []*Query + type Request struct { TimeRange TimeRange MaxDataPoints int diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go new file mode 100644 index 00000000000..2b2ccd382af --- /dev/null +++ b/pkg/tsdb/prometheus/prometheus.go @@ -0,0 +1,96 @@ +package prometheus + +import ( + "context" + "net/http" + "time" + + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/tsdb" + "github.com/prometheus/client_golang/api/prometheus" + pmodel "github.com/prometheus/common/model" +) + +type PrometheusExecutor struct { + *tsdb.DataSourceInfo +} + +func NewPrometheusExecutor(dsInfo *tsdb.DataSourceInfo) tsdb.Executor { + return &PrometheusExecutor{dsInfo} +} + +var ( + plog log.Logger + HttpClient http.Client +) + +func init() { + plog = log.New("tsdb.prometheus") + tsdb.RegisterExecutor("prometheus", NewPrometheusExecutor) +} + +func (e *PrometheusExecutor) getClient() (prometheus.QueryAPI, error) { + cfg := prometheus.Config{ + Address: e.DataSourceInfo.Url, + } + + client, err := prometheus.New(cfg) + if err != nil { + return nil, err + } + + return prometheus.NewQueryAPI(client), nil +} + +func (e *PrometheusExecutor) Execute(queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) *tsdb.BatchResult { + result := &tsdb.BatchResult{} + + client, err := e.getClient() + if err != nil { + result.Error = err + return result + } + + from, _ := queryContext.TimeRange.FromTime() + to, _ := queryContext.TimeRange.ToTime() + timeRange := prometheus.Range{ + Start: from, + End: to, + Step: time.Second, + } + + ctx := context.Background() + value, err := client.QueryRange(ctx, "counters_logins", timeRange) + + if err != nil { + result.Error = err + return result + } + + result.QueryResults = parseResponse(value) + return result +} + +func parseResponse(value pmodel.Value) map[string]*tsdb.QueryResult { + queryResults := make(map[string]*tsdb.QueryResult) + queryRes := &tsdb.QueryResult{} + + data := value.(pmodel.Matrix) + + for _, v := range data { + var points [][2]*float64 + for _, k := range v.Values { + dummie := float64(k.Timestamp) + d2 := float64(k.Value) + points = append(points, [2]*float64{&d2, &dummie}) + } + + queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ + Name: v.Metric.String(), + Points: points, + }) + } + + queryResults["A"] = queryRes + return queryResults +} diff --git a/pkg/tsdb/prometheus/types.go b/pkg/tsdb/prometheus/types.go new file mode 100644 index 00000000000..7b1b4c03ead --- /dev/null +++ b/pkg/tsdb/prometheus/types.go @@ -0,0 +1 @@ +package prometheus diff --git a/pkg/tsdb/query.go b/pkg/tsdb/query.go deleted file mode 100644 index bcead660450..00000000000 --- a/pkg/tsdb/query.go +++ /dev/null @@ -1,12 +0,0 @@ -package tsdb - -type Query struct { - RefId string - Query string - Depends []string - DataSource *DataSourceInfo - Results []*TimeSeries - Exclude bool -} - -type QuerySlice []*Query diff --git a/pkg/tsdb/time_range.go b/pkg/tsdb/time_range.go new file mode 100644 index 00000000000..e7e54cef5f9 --- /dev/null +++ b/pkg/tsdb/time_range.go @@ -0,0 +1,77 @@ +package tsdb + +import ( + "fmt" + "strings" + "time" +) + +func NewTimerange(from, to string) TimeRange { + return TimeRange{ + From: from, + To: to, + Now: time.Now(), + } +} + +type TimeRange struct { + From string + To string + Now time.Time +} + +func (tr TimeRange) FromUnix() (int64, error) { + fromRaw := strings.Replace(tr.From, "now-", "", 1) + + diff, err := time.ParseDuration("-" + fromRaw) + if err != nil { + return 0, err + } + + return tr.Now.Add(diff).Unix(), nil +} + +func (tr TimeRange) FromTime() (time.Time, error) { + fromRaw := strings.Replace(tr.From, "now-", "", 1) + + diff, err := time.ParseDuration("-" + fromRaw) + if err != nil { + return time.Time{}, err + } + + return tr.Now.Add(diff), nil +} + +func (tr TimeRange) ToUnix() (int64, error) { + if tr.To == "now" { + return tr.Now.Unix(), nil + } else if strings.HasPrefix(tr.To, "now-") { + withoutNow := strings.Replace(tr.To, "now-", "", 1) + + diff, err := time.ParseDuration("-" + withoutNow) + if err != nil { + return 0, nil + } + + return tr.Now.Add(diff).Unix(), nil + } + + return 0, fmt.Errorf("cannot parse to value %s", tr.To) +} + +func (tr TimeRange) ToTime() (time.Time, error) { + if tr.To == "now" { + return tr.Now, nil + } else if strings.HasPrefix(tr.To, "now-") { + withoutNow := strings.Replace(tr.To, "now-", "", 1) + + diff, err := time.ParseDuration("-" + withoutNow) + if err != nil { + return time.Time{}, nil + } + + return tr.Now.Add(diff), nil + } + + return time.Time{}, fmt.Errorf("cannot parse to value %s", tr.To) +} diff --git a/pkg/tsdb/time_range_test.go b/pkg/tsdb/time_range_test.go new file mode 100644 index 00000000000..d64eb8cc86e --- /dev/null +++ b/pkg/tsdb/time_range_test.go @@ -0,0 +1,78 @@ +package tsdb + +import ( + "testing" + "time" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestTimeRange(t *testing.T) { + Convey("Time range", t, func() { + + now := time.Now() + + Convey("Can parse 5m, now", func() { + tr := TimeRange{ + From: "5m", + To: "now", + Now: now, + } + + Convey("5m ago ", func() { + fiveMinAgo, _ := time.ParseDuration("-5m") + expected := now.Add(fiveMinAgo) + + res, err := tr.FromUnix() + So(err, ShouldBeNil) + So(res, ShouldAlmostEqual, expected.Unix()) + }) + + Convey("now ", func() { + res, err := tr.ToUnix() + So(err, ShouldBeNil) + So(res, ShouldAlmostEqual, now.Unix()) + }) + }) + + Convey("Can parse 5h, now-10m", func() { + tr := TimeRange{ + From: "5h", + To: "now-10m", + Now: now, + } + + Convey("5h ago ", func() { + fiveMinAgo, _ := time.ParseDuration("-5h") + expected := now.Add(fiveMinAgo) + + res, err := tr.FromUnix() + So(err, ShouldBeNil) + So(res, ShouldAlmostEqual, expected.Unix()) + }) + + Convey("now-10m ", func() { + fiveMinAgo, _ := time.ParseDuration("-10m") + expected := now.Add(fiveMinAgo) + res, err := tr.ToUnix() + So(err, ShouldBeNil) + So(res, ShouldAlmostEqual, expected.Unix()) + }) + }) + + Convey("Cannot parse asdf", func() { + var err error + tr := TimeRange{ + From: "asdf", + To: "asdf", + Now: now, + } + + _, err = tr.FromUnix() + So(err, ShouldNotBeNil) + + _, err = tr.ToUnix() + So(err, ShouldNotBeNil) + }) + }) +} diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index 01cacaf9740..ac4f6af5e38 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -227,8 +227,8 @@ export class AlertTabCtrl { var datasourceName = foundTarget.datasource || this.panel.datasource; this.datasourceSrv.get(datasourceName).then(ds => { - if (ds.meta.id !== 'graphite') { - this.error = 'Currently the alerting backend only supports Graphite queries'; + if (ds.meta.id !== 'graphite' && ds.meta.id !== 'prometheus') { + this.error = 'You datsource does not support alerting queries'; } else if (this.templateSrv.variableExists(foundTarget.target)) { this.error = 'Template variables are not supported in alert queries'; } else { diff --git a/vendor/github.com/prometheus/client_golang/LICENSE b/vendor/github.com/prometheus/client_golang/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/prometheus/client_golang/NOTICE b/vendor/github.com/prometheus/client_golang/NOTICE new file mode 100644 index 00000000000..dd878a30ee9 --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/NOTICE @@ -0,0 +1,23 @@ +Prometheus instrumentation library for Go applications +Copyright 2012-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). + + +The following components are included in this product: + +perks - a fork of https://github.com/bmizerany/perks +https://github.com/beorn7/perks +Copyright 2013-2015 Blake Mizerany, Björn Rabenstein +See https://github.com/beorn7/perks/blob/master/README.md for license details. + +Go support for Protocol Buffers - Google's data interchange format +http://github.com/golang/protobuf/ +Copyright 2010 The Go Authors +See source code for license details. + +Support for streaming Protocol Buffer messages for the Go language (golang). +https://github.com/matttproud/golang_protobuf_extensions +Copyright 2013 Matt T. Proud +Licensed under the Apache License, Version 2.0 diff --git a/vendor/github.com/prometheus/client_golang/api/prometheus/api.go b/vendor/github.com/prometheus/client_golang/api/prometheus/api.go new file mode 100644 index 00000000000..cc5cbc364d3 --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/api/prometheus/api.go @@ -0,0 +1,348 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package prometheus provides bindings to the Prometheus HTTP API: +// http://prometheus.io/docs/querying/api/ +package prometheus + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "path" + "strconv" + "strings" + "time" + + "github.com/prometheus/common/model" + "golang.org/x/net/context" + "golang.org/x/net/context/ctxhttp" +) + +const ( + statusAPIError = 422 + apiPrefix = "/api/v1" + + epQuery = "/query" + epQueryRange = "/query_range" + epLabelValues = "/label/:name/values" + epSeries = "/series" +) + +// ErrorType models the different API error types. +type ErrorType string + +// Possible values for ErrorType. +const ( + ErrBadData ErrorType = "bad_data" + ErrTimeout = "timeout" + ErrCanceled = "canceled" + ErrExec = "execution" + ErrBadResponse = "bad_response" +) + +// Error is an error returned by the API. +type Error struct { + Type ErrorType + Msg string +} + +func (e *Error) Error() string { + return fmt.Sprintf("%s: %s", e.Type, e.Msg) +} + +// CancelableTransport is like net.Transport but provides +// per-request cancelation functionality. +type CancelableTransport interface { + http.RoundTripper + CancelRequest(req *http.Request) +} + +// DefaultTransport is used if no Transport is set in Config. +var DefaultTransport CancelableTransport = &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, +} + +// Config defines configuration parameters for a new client. +type Config struct { + // The address of the Prometheus to connect to. + Address string + + // Transport is used by the Client to drive HTTP requests. If not + // provided, DefaultTransport will be used. + Transport CancelableTransport +} + +func (cfg *Config) transport() CancelableTransport { + if cfg.Transport == nil { + return DefaultTransport + } + return cfg.Transport +} + +// Client is the interface for an API client. +type Client interface { + url(ep string, args map[string]string) *url.URL + do(context.Context, *http.Request) (*http.Response, []byte, error) +} + +// New returns a new Client. +// +// It is safe to use the returned Client from multiple goroutines. +func New(cfg Config) (Client, error) { + u, err := url.Parse(cfg.Address) + if err != nil { + return nil, err + } + u.Path = strings.TrimRight(u.Path, "/") + apiPrefix + + return &httpClient{ + endpoint: u, + transport: cfg.transport(), + }, nil +} + +type httpClient struct { + endpoint *url.URL + transport CancelableTransport +} + +func (c *httpClient) url(ep string, args map[string]string) *url.URL { + p := path.Join(c.endpoint.Path, ep) + + for arg, val := range args { + arg = ":" + arg + p = strings.Replace(p, arg, val, -1) + } + + u := *c.endpoint + u.Path = p + + return &u +} + +func (c *httpClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { + resp, err := ctxhttp.Do(ctx, &http.Client{Transport: c.transport}, req) + + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + + if err != nil { + return nil, nil, err + } + + var body []byte + done := make(chan struct{}) + go func() { + body, err = ioutil.ReadAll(resp.Body) + close(done) + }() + + select { + case <-ctx.Done(): + err = resp.Body.Close() + <-done + if err == nil { + err = ctx.Err() + } + case <-done: + } + + return resp, body, err +} + +// apiClient wraps a regular client and processes successful API responses. +// Successful also includes responses that errored at the API level. +type apiClient struct { + Client +} + +type apiResponse struct { + Status string `json:"status"` + Data json.RawMessage `json:"data"` + ErrorType ErrorType `json:"errorType"` + Error string `json:"error"` +} + +func (c apiClient) do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) { + resp, body, err := c.Client.do(ctx, req) + if err != nil { + return resp, body, err + } + + code := resp.StatusCode + + if code/100 != 2 && code != statusAPIError { + return resp, body, &Error{ + Type: ErrBadResponse, + Msg: fmt.Sprintf("bad response code %d", resp.StatusCode), + } + } + + var result apiResponse + + if err = json.Unmarshal(body, &result); err != nil { + return resp, body, &Error{ + Type: ErrBadResponse, + Msg: err.Error(), + } + } + + if (code == statusAPIError) != (result.Status == "error") { + err = &Error{ + Type: ErrBadResponse, + Msg: "inconsistent body for response code", + } + } + + if code == statusAPIError && result.Status == "error" { + err = &Error{ + Type: result.ErrorType, + Msg: result.Error, + } + } + + return resp, []byte(result.Data), err +} + +// Range represents a sliced time range. +type Range struct { + // The boundaries of the time range. + Start, End time.Time + // The maximum time between two slices within the boundaries. + Step time.Duration +} + +// queryResult contains result data for a query. +type queryResult struct { + Type model.ValueType `json:"resultType"` + Result interface{} `json:"result"` + + // The decoded value. + v model.Value +} + +func (qr *queryResult) UnmarshalJSON(b []byte) error { + v := struct { + Type model.ValueType `json:"resultType"` + Result json.RawMessage `json:"result"` + }{} + + err := json.Unmarshal(b, &v) + if err != nil { + return err + } + + switch v.Type { + case model.ValScalar: + var sv model.Scalar + err = json.Unmarshal(v.Result, &sv) + qr.v = &sv + + case model.ValVector: + var vv model.Vector + err = json.Unmarshal(v.Result, &vv) + qr.v = vv + + case model.ValMatrix: + var mv model.Matrix + err = json.Unmarshal(v.Result, &mv) + qr.v = mv + + default: + err = fmt.Errorf("unexpected value type %q", v.Type) + } + return err +} + +// QueryAPI provides bindings the Prometheus's query API. +type QueryAPI interface { + // Query performs a query for the given time. + Query(ctx context.Context, query string, ts time.Time) (model.Value, error) + // Query performs a query for the given range. + QueryRange(ctx context.Context, query string, r Range) (model.Value, error) +} + +// NewQueryAPI returns a new QueryAPI for the client. +// +// It is safe to use the returned QueryAPI from multiple goroutines. +func NewQueryAPI(c Client) QueryAPI { + return &httpQueryAPI{client: apiClient{c}} +} + +type httpQueryAPI struct { + client Client +} + +func (h *httpQueryAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) { + u := h.client.url(epQuery, nil) + q := u.Query() + + q.Set("query", query) + q.Set("time", ts.Format(time.RFC3339Nano)) + + u.RawQuery = q.Encode() + + req, _ := http.NewRequest("GET", u.String(), nil) + + _, body, err := h.client.do(ctx, req) + if err != nil { + return nil, err + } + + var qres queryResult + err = json.Unmarshal(body, &qres) + + return model.Value(qres.v), err +} + +func (h *httpQueryAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) { + u := h.client.url(epQueryRange, nil) + q := u.Query() + + var ( + start = r.Start.Format(time.RFC3339Nano) + end = r.End.Format(time.RFC3339Nano) + step = strconv.FormatFloat(r.Step.Seconds(), 'f', 3, 64) + ) + + q.Set("query", query) + q.Set("start", start) + q.Set("end", end) + q.Set("step", step) + + u.RawQuery = q.Encode() + + req, _ := http.NewRequest("GET", u.String(), nil) + + _, body, err := h.client.do(ctx, req) + if err != nil { + return nil, err + } + + var qres queryResult + err = json.Unmarshal(body, &qres) + + return model.Value(qres.v), err +} diff --git a/vendor/github.com/prometheus/common/LICENSE b/vendor/github.com/prometheus/common/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/github.com/prometheus/common/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/prometheus/common/NOTICE b/vendor/github.com/prometheus/common/NOTICE new file mode 100644 index 00000000000..636a2c1a5e8 --- /dev/null +++ b/vendor/github.com/prometheus/common/NOTICE @@ -0,0 +1,5 @@ +Common libraries shared by Prometheus Go components. +Copyright 2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). diff --git a/vendor/github.com/prometheus/common/model/alert.go b/vendor/github.com/prometheus/common/model/alert.go new file mode 100644 index 00000000000..35e739c7ad2 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/alert.go @@ -0,0 +1,136 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "fmt" + "time" +) + +type AlertStatus string + +const ( + AlertFiring AlertStatus = "firing" + AlertResolved AlertStatus = "resolved" +) + +// Alert is a generic representation of an alert in the Prometheus eco-system. +type Alert struct { + // Label value pairs for purpose of aggregation, matching, and disposition + // dispatching. This must minimally include an "alertname" label. + Labels LabelSet `json:"labels"` + + // Extra key/value information which does not define alert identity. + Annotations LabelSet `json:"annotations"` + + // The known time range for this alert. Both ends are optional. + StartsAt time.Time `json:"startsAt,omitempty"` + EndsAt time.Time `json:"endsAt,omitempty"` + GeneratorURL string `json:"generatorURL"` +} + +// Name returns the name of the alert. It is equivalent to the "alertname" label. +func (a *Alert) Name() string { + return string(a.Labels[AlertNameLabel]) +} + +// Fingerprint returns a unique hash for the alert. It is equivalent to +// the fingerprint of the alert's label set. +func (a *Alert) Fingerprint() Fingerprint { + return a.Labels.Fingerprint() +} + +func (a *Alert) String() string { + s := fmt.Sprintf("%s[%s]", a.Name(), a.Fingerprint().String()[:7]) + if a.Resolved() { + return s + "[resolved]" + } + return s + "[active]" +} + +// Resolved returns true iff the activity interval ended in the past. +func (a *Alert) Resolved() bool { + return a.ResolvedAt(time.Now()) +} + +// ResolvedAt returns true off the activity interval ended before +// the given timestamp. +func (a *Alert) ResolvedAt(ts time.Time) bool { + if a.EndsAt.IsZero() { + return false + } + return !a.EndsAt.After(ts) +} + +// Status returns the status of the alert. +func (a *Alert) Status() AlertStatus { + if a.Resolved() { + return AlertResolved + } + return AlertFiring +} + +// Validate checks whether the alert data is inconsistent. +func (a *Alert) Validate() error { + if a.StartsAt.IsZero() { + return fmt.Errorf("start time missing") + } + if !a.EndsAt.IsZero() && a.EndsAt.Before(a.StartsAt) { + return fmt.Errorf("start time must be before end time") + } + if err := a.Labels.Validate(); err != nil { + return fmt.Errorf("invalid label set: %s", err) + } + if len(a.Labels) == 0 { + return fmt.Errorf("at least one label pair required") + } + if err := a.Annotations.Validate(); err != nil { + return fmt.Errorf("invalid annotations: %s", err) + } + return nil +} + +// Alert is a list of alerts that can be sorted in chronological order. +type Alerts []*Alert + +func (as Alerts) Len() int { return len(as) } +func (as Alerts) Swap(i, j int) { as[i], as[j] = as[j], as[i] } + +func (as Alerts) Less(i, j int) bool { + if as[i].StartsAt.Before(as[j].StartsAt) { + return true + } + if as[i].EndsAt.Before(as[j].EndsAt) { + return true + } + return as[i].Fingerprint() < as[j].Fingerprint() +} + +// HasFiring returns true iff one of the alerts is not resolved. +func (as Alerts) HasFiring() bool { + for _, a := range as { + if !a.Resolved() { + return true + } + } + return false +} + +// Status returns StatusFiring iff at least one of the alerts is firing. +func (as Alerts) Status() AlertStatus { + if as.HasFiring() { + return AlertFiring + } + return AlertResolved +} diff --git a/vendor/github.com/prometheus/common/model/fingerprinting.go b/vendor/github.com/prometheus/common/model/fingerprinting.go new file mode 100644 index 00000000000..fc4de4106e8 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/fingerprinting.go @@ -0,0 +1,105 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "fmt" + "strconv" +) + +// Fingerprint provides a hash-capable representation of a Metric. +// For our purposes, FNV-1A 64-bit is used. +type Fingerprint uint64 + +// FingerprintFromString transforms a string representation into a Fingerprint. +func FingerprintFromString(s string) (Fingerprint, error) { + num, err := strconv.ParseUint(s, 16, 64) + return Fingerprint(num), err +} + +// ParseFingerprint parses the input string into a fingerprint. +func ParseFingerprint(s string) (Fingerprint, error) { + num, err := strconv.ParseUint(s, 16, 64) + if err != nil { + return 0, err + } + return Fingerprint(num), nil +} + +func (f Fingerprint) String() string { + return fmt.Sprintf("%016x", uint64(f)) +} + +// Fingerprints represents a collection of Fingerprint subject to a given +// natural sorting scheme. It implements sort.Interface. +type Fingerprints []Fingerprint + +// Len implements sort.Interface. +func (f Fingerprints) Len() int { + return len(f) +} + +// Less implements sort.Interface. +func (f Fingerprints) Less(i, j int) bool { + return f[i] < f[j] +} + +// Swap implements sort.Interface. +func (f Fingerprints) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +// FingerprintSet is a set of Fingerprints. +type FingerprintSet map[Fingerprint]struct{} + +// Equal returns true if both sets contain the same elements (and not more). +func (s FingerprintSet) Equal(o FingerprintSet) bool { + if len(s) != len(o) { + return false + } + + for k := range s { + if _, ok := o[k]; !ok { + return false + } + } + + return true +} + +// Intersection returns the elements contained in both sets. +func (s FingerprintSet) Intersection(o FingerprintSet) FingerprintSet { + myLength, otherLength := len(s), len(o) + if myLength == 0 || otherLength == 0 { + return FingerprintSet{} + } + + subSet := s + superSet := o + + if otherLength < myLength { + subSet = o + superSet = s + } + + out := FingerprintSet{} + + for k := range subSet { + if _, ok := superSet[k]; ok { + out[k] = struct{}{} + } + } + + return out +} diff --git a/vendor/github.com/prometheus/common/model/fnv.go b/vendor/github.com/prometheus/common/model/fnv.go new file mode 100644 index 00000000000..038fc1c9003 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/fnv.go @@ -0,0 +1,42 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +// Inline and byte-free variant of hash/fnv's fnv64a. + +const ( + offset64 = 14695981039346656037 + prime64 = 1099511628211 +) + +// hashNew initializies a new fnv64a hash value. +func hashNew() uint64 { + return offset64 +} + +// hashAdd adds a string to a fnv64a hash value, returning the updated hash. +func hashAdd(h uint64, s string) uint64 { + for i := 0; i < len(s); i++ { + h ^= uint64(s[i]) + h *= prime64 + } + return h +} + +// hashAddByte adds a byte to a fnv64a hash value, returning the updated hash. +func hashAddByte(h uint64, b byte) uint64 { + h ^= uint64(b) + h *= prime64 + return h +} diff --git a/vendor/github.com/prometheus/common/model/labels.go b/vendor/github.com/prometheus/common/model/labels.go new file mode 100644 index 00000000000..3b72e7ff8f6 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/labels.go @@ -0,0 +1,206 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + "unicode/utf8" +) + +const ( + // AlertNameLabel is the name of the label containing the an alert's name. + AlertNameLabel = "alertname" + + // ExportedLabelPrefix is the prefix to prepend to the label names present in + // exported metrics if a label of the same name is added by the server. + ExportedLabelPrefix = "exported_" + + // MetricNameLabel is the label name indicating the metric name of a + // timeseries. + MetricNameLabel = "__name__" + + // SchemeLabel is the name of the label that holds the scheme on which to + // scrape a target. + SchemeLabel = "__scheme__" + + // AddressLabel is the name of the label that holds the address of + // a scrape target. + AddressLabel = "__address__" + + // MetricsPathLabel is the name of the label that holds the path on which to + // scrape a target. + MetricsPathLabel = "__metrics_path__" + + // ReservedLabelPrefix is a prefix which is not legal in user-supplied + // label names. + ReservedLabelPrefix = "__" + + // MetaLabelPrefix is a prefix for labels that provide meta information. + // Labels with this prefix are used for intermediate label processing and + // will not be attached to time series. + MetaLabelPrefix = "__meta_" + + // TmpLabelPrefix is a prefix for temporary labels as part of relabelling. + // Labels with this prefix are used for intermediate label processing and + // will not be attached to time series. This is reserved for use in + // Prometheus configuration files by users. + TmpLabelPrefix = "__tmp_" + + // ParamLabelPrefix is a prefix for labels that provide URL parameters + // used to scrape a target. + ParamLabelPrefix = "__param_" + + // JobLabel is the label name indicating the job from which a timeseries + // was scraped. + JobLabel = "job" + + // InstanceLabel is the label name used for the instance label. + InstanceLabel = "instance" + + // BucketLabel is used for the label that defines the upper bound of a + // bucket of a histogram ("le" -> "less or equal"). + BucketLabel = "le" + + // QuantileLabel is used for the label that defines the quantile in a + // summary. + QuantileLabel = "quantile" +) + +// LabelNameRE is a regular expression matching valid label names. +var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$") + +// A LabelName is a key for a LabelSet or Metric. It has a value associated +// therewith. +type LabelName string + +// IsValid is true iff the label name matches the pattern of LabelNameRE. +func (ln LabelName) IsValid() bool { + if len(ln) == 0 { + return false + } + for i, b := range ln { + if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { + return false + } + } + return true +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (ln *LabelName) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + if err := unmarshal(&s); err != nil { + return err + } + if !LabelNameRE.MatchString(s) { + return fmt.Errorf("%q is not a valid label name", s) + } + *ln = LabelName(s) + return nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (ln *LabelName) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + if !LabelNameRE.MatchString(s) { + return fmt.Errorf("%q is not a valid label name", s) + } + *ln = LabelName(s) + return nil +} + +// LabelNames is a sortable LabelName slice. In implements sort.Interface. +type LabelNames []LabelName + +func (l LabelNames) Len() int { + return len(l) +} + +func (l LabelNames) Less(i, j int) bool { + return l[i] < l[j] +} + +func (l LabelNames) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +func (l LabelNames) String() string { + labelStrings := make([]string, 0, len(l)) + for _, label := range l { + labelStrings = append(labelStrings, string(label)) + } + return strings.Join(labelStrings, ", ") +} + +// A LabelValue is an associated value for a LabelName. +type LabelValue string + +// IsValid returns true iff the string is a valid UTF8. +func (lv LabelValue) IsValid() bool { + return utf8.ValidString(string(lv)) +} + +// LabelValues is a sortable LabelValue slice. It implements sort.Interface. +type LabelValues []LabelValue + +func (l LabelValues) Len() int { + return len(l) +} + +func (l LabelValues) Less(i, j int) bool { + return string(l[i]) < string(l[j]) +} + +func (l LabelValues) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + +// LabelPair pairs a name with a value. +type LabelPair struct { + Name LabelName + Value LabelValue +} + +// LabelPairs is a sortable slice of LabelPair pointers. It implements +// sort.Interface. +type LabelPairs []*LabelPair + +func (l LabelPairs) Len() int { + return len(l) +} + +func (l LabelPairs) Less(i, j int) bool { + switch { + case l[i].Name > l[j].Name: + return false + case l[i].Name < l[j].Name: + return true + case l[i].Value > l[j].Value: + return false + case l[i].Value < l[j].Value: + return true + default: + return false + } +} + +func (l LabelPairs) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} diff --git a/vendor/github.com/prometheus/common/model/labelset.go b/vendor/github.com/prometheus/common/model/labelset.go new file mode 100644 index 00000000000..5f931cdb9b3 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/labelset.go @@ -0,0 +1,169 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "encoding/json" + "fmt" + "sort" + "strings" +) + +// A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet +// may be fully-qualified down to the point where it may resolve to a single +// Metric in the data store or not. All operations that occur within the realm +// of a LabelSet can emit a vector of Metric entities to which the LabelSet may +// match. +type LabelSet map[LabelName]LabelValue + +// Validate checks whether all names and values in the label set +// are valid. +func (ls LabelSet) Validate() error { + for ln, lv := range ls { + if !ln.IsValid() { + return fmt.Errorf("invalid name %q", ln) + } + if !lv.IsValid() { + return fmt.Errorf("invalid value %q", lv) + } + } + return nil +} + +// Equal returns true iff both label sets have exactly the same key/value pairs. +func (ls LabelSet) Equal(o LabelSet) bool { + if len(ls) != len(o) { + return false + } + for ln, lv := range ls { + olv, ok := o[ln] + if !ok { + return false + } + if olv != lv { + return false + } + } + return true +} + +// Before compares the metrics, using the following criteria: +// +// If m has fewer labels than o, it is before o. If it has more, it is not. +// +// If the number of labels is the same, the superset of all label names is +// sorted alphanumerically. The first differing label pair found in that order +// determines the outcome: If the label does not exist at all in m, then m is +// before o, and vice versa. Otherwise the label value is compared +// alphanumerically. +// +// If m and o are equal, the method returns false. +func (ls LabelSet) Before(o LabelSet) bool { + if len(ls) < len(o) { + return true + } + if len(ls) > len(o) { + return false + } + + lns := make(LabelNames, 0, len(ls)+len(o)) + for ln := range ls { + lns = append(lns, ln) + } + for ln := range o { + lns = append(lns, ln) + } + // It's probably not worth it to de-dup lns. + sort.Sort(lns) + for _, ln := range lns { + mlv, ok := ls[ln] + if !ok { + return true + } + olv, ok := o[ln] + if !ok { + return false + } + if mlv < olv { + return true + } + if mlv > olv { + return false + } + } + return false +} + +// Clone returns a copy of the label set. +func (ls LabelSet) Clone() LabelSet { + lsn := make(LabelSet, len(ls)) + for ln, lv := range ls { + lsn[ln] = lv + } + return lsn +} + +// Merge is a helper function to non-destructively merge two label sets. +func (l LabelSet) Merge(other LabelSet) LabelSet { + result := make(LabelSet, len(l)) + + for k, v := range l { + result[k] = v + } + + for k, v := range other { + result[k] = v + } + + return result +} + +func (l LabelSet) String() string { + lstrs := make([]string, 0, len(l)) + for l, v := range l { + lstrs = append(lstrs, fmt.Sprintf("%s=%q", l, v)) + } + + sort.Strings(lstrs) + return fmt.Sprintf("{%s}", strings.Join(lstrs, ", ")) +} + +// Fingerprint returns the LabelSet's fingerprint. +func (ls LabelSet) Fingerprint() Fingerprint { + return labelSetToFingerprint(ls) +} + +// FastFingerprint returns the LabelSet's Fingerprint calculated by a faster hashing +// algorithm, which is, however, more susceptible to hash collisions. +func (ls LabelSet) FastFingerprint() Fingerprint { + return labelSetToFastFingerprint(ls) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (l *LabelSet) UnmarshalJSON(b []byte) error { + var m map[LabelName]LabelValue + if err := json.Unmarshal(b, &m); err != nil { + return err + } + // encoding/json only unmarshals maps of the form map[string]T. It treats + // LabelName as a string and does not call its UnmarshalJSON method. + // Thus, we have to replicate the behavior here. + for ln := range m { + if !LabelNameRE.MatchString(string(ln)) { + return fmt.Errorf("%q is not a valid label name", ln) + } + } + *l = LabelSet(m) + return nil +} diff --git a/vendor/github.com/prometheus/common/model/metric.go b/vendor/github.com/prometheus/common/model/metric.go new file mode 100644 index 00000000000..a5da59a5055 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/metric.go @@ -0,0 +1,98 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "fmt" + "regexp" + "sort" + "strings" +) + +var ( + separator = []byte{0} + MetricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`) +) + +// A Metric is similar to a LabelSet, but the key difference is that a Metric is +// a singleton and refers to one and only one stream of samples. +type Metric LabelSet + +// Equal compares the metrics. +func (m Metric) Equal(o Metric) bool { + return LabelSet(m).Equal(LabelSet(o)) +} + +// Before compares the metrics' underlying label sets. +func (m Metric) Before(o Metric) bool { + return LabelSet(m).Before(LabelSet(o)) +} + +// Clone returns a copy of the Metric. +func (m Metric) Clone() Metric { + clone := Metric{} + for k, v := range m { + clone[k] = v + } + return clone +} + +func (m Metric) String() string { + metricName, hasName := m[MetricNameLabel] + numLabels := len(m) - 1 + if !hasName { + numLabels = len(m) + } + labelStrings := make([]string, 0, numLabels) + for label, value := range m { + if label != MetricNameLabel { + labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value)) + } + } + + switch numLabels { + case 0: + if hasName { + return string(metricName) + } + return "{}" + default: + sort.Strings(labelStrings) + return fmt.Sprintf("%s{%s}", metricName, strings.Join(labelStrings, ", ")) + } +} + +// Fingerprint returns a Metric's Fingerprint. +func (m Metric) Fingerprint() Fingerprint { + return LabelSet(m).Fingerprint() +} + +// FastFingerprint returns a Metric's Fingerprint calculated by a faster hashing +// algorithm, which is, however, more susceptible to hash collisions. +func (m Metric) FastFingerprint() Fingerprint { + return LabelSet(m).FastFingerprint() +} + +// IsValidMetricName returns true iff name matches the pattern of MetricNameRE. +func IsValidMetricName(n LabelValue) bool { + if len(n) == 0 { + return false + } + for i, b := range n { + if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == ':' || (b >= '0' && b <= '9' && i > 0)) { + return false + } + } + return true +} diff --git a/vendor/github.com/prometheus/common/model/model.go b/vendor/github.com/prometheus/common/model/model.go new file mode 100644 index 00000000000..a7b9691707e --- /dev/null +++ b/vendor/github.com/prometheus/common/model/model.go @@ -0,0 +1,16 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package model contains common data structures that are shared across +// Prometheus components and libraries. +package model diff --git a/vendor/github.com/prometheus/common/model/signature.go b/vendor/github.com/prometheus/common/model/signature.go new file mode 100644 index 00000000000..8762b13c63d --- /dev/null +++ b/vendor/github.com/prometheus/common/model/signature.go @@ -0,0 +1,144 @@ +// Copyright 2014 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "sort" +) + +// SeparatorByte is a byte that cannot occur in valid UTF-8 sequences and is +// used to separate label names, label values, and other strings from each other +// when calculating their combined hash value (aka signature aka fingerprint). +const SeparatorByte byte = 255 + +var ( + // cache the signature of an empty label set. + emptyLabelSignature = hashNew() +) + +// LabelsToSignature returns a quasi-unique signature (i.e., fingerprint) for a +// given label set. (Collisions are possible but unlikely if the number of label +// sets the function is applied to is small.) +func LabelsToSignature(labels map[string]string) uint64 { + if len(labels) == 0 { + return emptyLabelSignature + } + + labelNames := make([]string, 0, len(labels)) + for labelName := range labels { + labelNames = append(labelNames, labelName) + } + sort.Strings(labelNames) + + sum := hashNew() + for _, labelName := range labelNames { + sum = hashAdd(sum, labelName) + sum = hashAddByte(sum, SeparatorByte) + sum = hashAdd(sum, labels[labelName]) + sum = hashAddByte(sum, SeparatorByte) + } + return sum +} + +// labelSetToFingerprint works exactly as LabelsToSignature but takes a LabelSet as +// parameter (rather than a label map) and returns a Fingerprint. +func labelSetToFingerprint(ls LabelSet) Fingerprint { + if len(ls) == 0 { + return Fingerprint(emptyLabelSignature) + } + + labelNames := make(LabelNames, 0, len(ls)) + for labelName := range ls { + labelNames = append(labelNames, labelName) + } + sort.Sort(labelNames) + + sum := hashNew() + for _, labelName := range labelNames { + sum = hashAdd(sum, string(labelName)) + sum = hashAddByte(sum, SeparatorByte) + sum = hashAdd(sum, string(ls[labelName])) + sum = hashAddByte(sum, SeparatorByte) + } + return Fingerprint(sum) +} + +// labelSetToFastFingerprint works similar to labelSetToFingerprint but uses a +// faster and less allocation-heavy hash function, which is more susceptible to +// create hash collisions. Therefore, collision detection should be applied. +func labelSetToFastFingerprint(ls LabelSet) Fingerprint { + if len(ls) == 0 { + return Fingerprint(emptyLabelSignature) + } + + var result uint64 + for labelName, labelValue := range ls { + sum := hashNew() + sum = hashAdd(sum, string(labelName)) + sum = hashAddByte(sum, SeparatorByte) + sum = hashAdd(sum, string(labelValue)) + result ^= sum + } + return Fingerprint(result) +} + +// SignatureForLabels works like LabelsToSignature but takes a Metric as +// parameter (rather than a label map) and only includes the labels with the +// specified LabelNames into the signature calculation. The labels passed in +// will be sorted by this function. +func SignatureForLabels(m Metric, labels ...LabelName) uint64 { + if len(labels) == 0 { + return emptyLabelSignature + } + + sort.Sort(LabelNames(labels)) + + sum := hashNew() + for _, label := range labels { + sum = hashAdd(sum, string(label)) + sum = hashAddByte(sum, SeparatorByte) + sum = hashAdd(sum, string(m[label])) + sum = hashAddByte(sum, SeparatorByte) + } + return sum +} + +// SignatureWithoutLabels works like LabelsToSignature but takes a Metric as +// parameter (rather than a label map) and excludes the labels with any of the +// specified LabelNames from the signature calculation. +func SignatureWithoutLabels(m Metric, labels map[LabelName]struct{}) uint64 { + if len(m) == 0 { + return emptyLabelSignature + } + + labelNames := make(LabelNames, 0, len(m)) + for labelName := range m { + if _, exclude := labels[labelName]; !exclude { + labelNames = append(labelNames, labelName) + } + } + if len(labelNames) == 0 { + return emptyLabelSignature + } + sort.Sort(labelNames) + + sum := hashNew() + for _, labelName := range labelNames { + sum = hashAdd(sum, string(labelName)) + sum = hashAddByte(sum, SeparatorByte) + sum = hashAdd(sum, string(m[labelName])) + sum = hashAddByte(sum, SeparatorByte) + } + return sum +} diff --git a/vendor/github.com/prometheus/common/model/silence.go b/vendor/github.com/prometheus/common/model/silence.go new file mode 100644 index 00000000000..7538e299774 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/silence.go @@ -0,0 +1,106 @@ +// Copyright 2015 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "encoding/json" + "fmt" + "regexp" + "time" +) + +// Matcher describes a matches the value of a given label. +type Matcher struct { + Name LabelName `json:"name"` + Value string `json:"value"` + IsRegex bool `json:"isRegex"` +} + +func (m *Matcher) UnmarshalJSON(b []byte) error { + type plain Matcher + if err := json.Unmarshal(b, (*plain)(m)); err != nil { + return err + } + + if len(m.Name) == 0 { + return fmt.Errorf("label name in matcher must not be empty") + } + if m.IsRegex { + if _, err := regexp.Compile(m.Value); err != nil { + return err + } + } + return nil +} + +// Validate returns true iff all fields of the matcher have valid values. +func (m *Matcher) Validate() error { + if !m.Name.IsValid() { + return fmt.Errorf("invalid name %q", m.Name) + } + if m.IsRegex { + if _, err := regexp.Compile(m.Value); err != nil { + return fmt.Errorf("invalid regular expression %q", m.Value) + } + } else if !LabelValue(m.Value).IsValid() || len(m.Value) == 0 { + return fmt.Errorf("invalid value %q", m.Value) + } + return nil +} + +// Silence defines the representation of a silence definiton +// in the Prometheus eco-system. +type Silence struct { + ID uint64 `json:"id,omitempty"` + + Matchers []*Matcher `json:"matchers"` + + StartsAt time.Time `json:"startsAt"` + EndsAt time.Time `json:"endsAt"` + + CreatedAt time.Time `json:"createdAt,omitempty"` + CreatedBy string `json:"createdBy"` + Comment string `json:"comment,omitempty"` +} + +// Validate returns true iff all fields of the silence have valid values. +func (s *Silence) Validate() error { + if len(s.Matchers) == 0 { + return fmt.Errorf("at least one matcher required") + } + for _, m := range s.Matchers { + if err := m.Validate(); err != nil { + return fmt.Errorf("invalid matcher: %s", err) + } + } + if s.StartsAt.IsZero() { + return fmt.Errorf("start time missing") + } + if s.EndsAt.IsZero() { + return fmt.Errorf("end time missing") + } + if s.EndsAt.Before(s.StartsAt) { + return fmt.Errorf("start time must be before end time") + } + if s.CreatedBy == "" { + return fmt.Errorf("creator information missing") + } + if s.Comment == "" { + return fmt.Errorf("comment missing") + } + if s.CreatedAt.IsZero() { + return fmt.Errorf("creation timestamp missing") + } + return nil +} diff --git a/vendor/github.com/prometheus/common/model/time.go b/vendor/github.com/prometheus/common/model/time.go new file mode 100644 index 00000000000..548968aebe6 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/time.go @@ -0,0 +1,249 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "fmt" + "math" + "regexp" + "strconv" + "strings" + "time" +) + +const ( + // MinimumTick is the minimum supported time resolution. This has to be + // at least time.Second in order for the code below to work. + minimumTick = time.Millisecond + // second is the Time duration equivalent to one second. + second = int64(time.Second / minimumTick) + // The number of nanoseconds per minimum tick. + nanosPerTick = int64(minimumTick / time.Nanosecond) + + // Earliest is the earliest Time representable. Handy for + // initializing a high watermark. + Earliest = Time(math.MinInt64) + // Latest is the latest Time representable. Handy for initializing + // a low watermark. + Latest = Time(math.MaxInt64) +) + +// Time is the number of milliseconds since the epoch +// (1970-01-01 00:00 UTC) excluding leap seconds. +type Time int64 + +// Interval describes and interval between two timestamps. +type Interval struct { + Start, End Time +} + +// Now returns the current time as a Time. +func Now() Time { + return TimeFromUnixNano(time.Now().UnixNano()) +} + +// TimeFromUnix returns the Time equivalent to the Unix Time t +// provided in seconds. +func TimeFromUnix(t int64) Time { + return Time(t * second) +} + +// TimeFromUnixNano returns the Time equivalent to the Unix Time +// t provided in nanoseconds. +func TimeFromUnixNano(t int64) Time { + return Time(t / nanosPerTick) +} + +// Equal reports whether two Times represent the same instant. +func (t Time) Equal(o Time) bool { + return t == o +} + +// Before reports whether the Time t is before o. +func (t Time) Before(o Time) bool { + return t < o +} + +// After reports whether the Time t is after o. +func (t Time) After(o Time) bool { + return t > o +} + +// Add returns the Time t + d. +func (t Time) Add(d time.Duration) Time { + return t + Time(d/minimumTick) +} + +// Sub returns the Duration t - o. +func (t Time) Sub(o Time) time.Duration { + return time.Duration(t-o) * minimumTick +} + +// Time returns the time.Time representation of t. +func (t Time) Time() time.Time { + return time.Unix(int64(t)/second, (int64(t)%second)*nanosPerTick) +} + +// Unix returns t as a Unix time, the number of seconds elapsed +// since January 1, 1970 UTC. +func (t Time) Unix() int64 { + return int64(t) / second +} + +// UnixNano returns t as a Unix time, the number of nanoseconds elapsed +// since January 1, 1970 UTC. +func (t Time) UnixNano() int64 { + return int64(t) * nanosPerTick +} + +// The number of digits after the dot. +var dotPrecision = int(math.Log10(float64(second))) + +// String returns a string representation of the Time. +func (t Time) String() string { + return strconv.FormatFloat(float64(t)/float64(second), 'f', -1, 64) +} + +// MarshalJSON implements the json.Marshaler interface. +func (t Time) MarshalJSON() ([]byte, error) { + return []byte(t.String()), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (t *Time) UnmarshalJSON(b []byte) error { + p := strings.Split(string(b), ".") + switch len(p) { + case 1: + v, err := strconv.ParseInt(string(p[0]), 10, 64) + if err != nil { + return err + } + *t = Time(v * second) + + case 2: + v, err := strconv.ParseInt(string(p[0]), 10, 64) + if err != nil { + return err + } + v *= second + + prec := dotPrecision - len(p[1]) + if prec < 0 { + p[1] = p[1][:dotPrecision] + } else if prec > 0 { + p[1] = p[1] + strings.Repeat("0", prec) + } + + va, err := strconv.ParseInt(p[1], 10, 32) + if err != nil { + return err + } + + *t = Time(v + va) + + default: + return fmt.Errorf("invalid time %q", string(b)) + } + return nil +} + +// Duration wraps time.Duration. It is used to parse the custom duration format +// from YAML. +// This type should not propagate beyond the scope of input/output processing. +type Duration time.Duration + +var durationRE = regexp.MustCompile("^([0-9]+)(y|w|d|h|m|s|ms)$") + +// StringToDuration parses a string into a time.Duration, assuming that a year +// always has 365d, a week always has 7d, and a day always has 24h. +func ParseDuration(durationStr string) (Duration, error) { + matches := durationRE.FindStringSubmatch(durationStr) + if len(matches) != 3 { + return 0, fmt.Errorf("not a valid duration string: %q", durationStr) + } + var ( + n, _ = strconv.Atoi(matches[1]) + dur = time.Duration(n) * time.Millisecond + ) + switch unit := matches[2]; unit { + case "y": + dur *= 1000 * 60 * 60 * 24 * 365 + case "w": + dur *= 1000 * 60 * 60 * 24 * 7 + case "d": + dur *= 1000 * 60 * 60 * 24 + case "h": + dur *= 1000 * 60 * 60 + case "m": + dur *= 1000 * 60 + case "s": + dur *= 1000 + case "ms": + // Value already correct + default: + return 0, fmt.Errorf("invalid time unit in duration string: %q", unit) + } + return Duration(dur), nil +} + +func (d Duration) String() string { + var ( + ms = int64(time.Duration(d) / time.Millisecond) + unit = "ms" + ) + factors := map[string]int64{ + "y": 1000 * 60 * 60 * 24 * 365, + "w": 1000 * 60 * 60 * 24 * 7, + "d": 1000 * 60 * 60 * 24, + "h": 1000 * 60 * 60, + "m": 1000 * 60, + "s": 1000, + "ms": 1, + } + + switch int64(0) { + case ms % factors["y"]: + unit = "y" + case ms % factors["w"]: + unit = "w" + case ms % factors["d"]: + unit = "d" + case ms % factors["h"]: + unit = "h" + case ms % factors["m"]: + unit = "m" + case ms % factors["s"]: + unit = "s" + } + return fmt.Sprintf("%v%v", ms/factors[unit], unit) +} + +// MarshalYAML implements the yaml.Marshaler interface. +func (d Duration) MarshalYAML() (interface{}, error) { + return d.String(), nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + if err := unmarshal(&s); err != nil { + return err + } + dur, err := ParseDuration(s) + if err != nil { + return err + } + *d = dur + return nil +} diff --git a/vendor/github.com/prometheus/common/model/value.go b/vendor/github.com/prometheus/common/model/value.go new file mode 100644 index 00000000000..dbf5d10e431 --- /dev/null +++ b/vendor/github.com/prometheus/common/model/value.go @@ -0,0 +1,403 @@ +// Copyright 2013 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "encoding/json" + "fmt" + "math" + "sort" + "strconv" + "strings" +) + +// A SampleValue is a representation of a value for a given sample at a given +// time. +type SampleValue float64 + +// MarshalJSON implements json.Marshaler. +func (v SampleValue) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (v *SampleValue) UnmarshalJSON(b []byte) error { + if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { + return fmt.Errorf("sample value must be a quoted string") + } + f, err := strconv.ParseFloat(string(b[1:len(b)-1]), 64) + if err != nil { + return err + } + *v = SampleValue(f) + return nil +} + +// Equal returns true if the value of v and o is equal or if both are NaN. Note +// that v==o is false if both are NaN. If you want the conventional float +// behavior, use == to compare two SampleValues. +func (v SampleValue) Equal(o SampleValue) bool { + if v == o { + return true + } + return math.IsNaN(float64(v)) && math.IsNaN(float64(o)) +} + +func (v SampleValue) String() string { + return strconv.FormatFloat(float64(v), 'f', -1, 64) +} + +// SamplePair pairs a SampleValue with a Timestamp. +type SamplePair struct { + Timestamp Time + Value SampleValue +} + +// MarshalJSON implements json.Marshaler. +func (s SamplePair) MarshalJSON() ([]byte, error) { + t, err := json.Marshal(s.Timestamp) + if err != nil { + return nil, err + } + v, err := json.Marshal(s.Value) + if err != nil { + return nil, err + } + return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (s *SamplePair) UnmarshalJSON(b []byte) error { + v := [...]json.Unmarshaler{&s.Timestamp, &s.Value} + return json.Unmarshal(b, &v) +} + +// Equal returns true if this SamplePair and o have equal Values and equal +// Timestamps. The sematics of Value equality is defined by SampleValue.Equal. +func (s *SamplePair) Equal(o *SamplePair) bool { + return s == o || (s.Value.Equal(o.Value) && s.Timestamp.Equal(o.Timestamp)) +} + +func (s SamplePair) String() string { + return fmt.Sprintf("%s @[%s]", s.Value, s.Timestamp) +} + +// Sample is a sample pair associated with a metric. +type Sample struct { + Metric Metric `json:"metric"` + Value SampleValue `json:"value"` + Timestamp Time `json:"timestamp"` +} + +// Equal compares first the metrics, then the timestamp, then the value. The +// sematics of value equality is defined by SampleValue.Equal. +func (s *Sample) Equal(o *Sample) bool { + if s == o { + return true + } + + if !s.Metric.Equal(o.Metric) { + return false + } + if !s.Timestamp.Equal(o.Timestamp) { + return false + } + if s.Value.Equal(o.Value) { + return false + } + + return true +} + +func (s Sample) String() string { + return fmt.Sprintf("%s => %s", s.Metric, SamplePair{ + Timestamp: s.Timestamp, + Value: s.Value, + }) +} + +// MarshalJSON implements json.Marshaler. +func (s Sample) MarshalJSON() ([]byte, error) { + v := struct { + Metric Metric `json:"metric"` + Value SamplePair `json:"value"` + }{ + Metric: s.Metric, + Value: SamplePair{ + Timestamp: s.Timestamp, + Value: s.Value, + }, + } + + return json.Marshal(&v) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (s *Sample) UnmarshalJSON(b []byte) error { + v := struct { + Metric Metric `json:"metric"` + Value SamplePair `json:"value"` + }{ + Metric: s.Metric, + Value: SamplePair{ + Timestamp: s.Timestamp, + Value: s.Value, + }, + } + + if err := json.Unmarshal(b, &v); err != nil { + return err + } + + s.Metric = v.Metric + s.Timestamp = v.Value.Timestamp + s.Value = v.Value.Value + + return nil +} + +// Samples is a sortable Sample slice. It implements sort.Interface. +type Samples []*Sample + +func (s Samples) Len() int { + return len(s) +} + +// Less compares first the metrics, then the timestamp. +func (s Samples) Less(i, j int) bool { + switch { + case s[i].Metric.Before(s[j].Metric): + return true + case s[j].Metric.Before(s[i].Metric): + return false + case s[i].Timestamp.Before(s[j].Timestamp): + return true + default: + return false + } +} + +func (s Samples) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Equal compares two sets of samples and returns true if they are equal. +func (s Samples) Equal(o Samples) bool { + if len(s) != len(o) { + return false + } + + for i, sample := range s { + if !sample.Equal(o[i]) { + return false + } + } + return true +} + +// SampleStream is a stream of Values belonging to an attached COWMetric. +type SampleStream struct { + Metric Metric `json:"metric"` + Values []SamplePair `json:"values"` +} + +func (ss SampleStream) String() string { + vals := make([]string, len(ss.Values)) + for i, v := range ss.Values { + vals[i] = v.String() + } + return fmt.Sprintf("%s =>\n%s", ss.Metric, strings.Join(vals, "\n")) +} + +// Value is a generic interface for values resulting from a query evaluation. +type Value interface { + Type() ValueType + String() string +} + +func (Matrix) Type() ValueType { return ValMatrix } +func (Vector) Type() ValueType { return ValVector } +func (*Scalar) Type() ValueType { return ValScalar } +func (*String) Type() ValueType { return ValString } + +type ValueType int + +const ( + ValNone ValueType = iota + ValScalar + ValVector + ValMatrix + ValString +) + +// MarshalJSON implements json.Marshaler. +func (et ValueType) MarshalJSON() ([]byte, error) { + return json.Marshal(et.String()) +} + +func (et *ValueType) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + switch s { + case "": + *et = ValNone + case "scalar": + *et = ValScalar + case "vector": + *et = ValVector + case "matrix": + *et = ValMatrix + case "string": + *et = ValString + default: + return fmt.Errorf("unknown value type %q", s) + } + return nil +} + +func (e ValueType) String() string { + switch e { + case ValNone: + return "" + case ValScalar: + return "scalar" + case ValVector: + return "vector" + case ValMatrix: + return "matrix" + case ValString: + return "string" + } + panic("ValueType.String: unhandled value type") +} + +// Scalar is a scalar value evaluated at the set timestamp. +type Scalar struct { + Value SampleValue `json:"value"` + Timestamp Time `json:"timestamp"` +} + +func (s Scalar) String() string { + return fmt.Sprintf("scalar: %v @[%v]", s.Value, s.Timestamp) +} + +// MarshalJSON implements json.Marshaler. +func (s Scalar) MarshalJSON() ([]byte, error) { + v := strconv.FormatFloat(float64(s.Value), 'f', -1, 64) + return json.Marshal([...]interface{}{s.Timestamp, string(v)}) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (s *Scalar) UnmarshalJSON(b []byte) error { + var f string + v := [...]interface{}{&s.Timestamp, &f} + + if err := json.Unmarshal(b, &v); err != nil { + return err + } + + value, err := strconv.ParseFloat(f, 64) + if err != nil { + return fmt.Errorf("error parsing sample value: %s", err) + } + s.Value = SampleValue(value) + return nil +} + +// String is a string value evaluated at the set timestamp. +type String struct { + Value string `json:"value"` + Timestamp Time `json:"timestamp"` +} + +func (s *String) String() string { + return s.Value +} + +// MarshalJSON implements json.Marshaler. +func (s String) MarshalJSON() ([]byte, error) { + return json.Marshal([]interface{}{s.Timestamp, s.Value}) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (s *String) UnmarshalJSON(b []byte) error { + v := [...]interface{}{&s.Timestamp, &s.Value} + return json.Unmarshal(b, &v) +} + +// Vector is basically only an alias for Samples, but the +// contract is that in a Vector, all Samples have the same timestamp. +type Vector []*Sample + +func (vec Vector) String() string { + entries := make([]string, len(vec)) + for i, s := range vec { + entries[i] = s.String() + } + return strings.Join(entries, "\n") +} + +func (vec Vector) Len() int { return len(vec) } +func (vec Vector) Swap(i, j int) { vec[i], vec[j] = vec[j], vec[i] } + +// Less compares first the metrics, then the timestamp. +func (vec Vector) Less(i, j int) bool { + switch { + case vec[i].Metric.Before(vec[j].Metric): + return true + case vec[j].Metric.Before(vec[i].Metric): + return false + case vec[i].Timestamp.Before(vec[j].Timestamp): + return true + default: + return false + } +} + +// Equal compares two sets of samples and returns true if they are equal. +func (vec Vector) Equal(o Vector) bool { + if len(vec) != len(o) { + return false + } + + for i, sample := range vec { + if !sample.Equal(o[i]) { + return false + } + } + return true +} + +// Matrix is a list of time series. +type Matrix []*SampleStream + +func (m Matrix) Len() int { return len(m) } +func (m Matrix) Less(i, j int) bool { return m[i].Metric.Before(m[j].Metric) } +func (m Matrix) Swap(i, j int) { m[i], m[j] = m[j], m[i] } + +func (mat Matrix) String() string { + matCp := make(Matrix, len(mat)) + copy(matCp, mat) + sort.Sort(matCp) + + strs := make([]string, len(matCp)) + + for i, ss := range matCp { + strs[i] = ss.String() + } + + return strings.Join(strs, "\n") +} diff --git a/vendor/golang.org/x/net/LICENSE b/vendor/golang.org/x/net/LICENSE new file mode 100644 index 00000000000..6a66aea5eaf --- /dev/null +++ b/vendor/golang.org/x/net/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/net/PATENTS b/vendor/golang.org/x/net/PATENTS new file mode 100644 index 00000000000..733099041f8 --- /dev/null +++ b/vendor/golang.org/x/net/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go new file mode 100644 index 00000000000..606cf1f9726 --- /dev/null +++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go @@ -0,0 +1,74 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7 + +// Package ctxhttp provides helper functions for performing context-aware HTTP requests. +package ctxhttp // import "golang.org/x/net/context/ctxhttp" + +import ( + "io" + "net/http" + "net/url" + "strings" + + "golang.org/x/net/context" +) + +// Do sends an HTTP request with the provided http.Client and returns +// an HTTP response. +// +// If the client is nil, http.DefaultClient is used. +// +// The provided ctx must be non-nil. If it is canceled or times out, +// ctx.Err() will be returned. +func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { + if client == nil { + client = http.DefaultClient + } + resp, err := client.Do(req.WithContext(ctx)) + // If we got an error, and the context has been canceled, + // the context's error is probably more useful. + if err != nil { + select { + case <-ctx.Done(): + err = ctx.Err() + default: + } + } + return resp, err +} + +// Get issues a GET request via the Do function. +func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Head issues a HEAD request via the Do function. +func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Post issues a POST request via the Do function. +func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", bodyType) + return Do(ctx, client, req) +} + +// PostForm issues a POST request via the Do function. +func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) { + return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go new file mode 100644 index 00000000000..926870cc23f --- /dev/null +++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go @@ -0,0 +1,147 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.7 + +package ctxhttp // import "golang.org/x/net/context/ctxhttp" + +import ( + "io" + "net/http" + "net/url" + "strings" + + "golang.org/x/net/context" +) + +func nop() {} + +var ( + testHookContextDoneBeforeHeaders = nop + testHookDoReturned = nop + testHookDidBodyClose = nop +) + +// Do sends an HTTP request with the provided http.Client and returns an HTTP response. +// If the client is nil, http.DefaultClient is used. +// If the context is canceled or times out, ctx.Err() will be returned. +func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { + if client == nil { + client = http.DefaultClient + } + + // TODO(djd): Respect any existing value of req.Cancel. + cancel := make(chan struct{}) + req.Cancel = cancel + + type responseAndError struct { + resp *http.Response + err error + } + result := make(chan responseAndError, 1) + + // Make local copies of test hooks closed over by goroutines below. + // Prevents data races in tests. + testHookDoReturned := testHookDoReturned + testHookDidBodyClose := testHookDidBodyClose + + go func() { + resp, err := client.Do(req) + testHookDoReturned() + result <- responseAndError{resp, err} + }() + + var resp *http.Response + + select { + case <-ctx.Done(): + testHookContextDoneBeforeHeaders() + close(cancel) + // Clean up after the goroutine calling client.Do: + go func() { + if r := <-result; r.resp != nil { + testHookDidBodyClose() + r.resp.Body.Close() + } + }() + return nil, ctx.Err() + case r := <-result: + var err error + resp, err = r.resp, r.err + if err != nil { + return resp, err + } + } + + c := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + close(cancel) + case <-c: + // The response's Body is closed. + } + }() + resp.Body = ¬ifyingReader{resp.Body, c} + + return resp, nil +} + +// Get issues a GET request via the Do function. +func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Head issues a HEAD request via the Do function. +func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Post issues a POST request via the Do function. +func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", bodyType) + return Do(ctx, client, req) +} + +// PostForm issues a POST request via the Do function. +func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) { + return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} + +// notifyingReader is an io.ReadCloser that closes the notify channel after +// Close is called or a Read fails on the underlying ReadCloser. +type notifyingReader struct { + io.ReadCloser + notify chan<- struct{} +} + +func (r *notifyingReader) Read(p []byte) (int, error) { + n, err := r.ReadCloser.Read(p) + if err != nil && r.notify != nil { + close(r.notify) + r.notify = nil + } + return n, err +} + +func (r *notifyingReader) Close() error { + err := r.ReadCloser.Close() + if r.notify != nil { + close(r.notify) + r.notify = nil + } + return err +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 4bc76ca4d73..1d4dafbb9ae 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1,6 +1,25 @@ { "comment": "", "ignore": "test", - "package": [], + "package": [ + { + "checksumSHA1": "SMUvX2B8eoFd9wnPofwBKlN6btE=", + "path": "github.com/prometheus/client_golang/api/prometheus", + "revision": "5636dc67ae776adf5590da7349e70fbb9559972d", + "revisionTime": "2016-09-16T18:03:40Z" + }, + { + "checksumSHA1": "Jx0GXl5hGnO25s3ryyvtdWHdCpw=", + "path": "github.com/prometheus/common/model", + "revision": "9a94032291f2192936512bab367bc45e77990d6a", + "revisionTime": "2016-09-17T18:44:01Z" + }, + { + "checksumSHA1": "WHc3uByvGaMcnSoI21fhzYgbOgg=", + "path": "golang.org/x/net/context/ctxhttp", + "revision": "71a035914f99bb58fe82eac0f1289f10963d876c", + "revisionTime": "2016-09-12T21:59:12Z" + } + ], "rootPath": "github.com/grafana/grafana" } From 4c88db3e43e1772ef6a193574a5ad38d9a310962 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Sep 2016 11:17:29 +0200 Subject: [PATCH 02/12] feat(prometheus): add support for legend formatting --- pkg/services/alerting/conditions/query.go | 2 +- pkg/tsdb/graphite/graphite.go | 2 +- pkg/tsdb/models.go | 3 ++ pkg/tsdb/prometheus/prometheus.go | 41 ++++++++++++++++--- pkg/tsdb/prometheus/prometheus_test.go | 26 ++++++++++++ pkg/tsdb/prometheus/types.go | 8 ++++ .../datasource/prometheus/datasource.ts | 1 - 7 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 pkg/tsdb/prometheus/prometheus_test.go diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index 208527287f3..15db31838b0 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -111,7 +111,7 @@ func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource, timera Queries: []*tsdb.Query{ { RefId: "A", - Query: c.Query.Model.Get("target").MustString(), + Model: c.Query.Model, DataSource: &tsdb.DataSourceInfo{ Id: datasource.Id, Name: datasource.Name, diff --git a/pkg/tsdb/graphite/graphite.go b/pkg/tsdb/graphite/graphite.go index 4042702378c..32e1ab4fa76 100644 --- a/pkg/tsdb/graphite/graphite.go +++ b/pkg/tsdb/graphite/graphite.go @@ -54,7 +54,7 @@ func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryC } for _, query := range queries { - formData["target"] = []string{query.Query} + formData["target"] = []string{query.Model.Get("target").MustString()} } if setting.Env == setting.DEV { diff --git a/pkg/tsdb/models.go b/pkg/tsdb/models.go index 117b00efe3a..262be5cbd24 100644 --- a/pkg/tsdb/models.go +++ b/pkg/tsdb/models.go @@ -1,8 +1,11 @@ package tsdb +import "github.com/grafana/grafana/pkg/components/simplejson" + type Query struct { RefId string Query string + Model *simplejson.Json Depends []string DataSource *DataSourceInfo Results []*TimeSeries diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index 2b2ccd382af..d835d5a822c 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -3,6 +3,8 @@ package prometheus import ( "context" "net/http" + "regexp" + "strings" "time" "github.com/grafana/grafana/pkg/log" @@ -53,25 +55,52 @@ func (e *PrometheusExecutor) Execute(queries tsdb.QuerySlice, queryContext *tsdb from, _ := queryContext.TimeRange.FromTime() to, _ := queryContext.TimeRange.ToTime() + + query := parseQuery(queries) + timeRange := prometheus.Range{ Start: from, End: to, - Step: time.Second, + Step: query.Step, } - ctx := context.Background() - value, err := client.QueryRange(ctx, "counters_logins", timeRange) + value, err := client.QueryRange(context.Background(), query.Expr, timeRange) if err != nil { result.Error = err return result } - result.QueryResults = parseResponse(value) + result.QueryResults = parseResponse(value, query) return result } -func parseResponse(value pmodel.Value) map[string]*tsdb.QueryResult { +func formatLegend(metric pmodel.Metric, query PrometheusQuery) string { + r, _ := regexp.Compile(`\{\{\s*(.+?)\s*\}\}`) + + result := r.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte { + ind := strings.Replace(strings.Replace(string(in), "{{", "", 1), "}}", "", 1) + if val, exists := metric[pmodel.LabelName(ind)]; exists { + return []byte(val) + } + + return in + }) + + return string(result) +} + +func parseQuery(queries tsdb.QuerySlice) PrometheusQuery { + queryModel := queries[0] + + return PrometheusQuery{ + Expr: queryModel.Model.Get("expr").MustString(), + Step: time.Second * time.Duration(queryModel.Model.Get("step").MustInt64(1)), + LegendFormat: queryModel.Model.Get("legendFormat").MustString(), + } +} + +func parseResponse(value pmodel.Value, query PrometheusQuery) map[string]*tsdb.QueryResult { queryResults := make(map[string]*tsdb.QueryResult) queryRes := &tsdb.QueryResult{} @@ -86,7 +115,7 @@ func parseResponse(value pmodel.Value) map[string]*tsdb.QueryResult { } queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ - Name: v.Metric.String(), + Name: formatLegend(v.Metric, query), Points: points, }) } diff --git a/pkg/tsdb/prometheus/prometheus_test.go b/pkg/tsdb/prometheus/prometheus_test.go new file mode 100644 index 00000000000..6edb6260a09 --- /dev/null +++ b/pkg/tsdb/prometheus/prometheus_test.go @@ -0,0 +1,26 @@ +package prometheus + +import ( + "testing" + + p "github.com/prometheus/common/model" + . "github.com/smartystreets/goconvey/convey" +) + +func TestPrometheus(t *testing.T) { + Convey("Prometheus", t, func() { + + Convey("converting metric name", func() { + metric := map[p.LabelName]p.LabelValue{ + p.LabelName("app"): p.LabelValue("backend"), + p.LabelName("device"): p.LabelValue("mobile"), + } + + query := PrometheusQuery{ + LegendFormat: "legend {{app}} {{device}} {{broken}}", + } + + So(formatLegend(metric, query), ShouldEqual, "legend backend mobile {{broken}}") + }) + }) +} diff --git a/pkg/tsdb/prometheus/types.go b/pkg/tsdb/prometheus/types.go index 7b1b4c03ead..67d9ae40670 100644 --- a/pkg/tsdb/prometheus/types.go +++ b/pkg/tsdb/prometheus/types.go @@ -1 +1,9 @@ package prometheus + +import "time" + +type PrometheusQuery struct { + Expr string + Step time.Duration + LegendFormat string +} diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index 53ce91144a4..1d4112789da 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -113,7 +113,6 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS throw response.error; } delete self.lastErrors.query; - _.each(response.data.data.result, function(metricData) { result.push(self.transformMetricData(metricData, activeTargets[index], start, end)); }); From 3e73be8d2e34ec09f1b75516b23e576628f2a376 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Sep 2016 11:31:56 +0200 Subject: [PATCH 03/12] feat(prometheus): improve error handling --- pkg/tsdb/prometheus/prometheus.go | 46 +++++++++++++++++++------- pkg/tsdb/prometheus/prometheus_test.go | 2 +- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index d835d5a822c..b32a99220b2 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -49,14 +49,17 @@ func (e *PrometheusExecutor) Execute(queries tsdb.QuerySlice, queryContext *tsdb client, err := e.getClient() if err != nil { - result.Error = err - return result + return resultWithError(result, err) } from, _ := queryContext.TimeRange.FromTime() to, _ := queryContext.TimeRange.ToTime() - query := parseQuery(queries) + query, err := parseQuery(queries) + + if err != nil { + return resultWithError(result, err) + } timeRange := prometheus.Range{ Start: from, @@ -67,15 +70,14 @@ func (e *PrometheusExecutor) Execute(queries tsdb.QuerySlice, queryContext *tsdb value, err := client.QueryRange(context.Background(), query.Expr, timeRange) if err != nil { - result.Error = err - return result + return resultWithError(result, err) } result.QueryResults = parseResponse(value, query) return result } -func formatLegend(metric pmodel.Metric, query PrometheusQuery) string { +func formatLegend(metric pmodel.Metric, query *PrometheusQuery) string { r, _ := regexp.Compile(`\{\{\s*(.+?)\s*\}\}`) result := r.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte { @@ -90,17 +92,32 @@ func formatLegend(metric pmodel.Metric, query PrometheusQuery) string { return string(result) } -func parseQuery(queries tsdb.QuerySlice) PrometheusQuery { +func parseQuery(queries tsdb.QuerySlice) (*PrometheusQuery, error) { queryModel := queries[0] - return PrometheusQuery{ - Expr: queryModel.Model.Get("expr").MustString(), - Step: time.Second * time.Duration(queryModel.Model.Get("step").MustInt64(1)), - LegendFormat: queryModel.Model.Get("legendFormat").MustString(), + expr, err := queryModel.Model.Get("expr").String() + if err != nil { + return nil, err } + + step, err := queryModel.Model.Get("step").Int64() + if err != nil { + return nil, err + } + + format, err := queryModel.Model.Get("legendFormat").String() + if err != nil { + return nil, err + } + + return &PrometheusQuery{ + Expr: expr, + Step: time.Second * time.Duration(step), + LegendFormat: format, + }, nil } -func parseResponse(value pmodel.Value, query PrometheusQuery) map[string]*tsdb.QueryResult { +func parseResponse(value pmodel.Value, query *PrometheusQuery) map[string]*tsdb.QueryResult { queryResults := make(map[string]*tsdb.QueryResult) queryRes := &tsdb.QueryResult{} @@ -123,3 +140,8 @@ func parseResponse(value pmodel.Value, query PrometheusQuery) map[string]*tsdb.Q queryResults["A"] = queryRes return queryResults } + +func resultWithError(result *tsdb.BatchResult, err error) *tsdb.BatchResult { + result.Error = err + return result +} diff --git a/pkg/tsdb/prometheus/prometheus_test.go b/pkg/tsdb/prometheus/prometheus_test.go index 6edb6260a09..f7489ae9afc 100644 --- a/pkg/tsdb/prometheus/prometheus_test.go +++ b/pkg/tsdb/prometheus/prometheus_test.go @@ -16,7 +16,7 @@ func TestPrometheus(t *testing.T) { p.LabelName("device"): p.LabelValue("mobile"), } - query := PrometheusQuery{ + query := &PrometheusQuery{ LegendFormat: "legend {{app}} {{device}} {{broken}}", } From ee0f1a0f36826a3f1458feac9d630c3cdbcd357e Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Sep 2016 13:17:36 +0200 Subject: [PATCH 04/12] feat(prometheus): handle more errors --- pkg/tsdb/prometheus/prometheus.go | 28 ++++++++++++++++++---------- pkg/tsdb/prometheus/types.go | 2 ++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index b32a99220b2..2ac21794ef9 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -52,18 +52,14 @@ func (e *PrometheusExecutor) Execute(queries tsdb.QuerySlice, queryContext *tsdb return resultWithError(result, err) } - from, _ := queryContext.TimeRange.FromTime() - to, _ := queryContext.TimeRange.ToTime() - - query, err := parseQuery(queries) - + query, err := parseQuery(queries, queryContext) if err != nil { return resultWithError(result, err) } timeRange := prometheus.Range{ - Start: from, - End: to, + Start: query.Start, + End: query.End, Step: query.Step, } @@ -78,9 +74,9 @@ func (e *PrometheusExecutor) Execute(queries tsdb.QuerySlice, queryContext *tsdb } func formatLegend(metric pmodel.Metric, query *PrometheusQuery) string { - r, _ := regexp.Compile(`\{\{\s*(.+?)\s*\}\}`) + reg, _ := regexp.Compile(`\{\{\s*(.+?)\s*\}\}`) - result := r.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte { + result := reg.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte { ind := strings.Replace(strings.Replace(string(in), "{{", "", 1), "}}", "", 1) if val, exists := metric[pmodel.LabelName(ind)]; exists { return []byte(val) @@ -92,7 +88,7 @@ func formatLegend(metric pmodel.Metric, query *PrometheusQuery) string { return string(result) } -func parseQuery(queries tsdb.QuerySlice) (*PrometheusQuery, error) { +func parseQuery(queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) (*PrometheusQuery, error) { queryModel := queries[0] expr, err := queryModel.Model.Get("expr").String() @@ -110,10 +106,22 @@ func parseQuery(queries tsdb.QuerySlice) (*PrometheusQuery, error) { return nil, err } + start, err := queryContext.TimeRange.FromTime() + if err != nil { + return nil, err + } + + end, err := queryContext.TimeRange.ToTime() + if err != nil { + return nil, err + } + return &PrometheusQuery{ Expr: expr, Step: time.Second * time.Duration(step), LegendFormat: format, + Start: start, + End: end, }, nil } diff --git a/pkg/tsdb/prometheus/types.go b/pkg/tsdb/prometheus/types.go index 67d9ae40670..8ed665d0123 100644 --- a/pkg/tsdb/prometheus/types.go +++ b/pkg/tsdb/prometheus/types.go @@ -6,4 +6,6 @@ type PrometheusQuery struct { Expr string Step time.Duration LegendFormat string + Start time.Time + End time.Time } From 9534a04d3de7a8577ea92e16cde2002f70c8b221 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Sep 2016 13:30:41 +0200 Subject: [PATCH 05/12] fix(prometheus): only accept matrix result --- pkg/tsdb/prometheus/prometheus.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index 2ac21794ef9..781eb91dd8f 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -2,6 +2,7 @@ package prometheus import ( "context" + "fmt" "net/http" "regexp" "strings" @@ -69,7 +70,11 @@ func (e *PrometheusExecutor) Execute(queries tsdb.QuerySlice, queryContext *tsdb return resultWithError(result, err) } - result.QueryResults = parseResponse(value, query) + queryResult, err := parseResponse(value, query) + if err != nil { + return resultWithError(result, err) + } + result.QueryResults = queryResult return result } @@ -125,18 +130,21 @@ func parseQuery(queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) (*Prom }, nil } -func parseResponse(value pmodel.Value, query *PrometheusQuery) map[string]*tsdb.QueryResult { +func parseResponse(value pmodel.Value, query *PrometheusQuery) (map[string]*tsdb.QueryResult, error) { queryResults := make(map[string]*tsdb.QueryResult) queryRes := &tsdb.QueryResult{} - data := value.(pmodel.Matrix) + data, ok := value.(pmodel.Matrix) + if !ok { + return queryResults, fmt.Errorf("Unsupported result format: %s", value.Type().String()) + } for _, v := range data { var points [][2]*float64 for _, k := range v.Values { - dummie := float64(k.Timestamp) - d2 := float64(k.Value) - points = append(points, [2]*float64{&d2, &dummie}) + timestamp := float64(k.Timestamp) + val := float64(k.Value) + points = append(points, [2]*float64{&val, ×tamp}) } queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ @@ -146,7 +154,7 @@ func parseResponse(value pmodel.Value, query *PrometheusQuery) map[string]*tsdb. } queryResults["A"] = queryRes - return queryResults + return queryResults, nil } func resultWithError(result *tsdb.BatchResult, err error) *tsdb.BatchResult { From af551b8825dfa811d2fe991d46d698effd36416e Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Sep 2016 13:41:25 +0200 Subject: [PATCH 06/12] refactor(tsdb): remove toUnix from timerange --- pkg/tsdb/time_range.go | 28 ---------------------------- pkg/tsdb/time_range_test.go | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 40 deletions(-) diff --git a/pkg/tsdb/time_range.go b/pkg/tsdb/time_range.go index e7e54cef5f9..8e1a1c66e3d 100644 --- a/pkg/tsdb/time_range.go +++ b/pkg/tsdb/time_range.go @@ -20,17 +20,6 @@ type TimeRange struct { Now time.Time } -func (tr TimeRange) FromUnix() (int64, error) { - fromRaw := strings.Replace(tr.From, "now-", "", 1) - - diff, err := time.ParseDuration("-" + fromRaw) - if err != nil { - return 0, err - } - - return tr.Now.Add(diff).Unix(), nil -} - func (tr TimeRange) FromTime() (time.Time, error) { fromRaw := strings.Replace(tr.From, "now-", "", 1) @@ -42,23 +31,6 @@ func (tr TimeRange) FromTime() (time.Time, error) { return tr.Now.Add(diff), nil } -func (tr TimeRange) ToUnix() (int64, error) { - if tr.To == "now" { - return tr.Now.Unix(), nil - } else if strings.HasPrefix(tr.To, "now-") { - withoutNow := strings.Replace(tr.To, "now-", "", 1) - - diff, err := time.ParseDuration("-" + withoutNow) - if err != nil { - return 0, nil - } - - return tr.Now.Add(diff).Unix(), nil - } - - return 0, fmt.Errorf("cannot parse to value %s", tr.To) -} - func (tr TimeRange) ToTime() (time.Time, error) { if tr.To == "now" { return tr.Now, nil diff --git a/pkg/tsdb/time_range_test.go b/pkg/tsdb/time_range_test.go index d64eb8cc86e..56ea9d24490 100644 --- a/pkg/tsdb/time_range_test.go +++ b/pkg/tsdb/time_range_test.go @@ -23,15 +23,15 @@ func TestTimeRange(t *testing.T) { fiveMinAgo, _ := time.ParseDuration("-5m") expected := now.Add(fiveMinAgo) - res, err := tr.FromUnix() + res, err := tr.FromTime() So(err, ShouldBeNil) - So(res, ShouldAlmostEqual, expected.Unix()) + So(res.Unix(), ShouldEqual, expected.Unix()) }) Convey("now ", func() { - res, err := tr.ToUnix() + res, err := tr.ToTime() So(err, ShouldBeNil) - So(res, ShouldAlmostEqual, now.Unix()) + So(res.Unix(), ShouldEqual, now.Unix()) }) }) @@ -43,20 +43,20 @@ func TestTimeRange(t *testing.T) { } Convey("5h ago ", func() { - fiveMinAgo, _ := time.ParseDuration("-5h") - expected := now.Add(fiveMinAgo) + fiveHourAgo, _ := time.ParseDuration("-5h") + expected := now.Add(fiveHourAgo) - res, err := tr.FromUnix() + res, err := tr.FromTime() So(err, ShouldBeNil) - So(res, ShouldAlmostEqual, expected.Unix()) + So(res.Unix(), ShouldEqual, expected.Unix()) }) Convey("now-10m ", func() { fiveMinAgo, _ := time.ParseDuration("-10m") expected := now.Add(fiveMinAgo) - res, err := tr.ToUnix() + res, err := tr.ToTime() So(err, ShouldBeNil) - So(res, ShouldAlmostEqual, expected.Unix()) + So(res.Unix(), ShouldEqual, expected.Unix()) }) }) @@ -68,10 +68,10 @@ func TestTimeRange(t *testing.T) { Now: now, } - _, err = tr.FromUnix() + _, err = tr.FromTime() So(err, ShouldNotBeNil) - _, err = tr.ToUnix() + _, err = tr.ToTime() So(err, ShouldNotBeNil) }) }) From c084145cba96499de04e67f91abbd4b553c9f9af Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Sep 2016 13:46:10 +0200 Subject: [PATCH 07/12] refactor(prometheus): add timerange to alert context --- pkg/services/alerting/conditions/query.go | 8 ++++---- pkg/services/alerting/eval_context.go | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index 15db31838b0..aea4fdebd7b 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -34,8 +34,8 @@ type AlertQuery struct { } func (c *QueryCondition) Eval(context *alerting.EvalContext) { - timerange := tsdb.NewTimerange(c.Query.From, c.Query.To) - seriesList, err := c.executeQuery(context, timerange) + context.TimeRange = tsdb.NewTimerange(c.Query.From, c.Query.To) + seriesList, err := c.executeQuery(context) if err != nil { context.Error = err return @@ -69,7 +69,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) { context.Firing = len(context.EvalMatches) > 0 } -func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timerange tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) { +func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeSeriesSlice, error) { getDsInfo := &m.GetDataSourceByIdQuery{ Id: c.Query.DatasourceId, OrgId: context.Rule.OrgId, @@ -79,7 +79,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timerange t return nil, fmt.Errorf("Could not find datasource") } - req := c.getRequestForAlertRule(getDsInfo.Result, timerange) + req := c.getRequestForAlertRule(getDsInfo.Result, context.TimeRange) result := make(tsdb.TimeSeriesSlice, 0) resp, err := c.HandleRequest(req) diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 13067c25f08..aa1442a0ca3 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tsdb" ) type EvalContext struct { @@ -28,6 +29,7 @@ type EvalContext struct { ImageOnDiskPath string NoDataFound bool RetryCount int + TimeRange tsdb.TimeRange } type StateDescription struct { From ae7345b04db505c243d0b1f5310de1f7cd712fb4 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Sep 2016 13:46:26 +0200 Subject: [PATCH 08/12] style(prometheus): remove commented test --- pkg/tsdb/graphite/graphite_test.go | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/pkg/tsdb/graphite/graphite_test.go b/pkg/tsdb/graphite/graphite_test.go index 59007b43ed5..7a3bba9035b 100644 --- a/pkg/tsdb/graphite/graphite_test.go +++ b/pkg/tsdb/graphite/graphite_test.go @@ -1,23 +1 @@ package graphite - -// func TestGraphite(t *testing.T) { -// -// Convey("When executing graphite query", t, func() { -// executor := NewGraphiteExecutor(&tsdb.DataSourceInfo{ -// Url: "http://localhost:8080", -// }) -// -// queries := tsdb.QuerySlice{ -// &tsdb.Query{Query: "{\"target\": \"apps.backend.*.counters.requests.count\"}"}, -// } -// -// context := tsdb.NewQueryContext(queries, tsdb.TimeRange{}) -// result := executor.Execute(queries, context) -// So(result.Error, ShouldBeNil) -// -// Convey("Should return series", func() { -// So(result.QueryResults, ShouldNotBeEmpty) -// }) -// }) -// -// } From b856d7e193bd627f7f7c62dbb4e2d08d155ec196 Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 21 Sep 2016 13:55:42 +0200 Subject: [PATCH 09/12] fix(prometheus): remove timerange from context --- pkg/services/alerting/conditions/query.go | 8 ++++---- pkg/services/alerting/eval_context.go | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index aea4fdebd7b..15db31838b0 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -34,8 +34,8 @@ type AlertQuery struct { } func (c *QueryCondition) Eval(context *alerting.EvalContext) { - context.TimeRange = tsdb.NewTimerange(c.Query.From, c.Query.To) - seriesList, err := c.executeQuery(context) + timerange := tsdb.NewTimerange(c.Query.From, c.Query.To) + seriesList, err := c.executeQuery(context, timerange) if err != nil { context.Error = err return @@ -69,7 +69,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) { context.Firing = len(context.EvalMatches) > 0 } -func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeSeriesSlice, error) { +func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timerange tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) { getDsInfo := &m.GetDataSourceByIdQuery{ Id: c.Query.DatasourceId, OrgId: context.Rule.OrgId, @@ -79,7 +79,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeS return nil, fmt.Errorf("Could not find datasource") } - req := c.getRequestForAlertRule(getDsInfo.Result, context.TimeRange) + req := c.getRequestForAlertRule(getDsInfo.Result, timerange) result := make(tsdb.TimeSeriesSlice, 0) resp, err := c.HandleRequest(req) diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index aa1442a0ca3..13067c25f08 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/tsdb" ) type EvalContext struct { @@ -29,7 +28,6 @@ type EvalContext struct { ImageOnDiskPath string NoDataFound bool RetryCount int - TimeRange tsdb.TimeRange } type StateDescription struct { From 76b8cff44533dc49a201cfacd60b913cb0bc7d9e Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 22 Sep 2016 16:16:58 +0200 Subject: [PATCH 10/12] fix(gnet): remove trailing , --- pkg/plugins/update_checker.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/plugins/update_checker.go b/pkg/plugins/update_checker.go index ed43398357e..980c813888f 100644 --- a/pkg/plugins/update_checker.go +++ b/pkg/plugins/update_checker.go @@ -39,17 +39,16 @@ func StartPluginUpdateChecker() { } func getAllExternalPluginSlugs() string { - str := "" - + var result []string for _, plug := range Plugins { if plug.IsCorePlugin { continue } - str += plug.Id + "," + result = append(result, plug.Id) } - return str + return strings.Join(result, ",") } func checkForUpdates() { From a49c21df3a93a368e2859aac9dbe92add77c02d6 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 22 Sep 2016 19:24:18 +0200 Subject: [PATCH 11/12] fix(png-renderer): increase timeouts --- pkg/services/alerting/notifier.go | 4 ++-- pkg/services/notifications/webhook.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index bb48f71cdcd..61cc0bc55d3 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -72,8 +72,8 @@ func (n *RootNotifier) uploadImage(context *EvalContext) error { Url: imageUrl, Width: "800", Height: "400", - SessionId: "123", - Timeout: "10", + SessionId: "cef0256d482b4293", + Timeout: "30", } if imagePath, err := renderer.RenderToPng(renderOpts); err != nil { diff --git a/pkg/services/notifications/webhook.go b/pkg/services/notifications/webhook.go index 31f00baebd3..67ffa43900a 100644 --- a/pkg/services/notifications/webhook.go +++ b/pkg/services/notifications/webhook.go @@ -44,7 +44,7 @@ func sendWebRequest(webhook *Webhook) error { webhookLog.Debug("Sending webhook", "url", webhook.Url) client := http.Client{ - Timeout: time.Duration(3 * time.Second), + Timeout: time.Duration(10 * time.Second), } request, err := http.NewRequest("POST", webhook.Url, bytes.NewReader([]byte(webhook.Body))) From 2c3dd84ebb82f59090b6cdc1b54c56cfea8f1da1 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Thu, 22 Sep 2016 13:46:11 -0400 Subject: [PATCH 12/12] allow non-admin users to view plugin readme --- pkg/api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 71331acda9f..b7f767975f1 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -202,9 +202,9 @@ func Register(r *macaron.Macaron) { r.Get("/plugins", wrap(GetPluginList)) r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById)) + r.Get("/plugins/:pluginId/readme", wrap(GetPluginReadme)) r.Group("/plugins", func() { - r.Get("/:pluginId/readme", wrap(GetPluginReadme)) r.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards)) r.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting)) }, reqOrgAdmin)