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

Instrumentation api prototype #305

Closed

Conversation

LikeTheSalad
Copy link
Contributor

@LikeTheSalad LikeTheSalad commented Apr 16, 2024

Related to this issue.

POC display. Not meant to be merged. The tests haven't been updated for this poc so the Github checks are not supposed to pass. The purpose of this draft is to provide a general view of what an instrumentation API would look like and what it would take to implement it in the existing instrumentation modules.

These slides show what it's meant to be changed by this POC: https://docs.google.com/presentation/d/1ftU8GgdulR2_kHHTSCr6j5x1_dH4XotTq_PXNo5bzx4/edit?usp=sharing

@LikeTheSalad LikeTheSalad changed the title Instrumentation api Instrumentation api prototype Apr 16, 2024
}
}

fun apply(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think apply is a little too vague/generic. To a novice, it could be unclear if apply() is/should-be called more than once, like maybe each time you call apply() it generates data or something.

I think a verb like install() could help things be a little clearer.

Comment on lines +24 to +33
private var screenNameExtractor: ScreenNameExtractor = ScreenNameExtractor.DEFAULT
private var tracerCustomizer: (Tracer) -> Tracer = { it }

fun setTracerCustomizer(customizer: (Tracer) -> Tracer) {
tracerCustomizer = customizer
}

fun setScreenNameExtractor(screenNameExtractor: ScreenNameExtractor) {
this.screenNameExtractor = screenNameExtractor
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's totally fine for the purposes of a prototype, but I think we should be favoring immutability, and these should be injected rather than .set().

Copy link
Contributor

Choose a reason for hiding this comment

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

...and I'm not just being dogmatic. I think the setters can give the user a sense that they can change the instrumentation at runtime (although they probably won't have access to it anyway).


AnrDetectorToggler listener = new AnrDetectorToggler(anrWatcher, scheduler);
// call it manually the first time to enable the ANR detection
listener.onApplicationForegrounded();

instrumentedApplication.registerApplicationStateListener(listener);
ServiceManager.get().getService(AppLifecycleService.class).registerListener(listener);
Copy link
Contributor

Choose a reason for hiding this comment

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

I've been bitten by the service locator antipattern so so many times. I would love to discuss ways of trying to avoid doing this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Given that we have a means of identifying Services via the interface and determining if each should be on, is there a more explicit way of registering them in a shared way so that the registry is exposed for consumption scenarios like this, but it'll just be a simple map lookup?

Another thought: are any instrumentations and services able to access any registered service? That seems a bit chaotic, especially if certain services are optional, so we'll effectively have another dependency graph on our hands that isn't explicitly managed. Is there a way this can be closed off, or otherwise make them part of the interface the Agent exposes so they can be referenced explicitly rather than looked up?

Copy link
Contributor

Choose a reason for hiding this comment

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

effectively have another dependency graph on our hands that isn't explicitly managed

Yeah this is exactly the main issue! And it's global/static and very difficult to unweave later.

import java.util.List;

@AutoService(AndroidInstrumentation.class)
public final class NetworkChangeMonitorInstrumentation implements AndroidInstrumentation {
Copy link
Contributor

Choose a reason for hiding this comment

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

This reads a little clunky now -- I think something just like NetworkInstrumentation is sufficient, or even NetworkChangeInstrumentation. The "monitor" part of it was supposed to represent something that is looking out for network changes. Maybe a little pedantic, but this instrumentation isn't for the monitor.

* time.
*/
@AutoService(AndroidInstrumentation.class)
public final class SlowRenderingDetectorInstrumentation implements AndroidInstrumentation {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to the network, this simply becomes SlowRenderingInstrumentation or SlowRenderInstrumentation.

Copy link
Contributor

@bidetofevil bidetofevil left a comment

Choose a reason for hiding this comment

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

Let a few comments. Nothing blocking, just want to clarify the core concepts and responsibilities and how they should accessed.

import io.opentelemetry.android.OpenTelemetryRum

interface AndroidInstrumentation {
companion object {
Copy link
Contributor

Choose a reason for hiding this comment

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

Stylistically, since they are working in concert, it may be better to put related singletons in a regular object rather than a companion object on each of the interfaces? (Comment applies to other similar changes too)

import android.app.Application
import io.opentelemetry.android.OpenTelemetryRum

interface AndroidInstrumentation {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to confirm, the idea is that each AndroidInstrumentation works independently, and they only need to interact with OpenTelemetryRum to access the OTel SDK bits (e.g. Tracer)? Will the Agent ever need to reference the Instrumentations at all, for validations, throttling, or other needs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to confirm, the idea is that each AndroidInstrumentation works independently, and they only need to interact with OpenTelemetryRum to access the OTel SDK bits (e.g. Tracer)?

I don't remember if we discussed this in the SIG, but yes, this is correct.

Will the Agent ever need to reference the Instrumentations at all, for validations, throttling, or other needs?

Right now that's not the case, the agent doesn't need to do anything else with the instrumentations other than "activating" them by calling AndroidInstrumentation.apply (or install, depending on what name we decide to use) to provide them with the rum object plus the Application object. Though this can be changed if needed, I just closed this PR to start working on the changes in smaller PRs where we could discuss it all more in detail.


AnrDetectorToggler listener = new AnrDetectorToggler(anrWatcher, scheduler);
// call it manually the first time to enable the ANR detection
listener.onApplicationForegrounded();

instrumentedApplication.registerApplicationStateListener(listener);
ServiceManager.get().getService(AppLifecycleService.class).registerListener(listener);
Copy link
Contributor

Choose a reason for hiding this comment

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

Given that we have a means of identifying Services via the interface and determining if each should be on, is there a more explicit way of registering them in a shared way so that the registry is exposed for consumption scenarios like this, but it'll just be a simple map lookup?

Another thought: are any instrumentations and services able to access any registered service? That seems a bit chaotic, especially if certain services are optional, so we'll effectively have another dependency graph on our hands that isn't explicitly managed. Is there a way this can be closed off, or otherwise make them part of the interface the Agent exposes so they can be referenced explicitly rather than looked up?

@LikeTheSalad
Copy link
Contributor Author

Will close this as it will get implemented in other PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants