After increasing unit test coverage, it was found that the split
function call nature of metric matching wasn't working well in many
cases. By increasing test coverage, we've ensured that both the fast
path and fallback collision path are working appropriately.
With these changes, there is a further performance hit, but now the
results are ensured to be correct.
Signed-off-by: Stephen J Day <stephen.day@docker.com>
While hash collisions are quite rare, the current state of the client
library carries a risk of merging two separate label values into a
single metric bucket. The effects are near impossible to detect and the
result will be missing or incorrect counters.
This changeset handles hash collisions by falling back to collision
resolution if multiple label values hash to the same value. This works
similar to separate chaining using a slice. Extra storage is minimized
to only the value key slice to that metrics can be differentiated
within a bucket.
In general, the cost of handling collisions is completely minimized
under normal operation. Performance does show slight increases in
certain areas, but these are more likely statistically anomalies. More
importantly, zero allocation behavior for metrics is preserved on the
fast path. Minimal allocations may be made during collision handling but
this has minimal effect.
Benchmark comparisons with and without collision resolution follow.
```
benchmark old ns/op new ns/op delta
BenchmarkCounterWithLabelValues-4 99.0 107 +8.08%
BenchmarkCounterWithLabelValuesConcurrent-4 79.6 91.0 +14.32%
BenchmarkCounterWithMappedLabels-4 518 542 +4.63%
BenchmarkCounterWithPreparedMappedLabels-4 127 137 +7.87%
BenchmarkCounterNoLabels-4 19.5 19.1 -2.05%
BenchmarkGaugeWithLabelValues-4 97.4 110 +12.94%
BenchmarkGaugeNoLabels-4 12.4 10.3 -16.94%
BenchmarkSummaryWithLabelValues-4 1204 915 -24.00%
BenchmarkSummaryNoLabels-4 936 847 -9.51%
BenchmarkHistogramWithLabelValues-4 147 147 +0.00%
BenchmarkHistogramNoLabels-4 50.6 49.3 -2.57%
BenchmarkHistogramObserve1-4 37.9 37.5 -1.06%
BenchmarkHistogramObserve2-4 122 137 +12.30%
BenchmarkHistogramObserve4-4 310 352 +13.55%
BenchmarkHistogramObserve8-4 691 729 +5.50%
BenchmarkHistogramWrite1-4 3374 3097 -8.21%
BenchmarkHistogramWrite2-4 5310 5051 -4.88%
BenchmarkHistogramWrite4-4 12094 10690 -11.61%
BenchmarkHistogramWrite8-4 19416 17755 -8.55%
BenchmarkHandler-4 11934304 13765894 +15.35%
BenchmarkSummaryObserve1-4 1119 1105 -1.25%
BenchmarkSummaryObserve2-4 3679 3430 -6.77%
BenchmarkSummaryObserve4-4 10678 7982 -25.25%
BenchmarkSummaryObserve8-4 22974 16689 -27.36%
BenchmarkSummaryWrite1-4 25962 14680 -43.46%
BenchmarkSummaryWrite2-4 38019 35073 -7.75%
BenchmarkSummaryWrite4-4 78027 56816 -27.18%
BenchmarkSummaryWrite8-4 117220 132248 +12.82%
BenchmarkMetricVecWithLabelValuesBasic-4 138 133 -3.62%
BenchmarkMetricVecWithLabelValues2Keys10ValueCardinality-4 150 144 -4.00%
BenchmarkMetricVecWithLabelValues4Keys10ValueCardinality-4 263 256 -2.66%
BenchmarkMetricVecWithLabelValues2Keys100ValueCardinality-4 145 155 +6.90%
BenchmarkMetricVecWithLabelValues10Keys100ValueCardinality-4 606 634 +4.62%
BenchmarkMetricVecWithLabelValues10Keys1000ValueCardinality-4 746 641 -14.08%
benchmark old allocs new allocs delta
BenchmarkCounterWithLabelValues-4 0 0 +0.00%
BenchmarkCounterWithLabelValuesConcurrent-4 0 0 +0.00%
BenchmarkCounterWithMappedLabels-4 2 2 +0.00%
BenchmarkCounterWithPreparedMappedLabels-4 0 0 +0.00%
BenchmarkCounterNoLabels-4 0 0 +0.00%
BenchmarkGaugeWithLabelValues-4 0 0 +0.00%
BenchmarkGaugeNoLabels-4 0 0 +0.00%
BenchmarkSummaryWithLabelValues-4 0 0 +0.00%
BenchmarkSummaryNoLabels-4 0 0 +0.00%
BenchmarkHistogramWithLabelValues-4 0 0 +0.00%
BenchmarkHistogramNoLabels-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValuesBasic-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValues2Keys10ValueCardinality-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValues4Keys10ValueCardinality-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValues2Keys100ValueCardinality-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValues10Keys100ValueCardinality-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValues10Keys1000ValueCardinality-4 0 0 +0.00%
benchmark old bytes new bytes delta
BenchmarkCounterWithLabelValues-4 0 0 +0.00%
BenchmarkCounterWithLabelValuesConcurrent-4 0 0 +0.00%
BenchmarkCounterWithMappedLabels-4 336 336 +0.00%
BenchmarkCounterWithPreparedMappedLabels-4 0 0 +0.00%
BenchmarkCounterNoLabels-4 0 0 +0.00%
BenchmarkGaugeWithLabelValues-4 0 0 +0.00%
BenchmarkGaugeNoLabels-4 0 0 +0.00%
BenchmarkSummaryWithLabelValues-4 0 0 +0.00%
BenchmarkSummaryNoLabels-4 0 0 +0.00%
BenchmarkHistogramWithLabelValues-4 0 0 +0.00%
BenchmarkHistogramNoLabels-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValuesBasic-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValues2Keys10ValueCardinality-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValues4Keys10ValueCardinality-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValues2Keys100ValueCardinality-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValues10Keys100ValueCardinality-4 0 0 +0.00%
BenchmarkMetricVecWithLabelValues10Keys1000ValueCardinality-4 0 0 +0.00%
```
Signed-off-by: Stephen J Day <stephen.day@docker.com>
This allows to finally get rid of the infamous injection hook in the
interface. The old SetMetricFamilyInjectionHook still exist as a
deprecated function but is now implemented with the new plumbing under
the hood.
Now that we have multiple Gatherer implementation, I renamed
push.Registry to push.FromGatherer.
This commit also improves the consistency checks, which happened as a
byproduct of the refactoring to allow checking in both the "merge
gatherer" Gatherers as well as in the normal Registry.
To keep backwards compatibility while not creating circular import
chains, some code had to be duplicated. But all functions using it
have been declared deprecated hereby.
The new ways of instrumenting handlers will all go into the new
package, and ultimately, the prometheus package itself will be
completely igorant of HTTP.
- Moved the Deliverer parameter to the end of the list to mirror
Collectors in push.Collectors.
- Improved doc comment and added an example for push.Registry.
Registry is now a struct, which implements two interfaces, Registrerer
and Deliverer. The latter is particularly important as it is now the
argument type for pushes and HTTP handler construction (i.e. it is
easy to implement a custom Deliverer for testing or other
purposes). The Registerer interface is not used as a parameter type
but can (and should) be used by users of custom registries so that
they can easily do things like mocking it out for testing purposes.
With the broken up interfaces, adding MustRegister to the interface is
not such a big deal anymore (interface is still small). And since
setting the injection hook is such a rare thing to happen, it is
acceptable to not have it in any of the interfaces.
The renaming from `Collect` to `Deliver` was done to avoid confusion
with Collectors. (The registry _collects_ from the Collectors, and
then _delivers_ to the exposition mechanism.)
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/46https://github.com/prometheus/client_golang/issues/100https://github.com/prometheus/client_golang/issues/170https://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.
This test fails when max_fds is a large value; say 4.5e+06, for example. Change it to match virtual_memory_bytes, which also has to handle large values.
This circumvents the following problem:
• The prometheus client library appends “/metrics/…” to the pushURL.
• When pushURL ends in a trailing slash, the URL becomes e.g.
http://pushgateway.example.com:9091//metrics/…
• The pushgateway will reply with an HTTP 307 status code
(temporary redirect).
• While Go’s net/http client follows redirects by default, it will only
follow HTTP 302 (Found) and HTTP 303 (See Other) redirects for PUT and
POST requests, which the prometheus client library uses.
Hence, when calling e.g.:
prometheus.Push("foo", "bar", "http://pushgateway.example.com:9091/")
…your metrics would not actually get pushed successfully, but rather
you’d see the error message:
2015/11/26 10:59:49 main.go:209: unexpected status code 307 while pushing to http://push...
These tests are always timing out in our Jenkins CI environment. We have moved our timeout to 2 minutes. I have added a check for the short flag so other can skip these tests as we are until this can be identified.
We are running into a timeout with TestHistogramConcurrency on our Jenkins box. I noticed in the stack trace for the timeout this block.
goroutine 2348 [chan send]:
github.comcast.com/ventris/kober/vnd/github.com/prometheus/client_golang/prometheus.(*goCollector).Collect(0xc20801e8e0, 0xc20800a7e0)
/var/lib/jenkins/jobs/Kober/workspace/src/github.comcast.com/ventris/kober/vnd/github.com/prometheus/client_golang/prometheus/go_collector.go:49 +0x6dd
github.comcast.com/ventris/kober/vnd/github.com/prometheus/client_golang/prometheus.func·028()
/var/lib/jenkins/jobs/Kober/workspace/src/github.comcast.com/ventris/kober/vnd/github.com/prometheus/client_golang/prometheus/go_collector_test.go:27 +0x11a
created by github.comcast.com/ventris/kober/vnd/github.com/prometheus/client_golang/prometheus.TestGoCollector
/var/lib/jenkins/jobs/Kober/workspace/src/github.comcast.com/ventris/kober/vnd/github.com/prometheus/client_golang/prometheus/go_collector_test.go:28 +0x35e
This suggested that even though the TestGoCollector test was finished, a goroutine was still hanging around. I traced it back to the call to c.Collect always sending twice of the provided channel. This change receives that second value and allows the goroutine to finish with the test.
Still can't figure out why TestHistogramConcurrency is timing out after 2 minutes :(
- Clarify documentation about sorting requirements.
- Add missing histogram support in consistency check.
- Add label sorting to consistency check.
- Improve error messages when reporting a metric.
(Previously, the metric name was not printed.)
InstrumentHandler provides proxy object over given http.ResponseWriter
- responseWriterDelegator that only implements the bare minimum
required of an http.ResponseWriter and doesn’t implement any interface
upgrades (http.Flusher, http.Hijacker, etc.). This commit fixes it by
providing fancyResponseWriterDelegator with all the fancy bells and
whistles if standard library http.ResponseWriter is detected.
Heavily inspired by Goji's middleware.
https://avtok.com/2014/11/05/interface-upgrades.html#the-proxy-problem
The cgo dependency in the "procfs" package is being removed in:
https://github.com/prometheus/procfs/pull/4
So now it should be fine to always import the "procfs" package and have
it determine dynamically at runtime whether the proc filesystem is
actually there.
This fixes a problem for users which were vendoring client_golang on
MacOS X, but building the result on Linux (the procfs package was
missing in the vendored packages).
If a metric family returned by the injection hook already exists (with
the same name), then its metrics are simply merged into that metric
family. With enabled collect-time checks, even uniqueness is checked,
but in general, things stay the same that the caller is responsible to
ensure metric consistency.
This fixes https://github.com/prometheus/pushgateway/issues/27 .
The new vendoring was produced by running:
godep save -r ./examples/... ./prometheus/... ./text/... ./model/... ./extraction/...
Two things to note:
- "extraction/processor0_0_{1,2}_test.go" imported a package from
"github.com/prometheus/prometheus", all for just one tiny testing
function. To not have to deal with a circular vendoring dependency, I
simply replaced the usage of the function by some in-line logic.
- godep grouped the rewritten imports slightly differently for some
reason, but at least the standard library imports are still in a
separate section. Not sure if it's worth manually keeping our old
import grouping scheme or if we should simply use that godep-generated
one.