Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prometheus exporter #334

Merged
merged 38 commits into from
Nov 26, 2019
Merged

Prometheus exporter #334

merged 38 commits into from
Nov 26, 2019

Conversation

paivagustavo
Copy link
Member

@paivagustavo paivagustavo commented Nov 19, 2019

This adds prometheus metrics exporter.

This was started by @shreyassrivatsan and I've updated it with the last updates of the metrics sdk and support for measures (histogram and summary).

Copy link
Contributor

@jmacd jmacd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good start! Thanks @paivagustavo. FYI @shreyassrivatsan we've taken this over, thanks for the initial draft and we'll keep you in the loop.

example/prometheus/main.go Outdated Show resolved Hide resolved
example/prometheus/main.go Outdated Show resolved Hide resolved
example/prometheus/main.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
return tagKeys
}

func getCanonicalID(desc *export.Descriptor, labels export.Labels) (string, []string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way this is written, we need to use the defaultkeys Batcher, which ensures that the labels on each export Record are the same. I think this is another reason to integrate the exporter with the controller (and let it construct a Batcher).

The defaultkeys Batcher will let us simplify this code, since we can be fairly sure that the calls to With() will match the tags declared for the vector.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is not needed anymore so I have just deleted.

Do you mean this because of the labels.Encoded() call? because I've just moved it to another place.

I guess I should make the Prometheus exporter implements the LabelEncoder with a defaultkeys batcher for the moment?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that no two -Vec types can exist with the same name and different key sets. The defaultkeys batcher will ensure that the tag sets are always equal--that's why it makes sense as a default. I still envision other batchers being created to support greater configuration, but for now yes, I would use defaultkeys.

Also, yes, it makes sense to use the LabelEncoder interface to compute anything needed to map the -Vec type into the handle-like interface. If the map[string]string input to prometheus can be changed to a slice of values (as you said it could), then maybe the Encode() function will compute a slice of values.

exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/sanitize.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/sanitize.go Show resolved Hide resolved
exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/sanitize.go Show resolved Hide resolved
Copy link
Contributor

@jmacd jmacd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good!

exporter/metric/prometheus/sanitize.go Show resolved Hide resolved
example/prometheus/main.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
encoded: record.Labels().Encoded(),
}

// TODO(paivagustavo): how to choose between Histogram and Summary?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for now this can be in the exporter config. The user decides between histogram and summary at construction time.

In the future it would be nice to give a way to configure this on a per metric basis, but I am not very certain how that would looke..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm interested in more flexible and configurable exporters. I believe there's a question as to whether such a configuration can be in any way shared by more than one exporter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added an option to change how measures are exported, defaulting to histograms.

exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
}

func (e *Exporter) getCounterVec(desc *export.Descriptor, labels export.Labels) (*prometheus.CounterVec, error) {
if c, ok := e.counterVecs[desc]; ok {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a separate loc for the *Vec methods?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I actually didn't understand your suggestion here, could you discuss a little further?

exporter/metric/prometheus/sanitize.go Outdated Show resolved Hide resolved
exporter/metric/internal/statsd/conn_test.go Show resolved Hide resolved
labels := record.Labels()

var encoded string
if labels.Encoder() == e {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me that this matters. Any label encoder should uphold the contract, which is that encodings are unique. I'm not sure it needs to be part of the options either. If we're not using the encoded form directly for export (if it's only a map key), any encoder should do and we should just take the encoding we get.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've mixed things up.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I'm thinking about it. We talked about parsing the encoded labels instead of calling Emit on the labelValues method, to make that we would need to use a specific encoding. So I guess we would need this and remove option for changing the encoding or dropping this and the TODO about parsing labels.

For reference:

func labelValues(labels export.Labels) []string {
	// TODO(paivagustavo): parse the labels.Encoded() instead of calling `Emit()` directly
	//  this would avoid unnecessary allocations.
	values := make([]string, 0, labels.Len())
	for _, label := range labels.Ordered() {
		values = append(values, label.Value.Emit())
	}
	return values
}

exporter/metric/prometheus/prometheus.go Outdated Show resolved Hide resolved
counterVecs map[*export.Descriptor]*prometheus.CounterVec
}

func newCounters(registerer prometheus.Registerer) counters {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like this refactor..

this should probably return a pointer, no? the methods on the object rightly have a pointer receiver, so might as well return a pointer and reduce a copy here..

same applies to histograms and gauges too..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are placed in structs, so the pointer methods work. I'm happy with fewer pointers, fwiw.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could reduce a copy using pointer or save an alloc not using a pointer. In the end both should have little to no difference since exporters are created once.

Are you okay leaving this that way or do you have a strong objections against this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave this. In the Go base libraries, this sort of class is often accompanied by a "Must not be copied after first use" sort of comment--would that help?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually don't know if that solves it. Copies could be done, it would just be a shallow copy, It is not like Mutex that a copy could have inconsistent and incorrect state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a problem, anyway.

exporter/metric/prometheus/sanitize.go Show resolved Hide resolved
@paivagustavo
Copy link
Member Author

Thanks @shreyassrivatsan and @jmacd, for helping and reviewing this during the development. I've responded to all the open discussions.

I think we can now open this for others to review.

@paivagustavo paivagustavo marked this pull request as ready for review November 24, 2019 05:33
@paivagustavo paivagustavo added area:metrics Part of OpenTelemetry Metrics pkg:SDK Related to an SDK package labels Nov 24, 2019
)

func initMeter() *push.Controller {
selector := simple.NewWithExactMeasure()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to create a simple wrapper for this block of code, as mentioned in #345. It's OK if you think we should wait for a future PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should take that on a next PR, I would happily take it when this gets merged.

counterVecs map[*export.Descriptor]*prometheus.CounterVec
}

func newCounters(registerer prometheus.Registerer) counters {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave this. In the Go base libraries, this sort of class is often accompanied by a "Must not be copied after first use" sort of comment--would that help?

// Export exports the provide metric record to prometheus.
func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error {
var forEachError error
checkpointSet.ForEach(func(record export.Record) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't a guard required if forEachError is nil or not ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've being following the thought that if a single record fails to be exported that should not prevent the rest of the records to be processed.

There is still some questions we need to figure out about how to handle errors better on this ForEach method. One of the suggestions is to change its argument from func(export.Record) to func (export.Record) error to have a more uniform and simpler error handling on all exporters. But that is an open question that I think should be taken on a separate PR since it would touch not only this exporter but others too.

@rghetia
Copy link
Contributor

rghetia commented Nov 26, 2019

@paivagustavo, I merged it to master but it failed due to #356 . Can you please fix the build? Thx.

@paivagustavo
Copy link
Member Author

@rghetia just fixed it :)

@rghetia rghetia merged commit 3d78564 into open-telemetry:master Nov 26, 2019
@paivagustavo paivagustavo deleted the prometheus_exporter branch November 26, 2019 20:18
hstan referenced this pull request in hstan/opentelemetry-go Oct 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metrics Part of OpenTelemetry Metrics pkg:SDK Related to an SDK package
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants