diff --git a/CHANGELOG.md b/CHANGELOG.md index 5099ccc779..deda81bde3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,56 @@ +# 3.6.0 / 2026-01-27 + +* [FEATURE] New feature - Profiling. See [docs](https://docs.datadoghq.com/real_user_monitoring/correlate_with_other_telemetry/profiling/) for more details. + - Create profiling module. See [#2893](https://github.com/DataDog/dd-sdk-android/pull/2893) + - Integrate Perfetto `ProfilingManager` API. See [#2902](https://github.com/DataDog/dd-sdk-android/pull/2902) + - Create Profiling Event schema. See [#2912](https://github.com/DataDog/dd-sdk-android/pull/2912) + - Implement Profiling writer and request factory. See [#2915](https://github.com/DataDog/dd-sdk-android/pull/2915) + - Implement Profiling Feature. See [#2918](https://github.com/DataDog/dd-sdk-android/pull/2918) + - Send TTID event to Profiling feature to stop profiling. See [#2933](https://github.com/DataDog/dd-sdk-android/pull/2933) + - Defer `requestProfiling` to next app launch. See [#2950](https://github.com/DataDog/dd-sdk-android/pull/2950) + - Add file size in profiling telemetry & fix unit test. See [#2964](https://github.com/DataDog/dd-sdk-android/pull/2964) + - Rename `ProfilingEvent` to `ProfileEvent`. See [#2969](https://github.com/DataDog/dd-sdk-android/pull/2969) + - Add missing `ProfileEvent` tags. See [#2992](https://github.com/DataDog/dd-sdk-android/pull/2992) + - Attach RUM information on profiling event. See [#3017](https://github.com/DataDog/dd-sdk-android/pull/3017) + - Add profiling status in RUM vital event. See [#3021](https://github.com/DataDog/dd-sdk-android/pull/3021) + - Handle multi SDK instance in profiling. See [#3026](https://github.com/DataDog/dd-sdk-android/pull/3026) + - Fix RUM context bundling for `ProfileEvent`. See [#3036](https://github.com/DataDog/dd-sdk-android/pull/3036) + - Reduce Profiling max duration to 10 seconds. See [#3040](https://github.com/DataDog/dd-sdk-android/pull/3040) + - Add build ID in profiling event tags. See [#3046](https://github.com/DataDog/dd-sdk-android/pull/3046) + - Add `Profiling.profileNextAppStartup` API. See [#3045](https://github.com/DataDog/dd-sdk-android/pull/3045) + - Fix `null` build ID in profile event tags. See [#3059](https://github.com/DataDog/dd-sdk-android/pull/3059)= + - Add `ProfilingFragment` into Sample Application. See [#3074](https://github.com/DataDog/dd-sdk-android/pull/3074) + - Rebuild profiling request for every profiling start. See [#3076](https://github.com/DataDog/dd-sdk-android/pull/3076) + - Initialize Profiler only once for multiple start calls. See [#3081](https://github.com/DataDog/dd-sdk-android/pull/3081) + - Expose Profiling start/stop as public API. See [#3092](https://github.com/DataDog/dd-sdk-android/pull/3092) + - Address concurrency and app start detection issues in Profiling. See [#3117](https://github.com/DataDog/dd-sdk-android/pull/3117) + - Remove `profileNextAppStartup` API. See [#3121](https://github.com/DataDog/dd-sdk-android/pull/3121) + - Add sample rate for App launch profiling. See [#3109](https://github.com/DataDog/dd-sdk-android/pull/3109) + - Set profiling sampling rate to 15%, improve profiling logic. See [#3127](https://github.com/DataDog/dd-sdk-android/pull/3127) + - Add `operation:launch` tag for profiling. See [#3133](https://github.com/DataDog/dd-sdk-android/pull/3133) + - Update Profiling App launch telemetry. See [#3134](https://github.com/DataDog/dd-sdk-android/pull/3134) + - Add experimental annotation on Profiling APIs & Bug fix. See [#3143](https://github.com/DataDog/dd-sdk-android/pull/3143) + - Update Profiler sampling frequency to 201Hz. See [#3148](https://github.com/DataDog/dd-sdk-android/pull/3148) + - Remove custom profiling from public API. See [#3152](https://github.com/DataDog/dd-sdk-android/pull/3152) +* [FEATURE] Flags compatibility with OpenFeature + - Scaffold OpenFeature provider module. See [#2996](https://github.com/DataDog/dd-sdk-android/pull/2996) + - Add OpenFeature type conversion utilities. See [#3103](https://github.com/DataDog/dd-sdk-android/pull/3103) + - OpenFeature Provider implementation powered by Datadog Flags. See [#2998](https://github.com/DataDog/dd-sdk-android/pull/2998) + - Add OpenFeature Flags to test app. See [#3130](https://github.com/DataDog/dd-sdk-android/pull/3130) +* [BUGFIX] Mark fatal ANR as processed before RUM events are written. See [#3129](https://github.com/DataDog/dd-sdk-android/pull/3129) +* [IMPROVEMENT] Flags: State change notification for flags client (for OpenFeature). See [#3042](https://github.com/DataDog/dd-sdk-android/pull/3042) +* [IMPROVEMENT] Flags: Resolve map of primitives. See [#3044](https://github.com/DataDog/dd-sdk-android/pull/3044) +* [IMRPOVEMENT] Flags: Add support for global flags via `EvaluationContext.EMPTY`. See [#3104](https://github.com/DataDog/dd-sdk-android/pull/3104) +* [IMPROVEMENT] Flags: Add `STATIC` resolution reason. See [#3131](https://github.com/DataDog/dd-sdk-android/pull/3131) +* [IMPROVEMENT] Add `setVersion` method to the configuration. See [#3137](https://github.com/DataDog/dd-sdk-android/pull/3137) +* [MAINTENANCE] Update Kotlin AST parser dependency version. See [#3112](https://github.com/DataDog/dd-sdk-android/pull/3112) +* [MAINTENANCE] Skip profiling schema files in session replay module. See [#3119](https://github.com/DataDog/dd-sdk-android/pull/3119) +* [MAINTENANCE] Refactor publishing on CI to use a single job. See [#3122](https://github.com/DataDog/dd-sdk-android/pull/3122) +* [MAINTENANCE] Use a single gradle command for `customDetektRules`. See [#3123](https://github.com/DataDog/dd-sdk-android/pull/3123) +* [MAINTENANCE] Drop `Rum` prefix for the generated `Vital` models. See [#3125](https://github.com/DataDog/dd-sdk-android/pull/3125) +* [MAINTENANCE] Move `:tools:benchmark` tests to the correct package. See [#3150](https://github.com/DataDog/dd-sdk-android/pull/3150) +* [DOCS] Use Kotlin DSL for buildscript snippets in `README` files. See [#3138](https://github.com/DataDog/dd-sdk-android/pull/3138) + # 3.5.0 / 2026-01-08 * [FEATURE] RUM Debug Widget. See [#2943](https://github.com/DataDog/dd-sdk-android/pull/2943) diff --git a/features/dd-sdk-android-profiling/api/apiSurface b/features/dd-sdk-android-profiling/api/apiSurface index 95266edfa8..fe9314a567 100644 --- a/features/dd-sdk-android-profiling/api/apiSurface +++ b/features/dd-sdk-android-profiling/api/apiSurface @@ -8,8 +8,6 @@ class com.datadog.android.profiling.DdProfilingContentProvider : android.content annotation com.datadog.android.profiling.ExperimentalProfilingApi object com.datadog.android.profiling.Profiling fun enable(ProfilingConfiguration = ProfilingConfiguration.DEFAULT, com.datadog.android.api.SdkCore = Datadog.getInstance()) - fun start(android.content.Context, com.datadog.android.api.SdkCore = Datadog.getInstance()) - fun stop(com.datadog.android.api.SdkCore = Datadog.getInstance()) data class com.datadog.android.profiling.ProfilingConfiguration class Builder fun setApplicationLaunchSampleRate(Float): Builder diff --git a/features/dd-sdk-android-profiling/api/dd-sdk-android-profiling.api b/features/dd-sdk-android-profiling/api/dd-sdk-android-profiling.api index 77ec368863..33a6d65088 100644 --- a/features/dd-sdk-android-profiling/api/dd-sdk-android-profiling.api +++ b/features/dd-sdk-android-profiling/api/dd-sdk-android-profiling.api @@ -17,10 +17,6 @@ public final class com/datadog/android/profiling/Profiling { public static final fun enable (Lcom/datadog/android/profiling/ProfilingConfiguration;)V public static final fun enable (Lcom/datadog/android/profiling/ProfilingConfiguration;Lcom/datadog/android/api/SdkCore;)V public static synthetic fun enable$default (Lcom/datadog/android/profiling/ProfilingConfiguration;Lcom/datadog/android/api/SdkCore;ILjava/lang/Object;)V - public final fun start (Landroid/content/Context;Lcom/datadog/android/api/SdkCore;)V - public static synthetic fun start$default (Lcom/datadog/android/profiling/Profiling;Landroid/content/Context;Lcom/datadog/android/api/SdkCore;ILjava/lang/Object;)V - public final fun stop (Lcom/datadog/android/api/SdkCore;)V - public static synthetic fun stop$default (Lcom/datadog/android/profiling/Profiling;Lcom/datadog/android/api/SdkCore;ILjava/lang/Object;)V } public final class com/datadog/android/profiling/ProfilingConfiguration { diff --git a/features/dd-sdk-android-profiling/src/main/java/com/datadog/android/profiling/Profiling.kt b/features/dd-sdk-android-profiling/src/main/java/com/datadog/android/profiling/Profiling.kt index be97f41035..7f3c750bc2 100644 --- a/features/dd-sdk-android-profiling/src/main/java/com/datadog/android/profiling/Profiling.kt +++ b/features/dd-sdk-android-profiling/src/main/java/com/datadog/android/profiling/Profiling.kt @@ -75,7 +75,7 @@ object Profiling { * @param sdkCore SDK instance to start profiling with. If not provided, default SDK instance. */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) - fun start(context: Context, sdkCore: SdkCore = Datadog.getInstance()) { + internal fun start(context: Context, sdkCore: SdkCore = Datadog.getInstance()) { start(context, setOf(sdkCore.name)) } @@ -84,7 +84,7 @@ object Profiling { * * @param sdkCore SDK instance to stop profiling. If not provided, default SDK instance. */ - fun stop(sdkCore: SdkCore = Datadog.getInstance()) { + internal fun stop(sdkCore: SdkCore = Datadog.getInstance()) { profiler.stop(sdkCore.name) } diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt index 79cd571e08..0dde2e3409 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt @@ -59,7 +59,6 @@ internal class HomeFragment : R.id.navigation_crash -> R.id.fragment_crash R.id.navigation_traces -> R.id.fragment_trace R.id.navigation_otel_traces -> R.id.fragment_otel_traces - R.id.navigation_profiling -> R.id.fragment_profiling R.id.navigation_vitals -> R.id.fragment_vitals R.id.navigation_webview -> R.id.fragment_webview R.id.navigation_data_list -> R.id.fragment_data_list diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/profiling/HeavyCPUWork.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/profiling/HeavyCPUWork.kt deleted file mode 100644 index 6705b37bd1..0000000000 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/profiling/HeavyCPUWork.kt +++ /dev/null @@ -1,198 +0,0 @@ -/* - * 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.android.sample.profiling - -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.math.cos -import kotlin.math.pow -import kotlin.math.sin -import kotlin.math.sqrt -import kotlin.random.Random - -/** - * Simulates heavy CPU computation for profiling purposes. - * This class performs various CPU-intensive operations including: - * - Complex mathematical calculations - * - String manipulation - * - List/array operations - * - Prime number calculations - */ -@Suppress("MagicNumber", "TooManyFunctions") -class HeavyCPUWork { - - private val isRunning = AtomicBoolean(false) - private var workerThread: Thread? = null - - /** - * Starts the heavy CPU computation on a background thread. - */ - fun start() { - if (isRunning.getAndSet(true)) { - // Already running - return - } - - workerThread = Thread { - while (isRunning.get()) { - performHeavyComputation() - } - }.apply { - name = "HeavyCPUWork-Thread" - start() - } - } - - /** - * Stops the heavy CPU computation. - */ - fun stop() { - isRunning.set(false) - workerThread = null - } - - /** - * Performs various CPU-intensive operations. - */ - private fun performHeavyComputation() { - // Mix of different CPU-heavy operations - when (Random.nextInt(5)) { - 0 -> calculatePrimes(1000) - 1 -> performMatrixMultiplication(50) - 2 -> performStringOperations(1000) - 3 -> performComplexMath(10000) - 4 -> performListOperations(5000) - } - } - - /** - * Calculate prime numbers up to a limit. - */ - private fun calculatePrimes(limit: Int): List { - val primes = mutableListOf() - for (num in 2..limit) { - if (isPrime(num)) { - primes.add(num) - } - } - return primes - } - - @Suppress("ReturnCount") - private fun isPrime(n: Int): Boolean { - if (n < 2) return false - if (n == 2) return true - if (n % 2 == 0) return false - - val sqrt = sqrt(n.toDouble()).toInt() - for (i in 3..sqrt step 2) { - if (n % i == 0) return false - } - return true - } - - /** - * Perform matrix multiplication. - */ - private fun performMatrixMultiplication(size: Int) { - val matrix1 = Array(size) { DoubleArray(size) { Random.nextDouble() } } - val matrix2 = Array(size) { DoubleArray(size) { Random.nextDouble() } } - val result = Array(size) { DoubleArray(size) } - - for (i in 0 until size) { - for (j in 0 until size) { - var sum = 0.0 - for (k in 0 until size) { - sum += matrix1[i][k] * matrix2[k][j] - } - result[i][j] = sum - } - } - } - - /** - * Perform intensive string operations. - */ - private fun performStringOperations(iterations: Int) { - var result = "" - for (i in 0 until iterations) { - result = "Iteration $i: ${generateRandomString(10)}" - result = result.reversed() - result = result.uppercase() - result = result.lowercase() - - // String contains check - if (result.contains("test")) { - result = result.replace("test", "") - } - } - } - - private fun generateRandomString(length: Int): String { - val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9') - return (1..length) - .map { chars.random() } - .joinToString("") - } - - /** - * Perform complex mathematical calculations. - */ - private fun performComplexMath(iterations: Int) { - var result = 0.0 - for (i in 0 until iterations) { - val x = Random.nextDouble() * 100 - result += sin(x) * cos(x) - result += sqrt(x.pow(2) + x.pow(3)) - result += x.pow(4) / (x + 1) - - // Fibonacci-like calculation - result += fibonacci(20) - } - } - - private fun fibonacci(n: Int): Long { - if (n <= 1) return n.toLong() - var a = 0L - var b = 1L - for (i in 2..n) { - val temp = a + b - a = b - b = temp - } - return b - } - - /** - * Perform list/collection operations. - */ - private fun performListOperations(size: Int) { - // Create and manipulate large lists - val list = (0 until size).map { Random.nextInt(10000) }.toMutableList() - - // Sorting - list.sortDescending() - list.shuffle() - list.sort() - - // Filtering and mapping - val filtered = list.filter { it % 2 == 0 } - filtered.map { it * 2 } - - // Finding operations - list.maxOrNull() - list.minOrNull() - list.sum() - list.average() - - // Group by operations - list.groupBy { it % 10 } - - // Distinct and contains - list.distinct() - list.contains(5000) - } -} diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/profiling/ProfilingFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/profiling/ProfilingFragment.kt deleted file mode 100644 index f0acc10ce3..0000000000 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/profiling/ProfilingFragment.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.android.sample.profiling - -import android.content.Context -import android.os.Build -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.ProgressBar -import android.widget.Toast -import androidx.annotation.RequiresApi -import androidx.fragment.app.Fragment -import com.datadog.android.profiling.Profiling -import com.datadog.android.sample.R - -@Suppress("MagicNumber") -internal class ProfilingFragment : Fragment() { - - private lateinit var button1s: Button - private lateinit var button10s: Button - private lateinit var button60s: Button - - private lateinit var progressBar1s: ProgressBar - private lateinit var progressBar10s: ProgressBar - private lateinit var progressBar60s: ProgressBar - - private val handler = Handler(Looper.getMainLooper()) - private var currentProfilingRunnable: Runnable? = null - - private val heavyCPUWork = HeavyCPUWork() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_profiling, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - // Initialize views - button1s = view.findViewById(R.id.button_profile_1s) - button10s = view.findViewById(R.id.button_profile_10s) - button60s = view.findViewById(R.id.button_profile_60s) - - progressBar1s = view.findViewById(R.id.progress_bar_1s) - progressBar10s = view.findViewById(R.id.progress_bar_10s) - progressBar60s = view.findViewById(R.id.progress_bar_60s) - - // Set click listeners - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { - button1s.setOnClickListener { - startProfiling(requireContext(), duration = 1, progressBar = progressBar1s) - } - button10s.setOnClickListener { - startProfiling(requireContext(), duration = 10, progressBar = progressBar10s) - } - button60s.setOnClickListener { - startProfiling(requireContext(), duration = 60, progressBar = progressBar60s) - } - } else { - Toast.makeText( - requireContext(), - "Profiling is only working for API 35 and above", - Toast.LENGTH_SHORT - ).show() - } - } - - @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) - private fun startProfiling(context: Context, duration: Int, progressBar: ProgressBar) { - // Cancel any ongoing profiling - currentProfilingRunnable?.let { handler.removeCallbacks(it) } - - // Reset all progress bars - resetAllProgressBars() - - // Disable all buttons during profiling - setAllButtonsEnabled(false) - - // Start heavy CPU work for profiling - heavyCPUWork.start() - - Profiling.start(context) - - // Track progress - val startTime = System.currentTimeMillis() - val durationMs = duration * 1000L - val updateInterval = 50L // Update every 50ms for smooth animation - - val profilingRunnable = object : Runnable { - override fun run() { - val elapsed = System.currentTimeMillis() - startTime - val progress = ((elapsed.toFloat() / durationMs) * 100).toInt().coerceIn(0, 100) - - progressBar.progress = progress - - if (elapsed < durationMs) { - // Continue updating progress - handler.postDelayed(this, updateInterval) - } else { - // Profiling complete - progressBar.progress = 100 - - // Stop heavy CPU work - heavyCPUWork.stop() - - Profiling.stop() - - // Re-enable all buttons - setAllButtonsEnabled(true) - - handler.post { - progressBar.progress = 0 - } - - currentProfilingRunnable = null - } - } - } - - currentProfilingRunnable = profilingRunnable - handler.post(profilingRunnable) - } - - private fun resetAllProgressBars() { - progressBar1s.progress = 0 - progressBar10s.progress = 0 - progressBar60s.progress = 0 - } - - private fun setAllButtonsEnabled(enabled: Boolean) { - button1s.isEnabled = enabled - button10s.isEnabled = enabled - button60s.isEnabled = enabled - } - - override fun onDestroyView() { - super.onDestroyView() - // Clean up any pending callbacks - currentProfilingRunnable?.let { handler.removeCallbacks(it) } - // Stop heavy CPU work if still running - heavyCPUWork.stop() - } -} diff --git a/sample/kotlin/src/main/res/layout/fragment_home.xml b/sample/kotlin/src/main/res/layout/fragment_home.xml index 7644231609..9420dda2c6 100644 --- a/sample/kotlin/src/main/res/layout/fragment_home.xml +++ b/sample/kotlin/src/main/res/layout/fragment_home.xml @@ -86,17 +86,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/navigation_traces"/> - - + app:layout_constraintTop_toBottomOf="@id/navigation_otel_traces"/> - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sample/kotlin/src/main/res/navigation/nav_graph.xml b/sample/kotlin/src/main/res/navigation/nav_graph.xml index 5a4451921c..929df76e5f 100644 --- a/sample/kotlin/src/main/res/navigation/nav_graph.xml +++ b/sample/kotlin/src/main/res/navigation/nav_graph.xml @@ -149,8 +149,4 @@ android:name="com.datadog.android.sample.traces.OtelTracesFragment" android:label="@string/title_otel_traces"/> - - diff --git a/sample/kotlin/src/main/res/values/strings.xml b/sample/kotlin/src/main/res/values/strings.xml index 0aafd22c2b..c737e4bc84 100644 --- a/sample/kotlin/src/main/res/values/strings.xml +++ b/sample/kotlin/src/main/res/values/strings.xml @@ -199,11 +199,6 @@ Visible Password Default password OpenTelemetry Traces - Profiling - Profiling Demo - Profile 1 second - Profile 10 seconds - Profile 60 seconds Start Chained Contexts Test Start Async Operation Chained Otel Contexts Test