Merge v1.2.1-master

Signed-off-by: Emile Vauge <emile@vauge.com>
This commit is contained in:
Emile Vauge 2017-04-11 17:10:46 +02:00
parent a590155b0b
commit aeb17182b4
No known key found for this signature in database
GPG key ID: D808B4C167352E59
396 changed files with 27271 additions and 9969 deletions

View file

@ -35,8 +35,9 @@ type Alert struct {
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"`
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.
@ -60,10 +61,16 @@ func (a *Alert) String() string {
// 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(time.Now())
return !a.EndsAt.After(ts)
}
// Status returns the status of the alert.
@ -74,6 +81,26 @@ func (a *Alert) Status() AlertStatus {
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

42
vendor/github.com/prometheus/common/model/fnv.go generated vendored Normal file
View file

@ -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
}

View file

@ -17,8 +17,8 @@ import (
"encoding/json"
"fmt"
"regexp"
"sort"
"strings"
"unicode/utf8"
)
const (
@ -80,20 +80,37 @@ const (
QuantileLabel = "quantile"
)
// LabelNameRE is a regular expression matching valid label names.
// LabelNameRE is a regular expression matching valid label names. Note that the
// IsValid method of LabelName performs the same check but faster than a match
// with this regular expression.
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. This
// method, however, does not use LabelNameRE for the check but a much faster
// hardcoded implementation.
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) {
if !LabelName(s).IsValid() {
return fmt.Errorf("%q is not a valid label name", s)
}
*ln = LabelName(s)
@ -106,7 +123,7 @@ func (ln *LabelName) UnmarshalJSON(b []byte) error {
if err := json.Unmarshal(b, &s); err != nil {
return err
}
if !LabelNameRE.MatchString(s) {
if !LabelName(s).IsValid() {
return fmt.Errorf("%q is not a valid label name", s)
}
*ln = LabelName(s)
@ -139,6 +156,11 @@ func (l LabelNames) String() string {
// 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
@ -147,7 +169,7 @@ func (l LabelValues) Len() int {
}
func (l LabelValues) Less(i, j int) bool {
return sort.StringsAreSorted([]string{string(l[i]), string(l[j])})
return string(l[i]) < string(l[j])
}
func (l LabelValues) Swap(i, j int) {

View file

@ -27,6 +27,21 @@ import (
// 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
@ -90,6 +105,7 @@ func (ls LabelSet) Before(o LabelSet) bool {
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 {
@ -144,7 +160,7 @@ func (l *LabelSet) UnmarshalJSON(b []byte) error {
// 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)) {
if !ln.IsValid() {
return fmt.Errorf("%q is not a valid label name", ln)
}
}

View file

@ -15,11 +15,18 @@ package model
import (
"fmt"
"regexp"
"sort"
"strings"
)
var separator = []byte{0}
var (
separator = []byte{0}
// MetricNameRE is a regular expression matching valid metric
// names. Note that the IsValidMetricName function performs the same
// check but faster than a match with this regular expression.
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.
@ -37,7 +44,7 @@ func (m Metric) Before(o Metric) bool {
// Clone returns a copy of the Metric.
func (m Metric) Clone() Metric {
clone := Metric{}
clone := make(Metric, len(m))
for k, v := range m {
clone[k] = v
}
@ -79,3 +86,18 @@ func (m Metric) Fingerprint() Fingerprint {
func (m Metric) FastFingerprint() Fingerprint {
return LabelSet(m).FastFingerprint()
}
// IsValidMetricName returns true iff name matches the pattern of MetricNameRE.
// This function, however, does not use MetricNameRE for the check but a much
// faster hardcoded implementation.
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
}

View file

@ -12,5 +12,5 @@
// limitations under the License.
// Package model contains common data structures that are shared across
// Prometheus componenets and libraries.
// Prometheus components and libraries.
package model

View file

@ -14,11 +14,7 @@
package model
import (
"bytes"
"hash"
"hash/fnv"
"sort"
"sync"
)
// SeparatorByte is a byte that cannot occur in valid UTF-8 sequences and is
@ -28,30 +24,9 @@ const SeparatorByte byte = 255
var (
// cache the signature of an empty label set.
emptyLabelSignature = fnv.New64a().Sum64()
hashAndBufPool sync.Pool
emptyLabelSignature = hashNew()
)
type hashAndBuf struct {
h hash.Hash64
b bytes.Buffer
}
func getHashAndBuf() *hashAndBuf {
hb := hashAndBufPool.Get()
if hb == nil {
return &hashAndBuf{h: fnv.New64a()}
}
return hb.(*hashAndBuf)
}
func putHashAndBuf(hb *hashAndBuf) {
hb.h.Reset()
hb.b.Reset()
hashAndBufPool.Put(hb)
}
// 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.)
@ -66,18 +41,14 @@ func LabelsToSignature(labels map[string]string) uint64 {
}
sort.Strings(labelNames)
hb := getHashAndBuf()
defer putHashAndBuf(hb)
sum := hashNew()
for _, labelName := range labelNames {
hb.b.WriteString(labelName)
hb.b.WriteByte(SeparatorByte)
hb.b.WriteString(labels[labelName])
hb.b.WriteByte(SeparatorByte)
hb.h.Write(hb.b.Bytes())
hb.b.Reset()
sum = hashAdd(sum, labelName)
sum = hashAddByte(sum, SeparatorByte)
sum = hashAdd(sum, labels[labelName])
sum = hashAddByte(sum, SeparatorByte)
}
return hb.h.Sum64()
return sum
}
// labelSetToFingerprint works exactly as LabelsToSignature but takes a LabelSet as
@ -93,18 +64,14 @@ func labelSetToFingerprint(ls LabelSet) Fingerprint {
}
sort.Sort(labelNames)
hb := getHashAndBuf()
defer putHashAndBuf(hb)
sum := hashNew()
for _, labelName := range labelNames {
hb.b.WriteString(string(labelName))
hb.b.WriteByte(SeparatorByte)
hb.b.WriteString(string(ls[labelName]))
hb.b.WriteByte(SeparatorByte)
hb.h.Write(hb.b.Bytes())
hb.b.Reset()
sum = hashAdd(sum, string(labelName))
sum = hashAddByte(sum, SeparatorByte)
sum = hashAdd(sum, string(ls[labelName]))
sum = hashAddByte(sum, SeparatorByte)
}
return Fingerprint(hb.h.Sum64())
return Fingerprint(sum)
}
// labelSetToFastFingerprint works similar to labelSetToFingerprint but uses a
@ -116,17 +83,12 @@ func labelSetToFastFingerprint(ls LabelSet) Fingerprint {
}
var result uint64
hb := getHashAndBuf()
defer putHashAndBuf(hb)
for labelName, labelValue := range ls {
hb.b.WriteString(string(labelName))
hb.b.WriteByte(SeparatorByte)
hb.b.WriteString(string(labelValue))
hb.h.Write(hb.b.Bytes())
result ^= hb.h.Sum64()
hb.h.Reset()
hb.b.Reset()
sum := hashNew()
sum = hashAdd(sum, string(labelName))
sum = hashAddByte(sum, SeparatorByte)
sum = hashAdd(sum, string(labelValue))
result ^= sum
}
return Fingerprint(result)
}
@ -136,24 +98,20 @@ func labelSetToFastFingerprint(ls LabelSet) Fingerprint {
// 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(m) == 0 || len(labels) == 0 {
if len(labels) == 0 {
return emptyLabelSignature
}
sort.Sort(LabelNames(labels))
hb := getHashAndBuf()
defer putHashAndBuf(hb)
sum := hashNew()
for _, label := range labels {
hb.b.WriteString(string(label))
hb.b.WriteByte(SeparatorByte)
hb.b.WriteString(string(m[label]))
hb.b.WriteByte(SeparatorByte)
hb.h.Write(hb.b.Bytes())
hb.b.Reset()
sum = hashAdd(sum, string(label))
sum = hashAddByte(sum, SeparatorByte)
sum = hashAdd(sum, string(m[label]))
sum = hashAddByte(sum, SeparatorByte)
}
return hb.h.Sum64()
return sum
}
// SignatureWithoutLabels works like LabelsToSignature but takes a Metric as
@ -175,16 +133,12 @@ func SignatureWithoutLabels(m Metric, labels map[LabelName]struct{}) uint64 {
}
sort.Sort(labelNames)
hb := getHashAndBuf()
defer putHashAndBuf(hb)
sum := hashNew()
for _, labelName := range labelNames {
hb.b.WriteString(string(labelName))
hb.b.WriteByte(SeparatorByte)
hb.b.WriteString(string(m[labelName]))
hb.b.WriteByte(SeparatorByte)
hb.h.Write(hb.b.Bytes())
hb.b.Reset()
sum = hashAdd(sum, string(labelName))
sum = hashAddByte(sum, SeparatorByte)
sum = hashAdd(sum, string(m[labelName]))
sum = hashAddByte(sum, SeparatorByte)
}
return hb.h.Sum64()
return sum
}

View file

@ -44,6 +44,21 @@ func (m *Matcher) UnmarshalJSON(b []byte) error {
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 {
@ -58,3 +73,34 @@ type Silence struct {
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
}

View file

@ -163,51 +163,70 @@ func (t *Time) UnmarshalJSON(b []byte) error {
// 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
// a day always has 24h.
// 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)
}
durSeconds, _ := strconv.Atoi(matches[1])
dur := time.Duration(durSeconds) * time.Second
unit := matches[2]
switch unit {
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 *= 60 * 60 * 24
dur *= 1000 * 60 * 60 * 24
case "h":
dur *= 60 * 60
dur *= 1000 * 60 * 60
case "m":
dur *= 60
dur *= 1000 * 60
case "s":
dur *= 1
dur *= 1000
case "ms":
// Value already correct
default:
return 0, fmt.Errorf("invalid time unit in duration string: %q", unit)
}
return Duration(dur), nil
}
var durationRE = regexp.MustCompile("^([0-9]+)([ywdhms]+)$")
func (d Duration) String() string {
seconds := int64(time.Duration(d) / time.Second)
var (
ms = int64(time.Duration(d) / time.Millisecond)
unit = "ms"
)
factors := map[string]int64{
"d": 60 * 60 * 24,
"h": 60 * 60,
"m": 60,
"s": 1,
"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,
}
unit := "s"
switch int64(0) {
case seconds % factors["d"]:
case ms % factors["y"]:
unit = "y"
case ms % factors["w"]:
unit = "w"
case ms % factors["d"]:
unit = "d"
case seconds % factors["h"]:
case ms % factors["h"]:
unit = "h"
case seconds % factors["m"]:
case ms % factors["m"]:
unit = "m"
case ms % factors["s"]:
unit = "s"
}
return fmt.Sprintf("%v%v", seconds/factors[unit], unit)
return fmt.Sprintf("%v%v", ms/factors[unit], unit)
}
// MarshalYAML implements the yaml.Marshaler interface.

View file

@ -16,11 +16,28 @@ package model
import (
"encoding/json"
"fmt"
"math"
"sort"
"strconv"
"strings"
)
var (
// ZeroSamplePair is the pseudo zero-value of SamplePair used to signal a
// non-existing sample pair. It is a SamplePair with timestamp Earliest and
// value 0.0. Note that the natural zero value of SamplePair has a timestamp
// of 0, which is possible to appear in a real SamplePair and thus not
// suitable to signal a non-existing SamplePair.
ZeroSamplePair = SamplePair{Timestamp: Earliest}
// ZeroSample is the pseudo zero-value of Sample used to signal a
// non-existing sample. It is a Sample with timestamp Earliest, value 0.0,
// and metric nil. Note that the natural zero value of Sample has a timestamp
// of 0, which is possible to appear in a real Sample and thus not suitable
// to signal a non-existing Sample.
ZeroSample = Sample{Timestamp: Earliest}
)
// A SampleValue is a representation of a value for a given sample at a given
// time.
type SampleValue float64
@ -43,8 +60,14 @@ func (v *SampleValue) UnmarshalJSON(b []byte) error {
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 {
return v == o
if v == o {
return true
}
return math.IsNaN(float64(v)) && math.IsNaN(float64(o))
}
func (v SampleValue) String() string {
@ -77,9 +100,9 @@ func (s *SamplePair) UnmarshalJSON(b []byte) error {
}
// Equal returns true if this SamplePair and o have equal Values and equal
// Timestamps.
// Timestamps. The sematics of Value equality is defined by SampleValue.Equal.
func (s *SamplePair) Equal(o *SamplePair) bool {
return s == o || (s.Value == o.Value && s.Timestamp.Equal(o.Timestamp))
return s == o || (s.Value.Equal(o.Value) && s.Timestamp.Equal(o.Timestamp))
}
func (s SamplePair) String() string {
@ -93,7 +116,8 @@ type Sample struct {
Timestamp Time `json:"timestamp"`
}
// Equal compares first the metrics, then the timestamp, then the value.
// 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
@ -105,11 +129,8 @@ func (s *Sample) Equal(o *Sample) bool {
if !s.Timestamp.Equal(o.Timestamp) {
return false
}
if s.Value != o.Value {
return false
}
return true
return s.Value.Equal(o.Value)
}
func (s Sample) String() string {