-
Notifications
You must be signed in to change notification settings - Fork 825
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
refactor(metrics): export pipeline #1017
Conversation
3f48a87
to
8f1e7d8
Compare
Metrics exporting requires a clear policy about when to export. While MetricsProvider holds all meters created, it can act as a Controller role to integrate better export strategies like pulling.
8f1e7d8
to
af75f12
Compare
Codecov Report
@@ Coverage Diff @@
## master #1017 +/- ##
==========================================
+ Coverage 93.04% 93.30% +0.26%
==========================================
Files 137 140 +3
Lines 3794 3946 +152
Branches 784 808 +24
==========================================
+ Hits 3530 3682 +152
Misses 264 264
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are still unresolved few other issues:
- Updating examples to use PushControler instead of MeterProvider
- Update all README.md files (MeterProvider -> PushController)
- There is as
setGlobalMeterProvider
what will happen with this, should this be renamed and refactored ? - We have NoopMeterProvider, what about this ?
- In Api we still call it as MeterProvider but here is a PushController, which might be misleading, I think this should be unified either in one way or another.
export * from './Metric'; | ||
export * from './MetricObservable'; | ||
export * from './export/aggregators'; | ||
export * from './export/controllers'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you added index.ts
into export
folder I think this should be sufficient and below line
export * from './export/Controller'
should be a part of export/controllers/index.ts
as well as
export * from './export/ConsoleMetricExporter'
WDYT ?
describe('constructor', () => { | ||
it('should construct an instance without any options', () => { | ||
const provider = new MeterProvider(); | ||
assert.ok(provider instanceof MeterProvider); | ||
const provider = new PushController(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: is the variable name still suitable for provider
, maybe now should be a controller
?
@@ -0,0 +1,73 @@ | |||
/*! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to rename MeterProvider.ts
into PushController.ts
to preserve the history, as well as to avoid any merging conflicts in case there are changes from other PR at the same time. Exactly the same you did for tests
const DEFAULT_EXPORT_INTERVAL = 60_000; | ||
|
||
/** | ||
* This class represents a meter provider collecting metric instrument values periodically. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update doc pls
I am little confused about the this PR (esp. Currently, Basically, users have to do the following things:
|
Looks like this is similar to Python's implementation. See: https://github.com/open-telemetry/opentelemetry-python/blob/master/docs/examples/basic_meter/basic_metrics.py#L66 |
@mayurkale22 an alternative could be the pattern opentelemetry-go is using: (the example is written in JavaScript to illustrate possible API shapes) import prometheus from '@opentelemetry/exporter-prometheus'
import { metrics } from '@opentelemetry/api'
prometheus.installPipeline(); // registers the PullController as globalMeterProvider.
const meter = metrics.getMeter('example-meter');
const counter = meter.createCounter('foo');
// ... In this example, end users of The python example does work. But dangling a variable like WDYT? @mayurkale22 @obecny |
so the exporter decides to install push or pull controller as the global meter provider? |
Woudn't it mean that one meter provider can only be pull or push based, so you wouldn't be able to use two exporters at the same time that use each method ? |
Yes, push or pull strategy is based on how the metrics are exported. So I think this is more intuitive.
As a matter of fact, MeterProvider in @opentelemetry/metrics doesn't support multiple exporters already. Though the exporter can act like MultiSpanProcessor passing the metric records to all subsidiaries. |
# Conflicts: # packages/opentelemetry-metrics/src/export/controllers/PullController.ts # packages/opentelemetry-metrics/test/export/controllers/PushController.test.ts
@dyladan @mayurkale22 @obecny @vmarchaud hey, I've updated the PR to address the comments. Maybe there can be another round of review? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall lgtm, few questions though
* This class represents a controller collecting metric instrument values | ||
* periodically. | ||
*/ | ||
export class PushController extends PullController { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If some logic is shared between Push and Pull controller, could we move it to an abstract class BaseController
and then make the PushController extending it instead of the PullController ?
|
||
protected onExportResult(result: ExportResult): void { | ||
if (result !== ExportResult.SUCCESS) { | ||
// @todo: log error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth to add a logger option while are it, maybe as a optional parameter for now, WYTD ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like really good work. Having now taken some time to go through it, I think I like this better than the current approach.
Minor issue: with the naming change, it may not be obvious to users that the controller is the meter provider.
packages/opentelemetry-metrics/src/export/controllers/Controller.ts
Outdated
Show resolved
Hide resolved
packages/opentelemetry-metrics/src/export/controllers/PushController.ts
Outdated
Show resolved
Hide resolved
I've appended more ts-doc on controllers to explicitly indicate that those controllers are capable of been installed as the global metric provider. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spec says about MeterProvider, why should we call it then as PullController then ?
* limitations under the License. | ||
*/ | ||
|
||
import * as api from '@opentelemetry/api'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the installExportPipeline
for me it looks kind of magic from outside, not sure what is happening inside and how the exporter is being bound with meter and what if I want to change it. If we want to have similar pattern for other exporters (imho we should have those things unified) I would move installExportPipeline
to a core or metrics and allow setting exporter as dependency injection. This way it will look a bit more clear for me and I will still be able to use the same pattern for any other exporter that I want. WDYT ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The key point for installExportPipeline
is that the export policy mainly depends on how the exporter works. For example, the prometheus exporter (which starts a server awaiting incoming scrapping) would prefer a pulling strategy and the console exporter would prefer a periodically print of the metric records. For this reason, the installExportPipeline
was implemented for each exporter and in the installExportPipeline
there is nothing magic other than installing a metric exporting strategy (controller) as the global metric provider as the metric provider is the role who holds all meters/metrics.
If the installExportPipeline
is implemented as a method in opentelemetry/metric or core, there is no significant difference with the current approach since the exporting strategy still needs to be determined by who installs the pipeline which can be probably the end-user who uses the exporter rather than the exporter themselves.
e.g.
metrics.installExportPipeline({ exporter: new PrometheusExporter({ startServer: true }), strategy: 'pull' });
The assumption is based on that the end-user doesn't have to care about the strategy the exporter used. They do care about which exporter they are going to work with.
import { metrics } from '@opentelemetry/api' | ||
|
||
// Install the export pipeline before all subsequent call to metrics. | ||
ConsoleMetricExporter.installPipeline(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this static, have different name than in prometheus and the flow will also looks differently ?
I think this all should look the same doesn't matter if this is console exporter, prometheus exporter or any other exporter. I think we should have then one method in metrics which will install the export pipeline
by accepting an exporter as dependency injection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll rename those installExportPipeline
aligned if the above explanation make sense to you.
Maybe we can resolve naming confusion by naming the controllers |
I would keep the naming the same as in spec to avoid any confusion (MeterProvider) but then maybe some option can decide if that is a push or pull strategy ?.
|
What is the status of this? It has 3 reviews, but open comments from a maintainer. @obecny you are the only one with remaining open comments here. |
My main concern is that why this is done different then what spec says. Spec says about MeterProvider, not PushController. |
@obecny I've updated the PR the revert the changes made to MeterProvider naming. Since this update, the PR can be summarized as:
Example usage of the new pipeline: import { ConsoleMetricExporter } from '@opentelemetry/metrics'
import { metrics } from '@opentelemetry/api'
// Install the export pipeline before all subsequent call to metrics.
ConsoleMetricExporter.installExportPipeline();
// Identical to:
(function installExportPipeline() {
const exporter = new ConsoleMetricExporter();
const meterProvider = new MeterProvider({ exporter });
const controller = new PushController(meterProvider);
api.metrics.setGlobalMeterProvider(meterProvider);
})();
const meter = metrics.getMeter('example-meter');
const counter = meter.createCounter('foo'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks great now, just few smaller things with return type
static installExportPipeline( | ||
config?: PushControllerConfig, | ||
meterConfig?: MeterConfig | ||
) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing return type - I think it should be the same interface that you have in prometheus, we might use it later for CollectorMetricExporter as well
First, thank you for the work you have put into this. I know this has been a lot and the review process has been lengthy, but this is a large PR and there is a lot to unpack here. First, I have a couple of questions.
interface PullingExporter extends Exporter {
registerPullController(controller: PullController): void;
}
const pullingExporter = new Exporter1(); // implements pulling exporter
const pushingExporter = new Exporter2(); // implement exporter
const provider = new MeterProvider();
// option a
provider.addController(new PullController(pullingExporter));
provider.addController(new PushController(pushingExporter);
/// OR
// option b
provider.addPullExporter(pullingExporter);
provider.addExporter(pushingExporter);
provider.register(); In addition to being simpler, this would also have good symmetry with the tracing API. Between option a and b I wouldn't have a strong opinion.
const provider = new MeterProvider();
metrics.setMeterProvider(provider);
const meter = provider.getMeter("myapp");
const exporter = new Exporter();
exporter.installExportPipeline();
const counter = meter.createCounter();
// use counter in some way, metrics are not exported, user is confused Because this PR has the |
@dyladan Thanks for the questions. The idea behind the However, I found I'm fallen into a thought trap while reviewing the first question. My initial thought is that we can lift the decision like the strategy the exporter should use. Yet I found the symmetry between the metrics and tracing you mentioned is interesting idea on how the metrics export pipeline should be defined, regardless of how currently go or python SDK is implemented. My thoughts on the symmetry can be: Aggregator & Controller <-> Sampler. Which aggregator to use is decided by the batcher, which is installed at the construction of MeterProvider. Similarly, samplers will be installed on the construction of BasicTracerProvider. So for controllers, I'm wondering if the current approach is the right way to keep symmetry between metrics and tracing. I'm opening to revert the current changes to only migrating Controllers on the main branch to bind with |
The export pipeline user interface may be hard to be a stable design as the metric record export pipeline of metrics SDK specification is still been drafting actively. Closed now and keep tracking on #1528. |
…ts (open-telemetry#1017) * fix: remove cassandra types from exports * fix: remove router types from exports * fix: remove knex types from exports * fix: remove mysql2 types from exports * style: lint mysql2 source * fix: keep `typeof Router` in non-public API
Metrics exporting requires a clear policy about when to export. This PR extracts controllers from
metrics.Meter
and makes it installable onmetrics.MeterProvider
.Which problem is this PR solving?
Short description of the changes
metrics.MeterProvider
exposes a new methodcollect
that similar tometrics.Meter.collect
.metrics.MeterProvider
but not ametrics.Meter
.installExportPipeline
function to @opentelemetry/exporter-prometheus.installExportPipeline
function to ConsoleExporter in @opentelemetry/metrics to install a push controller on globalapi.metrics
.