This is a new revision of the discovery server. Relevant changes and non-changes: - Protocol towards clients is unchanged. - Recommended large scale design is still to be deployed nehind nginx (I tested, and it's still a lot faster at terminating TLS). - Database backend is leveldb again, only. It scales enough, is easy to setup, and we don't need any backend to take care of. - Server supports replication. This is a simple TCP channel - protect it with a firewall when deploying over the internet. (We deploy this within the same datacenter, and with firewall.) Any incoming client announces are sent over the replication channel(s) to other peer discosrvs. Incoming replication changes are applied to the database as if they came from clients, but without the TLS/certificate overhead. - Metrics are exposed using the prometheus library, when enabled. - The database values and replication protocol is protobuf, because JSON was quite CPU intensive when I tried that and benchmarked it. - The "Retry-After" value for failed lookups gets slowly increased from a default of 120 seconds, by 5 seconds for each failed lookup, independently by each discosrv. This lowers the query load over time for clients that are never seen. The Retry-After maxes out at 3600 after a couple of weeks of this increase. The number of failed lookups is stored in the database, now and then (avoiding making each lookup a database put). All in all this means clients can be pointed towards a cluster using just multiple A / AAAA records to gain both load sharing and redundancy (if one is down, clients will talk to the remaining ones). GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4648
229 lines
8.2 KiB
Go
229 lines
8.2 KiB
Go
// 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 prometheus
|
||
|
||
import (
|
||
"errors"
|
||
)
|
||
|
||
// Counter is a Metric that represents a single numerical value that only ever
|
||
// goes up. That implies that it cannot be used to count items whose number can
|
||
// also go down, e.g. the number of currently running goroutines. Those
|
||
// "counters" are represented by Gauges.
|
||
//
|
||
// A Counter is typically used to count requests served, tasks completed, errors
|
||
// occurred, etc.
|
||
//
|
||
// To create Counter instances, use NewCounter.
|
||
type Counter interface {
|
||
Metric
|
||
Collector
|
||
|
||
// Inc increments the counter by 1. Use Add to increment it by arbitrary
|
||
// non-negative values.
|
||
Inc()
|
||
// Add adds the given value to the counter. It panics if the value is <
|
||
// 0.
|
||
Add(float64)
|
||
}
|
||
|
||
// CounterOpts is an alias for Opts. See there for doc comments.
|
||
type CounterOpts Opts
|
||
|
||
// NewCounter creates a new Counter based on the provided CounterOpts.
|
||
func NewCounter(opts CounterOpts) Counter {
|
||
desc := NewDesc(
|
||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||
opts.Help,
|
||
nil,
|
||
opts.ConstLabels,
|
||
)
|
||
result := &counter{value: value{desc: desc, valType: CounterValue, labelPairs: desc.constLabelPairs}}
|
||
result.init(result) // Init self-collection.
|
||
return result
|
||
}
|
||
|
||
type counter struct {
|
||
value
|
||
}
|
||
|
||
func (c *counter) Add(v float64) {
|
||
if v < 0 {
|
||
panic(errors.New("counter cannot decrease in value"))
|
||
}
|
||
c.value.Add(v)
|
||
}
|
||
|
||
// CounterVec is a Collector that bundles a set of Counters that all share the
|
||
// same Desc, but have different values for their variable labels. This is used
|
||
// if you want to count the same thing partitioned by various dimensions
|
||
// (e.g. number of HTTP requests, partitioned by response code and
|
||
// method). Create instances with NewCounterVec.
|
||
type CounterVec struct {
|
||
*metricVec
|
||
}
|
||
|
||
// NewCounterVec creates a new CounterVec based on the provided CounterOpts and
|
||
// partitioned by the given label names.
|
||
func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
|
||
desc := NewDesc(
|
||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||
opts.Help,
|
||
labelNames,
|
||
opts.ConstLabels,
|
||
)
|
||
return &CounterVec{
|
||
metricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
||
result := &counter{value: value{
|
||
desc: desc,
|
||
valType: CounterValue,
|
||
labelPairs: makeLabelPairs(desc, lvs),
|
||
}}
|
||
result.init(result) // Init self-collection.
|
||
return result
|
||
}),
|
||
}
|
||
}
|
||
|
||
// GetMetricWithLabelValues returns the Counter for the given slice of label
|
||
// values (same order as the VariableLabels in Desc). If that combination of
|
||
// label values is accessed for the first time, a new Counter is created.
|
||
//
|
||
// It is possible to call this method without using the returned Counter to only
|
||
// create the new Counter but leave it at its starting value 0. See also the
|
||
// SummaryVec example.
|
||
//
|
||
// Keeping the Counter for later use is possible (and should be considered if
|
||
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
|
||
// Delete can be used to delete the Counter from the CounterVec. In that case,
|
||
// the Counter will still exist, but it will not be exported anymore, even if a
|
||
// Counter with the same label values is created later.
|
||
//
|
||
// An error is returned if the number of label values is not the same as the
|
||
// number of VariableLabels in Desc (minus any curried labels).
|
||
//
|
||
// Note that for more than one label value, this method is prone to mistakes
|
||
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
|
||
// an alternative to avoid that type of mistake. For higher label numbers, the
|
||
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
||
// with a performance overhead (for creating and processing the Labels map).
|
||
// See also the GaugeVec example.
|
||
func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
|
||
metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
|
||
if metric != nil {
|
||
return metric.(Counter), err
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
// GetMetricWith returns the Counter for the given Labels map (the label names
|
||
// must match those of the VariableLabels in Desc). If that label map is
|
||
// accessed for the first time, a new Counter is created. Implications of
|
||
// creating a Counter without using it and keeping the Counter for later use are
|
||
// the same as for GetMetricWithLabelValues.
|
||
//
|
||
// An error is returned if the number and names of the Labels are inconsistent
|
||
// with those of the VariableLabels in Desc (minus any curried labels).
|
||
//
|
||
// This method is used for the same purpose as
|
||
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
|
||
// methods.
|
||
func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
|
||
metric, err := v.metricVec.getMetricWith(labels)
|
||
if metric != nil {
|
||
return metric.(Counter), err
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
||
// GetMetricWithLabelValues would have returned an error. Not returning an
|
||
// error allows shortcuts like
|
||
// myVec.WithLabelValues("404", "GET").Add(42)
|
||
func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
|
||
c, err := v.GetMetricWithLabelValues(lvs...)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
return c
|
||
}
|
||
|
||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
||
// returned an error. Not returning an error allows shortcuts like
|
||
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
|
||
func (v *CounterVec) With(labels Labels) Counter {
|
||
c, err := v.GetMetricWith(labels)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
return c
|
||
}
|
||
|
||
// CurryWith returns a vector curried with the provided labels, i.e. the
|
||
// returned vector has those labels pre-set for all labeled operations performed
|
||
// on it. The cardinality of the curried vector is reduced accordingly. The
|
||
// order of the remaining labels stays the same (just with the curried labels
|
||
// taken out of the sequence – which is relevant for the
|
||
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
|
||
// vector, but only with labels not yet used for currying before.
|
||
//
|
||
// The metrics contained in the CounterVec are shared between the curried and
|
||
// uncurried vectors. They are just accessed differently. Curried and uncurried
|
||
// vectors behave identically in terms of collection. Only one must be
|
||
// registered with a given registry (usually the uncurried version). The Reset
|
||
// method deletes all metrics, even if called on a curried vector.
|
||
func (v *CounterVec) CurryWith(labels Labels) (*CounterVec, error) {
|
||
vec, err := v.curryWith(labels)
|
||
if vec != nil {
|
||
return &CounterVec{vec}, err
|
||
}
|
||
return nil, err
|
||
}
|
||
|
||
// MustCurryWith works as CurryWith but panics where CurryWith would have
|
||
// returned an error.
|
||
func (v *CounterVec) MustCurryWith(labels Labels) *CounterVec {
|
||
vec, err := v.CurryWith(labels)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
return vec
|
||
}
|
||
|
||
// CounterFunc is a Counter whose value is determined at collect time by calling a
|
||
// provided function.
|
||
//
|
||
// To create CounterFunc instances, use NewCounterFunc.
|
||
type CounterFunc interface {
|
||
Metric
|
||
Collector
|
||
}
|
||
|
||
// NewCounterFunc creates a new CounterFunc based on the provided
|
||
// CounterOpts. The value reported is determined by calling the given function
|
||
// from within the Write method. Take into account that metric collection may
|
||
// happen concurrently. If that results in concurrent calls to Write, like in
|
||
// the case where a CounterFunc is directly registered with Prometheus, the
|
||
// provided function must be concurrency-safe. The function should also honor
|
||
// the contract for a Counter (values only go up, not down), but compliance will
|
||
// not be checked.
|
||
func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc {
|
||
return newValueFunc(NewDesc(
|
||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||
opts.Help,
|
||
nil,
|
||
opts.ConstLabels,
|
||
), CounterValue, function)
|
||
}
|