mirror of https://github.com/grafana/grafana
parent
1cb45c3416
commit
43d8bd5a25
@ -0,0 +1,96 @@ |
|||||||
|
package prometheus |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"net/http" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/log" |
||||||
|
"github.com/grafana/grafana/pkg/tsdb" |
||||||
|
"github.com/prometheus/client_golang/api/prometheus" |
||||||
|
pmodel "github.com/prometheus/common/model" |
||||||
|
) |
||||||
|
|
||||||
|
type PrometheusExecutor struct { |
||||||
|
*tsdb.DataSourceInfo |
||||||
|
} |
||||||
|
|
||||||
|
func NewPrometheusExecutor(dsInfo *tsdb.DataSourceInfo) tsdb.Executor { |
||||||
|
return &PrometheusExecutor{dsInfo} |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
plog log.Logger |
||||||
|
HttpClient http.Client |
||||||
|
) |
||||||
|
|
||||||
|
func init() { |
||||||
|
plog = log.New("tsdb.prometheus") |
||||||
|
tsdb.RegisterExecutor("prometheus", NewPrometheusExecutor) |
||||||
|
} |
||||||
|
|
||||||
|
func (e *PrometheusExecutor) getClient() (prometheus.QueryAPI, error) { |
||||||
|
cfg := prometheus.Config{ |
||||||
|
Address: e.DataSourceInfo.Url, |
||||||
|
} |
||||||
|
|
||||||
|
client, err := prometheus.New(cfg) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return prometheus.NewQueryAPI(client), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (e *PrometheusExecutor) Execute(queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) *tsdb.BatchResult { |
||||||
|
result := &tsdb.BatchResult{} |
||||||
|
|
||||||
|
client, err := e.getClient() |
||||||
|
if err != nil { |
||||||
|
result.Error = err |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
from, _ := queryContext.TimeRange.FromTime() |
||||||
|
to, _ := queryContext.TimeRange.ToTime() |
||||||
|
timeRange := prometheus.Range{ |
||||||
|
Start: from, |
||||||
|
End: to, |
||||||
|
Step: time.Second, |
||||||
|
} |
||||||
|
|
||||||
|
ctx := context.Background() |
||||||
|
value, err := client.QueryRange(ctx, "counters_logins", timeRange) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
result.Error = err |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
result.QueryResults = parseResponse(value) |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
func parseResponse(value pmodel.Value) map[string]*tsdb.QueryResult { |
||||||
|
queryResults := make(map[string]*tsdb.QueryResult) |
||||||
|
queryRes := &tsdb.QueryResult{} |
||||||
|
|
||||||
|
data := value.(pmodel.Matrix) |
||||||
|
|
||||||
|
for _, v := range data { |
||||||
|
var points [][2]*float64 |
||||||
|
for _, k := range v.Values { |
||||||
|
dummie := float64(k.Timestamp) |
||||||
|
d2 := float64(k.Value) |
||||||
|
points = append(points, [2]*float64{&d2, &dummie}) |
||||||
|
} |
||||||
|
|
||||||
|
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ |
||||||
|
Name: v.Metric.String(), |
||||||
|
Points: points, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
queryResults["A"] = queryRes |
||||||
|
return queryResults |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
package prometheus |
@ -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,77 @@ |
|||||||
|
package tsdb |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
func NewTimerange(from, to string) TimeRange { |
||||||
|
return TimeRange{ |
||||||
|
From: from, |
||||||
|
To: to, |
||||||
|
Now: time.Now(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type TimeRange struct { |
||||||
|
From string |
||||||
|
To string |
||||||
|
Now time.Time |
||||||
|
} |
||||||
|
|
||||||
|
func (tr TimeRange) FromUnix() (int64, error) { |
||||||
|
fromRaw := strings.Replace(tr.From, "now-", "", 1) |
||||||
|
|
||||||
|
diff, err := time.ParseDuration("-" + fromRaw) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
|
||||||
|
return tr.Now.Add(diff).Unix(), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (tr TimeRange) FromTime() (time.Time, error) { |
||||||
|
fromRaw := strings.Replace(tr.From, "now-", "", 1) |
||||||
|
|
||||||
|
diff, err := time.ParseDuration("-" + fromRaw) |
||||||
|
if err != nil { |
||||||
|
return time.Time{}, err |
||||||
|
} |
||||||
|
|
||||||
|
return tr.Now.Add(diff), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (tr TimeRange) ToUnix() (int64, error) { |
||||||
|
if tr.To == "now" { |
||||||
|
return tr.Now.Unix(), nil |
||||||
|
} else if strings.HasPrefix(tr.To, "now-") { |
||||||
|
withoutNow := strings.Replace(tr.To, "now-", "", 1) |
||||||
|
|
||||||
|
diff, err := time.ParseDuration("-" + withoutNow) |
||||||
|
if err != nil { |
||||||
|
return 0, nil |
||||||
|
} |
||||||
|
|
||||||
|
return tr.Now.Add(diff).Unix(), nil |
||||||
|
} |
||||||
|
|
||||||
|
return 0, fmt.Errorf("cannot parse to value %s", tr.To) |
||||||
|
} |
||||||
|
|
||||||
|
func (tr TimeRange) ToTime() (time.Time, error) { |
||||||
|
if tr.To == "now" { |
||||||
|
return tr.Now, nil |
||||||
|
} else if strings.HasPrefix(tr.To, "now-") { |
||||||
|
withoutNow := strings.Replace(tr.To, "now-", "", 1) |
||||||
|
|
||||||
|
diff, err := time.ParseDuration("-" + withoutNow) |
||||||
|
if err != nil { |
||||||
|
return time.Time{}, nil |
||||||
|
} |
||||||
|
|
||||||
|
return tr.Now.Add(diff), nil |
||||||
|
} |
||||||
|
|
||||||
|
return time.Time{}, fmt.Errorf("cannot parse to value %s", tr.To) |
||||||
|
} |
@ -0,0 +1,78 @@ |
|||||||
|
package tsdb |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey" |
||||||
|
) |
||||||
|
|
||||||
|
func TestTimeRange(t *testing.T) { |
||||||
|
Convey("Time range", t, func() { |
||||||
|
|
||||||
|
now := time.Now() |
||||||
|
|
||||||
|
Convey("Can parse 5m, now", func() { |
||||||
|
tr := TimeRange{ |
||||||
|
From: "5m", |
||||||
|
To: "now", |
||||||
|
Now: now, |
||||||
|
} |
||||||
|
|
||||||
|
Convey("5m ago ", func() { |
||||||
|
fiveMinAgo, _ := time.ParseDuration("-5m") |
||||||
|
expected := now.Add(fiveMinAgo) |
||||||
|
|
||||||
|
res, err := tr.FromUnix() |
||||||
|
So(err, ShouldBeNil) |
||||||
|
So(res, ShouldAlmostEqual, expected.Unix()) |
||||||
|
}) |
||||||
|
|
||||||
|
Convey("now ", func() { |
||||||
|
res, err := tr.ToUnix() |
||||||
|
So(err, ShouldBeNil) |
||||||
|
So(res, ShouldAlmostEqual, now.Unix()) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
Convey("Can parse 5h, now-10m", func() { |
||||||
|
tr := TimeRange{ |
||||||
|
From: "5h", |
||||||
|
To: "now-10m", |
||||||
|
Now: now, |
||||||
|
} |
||||||
|
|
||||||
|
Convey("5h ago ", func() { |
||||||
|
fiveMinAgo, _ := time.ParseDuration("-5h") |
||||||
|
expected := now.Add(fiveMinAgo) |
||||||
|
|
||||||
|
res, err := tr.FromUnix() |
||||||
|
So(err, ShouldBeNil) |
||||||
|
So(res, ShouldAlmostEqual, expected.Unix()) |
||||||
|
}) |
||||||
|
|
||||||
|
Convey("now-10m ", func() { |
||||||
|
fiveMinAgo, _ := time.ParseDuration("-10m") |
||||||
|
expected := now.Add(fiveMinAgo) |
||||||
|
res, err := tr.ToUnix() |
||||||
|
So(err, ShouldBeNil) |
||||||
|
So(res, ShouldAlmostEqual, expected.Unix()) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
Convey("Cannot parse asdf", func() { |
||||||
|
var err error |
||||||
|
tr := TimeRange{ |
||||||
|
From: "asdf", |
||||||
|
To: "asdf", |
||||||
|
Now: now, |
||||||
|
} |
||||||
|
|
||||||
|
_, err = tr.FromUnix() |
||||||
|
So(err, ShouldNotBeNil) |
||||||
|
|
||||||
|
_, err = tr.ToUnix() |
||||||
|
So(err, ShouldNotBeNil) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -1,6 +1,25 @@ |
|||||||
{ |
{ |
||||||
"comment": "", |
"comment": "", |
||||||
"ignore": "test", |
"ignore": "test", |
||||||
"package": [], |
"package": [ |
||||||
|
{ |
||||||
|
"checksumSHA1": "SMUvX2B8eoFd9wnPofwBKlN6btE=", |
||||||
|
"path": "github.com/prometheus/client_golang/api/prometheus", |
||||||
|
"revision": "5636dc67ae776adf5590da7349e70fbb9559972d", |
||||||
|
"revisionTime": "2016-09-16T18:03:40Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"checksumSHA1": "Jx0GXl5hGnO25s3ryyvtdWHdCpw=", |
||||||
|
"path": "github.com/prometheus/common/model", |
||||||
|
"revision": "9a94032291f2192936512bab367bc45e77990d6a", |
||||||
|
"revisionTime": "2016-09-17T18:44:01Z" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"checksumSHA1": "WHc3uByvGaMcnSoI21fhzYgbOgg=", |
||||||
|
"path": "golang.org/x/net/context/ctxhttp", |
||||||
|
"revision": "71a035914f99bb58fe82eac0f1289f10963d876c", |
||||||
|
"revisionTime": "2016-09-12T21:59:12Z" |
||||||
|
} |
||||||
|
], |
||||||
"rootPath": "github.com/grafana/grafana" |
"rootPath": "github.com/grafana/grafana" |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue