Skip to content

RUM-9508: LogsCustom scenario for android benchmark app#2625

Merged
aleksandr-gringauz merged 3 commits into
developfrom
aleksandr-gringauz/RUM-9508/logs-scenarios
May 5, 2025
Merged

RUM-9508: LogsCustom scenario for android benchmark app#2625
aleksandr-gringauz merged 3 commits into
developfrom
aleksandr-gringauz/RUM-9508/logs-scenarios

Conversation

@aleksandr-gringauz

@aleksandr-gringauz aleksandr-gringauz commented Apr 30, 2025

Copy link
Copy Markdown
Contributor

What does this PR do?

  1. Implemented LogsCustom scenario for Android Benchmarking app that is described here. demo: link

  2. created a synthetics test described here. Link. I checked that is works for my local build.

  3. Also see comments in the pr.

Motivation

What inspired you to submit this pull request?

Additional Notes

Anything else we should know when reviewing?

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
  • Make sure you discussed the feature or bugfix with the maintaining team in an Issue
  • Make sure each commit and the PR mention the Issue number (cf the CONTRIBUTING doc)

}

@Composable
private fun <T> DropDownMenuView(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Quality Violation

Function names should be camel case. (...read more)

This rule mandates that function names in Kotlin should be written in camel case, starting with a lowercase letter using no underscores. This is convention in Kotlin and enhances readability and maintainability.

View in Datadog  Leave us feedback  Documentation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

import androidx.compose.ui.unit.dp

@Composable
internal fun LogsScreen(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Quality Violation

Function names should be camel case. (...read more)

This rule mandates that function names in Kotlin should be written in camel case, starting with a lowercase letter using no underscores. This is convention in Kotlin and enhances readability and maintainability.

View in Datadog  Leave us feedback  Documentation

}

@Composable
private fun <T> ExpandedItemView(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Quality Violation

Function names should be camel case. (...read more)

This rule mandates that function names in Kotlin should be written in camel case, starting with a lowercase letter using no underscores. This is convention in Kotlin and enhances readability and maintainability.

View in Datadog  Leave us feedback  Documentation


@Preview(showBackground = true)
@Composable
fun LogsScreenPreview() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Quality Violation

Function names should be camel case. (...read more)

This rule mandates that function names in Kotlin should be written in camel case, starting with a lowercase letter using no underscores. This is convention in Kotlin and enhances readability and maintainability.

View in Datadog  Leave us feedback  Documentation

}

@Composable
private fun IntervalItemView(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Quality Violation

Function names should be camel case. (...read more)

This rule mandates that function names in Kotlin should be written in camel case, starting with a lowercase letter using no underscores. This is convention in Kotlin and enhances readability and maintainability.

View in Datadog  Leave us feedback  Documentation

@aleksandr-gringauz
aleksandr-gringauz force-pushed the aleksandr-gringauz/RUM-9508/logs-scenarios branch 4 times, most recently from bca5d7e to 1971d70 Compare May 1, 2025 10:16

@Preview(showBackground = true)
@Composable
internal fun LogsScreenPreview() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Quality Violation

Suggested change
internal fun LogsScreenPreview() {
internal fun LogsScreenPreview {
An empty parentheses block before a lambda is redundant. (...read more)

If a function has a lambda parameter and you're passing a lambda expression, parentheses are unnecessary. Including unnecessary syntax makes the code less readable.

View in Datadog  Leave us feedback  Documentation

}

@Composable
private fun ValueChooserItemView(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Quality Violation

Function names should be camel case. (...read more)

This rule mandates that function names in Kotlin should be written in camel case, starting with a lowercase letter using no underscores. This is convention in Kotlin and enhances readability and maintainability.

View in Datadog  Leave us feedback  Documentation

Comment thread gradle/libs.versions.toml
androidXWorkManager = "2.8.1" # Next version will bring coroutines
googleAccompanist = "0.20.2"
googleMaterial = "1.3.0"
dagger = "2.56.2"

@aleksandr-gringauz aleksandr-gringauz May 1, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Integrated dagger in the benchmark app. If anyone has any objections - tell me.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If it doesn't bring a dramatic compilation time penalty, I'm personally okay with that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Benchmark app is a small app that compiles very fast. Dagger might add something, but it is not noticeable (I didn't measure, just describe the feeling) and will not be in any foreseeable future.

The Dagger annotation processor is run only when compiling the application module, it is not run when compiling dd-sdk-core for example.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two questions from me about this:

  1. it seems that you refactor a lot of code to adapt to Dagger2, is there anything that you can not handle without Dagger2 for your task? or it's just you prefer using it for dependency injection?
  2. If it's for dependency injection, why not Hilt instead of Dagger2?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

  1. Yes, I could do it without dagger or any di framework. But imho it is a great tool and it is the first thing I set up if I do any kind of android app event if it is a sample. It saves a lot of time.
  2. Why I didn't use hilt https://github.com/DataDog/dd-sdk-android/pull/2625/files#r2071751641

@codecov-commenter

codecov-commenter commented May 1, 2025

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 70.02%. Comparing base (0e0e229) to head (673b991).

Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #2625   +/-   ##
========================================
  Coverage    70.02%   70.02%           
========================================
  Files          820      820           
  Lines        30568    30568           
  Branches      5132     5132           
========================================
  Hits         21405    21405           
+ Misses        7733     7720   -13     
- Partials      1430     1443   +13     

see 35 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

* Class to control the benchmark test in sample application. It takes the [Config] which is parsed
* from Synthetics variables in order to decide which scenario it should run for the benchmark test.
*/
internal class DatadogBenchmark(config: Config) {

@aleksandr-gringauz aleksandr-gringauz May 1, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactored this file.

  1. Created DatadogFeaturesInitializer that is responsible of initializing different features of Datadog based on run and scenario.
  2. Moved DatadogBenchmark.Config out of DatadogBenchmark and renamed it BenchmarkConfig.
  3. DatadogBaseMeter is created inside a dagger module called DatadogActivityModule.
  4. deleted DatadogBenchmark class.

}

// the same as the default one
private const val CAPACITY_BACK_PRESSURE_STRATEGY = 1024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This value was 32 before. I think that for benchmarking we should use the default one that is most likely used by the clients.

~ Copyright 2016-Present Datadog, Inc.
-->

<navigation xmlns:android="http://schemas.android.com/apk/res/android"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed this, instead now use kotlin to configure navigation graph. See FragmentsNavigationManagerImpl.

  1. I prefer kotlin over xml for navigation
  2. Wanted to compute startDestination based on BenchmarkConfig.
  3. Create different navigationGraphs for different scenarios. Though it is not exactly necessary and I'm not sure I'll leave it this way.
  4. Maybe I'll use typesafe navigation in future prs.

2,3,4 can most likely be done in xml, but imho doing it in kotlin is much easier.

Assertions.assertThat(config.scenario).isEqualTo(null)
Assertions.assertThat(config.run).isEqualTo(null)
}

@Test
fun `M enable session replay W config run is instrumented`() {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this moved to DatadogFeaturesInitializerTest and I added some new tests there

Logs.enable(logsConfig, sdkCore)
}

private fun needToEnableRum(): Boolean {

@aleksandr-gringauz aleksandr-gringauz May 1, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Previously Benchmark App initialized Rum feature in Application.onCreate. Now it is done in Activity.onCreate. I did it because Logs feature doesn't actually need Rum to work and on iOS they don't initialize it in LogsCustom scenario (link).

And you only know what scenario it is in Activity.onCreate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The general recommendation we give is to initialize all the components at the Application.onCreate to have all the observability as early as possible.

If we choose to initialize things in the Activity.onCreate we may have it called several times in the application lifecycle (e.g. due to configuration change), meaning that we will have multiple calls to Rum.create.

This won't have any impact, because once RUM feature is initialized, the subsequent initialization calls won't do anything, but still, do we want to keep it as an example of the SDK usage? What is the benefit of this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What is the benefit of this change?

Right now we pass what scenario we want to run as intent extras. See this and this.

And the features we want to initialize depend on the scenario.

Thus we have to initialize the features in Activity.onCreate.

Prior to this pr sessionreplay was initialized in Activity.onCreate as well (link).

My pr added the Logs feature and I moved the initialization of Rum feature to Activity.onCreate (because Logs don't need it).

we may have it called several times in the application lifecycle (e.g. due to configuration change)

This is a good point. This can be mitigated by moving DatadogFeaturesInitializer to the application scope and creating some sort of isInitialized flag in it that allows initialization only once.

do we want to keep it as an example of the SDK usage?

I can write a comment in code explaining that you should consider initializing everything in Application.onCreate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

  1. added the comment for DatadogFeaturesInitializer
  2. added isInitialized flag in DatadogFeaturesInitializer

*/

package com.datadog.benchmark.sample.fragment
package com.datadog.benchmark.sample.ui.sessionreplay

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved all screen related files to the folder ui/scenario_name.

@aleksandr-gringauz
aleksandr-gringauz force-pushed the aleksandr-gringauz/RUM-9508/logs-scenarios branch from 1971d70 to e29760d Compare May 1, 2025 13:53
@aleksandr-gringauz
aleksandr-gringauz force-pushed the aleksandr-gringauz/RUM-9508/logs-scenarios branch from e29760d to 26b95d4 Compare May 1, 2025 13:58
@aleksandr-gringauz
aleksandr-gringauz marked this pull request as ready for review May 2, 2025 06:07
@aleksandr-gringauz
aleksandr-gringauz requested review from a team as code owners May 2, 2025 06:07
Logs.enable(logsConfig, sdkCore)
}

private fun needToEnableRum(): Boolean {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The general recommendation we give is to initialize all the components at the Application.onCreate to have all the observability as early as possible.

If we choose to initialize things in the Activity.onCreate we may have it called several times in the application lifecycle (e.g. due to configuration change), meaning that we will have multiple calls to Rum.create.

This won't have any impact, because once RUM feature is initialized, the subsequent initialization calls won't do anything, but still, do we want to keep it as an example of the SDK usage? What is the benefit of this change?

Comment thread gradle/libs.versions.toml
androidXWorkManager = "2.8.1" # Next version will bring coroutines
googleAccompanist = "0.20.2"
googleMaterial = "1.3.0"
dagger = "2.56.2"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If it doesn't bring a dramatic compilation time penalty, I'm personally okay with that.

config = BenchmarkConfig.resolveSyntheticsBundle(intent.extras)

benchmarkActivityComponent = DaggerBenchmarkActivityComponent.factory().create(
deps = (application as BenchmarkApplication).benchmarkAppComponent,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: maybe benchmarkAppComponent can be an internal extension property for the Application class? if used from multiple places

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

private fun createStartDestination(scenario: SyntheticsScenario?): String {
return when (scenario) {
SyntheticsScenario.SessionReplay -> SESSION_REPLAY_METERIAL_FRAGMENT_KEY
SyntheticsScenario.SessionReplayCompose -> TODO()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe create JIRA tickets in put a reference for all these TODO items?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

"os" to "iOS 17.2",
"battery" to "80%"
),
"network" to mapOf(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nb: this will clash with the default network property set by SDK (unfortunately, LogEvent has custom attribute in the shared root namespace) and so will be ignored.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

thanks for spotting this

changed all the attribute keys to have benchmark_ prefix.

Comment on lines +131 to +138
private fun MockedStatic<Rum>.verifyRumEnabled() {
verify {
Rum.enable(
rumConfiguration = any(),
sdkCore = any()
)
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The alternative to these checks is Logs.isEnabled, etc. and this will also allow to avoid using mockStatic, but then it won't be really a unit-test then.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

thanks for the info

for now didn't change anything here

.trackLongTasks(THRESHOLD_LONG_TASK_INTERVAL)
.trackNonFatalAnrs(true)
.setViewEventMapper { event ->
event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what's this attribute used for ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't know. I didn't change anything here, it's the same as it was before link

It is the same in the sample app link and it is pretty old code.

"benchmark_log_type" to "user_event",
"benchmark_session" to mapOf(
"id" to UUID.randomUUID().toString(),
"startTime" to "2024-02-27T12:00:00Z",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why is the date hardcoded ? same question regarding the user:id below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it is a copy paste from ios link

I suppose it doesn't matter what we put here. These are just test payloads that have different sizes.

0xnm
0xnm previously approved these changes May 2, 2025
@aleksandr-gringauz
aleksandr-gringauz merged commit dd4ee16 into develop May 5, 2025
@aleksandr-gringauz
aleksandr-gringauz deleted the aleksandr-gringauz/RUM-9508/logs-scenarios branch May 5, 2025 08:53
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.

5 participants