mirror of https://github.com/grafana/grafana
commit
3bbc940b88
@ -0,0 +1,55 @@ |
||||
package middleware |
||||
|
||||
import ( |
||||
"sync" |
||||
|
||||
m "github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/util" |
||||
) |
||||
|
||||
var renderKeysLock sync.Mutex |
||||
var renderKeys map[string]*m.SignedInUser = make(map[string]*m.SignedInUser) |
||||
|
||||
func initContextWithRenderAuth(ctx *Context) bool { |
||||
key := ctx.GetCookie("renderKey") |
||||
if key == "" { |
||||
return false |
||||
} |
||||
|
||||
renderKeysLock.Lock() |
||||
defer renderKeysLock.Unlock() |
||||
|
||||
if renderUser, exists := renderKeys[key]; !exists { |
||||
ctx.JsonApiErr(401, "Invalid Render Key", nil) |
||||
return true |
||||
} else { |
||||
|
||||
ctx.IsSignedIn = true |
||||
ctx.SignedInUser = renderUser |
||||
ctx.IsRenderCall = true |
||||
return true |
||||
} |
||||
} |
||||
|
||||
type renderContextFunc func(key string) (string, error) |
||||
|
||||
func AddRenderAuthKey(orgId int64) string { |
||||
renderKeysLock.Lock() |
||||
|
||||
key := util.GetRandomString(32) |
||||
|
||||
renderKeys[key] = &m.SignedInUser{ |
||||
OrgId: orgId, |
||||
OrgRole: m.ROLE_VIEWER, |
||||
} |
||||
|
||||
renderKeysLock.Unlock() |
||||
|
||||
return key |
||||
} |
||||
|
||||
func RemoveRenderAuthKey(key string) { |
||||
renderKeysLock.Lock() |
||||
delete(renderKeys, key) |
||||
renderKeysLock.Unlock() |
||||
} |
@ -0,0 +1,163 @@ |
||||
package prometheus |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/http" |
||||
"regexp" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/log" |
||||
"github.com/grafana/grafana/pkg/tsdb" |
||||
"github.com/prometheus/client_golang/api/prometheus" |
||||
"golang.org/x/net/context" |
||||
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 { |
||||
return resultWithError(result, err) |
||||
} |
||||
|
||||
query, err := parseQuery(queries, queryContext) |
||||
if err != nil { |
||||
return resultWithError(result, err) |
||||
} |
||||
|
||||
timeRange := prometheus.Range{ |
||||
Start: query.Start, |
||||
End: query.End, |
||||
Step: query.Step, |
||||
} |
||||
|
||||
value, err := client.QueryRange(context.Background(), query.Expr, timeRange) |
||||
|
||||
if err != nil { |
||||
return resultWithError(result, err) |
||||
} |
||||
|
||||
queryResult, err := parseResponse(value, query) |
||||
if err != nil { |
||||
return resultWithError(result, err) |
||||
} |
||||
result.QueryResults = queryResult |
||||
return result |
||||
} |
||||
|
||||
func formatLegend(metric pmodel.Metric, query *PrometheusQuery) string { |
||||
reg, _ := regexp.Compile(`\{\{\s*(.+?)\s*\}\}`) |
||||
|
||||
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) |
||||
} |
||||
|
||||
return in |
||||
}) |
||||
|
||||
return string(result) |
||||
} |
||||
|
||||
func parseQuery(queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) (*PrometheusQuery, error) { |
||||
queryModel := queries[0] |
||||
|
||||
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 |
||||
} |
||||
|
||||
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 |
||||
} |
||||
|
||||
func parseResponse(value pmodel.Value, query *PrometheusQuery) (map[string]*tsdb.QueryResult, error) { |
||||
queryResults := make(map[string]*tsdb.QueryResult) |
||||
queryRes := &tsdb.QueryResult{} |
||||
|
||||
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 { |
||||
timestamp := float64(k.Timestamp) |
||||
val := float64(k.Value) |
||||
points = append(points, [2]*float64{&val, ×tamp}) |
||||
} |
||||
|
||||
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ |
||||
Name: formatLegend(v.Metric, query), |
||||
Points: points, |
||||
}) |
||||
} |
||||
|
||||
queryResults["A"] = queryRes |
||||
return queryResults, nil |
||||
} |
||||
|
||||
func resultWithError(result *tsdb.BatchResult, err error) *tsdb.BatchResult { |
||||
result.Error = err |
||||
return result |
||||
} |
@ -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}}") |
||||
}) |
||||
}) |
||||
} |
@ -0,0 +1,11 @@ |
||||
package prometheus |
||||
|
||||
import "time" |
||||
|
||||
type PrometheusQuery struct { |
||||
Expr string |
||||
Step time.Duration |
||||
LegendFormat string |
||||
Start time.Time |
||||
End time.Time |
||||
} |
@ -1,12 +0,0 @@ |
||||
package tsdb |
||||
|
||||
type Query struct { |
||||
RefId string |
||||
Query string |
||||
Depends []string |
||||
DataSource *DataSourceInfo |
||||
Results []*TimeSeries |
||||
Exclude bool |
||||
} |
||||
|
||||
type QuerySlice []*Query |
@ -0,0 +1,49 @@ |
||||
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) 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) 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) |
||||
} |
@ -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.FromTime() |
||||
So(err, ShouldBeNil) |
||||
So(res.Unix(), ShouldEqual, expected.Unix()) |
||||
}) |
||||
|
||||
Convey("now ", func() { |
||||
res, err := tr.ToTime() |
||||
So(err, ShouldBeNil) |
||||
So(res.Unix(), ShouldEqual, now.Unix()) |
||||
}) |
||||
}) |
||||
|
||||
Convey("Can parse 5h, now-10m", func() { |
||||
tr := TimeRange{ |
||||
From: "5h", |
||||
To: "now-10m", |
||||
Now: now, |
||||
} |
||||
|
||||
Convey("5h ago ", func() { |
||||
fiveHourAgo, _ := time.ParseDuration("-5h") |
||||
expected := now.Add(fiveHourAgo) |
||||
|
||||
res, err := tr.FromTime() |
||||
So(err, ShouldBeNil) |
||||
So(res.Unix(), ShouldEqual, expected.Unix()) |
||||
}) |
||||
|
||||
Convey("now-10m ", func() { |
||||
fiveMinAgo, _ := time.ParseDuration("-10m") |
||||
expected := now.Add(fiveMinAgo) |
||||
res, err := tr.ToTime() |
||||
So(err, ShouldBeNil) |
||||
So(res.Unix(), ShouldEqual, expected.Unix()) |
||||
}) |
||||
}) |
||||
|
||||
Convey("Cannot parse asdf", func() { |
||||
var err error |
||||
tr := TimeRange{ |
||||
From: "asdf", |
||||
To: "asdf", |
||||
Now: now, |
||||
} |
||||
|
||||
_, err = tr.FromTime() |
||||
So(err, ShouldNotBeNil) |
||||
|
||||
_, err = tr.ToTime() |
||||
So(err, ShouldNotBeNil) |
||||
}) |
||||
}) |
||||
} |
@ -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. |
@ -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 |
@ -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 |
||||
} |
@ -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. |
@ -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/). |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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] |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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 |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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 |
||||
} |
@ -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 "<ValNone>": |
||||
*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 "<ValNone>" |
||||
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") |
||||
} |
@ -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. |
@ -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. |
@ -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())) |
||||
} |
@ -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 |
||||
} |
Loading…
Reference in new issue