-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Add sdk/metric/reader package structure to new_sdk/main #2809
Comments
The type Reader interface {
Register(Producer)
Flush(context.Context) error
Shutdown(context.Context) error
} |
The current package structure seems like an attempt to consolidate docs of the I think this package structure could use a revised design attempt here before we progress with a PR. |
After reading the metric SDK specification and looking at other SIG implementations I wonder if we can simplify the readers and make them more concrete in the implementation. Something more along these lines: type Exporter interface {
Export(context.Context, Metrics) error
Flush(context.Context) error
Shutdown(context.Context) error
}
type Reader struct{}
func NewReader(mp *MeterProvider, exp Exporter, options ...Option) *Reader
func (*Reader) Collect(context.Context) error
func (*Reader) Flush(context.Context) error
func (*Reader) Shutdown(context.Context) error
type PeriodicReader struct {
Reader
}
func NewPeriodicReader(mp *MeterProvider, exp Exporter, options ...PeriodicOption) *PeriodicReader This addresses a few issues:
Issues with this approach:
@jmacd thoughts? |
Abbreviated feedback from @MadVikingGod in SIG meeting on the above design is the Prometheus exporter needs to call |
I think it is possible to handle this with the above design, but the complexity of passing responses from the out-of-band call to Why not just have a reader pass the E.g. type Exporter interface {
Export(context.Context, Metrics) error
Flush(context.Context) error
Shutdown(context.Context) error
}
type Reader struct{}
func NewReader(mp *MeterProvider, options ...Option) *Reader
func (*Reader) Collect(context.Context) (Metrics, error)
func (*Reader) Flush(context.Context) error
func (*Reader) Shutdown(context.Context) error
type PeriodicReader struct {
Reader
}
func (*PeriodicReader) Collect(context.Context) error
func NewPeriodicReader(mp *MeterProvider, exp Exporter, options ...PeriodicOption) *PeriodicReader |
How does this interact with the |
They wouldn't interact as that would be the wrong approach with the alternate design. The If we wanted to add another Reader to the package we would add another concrete type. And it would follow with its own creation function that associated it with a |
What I don't think I understand how this will work is how prometheus can connect a specific call of its In prometheus the HTTP function calls A similar but different issue is how does the Reader collect the information? The MeterProvider doesn't have an exposed API for the reader of any kind to get this information. I think we may want to put the Edit: the reason the current API doesn't have a MeterProvider.Produce() (read Collect()) is that the Producer that is registered is not the MeterProvider but the ViewCompiler. This allows us to compile a reduced set of async instruments that must be processed when Produce() is eventually called. |
I don't follow. Why can't the Prometheus exporter replace this line:
With data, err := c.exp.reader.Collect(context.Background())
I think we have similar implementation ideas here. I see the func (*MeterProvider) collectFunc(ConfigForAViewCompiler) func(contect.Context) (Metric, error) The returned function would be a closure of this essentially. When a reader is created, the |
Given this is unexported, we would also be able to play with memory allocation optimizations. We could support this as well func (*MeterProvider) collectOverwriteFunc(ConfigForAViewCompiler) func(contect.Context, *Metric) error That way the function does not need overhead of checking how it should return data. Having these functions on the |
Maybe we don't want to create it with a MeterProvider, but with a ViewCompiler. The MeterProvider could instead of having a WithView option we could have a method that takes a View (config) and returns a ViewCompiler that the Reader could use. So it would looks like:
|
Ah, yeah! That looks like a promising approach. It would definitely simplify the |
In the API i last proposed the views will need a more robust API. They couldn't be just a configuration block, because when you add a new instrument the sdk will have to find all views that it belongs in (and set up what transforms might need to happen) and an API for the reader to get data from the aggregators associated with a view. So we it might looks something like:
And we will have to be able to update instruments as views are added |
Overall question that are being raised from design sync:
|
To answer the first two questions we can look at other sigs. In particular I surveyed Java, python, and C#. |
I want to acknowledge @MrAlias and work to provide better answers than I did in today's meeting. For the question about bi-directionality, I believe the direction is always the same but we have different points where a trigger originates, so different paths: (1) the user responsible for registering the SDK can Flush and Shutdown (which impacts all readers); (2) the user who instruments code can Add and Record synchronous instruments (which impact all readers); (3) the user who instruments code can Observe async instruments (which impact one reader); (4) the reader can Collect observations (impacts one reader). So there are per-reader and all-reader flows, that's part of the confusion. @MrAlias I think your question 3b deserves more study. I don't know that "simplified" is possible, but an arrangement of data types that is closer to your intuition is probably better, so possibly yes. On question 4. I muddled through something in our meeting today. I have removed any optimizations on this topic in my branch already, meaning that each configured View behavior maps to exactly one Aggregator and if there are more than one reader or behavior then a generic multi-reader/multi-instrument type is used (which is aware of per-reader and all-reader flows). I will follow on with more on this topic, investigating part 3b. |
Following up to this thread. I still think there are merits registering a MeterProvider with a Reader on creation. And I think it would still conform to the specification. However, my main reservation at this point is that it would be too dissimilar to other implementations and the tracing signal API. With that in mind, I would like to focus on the registration API a bit.
I believe the answer to these questions to be yes. The // produceFunc is a closure to produce metrics for a specific reader.
type produceFunc func(context.Context) (Metrics, error)
type Reader interface {
register(produceFunc)
Flush(context.Context) error
Shutdown(context.Context) error
} Similarly, the type MeterProvider struct{/*...*/}
// ...
func (*MeterProvider) produceFor(readerConfig) produceFunc Which would result in a similarly redacted type ManualReader struct {
produce produceFunc
}
func (m *ManualReader) Collect(ctx context.Context) (Metrics, error) { /*...*/ return m.produce(ctx) }
func (m *ManualReader) Flush(context.Context) error { /*...*/ }
func (m *ManualReader) Shutdown(context.Context) error { /*...*/ }
func (m *ManualReader) register(f produceFunc) { m.produce = f } Both the periodic reader and the Prometheus exporter can then wrap this with their own export logic. For example, the Prometheus exporter becomes: type PrometheusExporter struct {
// Embedding a ManualReader reader means the PrometheusExporter can be
// registered as a Reader with a MeterProvider.
ManualReader
/*...*/
}
/* ... */
func (exp *PrometheusExporter) Collect(ch chan<- prometheus.Metric) {
data, err := exp.ManualReader.Collect(context.Background())
// Handle err and export data ...
} |
Something I failed to mention above is that the |
Following the discussion in today's SIG meeting, here is the
with the same Under this scenario, the Prometheus "exporter" is a type that implements Just to make that clear, here's the
The question we left the SIG meeting with was about periodic readers. Here's what I would implement, roughly speaking:
|
By the way, this is totally untested! Suspect incorrect use of Not shown above, but from the same branch in the
The SDK specification talks about SDK auto-configured exporters. For an auto-configured exporter pattern, I would like to see each potentially auto-configurable exporter package support returning the appropriate For a Prometheus exporter, this means returning one of the structs that implements
For any push-based exporter, this will mean returning a PeriodicReader option, e.g.,
|
I've spent this morning tiring to implement the Manual Reader, and I think I've found an interesting problem. Conceptually should a reader always get the data streams from ALL instruments or just the ones from views associated with it? If it's the former, then the reader will need some facility to filter/transform/reaggergate data streams it receives from If we don't associate readers with views in some way I don't see how we could have multiple readers measuring different things, as implied by (3) in the data model examples https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md#example-use-cases |
@MadVikingGod FWIW the current SDK Specification says that Views are specified for the whole provider, not for the individual reader. I filed open-telemetry/opentelemetry-specification#2288 about this. The branch I'm working on in #2865 for the record has taken this route, since our 4/25/2022 "Metrics SDK design" special meeting, where it seemed everyone agreed to this position. That means, the Prior to last week, I had been following the specification on this topic, only allowing one set of Views to be configured at the MeterProvider level. I'm not sure if this helps us answer the question(s) in this issue, though, about Readers. |
For the record, #2885 proposes a very similar structure to what's in my latest examples above. That PR is close enough, IMO, that we should continue discussion there. |
Closed by #2885 |
Blocked by #2799Add in the needed interfaces and high-level types for the new SDK design from
new_sdk/example
. This is not expected to be a fully working implementation.sdk/metric/reader
type Reader interface
type Registeree interface
(might be a better idea to rename this idiomatic to GoRegisterer
)type Producer interface
type Metrics struct
type Scope struct
type Instrument struct
type Point struct
type Exporter interface
type Option interface
to be passed to the new Provider method optiontype config struct
stubThe text was updated successfully, but these errors were encountered: