From 32b10bd039fea561fe143c663db3f7c42ef2e4c3 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Fri, 27 Nov 2015 15:56:42 +0100 Subject: [PATCH] Initial refactoring. WIP. --- prometheus/collector.go | 7 +- prometheus/collectors/doc.go | 15 ++ .../{go_collector.go => collectors/go.go} | 0 .../go_test.go} | 0 .../process.go} | 10 +- .../process_test.go} | 2 +- prometheus/desc.go | 211 ++++++++++++++++-- prometheus/metric.go | 77 ++++--- prometheus/metric/collector.go | 54 +++++ prometheus/metric/desc.go | 49 ++++ prometheus/{ => metric}/fnv.go | 2 +- prometheus/metric/metric.go | 46 ++++ prometheus/promhttp/exposition.go | 53 +++++ .../{http.go => promhttp/instrumentation.go} | 6 +- prometheus/registry.go | 147 +++--------- prometheus/registry/doc.go | 17 ++ prometheus/registry/error.go | 24 ++ prometheus/registry/impl.go | 66 ++++++ prometheus/registry/interface.go | 96 ++++++++ prometheus/registry/util.go | 40 ++++ prometheus/vec.go | 1 + 21 files changed, 745 insertions(+), 178 deletions(-) create mode 100644 prometheus/collectors/doc.go rename prometheus/{go_collector.go => collectors/go.go} (100%) rename prometheus/{go_collector_test.go => collectors/go_test.go} (100%) rename prometheus/{process_collector.go => collectors/process.go} (96%) rename prometheus/{process_collector_test.go => collectors/process_test.go} (98%) create mode 100644 prometheus/metric/collector.go create mode 100644 prometheus/metric/desc.go rename prometheus/{ => metric}/fnv.go (96%) create mode 100644 prometheus/metric/metric.go create mode 100644 prometheus/promhttp/exposition.go rename prometheus/{http.go => promhttp/instrumentation.go} (98%) create mode 100644 prometheus/registry/doc.go create mode 100644 prometheus/registry/error.go create mode 100644 prometheus/registry/impl.go create mode 100644 prometheus/registry/interface.go create mode 100644 prometheus/registry/util.go diff --git a/prometheus/collector.go b/prometheus/collector.go index c046880..1905e15 100644 --- a/prometheus/collector.go +++ b/prometheus/collector.go @@ -24,6 +24,9 @@ package prometheus // already implemented in this library are the metric vectors (i.e. collection // of multiple instances of the same Metric but with different label values) // like GaugeVec or SummaryVec, and the ExpvarCollector. +// +// (Two Collectors are considered equal if their +// Describe method yields the same set of descriptors.) type Collector interface { // Describe sends the super-set of all possible descriptors of metrics // collected by this Collector to the provided channel and returns once @@ -46,7 +49,9 @@ type Collector interface { // therefore be implemented in a concurrency safe way. Blocking occurs // at the expense of total performance of rendering all registered // metrics. Ideally, Collector implementations support concurrent - // readers. + // readers. If a Collector finds itself unable to collect a metric, it + // can signal the error to the registry by sending a Metric that will + // return the error when its Write method is called. Collect(chan<- Metric) } diff --git a/prometheus/collectors/doc.go b/prometheus/collectors/doc.go new file mode 100644 index 0000000..032fabe --- /dev/null +++ b/prometheus/collectors/doc.go @@ -0,0 +1,15 @@ +// 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 collectors provides collector implemntations for various purposes. +package collectors diff --git a/prometheus/go_collector.go b/prometheus/collectors/go.go similarity index 100% rename from prometheus/go_collector.go rename to prometheus/collectors/go.go diff --git a/prometheus/go_collector_test.go b/prometheus/collectors/go_test.go similarity index 100% rename from prometheus/go_collector_test.go rename to prometheus/collectors/go_test.go diff --git a/prometheus/process_collector.go b/prometheus/collectors/process.go similarity index 96% rename from prometheus/process_collector.go rename to prometheus/collectors/process.go index d8cf0ed..473e52e 100644 --- a/prometheus/process_collector.go +++ b/prometheus/collectors/process.go @@ -11,13 +11,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheus +package collectors -import "github.com/prometheus/procfs" +import ( + "github.com/prometheus/procfs" + + "github.com/prometheus/client_golang/prometheus/metric" +) type processCollector struct { pid int - collectFn func(chan<- Metric) + collectFn func(chan<- metric.Metric) pidFn func() (int, error) cpuTotal Counter openFDs, maxFDs Gauge diff --git a/prometheus/process_collector_test.go b/prometheus/collectors/process_test.go similarity index 98% rename from prometheus/process_collector_test.go rename to prometheus/collectors/process_test.go index 829715a..6169842 100644 --- a/prometheus/process_collector_test.go +++ b/prometheus/collectors/process_test.go @@ -1,4 +1,4 @@ -package prometheus +package collectors import ( "io/ioutil" diff --git a/prometheus/desc.go b/prometheus/desc.go index ee02d9b..5a214e3 100644 --- a/prometheus/desc.go +++ b/prometheus/desc.go @@ -6,6 +6,7 @@ import ( "regexp" "sort" "strings" + "unicode/utf8" "github.com/golang/protobuf/proto" @@ -30,6 +31,197 @@ const reservedLabelPrefix = "__" // create a Desc. type Labels map[string]string +// Desc is used to describe the meta-data of a Metric (via its Desc method) or a +// group of metrics collected by a Collector (via its Describe method). Some of its +// methods are only used internally and are therefore not exported, which also +// prevents users to implement their own descriptors. Descriptor instances must +// be created via suitable NewXXXDesc functions and will in generally only be +// needed in custom collectors. +// +// Desc implementations are immutable by contract. +// +// A Desc that also implements the error interface is called an invalid +// descriptor, which is solely used to communicate an error and must never be +// processed further. +type Desc interface { + // String returns a string representation of the descriptor as usual. It + // is also used as an ID that must be unique among all descriptors + // registered by a Registry. + String() string + dims() string +} + +// NewInvalidDesc returns a descriptor that also implements the error +// interface. It is used to communicate an error during a call of Desc (Metric +// method) or Describe (Collector method). Create with NewInvalidDesc. +func NewInvalidDesc(err error) Desc { + return &invalidDesc{err: err} +} + +type invalidDesc struct { + err error +} + +func (d *invalidDesc) Error() string { + return d.err.Error() +} + +func (d *invalidDesc) String() string { + return "[invalid] " + d.err.Error() +} + +func (d *invalidDesc) dims() string { + return "" +} + +// NewPrefixDesc returns a descriptor that is used by Collectors that want to +// reserve a whole metric name prefix for their own use. An invalid descriptor +// is returned if the prefix is not a valid as the start of a metric +// name. However, an empty prefix is valid and reserves all metric names. +func NewPrefixDesc(prefix string) Desc { + if prefix != "" && !validMetricName(prefix) { + return NewInvalidDesc(fmt.Errorf("%q is not a valid metric name prefix", prefix)) + } + return &prefixDesc{pfx: prefix} +} + +type prefixDesc struct { + prefix string +} + +func (d *prefixDesc) String() string { + return "[prefix] " + d.prefix +} + +func (d *prefixDesc) dims() string { + return "" // PrefixDesc is for all dimensions. +} + +func (d *prefixDesc) overlapsWith(other Desc) bool { + switch o := other.(type) { + case *invalidDesc: + // Invalid descs never overlap. + return false + case *partialDesc, *fullDesc: + return strings.HasPrefix(o.fqName, d.prefix) + case *prefixDesc: + return strings.HasPrefix(o.prefix, d.prefix) || strings.HasPrefix(d.Prefix, o.prefix) + default: + panic(fmt.Errorf("unexpected type of descriptor %q", o)) + } +} + +// NewPartialDesc returns a descriptor that is used by Collectors that want to +// reserve a specific metric name and type with specific label dimensions of +// which some (but not all) label values might be set already. An invalid +// descriptor is returned in the following cases: The resulting label name +// (assembled from namespace, subsystem, and name) is invalid, the help string +// is empty, unsetLabels is empty or contains invalid label names, +// setLabels (which might be empty) contains invalid label names or label +// values, metricType is not a valid MetricType. +func NewPartialDesc( + namespace, subsystem, name string, + metricType MetricType, + help string, + setLabels Labels, + unsetLabels []string, +) Desc { + return nil // TODO +} + +// NewFullDesc returns a descriptor with fully specified name, type, and +// labels. It can be used by Collectors and must be used by Metrics. An invalid +// descriptor is returned if the resulting label name (assembled from namespace, +// subsystem, and name) is invalid, the help string is empty, metricType has an +// invalid value, or the labels contain invalid label names or values. The labels +// might be empty, though. +func NewFullDesc( + namespace, subsystem, name string, + metricType MetricType, + help string, + labels Labels, +) Desc { + return nil // TODO +} + +// FullySpecify returns a fully specified descriptor based on the provided +// partial descriptor by setting all the unset labels to the provided values (in +// the same order as they have been specified in the NewFullDesc or DeSpecify +// call). An invalid desc is returned if the provided desc is not a partial +// descriptor, the cardinality of labelValues does not fit, or labelValues +// contains invalid label values. +func FullySpecify(desc Desc, labelValues ...string) Desc { + d, ok := desc.(*partialDesc) + if !ok { + return NewInvalidDesc(fmt.Errorf("tried to fully specify non-partial descriptor %q", desc)) + } + return nil // TODO +} + +// DeSpecify creates a partial descriptor based on the provided full descriptor +// by adding un-set labels with the provided label names. An invalid desc is +// returned if the provided desc is not a full descriptor, or labelNames +// contains invalid label names (or no label names at all). +func DeSpecify(desc Desc, labelNames ...string) Desc { + d, ok := desc.(*fullDesc) + if !ok { + return NewInvalidDesc(fmt.Errorf("tried to de-specify non-full descriptor %q", desc)) + } + if len(ln) == 0 { + return NewInvalidDesc(fmt.Errorf("no label names provided to de-specify %q", desc)) + } + for _, ln := range labelNames { + if !validLabelName(ln) { + return NewInvalidDesc(fmt.Errorf("encountered invalid label name %q while de-specifying %q", ln, desc)) + } + } + return &partialDesc{*d, labelNames} +} + +type fullDesc struct { + fqName, help string + metricType MetricType + setLabels []*dto.LabelPair // Sorted. +} + +type partialDesc struct { + fullDesc + unsetLabels []string // Keep in original order. +} + +// buildFQName joins the given three name components by "_". Empty name +// components are ignored. If the name parameter itself is empty, an empty +// string is returned, no matter what. +func buildFQName(namespace, subsystem, name string) string { + if name == "" { + return "" + } + switch { + case namespace != "" && subsystem != "": + return namespace + "_" + subsystem + "_" + name + case namespace != "": + return namespace + "_" + name + case subsystem != "": + return subsystem + "_" + name + } + return name +} + +func validMetricName(n string) bool { + return metricNameRE.MatchString(n) +} + +func validLabelName(l string) bool { + return labelNameRE.MatchString(l) && + !strings.HasPrefix(l, reservedLabelPrefix) +} + +func validLabelValue(l string) bool { + return utf8.ValidString(l) +} + +// OLD CODE below. + // Desc is the descriptor used by every Prometheus Metric. It is essentially // the immutable meta-data of a Metric. The normal Metric implementations // included in this package manage their Desc under the hood. Users only have to @@ -102,7 +294,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) * labelNameSet := map[string]struct{}{} // First add only the const label names and sort them... for labelName := range constLabels { - if !checkLabelName(labelName) { + if !validLabelName(labelName) { d.err = fmt.Errorf("%q is not a valid label name", labelName) return d } @@ -118,7 +310,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) * // cannot be in a regular label name. That prevents matching the label // dimension with a different mix between preset and variable labels. for _, labelName := range variableLabels { - if !checkLabelName(labelName) { + if !validLabelName(labelName) { d.err = fmt.Errorf("%q is not a valid label name", labelName) return d } @@ -159,16 +351,6 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) * return d } -// NewInvalidDesc returns an invalid descriptor, i.e. a descriptor with the -// provided error set. If a collector returning such a descriptor is registered, -// registration will fail with the provided error. NewInvalidDesc can be used by -// a Collector to signal inability to describe itself. -func NewInvalidDesc(err error) *Desc { - return &Desc{ - err: err, - } -} - func (d *Desc) String() string { lpStrings := make([]string, 0, len(d.constLabelPairs)) for _, lp := range d.constLabelPairs { @@ -185,8 +367,3 @@ func (d *Desc) String() string { d.variableLabels, ) } - -func checkLabelName(l string) bool { - return labelNameRE.MatchString(l) && - !strings.HasPrefix(l, reservedLabelPrefix) -} diff --git a/prometheus/metric.go b/prometheus/metric.go index 86fd81c..6c31d9a 100644 --- a/prometheus/metric.go +++ b/prometheus/metric.go @@ -13,11 +13,7 @@ package prometheus -import ( - "strings" - - dto "github.com/prometheus/client_model/go" -) +import dto "github.com/prometheus/client_model/go" const separatorByte byte = 255 @@ -53,6 +49,42 @@ type Metric interface { Write(*dto.Metric) error } +// MetricType is an enumeration of metric types. It deliberately mirrors the +// MetricType enum from the protobuf specification to avoid exposing protobuf +// references to the user of this package. (The protobuf parts could be +// internally vendored.) +type MetricType int + +// Possible values for the MetricType enum. +const ( + CounterMetric MetricType = iota + GaugeMetric + SummaryMetric + UntypedMetric + HistogramMetric +) + +func (m MetricType) Valid() bool { + return m >= CounterMetric && m <= HistogramMetric +} + +func (m MetricType) String() string { + switch m { + case CounterMetric: + return "COUNTER" + case GaugeMetric: + return "GAUGE" + case SummaryMetric: + return "SUMMARY" + case UntypedMetric: + return "UNTYPED" + case HistogramMetric: + return "HISTOGRAM" + default: + return "INVALID" + } +} + // Opts bundles the options for creating most Metric types. Each metric // implementation XXX has its own XXXOpts type, but in most cases, it is just be // an alias of this type (which might change when the requirement arises.) @@ -96,28 +128,6 @@ type Opts struct { ConstLabels Labels } -// BuildFQName joins the given three name components by "_". Empty name -// components are ignored. If the name parameter itself is empty, an empty -// string is returned, no matter what. Metric implementations included in this -// library use this function internally to generate the fully-qualified metric -// name from the name component in their Opts. Users of the library will only -// need this function if they implement their own Metric or instantiate a Desc -// (with NewDesc) directly. -func BuildFQName(namespace, subsystem, name string) string { - if name == "" { - return "" - } - switch { - case namespace != "" && subsystem != "": - return strings.Join([]string{namespace, subsystem, name}, "_") - case namespace != "": - return strings.Join([]string{namespace, name}, "_") - case subsystem != "": - return strings.Join([]string{subsystem, name}, "_") - } - return name -} - // LabelPairSorter implements sort.Interface. It is used to sort a slice of // dto.LabelPair pointers. This is useful for implementing the Write method of // custom metrics. @@ -150,17 +160,18 @@ func (s hashSorter) Less(i, j int) bool { } type invalidMetric struct { - desc *Desc + desc Desc err error } // NewInvalidMetric returns a metric whose Write method always returns the -// provided error. It is useful if a Collector finds itself unable to collect -// a metric and wishes to report an error to the registry. -func NewInvalidMetric(desc *Desc, err error) Metric { - return &invalidMetric{desc, err} +// provided error, and whose descriptor is invalid, carrying the provided +// error. It is useful if a Collector finds itself unable to collect a metric +// and wishes to report an error to the registry. +func NewInvalidMetric(err error) Metric { + return &invalidMetric{NewInvalidDesc(err), err} } -func (m *invalidMetric) Desc() *Desc { return m.desc } +func (m *invalidMetric) Desc() Desc { return m.desc } func (m *invalidMetric) Write(*dto.Metric) error { return m.err } diff --git a/prometheus/metric/collector.go b/prometheus/metric/collector.go new file mode 100644 index 0000000..763fda5 --- /dev/null +++ b/prometheus/metric/collector.go @@ -0,0 +1,54 @@ +// 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 metric + +// Collector is the interface implemented by anything that can be used by +// Prometheus to collect metrics. A Collector has to be registered for +// collection. +// +// The stock metrics provided in their respective packages (like Gauge, Counter, +// Summary, Histogram) are also Collectors (which only ever collect one metric, +// namely itself). An implementer of Collector may, however, collect multiple +// metrics in a coordinated fashion and/or create metrics on the fly. Examples +// for collectors already implemented in this library are the metric vectors +// (i.e. collection of multiple instances of the same Metric but with different +// label values) like gauge.Vec or summary.Vec, and the ExpvarCollector. +// +// Two Collectors are considered equal if their Describe methods yield the same +// set of descriptors. +type Collector interface { + // Describe sends all descriptors required to describe the metrics + // collected by this Collector to the provided channel and returns once + // the last descriptor has been sent. The sent descriptors fulfill the + // consistency and uniqueness requirements described in the Desc + // documentation. This method idempotently sends the same descriptors + // throughout the lifetime of the Collector. If a Collector encounters + // an error while executing this method, it must send an invalid + // descriptor (created with NewInvalidDesc) to signal the error to the + // registry. + Describe(chan<- Desc) + // Collect is called by Prometheus when collecting metrics. The + // implementation sends each collected metric via the provided channel + // and returns once the last metric has been sent. Each sent metric must + // be consistent with one of the descriptors returned by + // Describe. Returned metrics that are described by the same descriptor + // must differ in their variable label values. This method may be called + // concurrently and must therefore be implemented in a concurrency safe + // way. Blocking occurs at the expense of total performance of rendering + // all registered metrics. Ideally, Collector implementations support + // concurrent readers. If a Collector finds itself unable to collect a + // metric, it can signal the error to the registry by sending a Metric + // that will return the error in its Write method.. + Collect(chan<- Metric) +} diff --git a/prometheus/metric/desc.go b/prometheus/metric/desc.go new file mode 100644 index 0000000..f5ae294 --- /dev/null +++ b/prometheus/metric/desc.go @@ -0,0 +1,49 @@ + +import "fmt" + +func NewDesc( + namespace, subsystem, name string, + help string, + constLabels Labels, + variableLabels []string, +) Desc { + fqName := buildFQName(namespace, subsystem, name) + if !metricNameRE.MatchString(fqName) { + return NewInvalidDesc(fmt.Errorf("%q is not a valid metric name", fqName)) + } + if help == "" { + return NewInvalidDesc(fmt.Errorf("empty help string for metric %q", fqName)) + } + + return nil // TODO +} + +type regularDesc struct { + baseDesc + fqName, help string + constLabelPairs []*dto.LabelPair + variableLabels []string +} + +type prefixDesc struct { + baseDesc + prefix string +} + +type Set struct { + regular map[string]*regularDesc + // The prefix ones should be tries. But it's unlikely to have many of them. + prefix []*prefixDesc +} + +func (s *Set) Add(d Desc) error { + if d.Error() != nil { + return d.Error() + } + return nil +} + +func (s *Set) Remove(d Desc) bool { + return false +} + diff --git a/prometheus/fnv.go b/prometheus/metric/fnv.go similarity index 96% rename from prometheus/fnv.go rename to prometheus/metric/fnv.go index e3b67df..ef0c224 100644 --- a/prometheus/fnv.go +++ b/prometheus/metric/fnv.go @@ -1,4 +1,4 @@ -package prometheus +package metric // Inline and byte-free variant of hash/fnv's fnv64a. diff --git a/prometheus/metric/metric.go b/prometheus/metric/metric.go new file mode 100644 index 0000000..9a42112 --- /dev/null +++ b/prometheus/metric/metric.go @@ -0,0 +1,46 @@ +// 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 metric + +import dto "github.com/prometheus/client_model/go" + +// A Metric models a single sample value with its meta data being exported to +// Prometheus. Implementers of Metric in this package include Gauge, Counter, +// Untyped, and Summary. Users can implement their own Metric types, but that +// should be rarely needed. +type Metric interface { + // Desc returns the descriptor for the Metric. This method idempotently + // returns the same descriptor throughout the lifetime of the Metric. A + // Metric unable to describe itself must return an invalid descriptor + // (created with NewInvalidDesc). + Desc() Desc + // Write encodes the Metric into a "Metric" Protocol Buffer data + // transmission object. + // + // Implementers of custom Metric types must observe concurrency safety + // as reads of this metric may occur at any time, and any blocking + // occurs at the expense of total performance of rendering all + // registered metrics. Ideally Metric implementations should support + // concurrent readers. + // + // The caller may minimize memory allocations by providing a + // pre-existing reset dto.Metric pointer. The caller may recycle the + // dto.Metric proto message, so Metric implementations should just + // populate the provided dto.Metric and then should not keep any + // reference to it. + // + // While populating dto.Metric, labels must be sorted lexicographically. + // (Implementers may find LabelPairSorter useful for that.) + Write(*dto.Metric) error +} diff --git a/prometheus/promhttp/exposition.go b/prometheus/promhttp/exposition.go new file mode 100644 index 0000000..79b581d --- /dev/null +++ b/prometheus/promhttp/exposition.go @@ -0,0 +1,53 @@ +// 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 promhttp + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus/registry" +) + +// Constants relevant to the HTTP interface. +const ( + // APIVersion is the version of the format of the exported data. This + // will match this library's version, which subscribes to the Semantic + // Versioning scheme. + APIVersion = "0.0.4" + + // DelimitedTelemetryContentType is the content type set on telemetry + // data responses in delimited protobuf format. + DelimitedTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited` + // TextTelemetryContentType is the content type set on telemetry data + // responses in text format. + TextTelemetryContentType = `text/plain; version=` + APIVersion + // ProtoTextTelemetryContentType is the content type set on telemetry + // data responses in protobuf text format. (Only used for debugging.) + ProtoTextTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text` + // ProtoCompactTextTelemetryContentType is the content type set on + // telemetry data responses in protobuf compact text format. (Only used + // for debugging.) + ProtoCompactTextTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text` + + contentTypeHeader = "Content-Type" + contentLengthHeader = "Content-Length" + contentEncodingHeader = "Content-Encoding" + + acceptEncodingHeader = "Accept-Encoding" + acceptHeader = "Accept" +) + +func Handler(r registry.Registry) http.Handler { + return nil // TODO +} diff --git a/prometheus/http.go b/prometheus/promhttp/instrumentation.go similarity index 98% rename from prometheus/http.go rename to prometheus/promhttp/instrumentation.go index eabe602..bd327bd 100644 --- a/prometheus/http.go +++ b/prometheus/promhttp/instrumentation.go @@ -1,4 +1,4 @@ -// Copyright 2014 The Prometheus Authors +// 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 @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package prometheus +package promhttp import ( "bufio" @@ -23,6 +23,8 @@ import ( "time" ) +// TODO: Rework handler instrumentation (histograms, more configurable...). + var instLabels = []string{"method", "code"} type nower interface { diff --git a/prometheus/registry.go b/prometheus/registry.go index 1dc2536..2f7f8eb 100644 --- a/prometheus/registry.go +++ b/prometheus/registry.go @@ -27,7 +27,6 @@ import ( "io" "net/http" "net/url" - "os" "sort" "strings" "sync" @@ -36,56 +35,19 @@ import ( "github.com/prometheus/common/expfmt" dto "github.com/prometheus/client_model/go" + + "github.com/prometheus/client_golang/prometheus/promhttp" ) -var ( - defRegistry = newDefaultRegistry() - errAlreadyReg = errors.New("duplicate metrics collector registration attempted") -) - -// Constants relevant to the HTTP interface. -const ( - // APIVersion is the version of the format of the exported data. This - // will match this library's version, which subscribes to the Semantic - // Versioning scheme. - APIVersion = "0.0.4" - - // DelimitedTelemetryContentType is the content type set on telemetry - // data responses in delimited protobuf format. - DelimitedTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited` - // TextTelemetryContentType is the content type set on telemetry data - // responses in text format. - TextTelemetryContentType = `text/plain; version=` + APIVersion - // ProtoTextTelemetryContentType is the content type set on telemetry - // data responses in protobuf text format. (Only used for debugging.) - ProtoTextTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text` - // ProtoCompactTextTelemetryContentType is the content type set on - // telemetry data responses in protobuf compact text format. (Only used - // for debugging.) - ProtoCompactTextTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text` - - // Constants for object pools. - numBufs = 4 - numMetricFamilies = 1000 - numMetrics = 10000 - - // Capacity for the channel to collect metrics and descriptors. - capMetricChan = 1000 - capDescChan = 10 - - contentTypeHeader = "Content-Type" - contentLengthHeader = "Content-Length" - contentEncodingHeader = "Content-Encoding" - - acceptEncodingHeader = "Accept-Encoding" - acceptHeader = "Accept" -) - -// Handler returns the HTTP handler for the global Prometheus registry. It is -// already instrumented with InstrumentHandler (using "prometheus" as handler -// name). Usually the handler is used to handle the "/metrics" endpoint. +// Handler returns an instrumented HTTP handler for the default Prometheus +// registry. It is already instrumented with InstrumentHandler (using +// "prometheus" as handler name). Usually the handler is used to handle the +// "/metrics" endpoint. func Handler() http.Handler { - return InstrumentHandler("prometheus", defRegistry) + return promhttp.InstrumentHandler( + "prometheus", + promhttp.Handler(registry.Default), + ) } // UninstrumentedHandler works in the same way as Handler, but the returned HTTP @@ -94,30 +56,26 @@ func Handler() http.Handler { // different handler name (or with a different instrumentation approach // altogether). See the InstrumentHandler example. func UninstrumentedHandler() http.Handler { - return defRegistry + return promhttp.Handler(registry.Default) } -// Register registers a new Collector to be included in metrics collection. It -// returns an error if the descriptors provided by the Collector are invalid or -// if they - in combination with descriptors of already registered Collectors - -// do not fulfill the consistency and uniqueness criteria described in the Desc -// documentation. +// Register registers a new Collector to be included in metrics collection with +// the default registry. It returns an error if the descriptors provided by the +// Collector are invalid or if they - in combination with descriptors of already +// registered Collectors - do not fulfill the consistency and uniqueness +// criteria described in the Desc documentation. // // Do not register the same Collector multiple times concurrently. (Registering // the same Collector twice would result in an error anyway, but on top of that, // it is not safe to do so concurrently.) -func Register(m Collector) error { - _, err := defRegistry.Register(m) - return err +func Register(c Collector) error { + return registry.Default.Register(c) } // MustRegister works like Register but panics where Register would have // returned an error. -func MustRegister(m Collector) { - err := Register(m) - if err != nil { - panic(err) - } +func MustRegister(c Collector) { + registry.MustRegister(registry.Default, c) } // RegisterOrGet works like Register but does not return an error if a Collector @@ -129,18 +87,14 @@ func MustRegister(m Collector) { // // As for Register, it is still not safe to call RegisterOrGet with the same // Collector multiple times concurrently. -func RegisterOrGet(m Collector) (Collector, error) { - return defRegistry.RegisterOrGet(m) +func RegisterOrGet(c Collector) (Collector, error) { + return registry.RegisterOrGet(registry.Default, c) } // MustRegisterOrGet works like Register but panics where RegisterOrGet would // have returned an error. -func MustRegisterOrGet(m Collector) Collector { - existing, err := RegisterOrGet(m) - if err != nil { - panic(err) - } - return existing +func MustRegisterOrGet(c Collector) Collector { + return registry.MustRegisterOrGet(registry.Default, c) } // Unregister unregisters the Collector that equals the Collector passed in as @@ -148,50 +102,10 @@ func MustRegisterOrGet(m Collector) Collector { // yields the same set of descriptors.) The function returns whether a Collector // was unregistered. func Unregister(c Collector) bool { - return defRegistry.Unregister(c) + return registry.Default.Unregister(c) } -// SetMetricFamilyInjectionHook sets a function that is called whenever metrics -// are collected. The hook function must be set before metrics collection begins -// (i.e. call SetMetricFamilyInjectionHook before setting the HTTP handler.) The -// MetricFamily protobufs returned by the hook function are merged with the -// metrics collected in the usual way. -// -// This is a way to directly inject MetricFamily protobufs managed and owned by -// the caller. The caller has full responsibility. As no registration of the -// injected metrics has happened, there is no descriptor to check against, and -// there are no registration-time checks. If collect-time checks are disabled -// (see function EnableCollectChecks), no sanity checks are performed on the -// returned protobufs at all. If collect-checks are enabled, type and uniqueness -// checks are performed, but no further consistency checks (which would require -// knowledge of a metric descriptor). -// -// Sorting concerns: The caller is responsible for sorting the label pairs in -// each metric. However, the order of metrics will be sorted by the registry as -// it is required anyway after merging with the metric families collected -// conventionally. -// -// The function must be callable at any time and concurrently. -func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) { - defRegistry.metricFamilyInjectionHook = hook -} - -// PanicOnCollectError sets the behavior whether a panic is caused upon an error -// while metrics are collected and served to the HTTP endpoint. By default, an -// internal server error (status code 500) is served with an error message. -func PanicOnCollectError(b bool) { - defRegistry.panicOnCollectError = b -} - -// EnableCollectChecks enables (or disables) additional consistency checks -// during metrics collection. These additional checks are not enabled by default -// because they inflict a performance penalty and the errors they check for can -// only happen if the used Metric and Collector types have internal programming -// errors. It can be helpful to enable these checks while working with custom -// Collectors or Metrics whose correctness is not well established yet. -func EnableCollectChecks(b bool) { - defRegistry.collectChecksEnabled = b -} +// TODO: Move out from here. // encoder is a function that writes a dto.MetricFamily to an io.Writer in a // certain encoding. It returns the number of bytes written and any error @@ -305,7 +219,7 @@ func (r *registry) Unregister(c Collector) bool { }() descIDs := map[uint64]struct{}{} - var collectorID uint64 // Just a sum of the desc IDs. + var collectorID uint64 // Just a sum of the desc IDs. TODO: should be fnv on its own for desc := range descChan { if _, exists := descIDs[desc.id]; !exists { collectorID += desc.id @@ -670,13 +584,6 @@ func newRegistry() *registry { } } -func newDefaultRegistry() *registry { - r := newRegistry() - r.Register(NewProcessCollector(os.Getpid(), "")) - r.Register(NewGoCollector()) - return r -} - // decorateWriter wraps a writer to handle gzip compression if requested. It // returns the decorated writer and the appropriate "Content-Encoding" header // (which is empty if no compression is enabled). diff --git a/prometheus/registry/doc.go b/prometheus/registry/doc.go new file mode 100644 index 0000000..1aa1022 --- /dev/null +++ b/prometheus/registry/doc.go @@ -0,0 +1,17 @@ +// 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 registry provides the interface of the metrics registry and means to +// create concrete instantiations thereof. + +package registry diff --git a/prometheus/registry/error.go b/prometheus/registry/error.go new file mode 100644 index 0000000..960d3d3 --- /dev/null +++ b/prometheus/registry/error.go @@ -0,0 +1,24 @@ +// 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 registry + +import "github.com/prometheus/client_golang/prometheus/metric" + +type AlreadyRegisteredError struct { + ExistingCollector, NewCollector metric.Collector +} + +func (err AlreadyRegisteredError) Error() string { + return "" // TODO +} diff --git a/prometheus/registry/impl.go b/prometheus/registry/impl.go new file mode 100644 index 0000000..07586b4 --- /dev/null +++ b/prometheus/registry/impl.go @@ -0,0 +1,66 @@ +// 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 registry + +import ( + "os" + + dto "github.com/prometheus/client_model/go" + + "github.com/prometheus/client_golang/prometheus" +) + +// TODO: These are from the old code. Vet! +const ( + // Constants for object pools. + numBufs = 4 + numMetricFamilies = 1000 + numMetrics = 10000 + + // Capacity for the channel to collect metrics and descriptors. + capMetricChan = 1000 + capDescChan = 10 +) + +func init() { + MustRegister(Default, collectors.NewProcessCollector(os.Getpid(), "")) + MustRegister(Default, collectors.NewGoCollector()) +} + +// Default is the default registry implicitly used by the top-level functions in +// the prometheus package. It is using the default value of Opts and has a +// ProcessCollector and a GoCollector pre-registered. +var Default Registry = New(Opts{}) + +func New(opts Opts) Registry { + return ®istry{} +} + +type registry struct { +} + +func (r *registry) Register(prometheus.Collector) error { + return nil // TODO +} + +func (r *registry) Unregister(prometheus.Collector) bool { + return false // TODO +} + +func (r *registry) Collect(names map[string]struct{}) <-chan struct { + *dto.MetricFamily + error +} { + return nil // TODO +} diff --git a/prometheus/registry/interface.go b/prometheus/registry/interface.go new file mode 100644 index 0000000..8b180bd --- /dev/null +++ b/prometheus/registry/interface.go @@ -0,0 +1,96 @@ +// 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 registry provides the interface of the metrics registry and means to +// instantiate implementations thereof. It also provides the so-called default +// registry, a pre-configured instantiation of a registry that should be +// sufficient for most use-cases. +package registry + +import ( + dto "github.com/prometheus/client_model/go" + + "github.com/prometheus/client_golang/prometheus/metric" +) + +// Registry is the interface for the metrics registry. +type Registry interface { + // Register registers a new Collector to be included in metrics + // collection. It returns an error if the descriptors provided by the + // Collector are invalid or if they - in combination with descriptors of + // already registered Collectors - do not fulfill the consistency and + // uniqueness criteria described in the documentation of metric.Desc. + // + // If the provided Collector is equal to a Collector already registered + // (which includes the case of re-registering the same Collector), the + // returned error is an instance of AlreadyRegisteredError, which + // contains the previously registered Collector. + // + // It is in general not safe to register the same Collector multiple + // times concurrently. + Register(metric.Collector) error + // Unregister unregisters the Collector that equals the Collector passed + // in as an argument. The function returns whether a Collector was + // unregistered. + Unregister(metric.Collector) bool + // Collect returns a channel that yields MetricFamily protobufs + // (collected from registered collectors) together with applicable + // errors. The metric family pointer returned with an error could be nil + // or point to a (presumably incomplete) metric family. Once all + // MetricFamilies have been read, the channel is closed. To not leak + // resources, the channel must always be read until closed, even if one + // ore more errors have been returned. If names is nil or empty, all + // MetricFamilies are returned. Otherwise, only MetricFamilies with a + // name contained in names are returned. Implementations should aim for + // lexicographical sorting of MetricFamilies if the resource cost of + // sorting is not prohibitive. + Collect(names map[string]struct{}) <-chan struct { + *dto.MetricFamily + error + } +} + +// Opts are options for the instantiation of new registries. The zero value of +// Opts is a safe default. +type Opts struct { + // If true, metrics are checked for consistency during collection. The + // check has a performance overhead and is not necessary with + // well-behaved collectors. It can be helpful to enable the check while + // working with custom Collectors whose correctness is not well + // established yet or where inconsistent collection might happen by + // design. + CollectCheckEnabled bool + // If true, the channel returned by the Collect method will never yield + // an error (so that no error handling has to be implemented when + // receiving from the channel). Instead, the program will panic. This + // behavior is useful in programs where collect errors cannot (or must + // not) happen. + PanicOnCollectError bool + // The MetricFamilyInjectionHook is a function that is called whenever + // metrics are collected. The MetricFamily protobufs returned by the + // hook function are merged with the metrics collected in the usual way. + // + // This is a way to directly inject MetricFamily protobufs managed and + // owned by the caller. The caller has full responsibility. As no + // registration of the injected metrics has happened, there was no check + // at registration-time. If CollectCheckEnabled is false, only very + // limited sanity checks are performed on the returned protobufs. + // + // Sorting concerns: The caller is responsible for sorting the label + // pairs in each metric. However, the order of metrics will be sorted by + // the registry as it is required anyway after merging with the metric + // families collected conventionally. + // + // The function must be callable at any time and concurrently. + MetricFamilyInjectionHook func() []*dto.MetricFamily +} diff --git a/prometheus/registry/util.go b/prometheus/registry/util.go new file mode 100644 index 0000000..85fe34d --- /dev/null +++ b/prometheus/registry/util.go @@ -0,0 +1,40 @@ +// 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 registry + +import "github.com/prometheus/client_golang/prometheus/metric" + +func MustRegister(r Registry, c metric.Collector) { + if err := r.Register(c); err != nil { + panic(err) + } +} + +func RegisterOrGet(r Registry, c metric.Collector) (metric.Collector, error) { + if err := r.Register(c); err != nil { + if are, ok := err.(AlreadyRegisteredError); ok { + return are.ExistingCollector, nil + } + return nil, err + } + return c, nil +} + +func MustRegisterOrGet(r Registry, c metric.Collector) metric.Collector { + existing, err := RegisterOrGet(r, c) + if err != nil { + panic(err) + } + return existing +} diff --git a/prometheus/vec.go b/prometheus/vec.go index 68f9461..c7dfc4b 100644 --- a/prometheus/vec.go +++ b/prometheus/vec.go @@ -241,6 +241,7 @@ func (m *MetricVec) getOrCreateMetric(hash uint64, labelValues ...string) Metric if !ok { // Copy labelValues. Otherwise, they would be allocated even if we don't go // down this code path. + // TODO: Use copy. copiedLabelValues := append(make([]string, 0, len(labelValues)), labelValues...) metric = m.newMetric(copiedLabelValues...) m.children[hash] = metric