Skip to content

Commit

Permalink
Merge pull request #2144 from DataDog/yl/add-datadog-meter
Browse files Browse the repository at this point in the history
RUM-5553: Add DatadogMeter to read vital data for benchmark
  • Loading branch information
ambushwork authored Jul 30, 2024
2 parents c447d95 + acf70d2 commit 3be8491
Show file tree
Hide file tree
Showing 13 changed files with 981 additions and 0 deletions.
105 changes: 105 additions & 0 deletions tools/benchmark/src/main/java/com/datadog/benchmark/DatadogMeter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.benchmark

import com.datadog.benchmark.exporter.DatadogMetricExporter
import com.datadog.benchmark.internal.reader.CPUVitalReader
import com.datadog.benchmark.internal.reader.FpsVitalReader
import com.datadog.benchmark.internal.reader.MemoryVitalReader
import com.datadog.benchmark.internal.reader.VitalReader
import com.datadog.benchmark.noop.NoOpObservableDoubleGauge
import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.api.metrics.Meter
import io.opentelemetry.api.metrics.ObservableDoubleGauge
import io.opentelemetry.sdk.OpenTelemetrySdk
import io.opentelemetry.sdk.metrics.SdkMeterProvider
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader
import java.util.concurrent.TimeUnit

/**
* This class is responsible for managing the performance gauges related to CPU, FPS, and memory usage.
* It provides functionalities to start and stop monitoring these metrics, which will be uploaded to
* Datadog metric API.
*/
class DatadogMeter private constructor(private val meter: Meter) {

private val cpuVitalReader: CPUVitalReader = CPUVitalReader()
private val memoryVitalReader: MemoryVitalReader = MemoryVitalReader()
private val fpsVitalReader: FpsVitalReader = FpsVitalReader()

private val gaugesByMetricName: MutableMap<String, ObservableDoubleGauge> = mutableMapOf()

/**
* Starts cpu, memory and fps gauges.
*/
fun startGauges() {
startGauge(cpuVitalReader)
startGauge(memoryVitalReader)
startGauge(fpsVitalReader)
}

/**
* Stops cpu, memory and fps gauges.
*/
fun stopAllGauges() {
stopGauge(cpuVitalReader)
stopGauge(memoryVitalReader)
stopGauge(fpsVitalReader)
}

private fun startGauge(reader: VitalReader) {
synchronized(reader) {
// Close the gauge if it exists before.
val metricName = reader.metricName()
gaugesByMetricName[metricName]?.close()
reader.start()
meter.gaugeBuilder(metricName).apply {
reader.unit()?.let { unit ->
setUnit(unit)
}
}.buildWithCallback { observableDoubleMeasurement ->
reader.readVitalData()?.let { data ->
observableDoubleMeasurement.record(data)
}
}.also { observableDoubleGauge ->
gaugesByMetricName[metricName] = observableDoubleGauge
}
}
}

private fun stopGauge(reader: VitalReader) {
synchronized(reader) {
reader.stop()
gaugesByMetricName[reader.metricName()]?.close()
gaugesByMetricName[reader.metricName()] = NoOpObservableDoubleGauge()
}
}

companion object {

/**
* Creates an instance of [DatadogMeter] with given configuration.
*/
fun create(datadogExporterConfiguration: DatadogExporterConfiguration): DatadogMeter {
val sdkMeterProvider = SdkMeterProvider.builder()
.registerMetricReader(
PeriodicMetricReader.builder(DatadogMetricExporter(datadogExporterConfiguration))
.setInterval(datadogExporterConfiguration.intervalInSeconds, TimeUnit.SECONDS)
.build()
)
.build()

val openTelemetry: OpenTelemetry = OpenTelemetrySdk.builder()
.setMeterProvider(sdkMeterProvider)
.build()
val meter = openTelemetry.getMeter(METER_INSTRUMENTATION_SCOPE_NAME)
return DatadogMeter(meter)
}

private const val METER_INSTRUMENTATION_SCOPE_NAME = "datadog.open-telemetry"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.datadog.benchmark.exporter

import android.os.Build
import com.datadog.benchmark.DatadogExporterConfiguration
import com.datadog.benchmark.internal.DatadogHttpClient
import com.datadog.benchmark.internal.model.MetricContext
import io.opentelemetry.sdk.common.CompletableResultCode
import io.opentelemetry.sdk.metrics.InstrumentType
import io.opentelemetry.sdk.metrics.data.AggregationTemporality
import io.opentelemetry.sdk.metrics.data.MetricData
import io.opentelemetry.sdk.metrics.export.MetricExporter

internal class DatadogMetricExporter(datadogExporterConfiguration: DatadogExporterConfiguration) : MetricExporter {

private val metricContext: MetricContext = MetricContext(
deviceModel = Build.MODEL,
osVersion = Build.VERSION.RELEASE,
run = datadogExporterConfiguration.run ?: DEFAULT_RUN_NAME,
applicationId = datadogExporterConfiguration.applicationId ?: DEFAULT_APPLICATION_ID,
intervalInSeconds = datadogExporterConfiguration.intervalInSeconds
)
private val metricHttpClient: DatadogHttpClient = DatadogHttpClient(
metricContext,
datadogExporterConfiguration
)

override fun getAggregationTemporality(instrumentType: InstrumentType): AggregationTemporality {
return AggregationTemporality.DELTA
}

override fun export(metrics: MutableCollection<MetricData>): CompletableResultCode {
// currently no aggregation is required
metricHttpClient.uploadMetric(metrics.toList())
return CompletableResultCode.ofSuccess()
}

override fun flush(): CompletableResultCode {
// currently do nothing
return CompletableResultCode.ofSuccess()
}

override fun shutdown(): CompletableResultCode {
// currently do nothing
return CompletableResultCode.ofSuccess()
}

companion object {
private const val DEFAULT_RUN_NAME = "unknown run"
private const val DEFAULT_APPLICATION_ID = "unassigned application id"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.benchmark.exporter

import android.os.Build
import android.util.Log
import com.datadog.benchmark.DatadogExporterConfiguration
import com.datadog.benchmark.internal.MetricRequestBodyBuilder
import com.datadog.benchmark.internal.model.MetricContext
import io.opentelemetry.sdk.common.CompletableResultCode
import io.opentelemetry.sdk.metrics.InstrumentType
import io.opentelemetry.sdk.metrics.data.AggregationTemporality
import io.opentelemetry.sdk.metrics.data.MetricData
import io.opentelemetry.sdk.metrics.export.MetricExporter

internal class LogsMetricExporter(datadogExporterConfiguration: DatadogExporterConfiguration) : MetricExporter {

private val metricContext: MetricContext = MetricContext(
deviceModel = Build.MODEL,
osVersion = Build.VERSION.RELEASE,
run = datadogExporterConfiguration.run ?: DEFAULT_RUN_NAME,
applicationId = datadogExporterConfiguration.applicationId ?: DEFAULT_APPLICATION_ID,
intervalInSeconds = datadogExporterConfiguration.intervalInSeconds
)

override fun getAggregationTemporality(instrumentType: InstrumentType): AggregationTemporality {
return AggregationTemporality.DELTA
}

override fun export(metrics: Collection<MetricData>): CompletableResultCode {
print(metrics)
return CompletableResultCode.ofSuccess()
}

private fun print(metrics: Collection<MetricData>) {
MetricRequestBodyBuilder(metricContext).buildJsonElement(metrics.toList()).toString().apply {
Log.i("LogsMetricExporter", this)
}
}

override fun flush(): CompletableResultCode {
// currently do nothing
return CompletableResultCode.ofSuccess()
}

override fun shutdown(): CompletableResultCode {
// currently do nothing
return CompletableResultCode.ofSuccess()
}

companion object {
private const val DEFAULT_RUN_NAME = "log run"
private const val DEFAULT_APPLICATION_ID = "unassigned application id"
}
}
91 changes: 91 additions & 0 deletions tools/benchmark/src/main/java/com/datadog/benchmark/ext/FileExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.benchmark.ext

import java.io.File
import java.nio.charset.Charset

/*
* The java.lang.File class throws a SecurityException for the following calls:
* - canRead()
* - canWrite()
* - delete()
* - exists()
* - isFile()
* - isDir()
* - listFiles(…)
* - length()
* The following set of extension make sure that every call to those methods
* is safeguarded to avoid crashing the customer's app.
*/

@Suppress("TooGenericExceptionCaught", "SwallowedException")
private fun <T> File.safeCall(
default: T,
lambda: File.() -> T
): T {
return try {
lambda()
} catch (e: SecurityException) {
default
} catch (e: Exception) {
default
}
}

/**
* Non-throwing version of [File.canRead]. If exception happens, false is returned.
*/

fun File.canReadSafe(): Boolean {
return safeCall(default = false) {
@Suppress("UnsafeThirdPartyFunctionCall")
canRead()
}
}

/**
* Non-throwing version of [File.exists]. If exception happens, false is returned.
*/

fun File.existsSafe(): Boolean {
return safeCall(default = false) {
@Suppress("UnsafeThirdPartyFunctionCall")
exists()
}
}

/**
* Non-throwing version of [File.readText]. If exception happens, null is returned.
*/

fun File.readTextSafe(charset: Charset = Charsets.UTF_8): String? {
return if (existsSafe() && canReadSafe()) {
safeCall(default = null) {
@Suppress("UnsafeThirdPartyFunctionCall")
readText(charset)
}
} else {
null
}
}

/**
* Non-throwing version of [File.readLines]. If exception happens, null is returned.
*/
fun File.readLinesSafe(
charset: Charset = Charsets.UTF_8
): List<String>? {
return if (existsSafe() && canReadSafe()) {
safeCall(default = null) {
@Suppress("UnsafeThirdPartyFunctionCall")
readLines(charset)
}
} else {
null
}
}
Loading

0 comments on commit 3be8491

Please sign in to comment.