Skip to content

Commit

Permalink
Instrumentation API - Part 1 (#396)
Browse files Browse the repository at this point in the history
* Created AndroidInstrumentation interface

* Validating loading instrumentations from the classpath

* Registering instrumentations

* Making AndroidInstrumentation.getAll response return an unmodifiable collection

* Created AndroidInstrumentationRegistry

* Updating docs

* Adding docs to AndroidInstrumentationRegistry

* Updating docs and adjusting AndroidInstrumentationRegistryImpl

* Adding singleton registry test

* Update android-agent/src/main/java/io/opentelemetry/android/instrumentation/AndroidInstrumentation.kt

Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com>

* Update android-agent/src/main/java/io/opentelemetry/android/instrumentation/AndroidInstrumentation.kt

Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com>

* Update android-agent/src/main/java/io/opentelemetry/android/instrumentation/AndroidInstrumentationRegistry.kt

Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com>

* Updating tests

* Update android-agent/src/main/java/io/opentelemetry/android/instrumentation/AndroidInstrumentationRegistryImpl.kt

Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com>

* Updating tests

* Avoid using java Collections

---------

Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com>
Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 3, 2024
1 parent c498047 commit a14dcaf
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.instrumentation

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

/**
* This interface defines a tool that automatically generates telemetry for a specific use-case,
* without the need for end users to directly interact with the OpenTelemetry SDK to create telemetry manually.
*
* Implementations of this interface should be focused on a single use-case and should attach themselves automatically
* to the tool that they are supposed to generate telemetry for. For example, an implementation that tracks
* Fragment lifecycle methods by generating OTel events in key places of a Fragment's lifecycle, should
* come with its own "FragmentLifecycleCallbacks" implementation (or similar callback mechanism that notifies when a fragment lifecycle state has changed)
* and should find a way to register its callback into all of the Fragments of the host app to automatically
* track their lifecycle without end users having to modify their project's code to make it work.
*
* Even though users shouldn't have to write code to make an AndroidInstrumentation implementation work,
* implementations should expose configurable options whenever possible to allow users to customize relevant
* options depending on the use-case.
*
* Access to an implementation, either to configure it or to install it, must be made through
* [AndroidInstrumentationRegistry.get] or [AndroidInstrumentationRegistry.getAll].
*/
interface AndroidInstrumentation {
/**
* This is the entry point of the instrumentation, it must be called once per implementation and it should
* only be called from [OpenTelemetryRum]'s builder once the [OpenTelemetryRum] instance is initialized and ready
* to use for generating telemetry.
*
* @param application The Android application being instrumented.
* @param openTelemetryRum The [OpenTelemetryRum] instance to use for creating signals.
*/
fun install(
application: Application,
openTelemetryRum: OpenTelemetryRum,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.instrumentation

/**
* Stores and provides all the available instrumentations.
*/
interface AndroidInstrumentationRegistry {
/**
* Provides a single instrumentation if available.
*
* @param type The type of the instrumentation to retrieve.
* @return The instrumentation instance if available, null otherwise.
*/
fun <T : AndroidInstrumentation> get(type: Class<out T>): T?

/**
* Provides all registered instrumentations.
*
* @return All registered instrumentations.
*/
fun getAll(): Collection<AndroidInstrumentation>

/**
* Stores an instrumentation as long as there is not other instrumentation already registered with the same
* type.
*
* @param instrumentation The instrumentation to register.
* @throws IllegalStateException If the instrumentation couldn't be registered.
*/
fun register(instrumentation: AndroidInstrumentation)

companion object {
private val instance: AndroidInstrumentationRegistry by lazy {
AndroidInstrumentationRegistryImpl()
}

@JvmStatic
fun get(): AndroidInstrumentationRegistry {
return instance
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.instrumentation

import java.util.ServiceLoader

internal class AndroidInstrumentationRegistryImpl : AndroidInstrumentationRegistry {
private val instrumentations: MutableMap<Class<out AndroidInstrumentation>, AndroidInstrumentation> by lazy {
ServiceLoader.load(AndroidInstrumentation::class.java).associateBy { it.javaClass }
.toMutableMap()
}

@Suppress("UNCHECKED_CAST")
override fun <T : AndroidInstrumentation> get(type: Class<out T>): T? {
return instrumentations[type] as? T
}

override fun getAll(): Collection<AndroidInstrumentation> {
return instrumentations.values.toList()
}

@Throws(IllegalStateException::class)
override fun register(instrumentation: AndroidInstrumentation) {
if (instrumentation::class.java in instrumentations) {
throw IllegalStateException("Instrumentation with type '${instrumentation::class.java}' already exists.")
}
instrumentations[instrumentation.javaClass] = instrumentation
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.instrumentation

import android.app.Application
import io.mockk.mockk
import io.opentelemetry.android.OpenTelemetryRum
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class AndroidInstrumentationRegistryImplTest {
private lateinit var registry: AndroidInstrumentationRegistryImpl

@BeforeEach
fun setUp() {
registry = AndroidInstrumentationRegistryImpl()
}

@Test
fun `Find and register implementations available in the classpath when querying an instrumentation`() {
val instrumentation = registry.get(TestAndroidInstrumentation::class.java)!!

assertThat(instrumentation.installed).isFalse()

instrumentation.install(mockk(), mockk())

assertThat(registry.get(TestAndroidInstrumentation::class.java)!!.installed).isTrue()
}

@Test
fun `Find and register implementations available in the classpath when querying all instrumentations`() {
val instrumentations = registry.getAll()

assertThat(instrumentations).hasSize(1)
assertThat(instrumentations.first()).isInstanceOf(TestAndroidInstrumentation::class.java)
}

@Test
fun `Register instrumentations`() {
val instrumentation = DummyInstrumentation("test")

registry.register(instrumentation)

assertThat(registry.get(DummyInstrumentation::class.java)!!.name).isEqualTo("test")
}

@Test
fun `Register only one instrumentation per type`() {
val instrumentation = DummyInstrumentation("test")
val instrumentation2 = DummyInstrumentation("test2")

registry.register(instrumentation)

assertThatThrownBy {
registry.register(instrumentation2)
}.isInstanceOf(IllegalStateException::class.java)
.hasMessage("Instrumentation with type '${DummyInstrumentation::class.java}' already exists.")
}

private class DummyInstrumentation(val name: String) : AndroidInstrumentation {
override fun install(
application: Application,
openTelemetryRum: OpenTelemetryRum,
) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.instrumentation

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

class AndroidInstrumentationRegistryTest {
@Test
fun `Verify singleton`() {
val registry = AndroidInstrumentationRegistry.get()

assertThat(registry).isEqualTo(AndroidInstrumentationRegistry.get())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.instrumentation

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

class TestAndroidInstrumentation : AndroidInstrumentation {
var installed = false
private set

override fun install(
application: Application,
openTelemetryRum: OpenTelemetryRum,
) {
installed = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.opentelemetry.android.instrumentation.TestAndroidInstrumentation

0 comments on commit a14dcaf

Please sign in to comment.