2015-02-02 17:14:36 +03:00
|
|
|
// Copyright 2014 The Prometheus Authors
|
2014-05-07 22:08:33 +04:00
|
|
|
// 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 (
|
2015-06-05 00:24:54 +03:00
|
|
|
"bufio"
|
Create a public registry interface and separate out HTTP exposition
General context and approch
===========================
This is the first part of the long awaited wider refurbishment of
`client_golang/prometheus/...`. After a lot of struggling, I decided
to not go for one breaking big-bang, but cut things into smaller steps
after all, mostly to keep the changes manageable and easy to
review. I'm aiming for having the invasive breaking changes
concentrated in as few steps as possible (ideally one). Some steps
will not be breaking at all, but typically there will be breaking
changes that only affect quite special cases so that 95+% of users
will not be affected. This first step is an example for that, see
details below.
What's happening in this commit?
================================
This step is about finally creating an exported registry
interface. This could not be done by simply export the existing
internal implementation because the interface would be _way_ too
fat. This commit introduces a qutie lean `Registry` interface
(compared to the previous interval implementation). The functions that
act on the default registry are retained (with very few exceptions) so
that most use cases won't see a change. However, several of those are
deprecated now to clean up the namespace in the future.
The default registry is kept in the public variable
`DefaultRegistry`. This follows the example of the http package in the
standard library (cf. `http.DefaultServeMux`, `http.DefaultClient`)
with the same implications. (This pattern is somewhat disputed within
the Go community but I chose to go with the devil you know instead of
creating something more complex or even disallowing any changes to the
default registry. The current approach gives everybody the freedom to
not touch DefaultRegistry or to do everything with a custom registry
to play save.)
Another important part in making the registry lean is the extraction
of the HTTP exposition, which also allows for customization of the
HTTP exposition. Note that the separation of metric collection and
exposition has the side effect that managing the MetricFamily and
Metric protobuf objects in a free-list or pool isn't really feasible
anymore. By now (with better GC in more recent Go versions), the
returns were anyway dimisishing. To be effective at all, scrapes had
to happen more often than GC cycles, and even then most elements of
the protobufs (everything excetp the MetricFamily and Metric structs
themselves) would still cause allocation churn. In a future breaking
change, the signature of the Write method in the Metric interface will
be adjusted accordingly. In this commit, avoiding breakage is more
important.
The following issues are fixed by this commit (some solved "on the
fly" now that I was touching the code anyway and it would have been
stupid to port the bugs):
https://github.com/prometheus/client_golang/issues/46
https://github.com/prometheus/client_golang/issues/100
https://github.com/prometheus/client_golang/issues/170
https://github.com/prometheus/client_golang/issues/205
Documentation including examples have been amended as required.
What future changes does this commit enable?
============================================
The following items are not yet implemented, but this commit opens the
possibility of implementing these independently.
- The separation of the HTTP exposition allows the implementation of
other exposition methods based on the Registry interface, as known
from other Prometheus client libraries, e.g. sending the metrics to
Graphite.
Cf. https://github.com/prometheus/client_golang/issues/197
- The public `Registry` interface allows the implementation of
convenience tools for testing metrics collection. Those tools can
inspect the collected MetricFamily protobufs and compare them to
expectation. Also, tests can use their own testing instance of a
registry.
Cf. https://github.com/prometheus/client_golang/issues/58
Notable non-goals of this commit
================================
Non-goals that will be tackled later
------------------------------------
The following two issues are quite closely connected to the changes in
this commit but the line has been drawn deliberately to address them
in later steps of the refurbishment:
- `InstrumentHandler` has many known problems. The plan is to create a
saner way to conveniently intrument HTTP handlers and remove the old
`InstrumentHandler` altogether. To keep breakage low for now, even
the default handler to expose metrics is still using the old
`InstrumentHandler`. This leads to weird naming inconsistencies but
I have deemed it better to not break the world right now but do it
in the change that provides better ways of instrumenting HTTP
handlers.
Cf. https://github.com/prometheus/client_golang/issues/200
- There is work underway to make the whole handling of metric
descriptors (`Desc`) more intuitive and transparent for the user
(including an ability for less strict checking,
cf. https://github.com/prometheus/client_golang/issues/47). That's
quite invasive from the perspective of the internal code, namely the
registry. I deliberately kept those changes out of this commit.
- While this commit adds new external dependency, the effort to vendor
anything within the library that is not visible in any exported
types will have to be done later.
Non-goals that _might_ be tackled later
---------------------------------------
There is a strong and understandable urge to divide the `prometheus`
package into a number of sub-packages (like `registry`, `collectors`,
`http`, `metrics`, …). However, to not run into a multitude of
circular import chains, this would need to break every single existing
usage of the library. (As just one example, if the ubiquitious
`prometheus.MustRegister` (with more than 2,000 uses on GitHub alone)
is kept in the `prometheus` package, but the other registry concerns
go into a new `registry` package, then the `prometheus` package would
import the `registry` package (to call the actual register method),
while at the same time the `registry` package needs to import the
`prometheus` package to access `Collector`, `Metric`, `Desc` and
more. If we moved `MustRegister` into the `registry` package,
thousands of code lines would have to be fixed (which would be easy if
the world was a mono repo, but it is not). If we moved everything else
the proposed registry package needs into packages of their own, we
would break thousands of other code lines.)
The main problem is really the top-level functions like
`MustRegister`, `Handler`, …, which effectively pull everything into
one package. Those functions are however very convenient for the easy
and very frequent use-cases.
This problem has to be revisited later.
For now, I'm trying to keep the amount of exported names in the
package as low as possible (e.g. I unexported expvarCollector in this
commit because the NewExpvarCollector constructor is enough to export,
and it is now consistent with other collectors, like the goCollector).
Non-goals that won't be tackled anytime soon
--------------------------------------------
Something that I have played with a lot is "streaming collection",
i.e. allow an implementation of the `Registry` interface that collects
metrics incrementally and serves them while doing so. As it has turned
out, this has many many issues and makes the `Registry` interface very
clunky. Eventually, I made the call that it is unlikely we will really
implement streaming collection; and making the interface more clunky
for something that might not even happen is really a big no-no. Note
that the `Registry` interface only creates the in-memory
representation of the metric family protobufs in one go. The
serializaton onto the wire can still be handled in a streaming fashion
(which hasn't been done so far, without causing any trouble, but might
be done in the future without breaking any interfaces).
What are the breaking changes?
==============================
- Signatures of functions pushing to Pushgateway have changed to allow
arbitrary grouping (which was planned for a long time anyway, and
now that I had to work on the Push code anyway for the registry
refurbishment, I finally did it,
cf. https://github.com/prometheus/client_golang/issues/100).
With the gained insight that pushing to the default registry is almost
never the right thing, and now that we are breaking the Push call
anyway, all the Push functions were moved to their own package,
which cleans up the namespace and is more idiomatic (pushing
Collectors is now literally done by `push.Collectors(...)`).
- The registry is doing more consistency checks by default now. Past
creators of inconsistent metrics could have masked the problem by
not setting `EnableCollectChecks`. Those inconsistencies will now be
detected. (But note that a "best effort" metrics collection is now
possible with `HandlerOpts.ErrorHandling = ContinueOnError`.)
- `EnableCollectChecks` is gone. The registry is now performing some
of those checks anyway (see previous item), and a registry with all
of those checks can now be created with `NewPedanticRegistry` (only
used for testing).
- `PanicOnCollectError` is gone. This behavior can now be configured
when creating a custom HTTP handler.
2016-07-20 18:11:14 +03:00
|
|
|
"compress/gzip"
|
|
|
|
"fmt"
|
2015-06-05 00:24:54 +03:00
|
|
|
"io"
|
|
|
|
"net"
|
2014-05-07 22:08:33 +04:00
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
Create a public registry interface and separate out HTTP exposition
General context and approch
===========================
This is the first part of the long awaited wider refurbishment of
`client_golang/prometheus/...`. After a lot of struggling, I decided
to not go for one breaking big-bang, but cut things into smaller steps
after all, mostly to keep the changes manageable and easy to
review. I'm aiming for having the invasive breaking changes
concentrated in as few steps as possible (ideally one). Some steps
will not be breaking at all, but typically there will be breaking
changes that only affect quite special cases so that 95+% of users
will not be affected. This first step is an example for that, see
details below.
What's happening in this commit?
================================
This step is about finally creating an exported registry
interface. This could not be done by simply export the existing
internal implementation because the interface would be _way_ too
fat. This commit introduces a qutie lean `Registry` interface
(compared to the previous interval implementation). The functions that
act on the default registry are retained (with very few exceptions) so
that most use cases won't see a change. However, several of those are
deprecated now to clean up the namespace in the future.
The default registry is kept in the public variable
`DefaultRegistry`. This follows the example of the http package in the
standard library (cf. `http.DefaultServeMux`, `http.DefaultClient`)
with the same implications. (This pattern is somewhat disputed within
the Go community but I chose to go with the devil you know instead of
creating something more complex or even disallowing any changes to the
default registry. The current approach gives everybody the freedom to
not touch DefaultRegistry or to do everything with a custom registry
to play save.)
Another important part in making the registry lean is the extraction
of the HTTP exposition, which also allows for customization of the
HTTP exposition. Note that the separation of metric collection and
exposition has the side effect that managing the MetricFamily and
Metric protobuf objects in a free-list or pool isn't really feasible
anymore. By now (with better GC in more recent Go versions), the
returns were anyway dimisishing. To be effective at all, scrapes had
to happen more often than GC cycles, and even then most elements of
the protobufs (everything excetp the MetricFamily and Metric structs
themselves) would still cause allocation churn. In a future breaking
change, the signature of the Write method in the Metric interface will
be adjusted accordingly. In this commit, avoiding breakage is more
important.
The following issues are fixed by this commit (some solved "on the
fly" now that I was touching the code anyway and it would have been
stupid to port the bugs):
https://github.com/prometheus/client_golang/issues/46
https://github.com/prometheus/client_golang/issues/100
https://github.com/prometheus/client_golang/issues/170
https://github.com/prometheus/client_golang/issues/205
Documentation including examples have been amended as required.
What future changes does this commit enable?
============================================
The following items are not yet implemented, but this commit opens the
possibility of implementing these independently.
- The separation of the HTTP exposition allows the implementation of
other exposition methods based on the Registry interface, as known
from other Prometheus client libraries, e.g. sending the metrics to
Graphite.
Cf. https://github.com/prometheus/client_golang/issues/197
- The public `Registry` interface allows the implementation of
convenience tools for testing metrics collection. Those tools can
inspect the collected MetricFamily protobufs and compare them to
expectation. Also, tests can use their own testing instance of a
registry.
Cf. https://github.com/prometheus/client_golang/issues/58
Notable non-goals of this commit
================================
Non-goals that will be tackled later
------------------------------------
The following two issues are quite closely connected to the changes in
this commit but the line has been drawn deliberately to address them
in later steps of the refurbishment:
- `InstrumentHandler` has many known problems. The plan is to create a
saner way to conveniently intrument HTTP handlers and remove the old
`InstrumentHandler` altogether. To keep breakage low for now, even
the default handler to expose metrics is still using the old
`InstrumentHandler`. This leads to weird naming inconsistencies but
I have deemed it better to not break the world right now but do it
in the change that provides better ways of instrumenting HTTP
handlers.
Cf. https://github.com/prometheus/client_golang/issues/200
- There is work underway to make the whole handling of metric
descriptors (`Desc`) more intuitive and transparent for the user
(including an ability for less strict checking,
cf. https://github.com/prometheus/client_golang/issues/47). That's
quite invasive from the perspective of the internal code, namely the
registry. I deliberately kept those changes out of this commit.
- While this commit adds new external dependency, the effort to vendor
anything within the library that is not visible in any exported
types will have to be done later.
Non-goals that _might_ be tackled later
---------------------------------------
There is a strong and understandable urge to divide the `prometheus`
package into a number of sub-packages (like `registry`, `collectors`,
`http`, `metrics`, …). However, to not run into a multitude of
circular import chains, this would need to break every single existing
usage of the library. (As just one example, if the ubiquitious
`prometheus.MustRegister` (with more than 2,000 uses on GitHub alone)
is kept in the `prometheus` package, but the other registry concerns
go into a new `registry` package, then the `prometheus` package would
import the `registry` package (to call the actual register method),
while at the same time the `registry` package needs to import the
`prometheus` package to access `Collector`, `Metric`, `Desc` and
more. If we moved `MustRegister` into the `registry` package,
thousands of code lines would have to be fixed (which would be easy if
the world was a mono repo, but it is not). If we moved everything else
the proposed registry package needs into packages of their own, we
would break thousands of other code lines.)
The main problem is really the top-level functions like
`MustRegister`, `Handler`, …, which effectively pull everything into
one package. Those functions are however very convenient for the easy
and very frequent use-cases.
This problem has to be revisited later.
For now, I'm trying to keep the amount of exported names in the
package as low as possible (e.g. I unexported expvarCollector in this
commit because the NewExpvarCollector constructor is enough to export,
and it is now consistent with other collectors, like the goCollector).
Non-goals that won't be tackled anytime soon
--------------------------------------------
Something that I have played with a lot is "streaming collection",
i.e. allow an implementation of the `Registry` interface that collects
metrics incrementally and serves them while doing so. As it has turned
out, this has many many issues and makes the `Registry` interface very
clunky. Eventually, I made the call that it is unlikely we will really
implement streaming collection; and making the interface more clunky
for something that might not even happen is really a big no-no. Note
that the `Registry` interface only creates the in-memory
representation of the metric family protobufs in one go. The
serializaton onto the wire can still be handled in a streaming fashion
(which hasn't been done so far, without causing any trouble, but might
be done in the future without breaking any interfaces).
What are the breaking changes?
==============================
- Signatures of functions pushing to Pushgateway have changed to allow
arbitrary grouping (which was planned for a long time anyway, and
now that I had to work on the Push code anyway for the registry
refurbishment, I finally did it,
cf. https://github.com/prometheus/client_golang/issues/100).
With the gained insight that pushing to the default registry is almost
never the right thing, and now that we are breaking the Push call
anyway, all the Push functions were moved to their own package,
which cleans up the namespace and is more idiomatic (pushing
Collectors is now literally done by `push.Collectors(...)`).
- The registry is doing more consistency checks by default now. Past
creators of inconsistent metrics could have masked the problem by
not setting `EnableCollectChecks`. Those inconsistencies will now be
detected. (But note that a "best effort" metrics collection is now
possible with `HandlerOpts.ErrorHandling = ContinueOnError`.)
- `EnableCollectChecks` is gone. The registry is now performing some
of those checks anyway (see previous item), and a registry with all
of those checks can now be created with `NewPedanticRegistry` (only
used for testing).
- `PanicOnCollectError` is gone. This behavior can now be configured
when creating a custom HTTP handler.
2016-07-20 18:11:14 +03:00
|
|
|
|
|
|
|
"github.com/prometheus/common/expfmt"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
contentTypeHeader = "Content-Type"
|
|
|
|
contentLengthHeader = "Content-Length"
|
|
|
|
contentEncodingHeader = "Content-Encoding"
|
|
|
|
acceptEncodingHeader = "Accept-Encoding"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Handler returns an HTTP handler for the DefaultRegistry. It is
|
|
|
|
// already instrumented with InstrumentHandler (using "prometheus" as handler
|
|
|
|
// name).
|
|
|
|
//
|
|
|
|
// Please note the issues described in the doc comment of InstrumentHandler. You
|
|
|
|
// might want to consider using UninstrumentedHandler instead. In fact, the
|
|
|
|
// instrumentation of the handler is DEPRECATED. In future versions of this
|
|
|
|
// package, the Handler function will return an uninstrumented handler, and the
|
|
|
|
// UninstrumentedHandler function will be removed.
|
|
|
|
//
|
|
|
|
// The returned Handler is using the same HandlerOpts as the Handler returned by
|
|
|
|
// UninstrumentedHandler. See there for details.
|
|
|
|
func Handler() http.Handler {
|
|
|
|
return InstrumentHandler("prometheus", UninstrumentedHandler())
|
|
|
|
}
|
|
|
|
|
|
|
|
// UninstrumentedHandler returns an HTTP handler for the DefaultRegistry. The
|
|
|
|
// Handler uses the default HandlerOpts, i.e. report the first error as an HTTP
|
|
|
|
// error, no error logging, and compression if requested by the client.
|
|
|
|
//
|
|
|
|
// If you want to create a Handler for the DefaultRegistry with different
|
|
|
|
// HandlerOpts, create it with HandlerFor with the DefaultRegistry and your
|
|
|
|
// desired HandlerOpts.
|
|
|
|
//
|
|
|
|
// Note that in future versions of this package, UninstrumentedHandler will be
|
|
|
|
// replaced by Handler (which will then return an uninstrumented handler, see
|
|
|
|
// there for details).
|
|
|
|
func UninstrumentedHandler() http.Handler {
|
|
|
|
return HandlerFor(DefaultRegistry, HandlerOpts{})
|
|
|
|
}
|
|
|
|
|
2016-08-03 01:41:51 +03:00
|
|
|
// HandlerFor returns an http.Handler for the provided Deliverer. The behavior ef
|
Create a public registry interface and separate out HTTP exposition
General context and approch
===========================
This is the first part of the long awaited wider refurbishment of
`client_golang/prometheus/...`. After a lot of struggling, I decided
to not go for one breaking big-bang, but cut things into smaller steps
after all, mostly to keep the changes manageable and easy to
review. I'm aiming for having the invasive breaking changes
concentrated in as few steps as possible (ideally one). Some steps
will not be breaking at all, but typically there will be breaking
changes that only affect quite special cases so that 95+% of users
will not be affected. This first step is an example for that, see
details below.
What's happening in this commit?
================================
This step is about finally creating an exported registry
interface. This could not be done by simply export the existing
internal implementation because the interface would be _way_ too
fat. This commit introduces a qutie lean `Registry` interface
(compared to the previous interval implementation). The functions that
act on the default registry are retained (with very few exceptions) so
that most use cases won't see a change. However, several of those are
deprecated now to clean up the namespace in the future.
The default registry is kept in the public variable
`DefaultRegistry`. This follows the example of the http package in the
standard library (cf. `http.DefaultServeMux`, `http.DefaultClient`)
with the same implications. (This pattern is somewhat disputed within
the Go community but I chose to go with the devil you know instead of
creating something more complex or even disallowing any changes to the
default registry. The current approach gives everybody the freedom to
not touch DefaultRegistry or to do everything with a custom registry
to play save.)
Another important part in making the registry lean is the extraction
of the HTTP exposition, which also allows for customization of the
HTTP exposition. Note that the separation of metric collection and
exposition has the side effect that managing the MetricFamily and
Metric protobuf objects in a free-list or pool isn't really feasible
anymore. By now (with better GC in more recent Go versions), the
returns were anyway dimisishing. To be effective at all, scrapes had
to happen more often than GC cycles, and even then most elements of
the protobufs (everything excetp the MetricFamily and Metric structs
themselves) would still cause allocation churn. In a future breaking
change, the signature of the Write method in the Metric interface will
be adjusted accordingly. In this commit, avoiding breakage is more
important.
The following issues are fixed by this commit (some solved "on the
fly" now that I was touching the code anyway and it would have been
stupid to port the bugs):
https://github.com/prometheus/client_golang/issues/46
https://github.com/prometheus/client_golang/issues/100
https://github.com/prometheus/client_golang/issues/170
https://github.com/prometheus/client_golang/issues/205
Documentation including examples have been amended as required.
What future changes does this commit enable?
============================================
The following items are not yet implemented, but this commit opens the
possibility of implementing these independently.
- The separation of the HTTP exposition allows the implementation of
other exposition methods based on the Registry interface, as known
from other Prometheus client libraries, e.g. sending the metrics to
Graphite.
Cf. https://github.com/prometheus/client_golang/issues/197
- The public `Registry` interface allows the implementation of
convenience tools for testing metrics collection. Those tools can
inspect the collected MetricFamily protobufs and compare them to
expectation. Also, tests can use their own testing instance of a
registry.
Cf. https://github.com/prometheus/client_golang/issues/58
Notable non-goals of this commit
================================
Non-goals that will be tackled later
------------------------------------
The following two issues are quite closely connected to the changes in
this commit but the line has been drawn deliberately to address them
in later steps of the refurbishment:
- `InstrumentHandler` has many known problems. The plan is to create a
saner way to conveniently intrument HTTP handlers and remove the old
`InstrumentHandler` altogether. To keep breakage low for now, even
the default handler to expose metrics is still using the old
`InstrumentHandler`. This leads to weird naming inconsistencies but
I have deemed it better to not break the world right now but do it
in the change that provides better ways of instrumenting HTTP
handlers.
Cf. https://github.com/prometheus/client_golang/issues/200
- There is work underway to make the whole handling of metric
descriptors (`Desc`) more intuitive and transparent for the user
(including an ability for less strict checking,
cf. https://github.com/prometheus/client_golang/issues/47). That's
quite invasive from the perspective of the internal code, namely the
registry. I deliberately kept those changes out of this commit.
- While this commit adds new external dependency, the effort to vendor
anything within the library that is not visible in any exported
types will have to be done later.
Non-goals that _might_ be tackled later
---------------------------------------
There is a strong and understandable urge to divide the `prometheus`
package into a number of sub-packages (like `registry`, `collectors`,
`http`, `metrics`, …). However, to not run into a multitude of
circular import chains, this would need to break every single existing
usage of the library. (As just one example, if the ubiquitious
`prometheus.MustRegister` (with more than 2,000 uses on GitHub alone)
is kept in the `prometheus` package, but the other registry concerns
go into a new `registry` package, then the `prometheus` package would
import the `registry` package (to call the actual register method),
while at the same time the `registry` package needs to import the
`prometheus` package to access `Collector`, `Metric`, `Desc` and
more. If we moved `MustRegister` into the `registry` package,
thousands of code lines would have to be fixed (which would be easy if
the world was a mono repo, but it is not). If we moved everything else
the proposed registry package needs into packages of their own, we
would break thousands of other code lines.)
The main problem is really the top-level functions like
`MustRegister`, `Handler`, …, which effectively pull everything into
one package. Those functions are however very convenient for the easy
and very frequent use-cases.
This problem has to be revisited later.
For now, I'm trying to keep the amount of exported names in the
package as low as possible (e.g. I unexported expvarCollector in this
commit because the NewExpvarCollector constructor is enough to export,
and it is now consistent with other collectors, like the goCollector).
Non-goals that won't be tackled anytime soon
--------------------------------------------
Something that I have played with a lot is "streaming collection",
i.e. allow an implementation of the `Registry` interface that collects
metrics incrementally and serves them while doing so. As it has turned
out, this has many many issues and makes the `Registry` interface very
clunky. Eventually, I made the call that it is unlikely we will really
implement streaming collection; and making the interface more clunky
for something that might not even happen is really a big no-no. Note
that the `Registry` interface only creates the in-memory
representation of the metric family protobufs in one go. The
serializaton onto the wire can still be handled in a streaming fashion
(which hasn't been done so far, without causing any trouble, but might
be done in the future without breaking any interfaces).
What are the breaking changes?
==============================
- Signatures of functions pushing to Pushgateway have changed to allow
arbitrary grouping (which was planned for a long time anyway, and
now that I had to work on the Push code anyway for the registry
refurbishment, I finally did it,
cf. https://github.com/prometheus/client_golang/issues/100).
With the gained insight that pushing to the default registry is almost
never the right thing, and now that we are breaking the Push call
anyway, all the Push functions were moved to their own package,
which cleans up the namespace and is more idiomatic (pushing
Collectors is now literally done by `push.Collectors(...)`).
- The registry is doing more consistency checks by default now. Past
creators of inconsistent metrics could have masked the problem by
not setting `EnableCollectChecks`. Those inconsistencies will now be
detected. (But note that a "best effort" metrics collection is now
possible with `HandlerOpts.ErrorHandling = ContinueOnError`.)
- `EnableCollectChecks` is gone. The registry is now performing some
of those checks anyway (see previous item), and a registry with all
of those checks can now be created with `NewPedanticRegistry` (only
used for testing).
- `PanicOnCollectError` is gone. This behavior can now be configured
when creating a custom HTTP handler.
2016-07-20 18:11:14 +03:00
|
|
|
// the Handler is defined by the provided HandlerOpts. The Handler is NOT
|
|
|
|
// instrumented with InstrumentHandler.
|
2016-08-03 01:41:51 +03:00
|
|
|
func HandlerFor(reg Deliverer, opts HandlerOpts) http.Handler {
|
Create a public registry interface and separate out HTTP exposition
General context and approch
===========================
This is the first part of the long awaited wider refurbishment of
`client_golang/prometheus/...`. After a lot of struggling, I decided
to not go for one breaking big-bang, but cut things into smaller steps
after all, mostly to keep the changes manageable and easy to
review. I'm aiming for having the invasive breaking changes
concentrated in as few steps as possible (ideally one). Some steps
will not be breaking at all, but typically there will be breaking
changes that only affect quite special cases so that 95+% of users
will not be affected. This first step is an example for that, see
details below.
What's happening in this commit?
================================
This step is about finally creating an exported registry
interface. This could not be done by simply export the existing
internal implementation because the interface would be _way_ too
fat. This commit introduces a qutie lean `Registry` interface
(compared to the previous interval implementation). The functions that
act on the default registry are retained (with very few exceptions) so
that most use cases won't see a change. However, several of those are
deprecated now to clean up the namespace in the future.
The default registry is kept in the public variable
`DefaultRegistry`. This follows the example of the http package in the
standard library (cf. `http.DefaultServeMux`, `http.DefaultClient`)
with the same implications. (This pattern is somewhat disputed within
the Go community but I chose to go with the devil you know instead of
creating something more complex or even disallowing any changes to the
default registry. The current approach gives everybody the freedom to
not touch DefaultRegistry or to do everything with a custom registry
to play save.)
Another important part in making the registry lean is the extraction
of the HTTP exposition, which also allows for customization of the
HTTP exposition. Note that the separation of metric collection and
exposition has the side effect that managing the MetricFamily and
Metric protobuf objects in a free-list or pool isn't really feasible
anymore. By now (with better GC in more recent Go versions), the
returns were anyway dimisishing. To be effective at all, scrapes had
to happen more often than GC cycles, and even then most elements of
the protobufs (everything excetp the MetricFamily and Metric structs
themselves) would still cause allocation churn. In a future breaking
change, the signature of the Write method in the Metric interface will
be adjusted accordingly. In this commit, avoiding breakage is more
important.
The following issues are fixed by this commit (some solved "on the
fly" now that I was touching the code anyway and it would have been
stupid to port the bugs):
https://github.com/prometheus/client_golang/issues/46
https://github.com/prometheus/client_golang/issues/100
https://github.com/prometheus/client_golang/issues/170
https://github.com/prometheus/client_golang/issues/205
Documentation including examples have been amended as required.
What future changes does this commit enable?
============================================
The following items are not yet implemented, but this commit opens the
possibility of implementing these independently.
- The separation of the HTTP exposition allows the implementation of
other exposition methods based on the Registry interface, as known
from other Prometheus client libraries, e.g. sending the metrics to
Graphite.
Cf. https://github.com/prometheus/client_golang/issues/197
- The public `Registry` interface allows the implementation of
convenience tools for testing metrics collection. Those tools can
inspect the collected MetricFamily protobufs and compare them to
expectation. Also, tests can use their own testing instance of a
registry.
Cf. https://github.com/prometheus/client_golang/issues/58
Notable non-goals of this commit
================================
Non-goals that will be tackled later
------------------------------------
The following two issues are quite closely connected to the changes in
this commit but the line has been drawn deliberately to address them
in later steps of the refurbishment:
- `InstrumentHandler` has many known problems. The plan is to create a
saner way to conveniently intrument HTTP handlers and remove the old
`InstrumentHandler` altogether. To keep breakage low for now, even
the default handler to expose metrics is still using the old
`InstrumentHandler`. This leads to weird naming inconsistencies but
I have deemed it better to not break the world right now but do it
in the change that provides better ways of instrumenting HTTP
handlers.
Cf. https://github.com/prometheus/client_golang/issues/200
- There is work underway to make the whole handling of metric
descriptors (`Desc`) more intuitive and transparent for the user
(including an ability for less strict checking,
cf. https://github.com/prometheus/client_golang/issues/47). That's
quite invasive from the perspective of the internal code, namely the
registry. I deliberately kept those changes out of this commit.
- While this commit adds new external dependency, the effort to vendor
anything within the library that is not visible in any exported
types will have to be done later.
Non-goals that _might_ be tackled later
---------------------------------------
There is a strong and understandable urge to divide the `prometheus`
package into a number of sub-packages (like `registry`, `collectors`,
`http`, `metrics`, …). However, to not run into a multitude of
circular import chains, this would need to break every single existing
usage of the library. (As just one example, if the ubiquitious
`prometheus.MustRegister` (with more than 2,000 uses on GitHub alone)
is kept in the `prometheus` package, but the other registry concerns
go into a new `registry` package, then the `prometheus` package would
import the `registry` package (to call the actual register method),
while at the same time the `registry` package needs to import the
`prometheus` package to access `Collector`, `Metric`, `Desc` and
more. If we moved `MustRegister` into the `registry` package,
thousands of code lines would have to be fixed (which would be easy if
the world was a mono repo, but it is not). If we moved everything else
the proposed registry package needs into packages of their own, we
would break thousands of other code lines.)
The main problem is really the top-level functions like
`MustRegister`, `Handler`, …, which effectively pull everything into
one package. Those functions are however very convenient for the easy
and very frequent use-cases.
This problem has to be revisited later.
For now, I'm trying to keep the amount of exported names in the
package as low as possible (e.g. I unexported expvarCollector in this
commit because the NewExpvarCollector constructor is enough to export,
and it is now consistent with other collectors, like the goCollector).
Non-goals that won't be tackled anytime soon
--------------------------------------------
Something that I have played with a lot is "streaming collection",
i.e. allow an implementation of the `Registry` interface that collects
metrics incrementally and serves them while doing so. As it has turned
out, this has many many issues and makes the `Registry` interface very
clunky. Eventually, I made the call that it is unlikely we will really
implement streaming collection; and making the interface more clunky
for something that might not even happen is really a big no-no. Note
that the `Registry` interface only creates the in-memory
representation of the metric family protobufs in one go. The
serializaton onto the wire can still be handled in a streaming fashion
(which hasn't been done so far, without causing any trouble, but might
be done in the future without breaking any interfaces).
What are the breaking changes?
==============================
- Signatures of functions pushing to Pushgateway have changed to allow
arbitrary grouping (which was planned for a long time anyway, and
now that I had to work on the Push code anyway for the registry
refurbishment, I finally did it,
cf. https://github.com/prometheus/client_golang/issues/100).
With the gained insight that pushing to the default registry is almost
never the right thing, and now that we are breaking the Push call
anyway, all the Push functions were moved to their own package,
which cleans up the namespace and is more idiomatic (pushing
Collectors is now literally done by `push.Collectors(...)`).
- The registry is doing more consistency checks by default now. Past
creators of inconsistent metrics could have masked the problem by
not setting `EnableCollectChecks`. Those inconsistencies will now be
detected. (But note that a "best effort" metrics collection is now
possible with `HandlerOpts.ErrorHandling = ContinueOnError`.)
- `EnableCollectChecks` is gone. The registry is now performing some
of those checks anyway (see previous item), and a registry with all
of those checks can now be created with `NewPedanticRegistry` (only
used for testing).
- `PanicOnCollectError` is gone. This behavior can now be configured
when creating a custom HTTP handler.
2016-07-20 18:11:14 +03:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
2016-08-03 01:41:51 +03:00
|
|
|
mfs, err := reg.Deliver()
|
Create a public registry interface and separate out HTTP exposition
General context and approch
===========================
This is the first part of the long awaited wider refurbishment of
`client_golang/prometheus/...`. After a lot of struggling, I decided
to not go for one breaking big-bang, but cut things into smaller steps
after all, mostly to keep the changes manageable and easy to
review. I'm aiming for having the invasive breaking changes
concentrated in as few steps as possible (ideally one). Some steps
will not be breaking at all, but typically there will be breaking
changes that only affect quite special cases so that 95+% of users
will not be affected. This first step is an example for that, see
details below.
What's happening in this commit?
================================
This step is about finally creating an exported registry
interface. This could not be done by simply export the existing
internal implementation because the interface would be _way_ too
fat. This commit introduces a qutie lean `Registry` interface
(compared to the previous interval implementation). The functions that
act on the default registry are retained (with very few exceptions) so
that most use cases won't see a change. However, several of those are
deprecated now to clean up the namespace in the future.
The default registry is kept in the public variable
`DefaultRegistry`. This follows the example of the http package in the
standard library (cf. `http.DefaultServeMux`, `http.DefaultClient`)
with the same implications. (This pattern is somewhat disputed within
the Go community but I chose to go with the devil you know instead of
creating something more complex or even disallowing any changes to the
default registry. The current approach gives everybody the freedom to
not touch DefaultRegistry or to do everything with a custom registry
to play save.)
Another important part in making the registry lean is the extraction
of the HTTP exposition, which also allows for customization of the
HTTP exposition. Note that the separation of metric collection and
exposition has the side effect that managing the MetricFamily and
Metric protobuf objects in a free-list or pool isn't really feasible
anymore. By now (with better GC in more recent Go versions), the
returns were anyway dimisishing. To be effective at all, scrapes had
to happen more often than GC cycles, and even then most elements of
the protobufs (everything excetp the MetricFamily and Metric structs
themselves) would still cause allocation churn. In a future breaking
change, the signature of the Write method in the Metric interface will
be adjusted accordingly. In this commit, avoiding breakage is more
important.
The following issues are fixed by this commit (some solved "on the
fly" now that I was touching the code anyway and it would have been
stupid to port the bugs):
https://github.com/prometheus/client_golang/issues/46
https://github.com/prometheus/client_golang/issues/100
https://github.com/prometheus/client_golang/issues/170
https://github.com/prometheus/client_golang/issues/205
Documentation including examples have been amended as required.
What future changes does this commit enable?
============================================
The following items are not yet implemented, but this commit opens the
possibility of implementing these independently.
- The separation of the HTTP exposition allows the implementation of
other exposition methods based on the Registry interface, as known
from other Prometheus client libraries, e.g. sending the metrics to
Graphite.
Cf. https://github.com/prometheus/client_golang/issues/197
- The public `Registry` interface allows the implementation of
convenience tools for testing metrics collection. Those tools can
inspect the collected MetricFamily protobufs and compare them to
expectation. Also, tests can use their own testing instance of a
registry.
Cf. https://github.com/prometheus/client_golang/issues/58
Notable non-goals of this commit
================================
Non-goals that will be tackled later
------------------------------------
The following two issues are quite closely connected to the changes in
this commit but the line has been drawn deliberately to address them
in later steps of the refurbishment:
- `InstrumentHandler` has many known problems. The plan is to create a
saner way to conveniently intrument HTTP handlers and remove the old
`InstrumentHandler` altogether. To keep breakage low for now, even
the default handler to expose metrics is still using the old
`InstrumentHandler`. This leads to weird naming inconsistencies but
I have deemed it better to not break the world right now but do it
in the change that provides better ways of instrumenting HTTP
handlers.
Cf. https://github.com/prometheus/client_golang/issues/200
- There is work underway to make the whole handling of metric
descriptors (`Desc`) more intuitive and transparent for the user
(including an ability for less strict checking,
cf. https://github.com/prometheus/client_golang/issues/47). That's
quite invasive from the perspective of the internal code, namely the
registry. I deliberately kept those changes out of this commit.
- While this commit adds new external dependency, the effort to vendor
anything within the library that is not visible in any exported
types will have to be done later.
Non-goals that _might_ be tackled later
---------------------------------------
There is a strong and understandable urge to divide the `prometheus`
package into a number of sub-packages (like `registry`, `collectors`,
`http`, `metrics`, …). However, to not run into a multitude of
circular import chains, this would need to break every single existing
usage of the library. (As just one example, if the ubiquitious
`prometheus.MustRegister` (with more than 2,000 uses on GitHub alone)
is kept in the `prometheus` package, but the other registry concerns
go into a new `registry` package, then the `prometheus` package would
import the `registry` package (to call the actual register method),
while at the same time the `registry` package needs to import the
`prometheus` package to access `Collector`, `Metric`, `Desc` and
more. If we moved `MustRegister` into the `registry` package,
thousands of code lines would have to be fixed (which would be easy if
the world was a mono repo, but it is not). If we moved everything else
the proposed registry package needs into packages of their own, we
would break thousands of other code lines.)
The main problem is really the top-level functions like
`MustRegister`, `Handler`, …, which effectively pull everything into
one package. Those functions are however very convenient for the easy
and very frequent use-cases.
This problem has to be revisited later.
For now, I'm trying to keep the amount of exported names in the
package as low as possible (e.g. I unexported expvarCollector in this
commit because the NewExpvarCollector constructor is enough to export,
and it is now consistent with other collectors, like the goCollector).
Non-goals that won't be tackled anytime soon
--------------------------------------------
Something that I have played with a lot is "streaming collection",
i.e. allow an implementation of the `Registry` interface that collects
metrics incrementally and serves them while doing so. As it has turned
out, this has many many issues and makes the `Registry` interface very
clunky. Eventually, I made the call that it is unlikely we will really
implement streaming collection; and making the interface more clunky
for something that might not even happen is really a big no-no. Note
that the `Registry` interface only creates the in-memory
representation of the metric family protobufs in one go. The
serializaton onto the wire can still be handled in a streaming fashion
(which hasn't been done so far, without causing any trouble, but might
be done in the future without breaking any interfaces).
What are the breaking changes?
==============================
- Signatures of functions pushing to Pushgateway have changed to allow
arbitrary grouping (which was planned for a long time anyway, and
now that I had to work on the Push code anyway for the registry
refurbishment, I finally did it,
cf. https://github.com/prometheus/client_golang/issues/100).
With the gained insight that pushing to the default registry is almost
never the right thing, and now that we are breaking the Push call
anyway, all the Push functions were moved to their own package,
which cleans up the namespace and is more idiomatic (pushing
Collectors is now literally done by `push.Collectors(...)`).
- The registry is doing more consistency checks by default now. Past
creators of inconsistent metrics could have masked the problem by
not setting `EnableCollectChecks`. Those inconsistencies will now be
detected. (But note that a "best effort" metrics collection is now
possible with `HandlerOpts.ErrorHandling = ContinueOnError`.)
- `EnableCollectChecks` is gone. The registry is now performing some
of those checks anyway (see previous item), and a registry with all
of those checks can now be created with `NewPedanticRegistry` (only
used for testing).
- `PanicOnCollectError` is gone. This behavior can now be configured
when creating a custom HTTP handler.
2016-07-20 18:11:14 +03:00
|
|
|
if err != nil {
|
|
|
|
if opts.ErrorLog != nil {
|
|
|
|
opts.ErrorLog.Println("error collecting metrics:", err)
|
|
|
|
}
|
|
|
|
switch opts.ErrorHandling {
|
|
|
|
case PanicOnError:
|
|
|
|
panic(err)
|
|
|
|
case ContinueOnError:
|
|
|
|
if len(mfs) == 0 {
|
|
|
|
http.Error(w, "No metrics collected, last error:\n\n"+err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case HTTPErrorOnError:
|
|
|
|
http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
contentType := expfmt.Negotiate(req.Header)
|
|
|
|
buf := getBuf()
|
|
|
|
defer giveBuf(buf)
|
|
|
|
writer, encoding := decorateWriter(req, buf, opts.DisableCompression)
|
|
|
|
enc := expfmt.NewEncoder(writer, contentType)
|
|
|
|
var lastErr error
|
|
|
|
for _, mf := range mfs {
|
|
|
|
if err := enc.Encode(mf); err != nil {
|
|
|
|
lastErr = err
|
|
|
|
if opts.ErrorLog != nil {
|
|
|
|
opts.ErrorLog.Println("error encoding metric family:", err)
|
|
|
|
}
|
|
|
|
switch opts.ErrorHandling {
|
|
|
|
case PanicOnError:
|
|
|
|
panic(err)
|
|
|
|
case ContinueOnError:
|
|
|
|
// Handled later.
|
|
|
|
case HTTPErrorOnError:
|
|
|
|
http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if closer, ok := writer.(io.Closer); ok {
|
|
|
|
closer.Close()
|
|
|
|
}
|
|
|
|
if lastErr != nil && buf.Len() == 0 {
|
|
|
|
http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
header := w.Header()
|
|
|
|
header.Set(contentTypeHeader, string(contentType))
|
|
|
|
header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
|
|
|
|
if encoding != "" {
|
|
|
|
header.Set(contentEncodingHeader, encoding)
|
|
|
|
}
|
|
|
|
w.Write(buf.Bytes())
|
|
|
|
// TODO(beorn7): Consider streaming serving of metrics.
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandlerErrorHandling defines how a Handler serving metrics will handle
|
|
|
|
// errors.
|
|
|
|
type HandlerErrorHandling int
|
|
|
|
|
|
|
|
// These constants cause handlers serving metrics to behave as described if
|
|
|
|
// errors are encountered.
|
|
|
|
const (
|
|
|
|
// Serve an HTTP status code 500 upon the first error
|
|
|
|
// encountered. Report the error message in the body.
|
|
|
|
HTTPErrorOnError HandlerErrorHandling = iota
|
|
|
|
// Ignore errors and try to serve as many metrics as possible. However,
|
|
|
|
// if no metrics can be served, serve an HTTP status code 500 and the
|
|
|
|
// last error message in the body. Only use this in deliberate "best
|
|
|
|
// effort" metrics collection scenarios. It is recommended to at least
|
|
|
|
// log errors (by providing an ErrorLog in HandlerOpts) to not mask
|
|
|
|
// errors completely.
|
|
|
|
ContinueOnError
|
|
|
|
// Panic upon the first error encountered (useful for "crash only" apps).
|
|
|
|
PanicOnError
|
2014-05-07 22:08:33 +04:00
|
|
|
)
|
|
|
|
|
Create a public registry interface and separate out HTTP exposition
General context and approch
===========================
This is the first part of the long awaited wider refurbishment of
`client_golang/prometheus/...`. After a lot of struggling, I decided
to not go for one breaking big-bang, but cut things into smaller steps
after all, mostly to keep the changes manageable and easy to
review. I'm aiming for having the invasive breaking changes
concentrated in as few steps as possible (ideally one). Some steps
will not be breaking at all, but typically there will be breaking
changes that only affect quite special cases so that 95+% of users
will not be affected. This first step is an example for that, see
details below.
What's happening in this commit?
================================
This step is about finally creating an exported registry
interface. This could not be done by simply export the existing
internal implementation because the interface would be _way_ too
fat. This commit introduces a qutie lean `Registry` interface
(compared to the previous interval implementation). The functions that
act on the default registry are retained (with very few exceptions) so
that most use cases won't see a change. However, several of those are
deprecated now to clean up the namespace in the future.
The default registry is kept in the public variable
`DefaultRegistry`. This follows the example of the http package in the
standard library (cf. `http.DefaultServeMux`, `http.DefaultClient`)
with the same implications. (This pattern is somewhat disputed within
the Go community but I chose to go with the devil you know instead of
creating something more complex or even disallowing any changes to the
default registry. The current approach gives everybody the freedom to
not touch DefaultRegistry or to do everything with a custom registry
to play save.)
Another important part in making the registry lean is the extraction
of the HTTP exposition, which also allows for customization of the
HTTP exposition. Note that the separation of metric collection and
exposition has the side effect that managing the MetricFamily and
Metric protobuf objects in a free-list or pool isn't really feasible
anymore. By now (with better GC in more recent Go versions), the
returns were anyway dimisishing. To be effective at all, scrapes had
to happen more often than GC cycles, and even then most elements of
the protobufs (everything excetp the MetricFamily and Metric structs
themselves) would still cause allocation churn. In a future breaking
change, the signature of the Write method in the Metric interface will
be adjusted accordingly. In this commit, avoiding breakage is more
important.
The following issues are fixed by this commit (some solved "on the
fly" now that I was touching the code anyway and it would have been
stupid to port the bugs):
https://github.com/prometheus/client_golang/issues/46
https://github.com/prometheus/client_golang/issues/100
https://github.com/prometheus/client_golang/issues/170
https://github.com/prometheus/client_golang/issues/205
Documentation including examples have been amended as required.
What future changes does this commit enable?
============================================
The following items are not yet implemented, but this commit opens the
possibility of implementing these independently.
- The separation of the HTTP exposition allows the implementation of
other exposition methods based on the Registry interface, as known
from other Prometheus client libraries, e.g. sending the metrics to
Graphite.
Cf. https://github.com/prometheus/client_golang/issues/197
- The public `Registry` interface allows the implementation of
convenience tools for testing metrics collection. Those tools can
inspect the collected MetricFamily protobufs and compare them to
expectation. Also, tests can use their own testing instance of a
registry.
Cf. https://github.com/prometheus/client_golang/issues/58
Notable non-goals of this commit
================================
Non-goals that will be tackled later
------------------------------------
The following two issues are quite closely connected to the changes in
this commit but the line has been drawn deliberately to address them
in later steps of the refurbishment:
- `InstrumentHandler` has many known problems. The plan is to create a
saner way to conveniently intrument HTTP handlers and remove the old
`InstrumentHandler` altogether. To keep breakage low for now, even
the default handler to expose metrics is still using the old
`InstrumentHandler`. This leads to weird naming inconsistencies but
I have deemed it better to not break the world right now but do it
in the change that provides better ways of instrumenting HTTP
handlers.
Cf. https://github.com/prometheus/client_golang/issues/200
- There is work underway to make the whole handling of metric
descriptors (`Desc`) more intuitive and transparent for the user
(including an ability for less strict checking,
cf. https://github.com/prometheus/client_golang/issues/47). That's
quite invasive from the perspective of the internal code, namely the
registry. I deliberately kept those changes out of this commit.
- While this commit adds new external dependency, the effort to vendor
anything within the library that is not visible in any exported
types will have to be done later.
Non-goals that _might_ be tackled later
---------------------------------------
There is a strong and understandable urge to divide the `prometheus`
package into a number of sub-packages (like `registry`, `collectors`,
`http`, `metrics`, …). However, to not run into a multitude of
circular import chains, this would need to break every single existing
usage of the library. (As just one example, if the ubiquitious
`prometheus.MustRegister` (with more than 2,000 uses on GitHub alone)
is kept in the `prometheus` package, but the other registry concerns
go into a new `registry` package, then the `prometheus` package would
import the `registry` package (to call the actual register method),
while at the same time the `registry` package needs to import the
`prometheus` package to access `Collector`, `Metric`, `Desc` and
more. If we moved `MustRegister` into the `registry` package,
thousands of code lines would have to be fixed (which would be easy if
the world was a mono repo, but it is not). If we moved everything else
the proposed registry package needs into packages of their own, we
would break thousands of other code lines.)
The main problem is really the top-level functions like
`MustRegister`, `Handler`, …, which effectively pull everything into
one package. Those functions are however very convenient for the easy
and very frequent use-cases.
This problem has to be revisited later.
For now, I'm trying to keep the amount of exported names in the
package as low as possible (e.g. I unexported expvarCollector in this
commit because the NewExpvarCollector constructor is enough to export,
and it is now consistent with other collectors, like the goCollector).
Non-goals that won't be tackled anytime soon
--------------------------------------------
Something that I have played with a lot is "streaming collection",
i.e. allow an implementation of the `Registry` interface that collects
metrics incrementally and serves them while doing so. As it has turned
out, this has many many issues and makes the `Registry` interface very
clunky. Eventually, I made the call that it is unlikely we will really
implement streaming collection; and making the interface more clunky
for something that might not even happen is really a big no-no. Note
that the `Registry` interface only creates the in-memory
representation of the metric family protobufs in one go. The
serializaton onto the wire can still be handled in a streaming fashion
(which hasn't been done so far, without causing any trouble, but might
be done in the future without breaking any interfaces).
What are the breaking changes?
==============================
- Signatures of functions pushing to Pushgateway have changed to allow
arbitrary grouping (which was planned for a long time anyway, and
now that I had to work on the Push code anyway for the registry
refurbishment, I finally did it,
cf. https://github.com/prometheus/client_golang/issues/100).
With the gained insight that pushing to the default registry is almost
never the right thing, and now that we are breaking the Push call
anyway, all the Push functions were moved to their own package,
which cleans up the namespace and is more idiomatic (pushing
Collectors is now literally done by `push.Collectors(...)`).
- The registry is doing more consistency checks by default now. Past
creators of inconsistent metrics could have masked the problem by
not setting `EnableCollectChecks`. Those inconsistencies will now be
detected. (But note that a "best effort" metrics collection is now
possible with `HandlerOpts.ErrorHandling = ContinueOnError`.)
- `EnableCollectChecks` is gone. The registry is now performing some
of those checks anyway (see previous item), and a registry with all
of those checks can now be created with `NewPedanticRegistry` (only
used for testing).
- `PanicOnCollectError` is gone. This behavior can now be configured
when creating a custom HTTP handler.
2016-07-20 18:11:14 +03:00
|
|
|
// Logger is the minimal interface HandlerOpts needs for logging. Note that
|
|
|
|
// log.Logger from the standard library implements this interface, and it is
|
|
|
|
// easy to implement by custom loggers, if they don't do so already anyway.
|
|
|
|
type Logger interface {
|
|
|
|
Println(v ...interface{})
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandlerOpts specifies options how to serve metrics via an http.Handler. The
|
|
|
|
// zero value of HandlerOpts is a reasonable default.
|
|
|
|
type HandlerOpts struct {
|
|
|
|
// ErrorLog specifies an optional logger for errors collecting and
|
|
|
|
// serving metrics. If nil, errors are not logged at all.
|
|
|
|
ErrorLog Logger
|
|
|
|
// ErrorHandling defines how errors are handled. Note that errors are
|
|
|
|
// logged regardless of the configured ErrorHandling provided ErrorLog
|
|
|
|
// is not nil.
|
|
|
|
ErrorHandling HandlerErrorHandling
|
|
|
|
// If DisableCompression is true, the handler will never compress the
|
|
|
|
// response, even if requested by the client.
|
|
|
|
DisableCompression bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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).
|
|
|
|
func decorateWriter(request *http.Request, writer io.Writer, compressionDisabled bool) (io.Writer, string) {
|
|
|
|
if compressionDisabled {
|
|
|
|
return writer, ""
|
|
|
|
}
|
|
|
|
header := request.Header.Get(acceptEncodingHeader)
|
|
|
|
parts := strings.Split(header, ",")
|
|
|
|
for _, part := range parts {
|
|
|
|
part := strings.TrimSpace(part)
|
|
|
|
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
|
|
|
|
return gzip.NewWriter(writer), "gzip"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return writer, ""
|
|
|
|
}
|
|
|
|
|
2014-07-15 17:34:52 +04:00
|
|
|
var instLabels = []string{"method", "code"}
|
2014-05-07 22:08:33 +04:00
|
|
|
|
|
|
|
type nower interface {
|
|
|
|
Now() time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
type nowFunc func() time.Time
|
|
|
|
|
|
|
|
func (n nowFunc) Now() time.Time {
|
|
|
|
return n()
|
|
|
|
}
|
|
|
|
|
|
|
|
var now nower = nowFunc(func() time.Time {
|
|
|
|
return time.Now()
|
|
|
|
})
|
|
|
|
|
|
|
|
func nowSeries(t ...time.Time) nower {
|
|
|
|
return nowFunc(func() time.Time {
|
|
|
|
defer func() {
|
|
|
|
t = t[1:]
|
|
|
|
}()
|
|
|
|
|
|
|
|
return t[0]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// InstrumentHandler wraps the given HTTP handler for instrumentation. It
|
2015-02-19 17:34:04 +03:00
|
|
|
// registers four metric collectors (if not already done) and reports HTTP
|
2015-01-13 19:26:38 +03:00
|
|
|
// metrics to the (newly or already) registered collectors: http_requests_total
|
|
|
|
// (CounterVec), http_request_duration_microseconds (Summary),
|
|
|
|
// http_request_size_bytes (Summary), http_response_size_bytes (Summary). Each
|
|
|
|
// has a constant label named "handler" with the provided handlerName as
|
|
|
|
// value. http_requests_total is a metric vector partitioned by HTTP method
|
|
|
|
// (label name "method") and HTTP status code (label name "code").
|
2016-05-15 18:59:51 +03:00
|
|
|
//
|
|
|
|
// Note that InstrumentHandler has several issues:
|
|
|
|
//
|
|
|
|
// - It uses Summaries rather than Histograms. Summaries are not useful if
|
2016-06-27 17:26:48 +03:00
|
|
|
// aggregation across multiple instances is required.
|
2016-05-15 18:59:51 +03:00
|
|
|
//
|
2016-06-27 12:49:34 +03:00
|
|
|
// - It uses microseconds as unit, which is deprecated and should be replaced by
|
2016-06-27 17:26:48 +03:00
|
|
|
// seconds.
|
2016-05-15 18:59:51 +03:00
|
|
|
//
|
|
|
|
// - The size of the request is calculated in a separate goroutine. Since this
|
2016-06-27 17:26:48 +03:00
|
|
|
// calculator requires access to the request header, it creates a race with
|
|
|
|
// any writes to the header performed during request handling.
|
|
|
|
// httputil.ReverseProxy is a prominent example for a handler
|
|
|
|
// performing such writes.
|
2016-05-15 18:59:51 +03:00
|
|
|
//
|
|
|
|
// Upcoming versions of this package will provide ways of instrumenting HTTP
|
|
|
|
// handlers that are more flexible and have fewer issues. Consider this function
|
|
|
|
// DEPRECATED and prefer direct instrumentation in the meantime.
|
2014-05-07 22:08:33 +04:00
|
|
|
func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
|
2014-06-20 22:40:48 +04:00
|
|
|
return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
|
|
|
|
}
|
|
|
|
|
|
|
|
// InstrumentHandlerFunc wraps the given function for instrumentation. It
|
2016-05-15 18:59:51 +03:00
|
|
|
// otherwise works in the same way as InstrumentHandler (and shares the same
|
|
|
|
// issues).
|
2014-06-20 22:40:48 +04:00
|
|
|
func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
2014-07-15 17:34:52 +04:00
|
|
|
return InstrumentHandlerFuncWithOpts(
|
|
|
|
SummaryOpts{
|
|
|
|
Subsystem: "http",
|
|
|
|
ConstLabels: Labels{"handler": handlerName},
|
|
|
|
},
|
|
|
|
handlerFunc,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-05-15 18:59:51 +03:00
|
|
|
// InstrumentHandlerWithOpts works like InstrumentHandler (and shares the same
|
|
|
|
// issues) but provides more flexibility (at the cost of a more complex call
|
|
|
|
// syntax). As InstrumentHandler, this function registers four metric
|
|
|
|
// collectors, but it uses the provided SummaryOpts to create them. However, the
|
|
|
|
// fields "Name" and "Help" in the SummaryOpts are ignored. "Name" is replaced
|
|
|
|
// by "requests_total", "request_duration_microseconds", "request_size_bytes",
|
|
|
|
// and "response_size_bytes", respectively. "Help" is replaced by an appropriate
|
2015-01-13 19:26:38 +03:00
|
|
|
// help string. The names of the variable labels of the http_requests_total
|
|
|
|
// CounterVec are "method" (get, post, etc.), and "code" (HTTP status code).
|
2014-07-15 17:34:52 +04:00
|
|
|
//
|
|
|
|
// If InstrumentHandlerWithOpts is called as follows, it mimics exactly the
|
|
|
|
// behavior of InstrumentHandler:
|
|
|
|
//
|
|
|
|
// prometheus.InstrumentHandlerWithOpts(
|
|
|
|
// prometheus.SummaryOpts{
|
|
|
|
// Subsystem: "http",
|
|
|
|
// ConstLabels: prometheus.Labels{"handler": handlerName},
|
|
|
|
// },
|
|
|
|
// handler,
|
|
|
|
// )
|
|
|
|
//
|
|
|
|
// Technical detail: "requests_total" is a CounterVec, not a SummaryVec, so it
|
|
|
|
// cannot use SummaryOpts. Instead, a CounterOpts struct is created internally,
|
|
|
|
// and all its fields are set to the equally named fields in the provided
|
|
|
|
// SummaryOpts.
|
|
|
|
func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
|
|
|
|
return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
|
|
|
|
}
|
|
|
|
|
2016-05-15 18:59:51 +03:00
|
|
|
// InstrumentHandlerFuncWithOpts works like InstrumentHandlerFunc (and shares
|
|
|
|
// the same issues) but provides more flexibility (at the cost of a more complex
|
|
|
|
// call syntax). See InstrumentHandlerWithOpts for details how the provided
|
|
|
|
// SummaryOpts are used.
|
2014-07-15 17:34:52 +04:00
|
|
|
func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
|
|
|
reqCnt := NewCounterVec(
|
|
|
|
CounterOpts{
|
|
|
|
Namespace: opts.Namespace,
|
|
|
|
Subsystem: opts.Subsystem,
|
|
|
|
Name: "requests_total",
|
|
|
|
Help: "Total number of HTTP requests made.",
|
|
|
|
ConstLabels: opts.ConstLabels,
|
|
|
|
},
|
|
|
|
instLabels,
|
|
|
|
)
|
|
|
|
|
|
|
|
opts.Name = "request_duration_microseconds"
|
|
|
|
opts.Help = "The HTTP request latencies in microseconds."
|
2015-01-13 16:57:37 +03:00
|
|
|
reqDur := NewSummary(opts)
|
2014-07-15 17:34:52 +04:00
|
|
|
|
|
|
|
opts.Name = "request_size_bytes"
|
|
|
|
opts.Help = "The HTTP request sizes in bytes."
|
2015-01-13 16:57:37 +03:00
|
|
|
reqSz := NewSummary(opts)
|
2014-07-15 17:34:52 +04:00
|
|
|
|
|
|
|
opts.Name = "response_size_bytes"
|
|
|
|
opts.Help = "The HTTP response sizes in bytes."
|
2015-01-13 16:57:37 +03:00
|
|
|
resSz := NewSummary(opts)
|
2014-07-15 17:34:52 +04:00
|
|
|
|
2014-05-07 22:08:33 +04:00
|
|
|
regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec)
|
2015-01-13 16:57:37 +03:00
|
|
|
regReqDur := MustRegisterOrGet(reqDur).(Summary)
|
|
|
|
regReqSz := MustRegisterOrGet(reqSz).(Summary)
|
|
|
|
regResSz := MustRegisterOrGet(resSz).(Summary)
|
2014-05-07 22:08:33 +04:00
|
|
|
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
delegate := &responseWriterDelegator{ResponseWriter: w}
|
|
|
|
out := make(chan int)
|
2014-10-08 21:01:24 +04:00
|
|
|
urlLen := 0
|
|
|
|
if r.URL != nil {
|
|
|
|
urlLen = len(r.URL.String())
|
|
|
|
}
|
|
|
|
go computeApproximateRequestSize(r, out, urlLen)
|
2015-06-05 00:24:54 +03:00
|
|
|
|
|
|
|
_, cn := w.(http.CloseNotifier)
|
|
|
|
_, fl := w.(http.Flusher)
|
|
|
|
_, hj := w.(http.Hijacker)
|
|
|
|
_, rf := w.(io.ReaderFrom)
|
|
|
|
var rw http.ResponseWriter
|
|
|
|
if cn && fl && hj && rf {
|
|
|
|
rw = &fancyResponseWriterDelegator{delegate}
|
|
|
|
} else {
|
|
|
|
rw = delegate
|
|
|
|
}
|
|
|
|
handlerFunc(rw, r)
|
2014-05-07 22:08:33 +04:00
|
|
|
|
2014-06-20 22:40:48 +04:00
|
|
|
elapsed := float64(time.Since(now)) / float64(time.Microsecond)
|
2014-05-07 22:08:33 +04:00
|
|
|
|
|
|
|
method := sanitizeMethod(r.Method)
|
|
|
|
code := sanitizeCode(delegate.status)
|
2014-07-15 17:34:52 +04:00
|
|
|
regReqCnt.WithLabelValues(method, code).Inc()
|
2015-01-13 16:57:37 +03:00
|
|
|
regReqDur.Observe(elapsed)
|
|
|
|
regResSz.Observe(float64(delegate.written))
|
|
|
|
regReqSz.Observe(float64(<-out))
|
2014-05-07 22:08:33 +04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2014-10-08 21:01:24 +04:00
|
|
|
func computeApproximateRequestSize(r *http.Request, out chan int, s int) {
|
|
|
|
s += len(r.Method)
|
2014-05-07 22:08:33 +04:00
|
|
|
s += len(r.Proto)
|
|
|
|
for name, values := range r.Header {
|
|
|
|
s += len(name)
|
|
|
|
for _, value := range values {
|
|
|
|
s += len(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s += len(r.Host)
|
|
|
|
|
|
|
|
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
|
|
|
|
|
|
|
|
if r.ContentLength != -1 {
|
|
|
|
s += int(r.ContentLength)
|
|
|
|
}
|
|
|
|
out <- s
|
|
|
|
}
|
|
|
|
|
|
|
|
type responseWriterDelegator struct {
|
|
|
|
http.ResponseWriter
|
|
|
|
|
|
|
|
handler, method string
|
|
|
|
status int
|
2015-06-05 00:14:37 +03:00
|
|
|
written int64
|
2014-05-07 22:08:33 +04:00
|
|
|
wroteHeader bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *responseWriterDelegator) WriteHeader(code int) {
|
|
|
|
r.status = code
|
|
|
|
r.wroteHeader = true
|
|
|
|
r.ResponseWriter.WriteHeader(code)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
|
|
|
if !r.wroteHeader {
|
|
|
|
r.WriteHeader(http.StatusOK)
|
|
|
|
}
|
|
|
|
n, err := r.ResponseWriter.Write(b)
|
2015-06-05 00:14:37 +03:00
|
|
|
r.written += int64(n)
|
2014-05-07 22:08:33 +04:00
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
2015-06-05 00:24:54 +03:00
|
|
|
type fancyResponseWriterDelegator struct {
|
|
|
|
*responseWriterDelegator
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
|
|
|
|
return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fancyResponseWriterDelegator) Flush() {
|
|
|
|
f.ResponseWriter.(http.Flusher).Flush()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
|
|
return f.ResponseWriter.(http.Hijacker).Hijack()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *fancyResponseWriterDelegator) ReadFrom(r io.Reader) (int64, error) {
|
|
|
|
if !f.wroteHeader {
|
|
|
|
f.WriteHeader(http.StatusOK)
|
|
|
|
}
|
|
|
|
n, err := f.ResponseWriter.(io.ReaderFrom).ReadFrom(r)
|
|
|
|
f.written += n
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
2014-05-07 22:08:33 +04:00
|
|
|
func sanitizeMethod(m string) string {
|
|
|
|
switch m {
|
|
|
|
case "GET", "get":
|
|
|
|
return "get"
|
|
|
|
case "PUT", "put":
|
|
|
|
return "put"
|
|
|
|
case "HEAD", "head":
|
|
|
|
return "head"
|
|
|
|
case "POST", "post":
|
|
|
|
return "post"
|
|
|
|
case "DELETE", "delete":
|
|
|
|
return "delete"
|
|
|
|
case "CONNECT", "connect":
|
|
|
|
return "connect"
|
|
|
|
case "OPTIONS", "options":
|
|
|
|
return "options"
|
|
|
|
case "NOTIFY", "notify":
|
|
|
|
return "notify"
|
|
|
|
default:
|
|
|
|
return strings.ToLower(m)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func sanitizeCode(s int) string {
|
|
|
|
switch s {
|
|
|
|
case 100:
|
|
|
|
return "100"
|
|
|
|
case 101:
|
|
|
|
return "101"
|
|
|
|
|
|
|
|
case 200:
|
|
|
|
return "200"
|
|
|
|
case 201:
|
|
|
|
return "201"
|
|
|
|
case 202:
|
|
|
|
return "202"
|
|
|
|
case 203:
|
|
|
|
return "203"
|
|
|
|
case 204:
|
|
|
|
return "204"
|
|
|
|
case 205:
|
|
|
|
return "205"
|
|
|
|
case 206:
|
|
|
|
return "206"
|
|
|
|
|
|
|
|
case 300:
|
|
|
|
return "300"
|
|
|
|
case 301:
|
|
|
|
return "301"
|
|
|
|
case 302:
|
|
|
|
return "302"
|
|
|
|
case 304:
|
|
|
|
return "304"
|
|
|
|
case 305:
|
|
|
|
return "305"
|
|
|
|
case 307:
|
|
|
|
return "307"
|
|
|
|
|
|
|
|
case 400:
|
|
|
|
return "400"
|
|
|
|
case 401:
|
|
|
|
return "401"
|
|
|
|
case 402:
|
|
|
|
return "402"
|
|
|
|
case 403:
|
|
|
|
return "403"
|
|
|
|
case 404:
|
|
|
|
return "404"
|
|
|
|
case 405:
|
|
|
|
return "405"
|
|
|
|
case 406:
|
|
|
|
return "406"
|
|
|
|
case 407:
|
|
|
|
return "407"
|
|
|
|
case 408:
|
|
|
|
return "408"
|
|
|
|
case 409:
|
|
|
|
return "409"
|
|
|
|
case 410:
|
|
|
|
return "410"
|
|
|
|
case 411:
|
|
|
|
return "411"
|
|
|
|
case 412:
|
|
|
|
return "412"
|
|
|
|
case 413:
|
|
|
|
return "413"
|
|
|
|
case 414:
|
|
|
|
return "414"
|
|
|
|
case 415:
|
|
|
|
return "415"
|
|
|
|
case 416:
|
|
|
|
return "416"
|
|
|
|
case 417:
|
|
|
|
return "417"
|
|
|
|
case 418:
|
|
|
|
return "418"
|
|
|
|
|
|
|
|
case 500:
|
|
|
|
return "500"
|
|
|
|
case 501:
|
|
|
|
return "501"
|
|
|
|
case 502:
|
|
|
|
return "502"
|
|
|
|
case 503:
|
|
|
|
return "503"
|
|
|
|
case 504:
|
|
|
|
return "504"
|
|
|
|
case 505:
|
|
|
|
return "505"
|
|
|
|
|
|
|
|
case 428:
|
|
|
|
return "428"
|
|
|
|
case 429:
|
|
|
|
return "429"
|
|
|
|
case 431:
|
|
|
|
return "431"
|
|
|
|
case 511:
|
|
|
|
return "511"
|
|
|
|
|
|
|
|
default:
|
|
|
|
return strconv.Itoa(s)
|
|
|
|
}
|
|
|
|
}
|