Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions android-agent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {
implementation(project(":session"))
implementation(project(":services"))
implementation(libs.opentelemetry.exporter.otlp)
implementation(libs.opentelemetry.semconv.incubating)

// Default instrumentations:
api(project(":instrumentation:activity"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
package io.opentelemetry.android.agent

import android.app.Application
import io.opentelemetry.android.AndroidResource
import io.opentelemetry.android.OpenTelemetryRum
import io.opentelemetry.android.OpenTelemetryRumBuilder
import io.opentelemetry.android.agent.connectivity.EndpointConnectivity
import io.opentelemetry.android.agent.connectivity.HttpEndpointConnectivity
import io.opentelemetry.android.agent.metrics.FilteredResource
import io.opentelemetry.android.agent.metrics.MetricsConfig
import io.opentelemetry.android.agent.session.SessionConfig
import io.opentelemetry.android.agent.session.SessionIdTimeoutHandler
import io.opentelemetry.android.agent.session.SessionManager
Expand All @@ -32,7 +35,11 @@ import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor
import io.opentelemetry.sdk.metrics.InstrumentSelector
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder
import io.opentelemetry.sdk.metrics.View
import java.time.Duration
import java.util.function.BiFunction

object OpenTelemetryRumInitializer {
/**
Expand All @@ -45,6 +52,7 @@ object OpenTelemetryRumInitializer {
* @param logEndpointConnectivity Log-specific endpoint configuration.
* @param metricEndpointConnectivity Metric-specific endpoint configuration.
* @param rumConfig Configuration used by [OpenTelemetryRumBuilder].
* @param metricsConfig Configures which Attributes and Resource Attributes to include on metrics.
* @param sessionConfig The session configuration, which includes inactivity timeout and maximum lifetime durations.
* @param activityTracerCustomizer Tracer customizer for [ActivityLifecycleInstrumentation].
* @param activityNameExtractor Name extractor for [ActivityLifecycleInstrumentation].
Expand Down Expand Up @@ -77,6 +85,7 @@ object OpenTelemetryRumInitializer {
),
rumConfig: OtelRumConfig = OtelRumConfig(),
sessionConfig: SessionConfig = SessionConfig.withDefaults(),
metricsConfig: MetricsConfig = MetricsConfig.withDefaults(),
activityTracerCustomizer: ((Tracer) -> Tracer)? = null,
activityNameExtractor: ScreenNameExtractor? = null,
fragmentTracerCustomizer: ((Tracer) -> Tracer)? = null,
Expand Down Expand Up @@ -118,9 +127,37 @@ object OpenTelemetryRumInitializer {
.setEndpoint(metricEndpointConnectivity.getUrl())
.setHeaders(metricEndpointConnectivity::getHeaders)
.build()
}.build()
}.addMeterProviderCustomizer(createMetricsFilter(metricsConfig))
.build()
}

private fun createMetricsFilter(config: MetricsConfig): BiFunction<SdkMeterProviderBuilder, Application, SdkMeterProviderBuilder> =
BiFunction<SdkMeterProviderBuilder, Application, SdkMeterProviderBuilder> { builder, app ->
if (config.isEmpty()) {
builder
} else {
if (config.getMetricResourceKeysToInclude().isNotEmpty()) {
val resource = AndroidResource.createDefault(app)
val filteredResource =
FilteredResource(resource, config.getMetricResourceKeysToInclude())
builder.setResource(filteredResource.get())
}
if (config.getMetricAttributesToInclude().isNotEmpty()) {
builder.registerView(
InstrumentSelector
.builder()
.setName("*") // match all instruments
.build(),
View
.builder()
.setAttributeFilter(config.getMetricAttributesToInclude())
.build(),
)
}
builder
}
}

private fun createSessionProvider(
application: Application,
sessionConfig: SessionConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.agent.metrics

import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.AttributeType
import io.opentelemetry.sdk.resources.Resource
import io.opentelemetry.sdk.resources.ResourceBuilder

internal class FilteredResource(
private val resource: Resource,
private val includeKeys: Set<String>,
) {
fun get(): Resource {
val builder = Resource.builder().setSchemaUrl(resource.schemaUrl)
resource.attributes.forEach { key, value ->
if (wantKey(key)) {
put(builder, key, value)
}
}
return builder.build()
}

private fun put(
builder: ResourceBuilder,
key: AttributeKey<*>,
value: Any,
) {
when (key.type) {
AttributeType.STRING ->
builder.put(
key as AttributeKey<String>,
value as String,
)

AttributeType.LONG -> builder.put(key as AttributeKey<Long>, value as Long)
AttributeType.DOUBLE ->
builder.put(
key as AttributeKey<Double>,
value as Double,
)

AttributeType.BOOLEAN ->
builder.put(
key as AttributeKey<Boolean>,
value as Boolean,
)

AttributeType.STRING_ARRAY ->
builder.put(
key as AttributeKey<List<String>>,
value as List<String>,
)

AttributeType.LONG_ARRAY ->
builder.put(
key as AttributeKey<List<Long>>,
value as List<Long>,
)

AttributeType.DOUBLE_ARRAY ->
builder.put(
key as AttributeKey<List<Double>>,
value as List<Double>,
)

AttributeType.BOOLEAN_ARRAY ->
builder.put(
key as AttributeKey<List<Boolean>>,
value as List<Boolean>,
)
}
}

private fun wantKey(key: AttributeKey<*>): Boolean = includeKeys.contains(key.key)
}
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.agent.metrics

import io.opentelemetry.semconv.ServiceAttributes
import io.opentelemetry.semconv.incubating.OsIncubatingAttributes
import java.util.Collections.unmodifiableSet

class MetricsConfig {
private val metricResourceKeysToInclude: MutableSet<String> = HashSet()
private val metricAttributesToInclude: MutableSet<String> = HashSet()

fun includeMetricResourceAttributes(vararg keys: String): MetricsConfig {
metricResourceKeysToInclude.addAll(listOf(*keys))
return this
}

fun includeMetricAttributeKeys(vararg keys: String): MetricsConfig {
metricAttributesToInclude.addAll(listOf(*keys))
return this
}

fun getMetricResourceKeysToInclude(): Set<String> = unmodifiableSet(metricResourceKeysToInclude)

fun getMetricAttributesToInclude(): Set<String> = unmodifiableSet(metricAttributesToInclude)

fun hasMetricResourceKeysToInclude(): Boolean = metricResourceKeysToInclude.isEmpty()

fun isEmpty(): Boolean = metricAttributesToInclude.isEmpty() && metricResourceKeysToInclude.isEmpty()

companion object {
fun withDefaults(): MetricsConfig =
MetricsConfig()
// .includeMetricAttributeKeys("tbd")
.includeMetricResourceAttributes(
ServiceAttributes.SERVICE_NAME.key,
ServiceAttributes.SERVICE_VERSION.key,
OsIncubatingAttributes.OS_NAME.key,
OsIncubatingAttributes.OS_TYPE.key,
OsIncubatingAttributes.OS_VERSION.key,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.android.agent.metrics

import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.sdk.resources.Resource
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

class FilteredResourceTest {
@Test
fun `test filter`() {
val original =
Resource
.builder()
.setSchemaUrl("http://foo.bar.com")
.put("string", "string")
.put("skipme1", "xxx")
.put("long", 21L)
.put("double", 21.111)
.put("boolean", true)
.put("skipme2", true)
.put("string.array", "foo", "bar")
.put("long.array", 67, 68, 69)
.put("double.array", 1.1, 2.2, 3.3)
.put("bool.array", true, false, true)
.build()
val wantKeys = setOf("string", "long", "double", "boolean", "string.array", "long.array", "double.array", "bool.array")
val filteredResource = FilteredResource(original, wantKeys)
val result = filteredResource.get()
assertThat(result.getAttribute(AttributeKey.stringKey("string"))).isEqualTo("string")
assertThat(result.getAttribute(AttributeKey.longKey("long"))).isEqualTo(21L)
assertThat(result.getAttribute(AttributeKey.doubleKey("double"))).isEqualTo(21.111)
assertThat(result.getAttribute(AttributeKey.booleanKey("boolean"))).isTrue()
assertThat(result.getAttribute(AttributeKey.stringArrayKey("string.array"))).isEqualTo(listOf("foo", "bar"))
assertThat(result.getAttribute(AttributeKey.longArrayKey("long.array"))).isEqualTo(listOf(67L, 68L, 69L))
assertThat(result.getAttribute(AttributeKey.doubleArrayKey("double.array"))).isEqualTo(listOf(1.1, 2.2, 3.3))
assertThat(result.getAttribute(AttributeKey.booleanArrayKey("bool.array"))).isEqualTo(listOf(true, false, true))
assertThat(result.getAttribute(AttributeKey.stringKey("skipme1"))).isNull()
assertThat(result.getAttribute(AttributeKey.booleanKey("skipme2"))).isNull()
}
}

This file was deleted.

Loading