Skip to content

Conversation

@avazirna
Copy link
Contributor

@avazirna avazirna commented Aug 13, 2025

Product Description

This PR implements a performance custom trace for synchronous case list loading. This is part of an effort to collect performance data related key events in CommCare.

Ticket: https://dimagi.atlassian.net/browse/SAAS-18089

Safety Assurance

Safety story

This is supposed to be a lightweight, null-safe operation that has no impact on the user experience.

Labels and Review

  • Do we need to enhance the manual QA test coverage ? If yes, the "QA Note" label is set correctly
  • Does the PR introduce any major changes worth communicating ? If yes, the "Release Note" label is set and a "Release Note" is specified in PR description.
  • Risk label is set correctly
  • The set of people pinged as reviewers is appropriate for the level of risk of the change

@coderabbitai
Copy link

coderabbitai bot commented Aug 13, 2025

📝 Walkthrough

Walkthrough

  • Made CCAnalyticsParam.CCHQ_DOMAIN and CCAnalyticsParam.USERNAME public.
  • Added CCPerfMonitoring Kotlin object with constants SYNC_ENTITY_LIST_LOADING and NUM_CASES_LOADED.
  • Introduced EntityLoaderHelper.isAsyncNodeEntityFactory() and replaced inline type checks with this method.
  • Instrumented EntityLoaderTask.doInBackground with Firebase Performance: conditionally starts a trace for non-async node factories, tags domain/username, records number of entities loaded, and stops the trace. Adjusted flow to capture loaded entities and improved XPathException handling.

Sequence Diagram(s)

sequenceDiagram
    participant Task as EntityLoaderTask
    participant Helper as EntityLoaderHelper
    participant Perf as FirebasePerformance Trace
    participant Utils as ReportingUtils

    Task->>Helper: isAsyncNodeEntityFactory()
    alt Non-async factory
        Task->>Perf: startTrace(CCPerfMonitoring.SYNC_ENTITY_LIST_LOADING)
        Task->>Utils: addAttribute(CCAnalyticsParam.CCHQ_DOMAIN, domain)
        Task->>Utils: addAttribute(CCAnalyticsParam.USERNAME, username)
    end
    Task->>Helper: loadEntities()
    Helper-->>Task: entities
    alt Non-async factory
        Task->>Perf: putAttribute(CCPerfMonitoring.NUM_CASES_LOADED, entities.count)
        Task->>Perf: stopTrace()
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~9 minutes

Possibly related PRs

Suggested labels

skip-integration-tests

Suggested reviewers

  • pm-dimagi
  • OrangeAndGreen
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-case-list-loading-perf-monitoring-trace

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/src/org/commcare/tasks/EntityLoaderTask.java (1)

59-65: Optimize trace initialization and consider error handling.

The trace is always initialized but only started conditionally, which is inefficient. Consider moving the trace initialization inside the condition to avoid unnecessary object creation.

-        Trace trace = FirebasePerformance.getInstance().newTrace(CCPerfMonitoring.SYNC_ENTITY_LIST_LOADING);
-        if (entityLoaderHelper != null && !entityLoaderHelper.isAsyncNodeEntityFactory()) {
-            // Capture sync_case_list_loading trace for performance monitoring
-            trace.putAttribute(CCAnalyticsParam.CCHQ_DOMAIN, ReportingUtils.getDomain());
-            trace.putAttribute(CCAnalyticsParam.USERNAME, ReportingUtils.getUser());
-            trace.start();
-        }
+        Trace trace = null;
+        if (entityLoaderHelper != null && !entityLoaderHelper.isAsyncNodeEntityFactory()) {
+            // Capture sync_case_list_loading trace for performance monitoring
+            trace = FirebasePerformance.getInstance().newTrace(CCPerfMonitoring.SYNC_ENTITY_LIST_LOADING);
+            trace.putAttribute(CCAnalyticsParam.CCHQ_DOMAIN, ReportingUtils.getDomain());
+            trace.putAttribute(CCAnalyticsParam.USERNAME, ReportingUtils.getUser());
+            trace.start();
+        }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between beee7a0 and 509eeef.

📒 Files selected for processing (4)
  • app/src/org/commcare/google/services/analytics/CCAnalyticsParam.java (2 hunks)
  • app/src/org/commcare/google/services/analytics/CCPerfMonitoring.kt (1 hunks)
  • app/src/org/commcare/tasks/EntityLoaderHelper.kt (2 hunks)
  • app/src/org/commcare/tasks/EntityLoaderTask.java (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/src/org/commcare/tasks/EntityLoaderTask.java (2)
app/src/org/commcare/google/services/analytics/CCAnalyticsParam.java (1)
  • CCAnalyticsParam (7-41)
app/src/org/commcare/tasks/EntityLoaderHelper.kt (2)
  • isAsyncNodeEntityFactory (83-85)
  • loadEntities (58-81)
🔇 Additional comments (7)
app/src/org/commcare/google/services/analytics/CCPerfMonitoring.kt (1)

3-9: LGTM! Clean performance monitoring constants definition.

The Kotlin object provides a centralized location for Firebase Performance monitoring constants with clear naming and proper categorization. The constants follow standard naming conventions and align with the Firebase Performance API expectations.

app/src/org/commcare/tasks/EntityLoaderHelper.kt (2)

83-85: LGTM! Good refactoring to eliminate code duplication.

The extraction of the type check into a dedicated method improves code readability and maintainability by centralizing the logic for determining async factory behavior.


62-62: LGTM! Consistent usage of the extracted helper method.

The replacement of inline type checks with the isAsyncNodeEntityFactory() method call maintains the same behavior while improving code consistency and maintainability.

Also applies to: 75-75

app/src/org/commcare/google/services/analytics/CCAnalyticsParam.java (1)

13-13: LGTM! Appropriate visibility change for cross-module analytics.

Making CCHQ_DOMAIN and USERNAME constants public enables their use in the performance monitoring instrumentation while maintaining the existing functionality and string values.

Also applies to: 26-26

app/src/org/commcare/tasks/EntityLoaderTask.java (3)

5-6: LGTM! Appropriate imports for Firebase Performance instrumentation.

The imports are necessary for the performance monitoring functionality and follow standard Java import conventions.

Also applies to: 9-9, 12-13


66-66: LGTM! Good refactoring to capture load results.

Storing the load results in a local variable improves code readability and enables the performance monitoring logic to access the entity count for metrics recording.

Also applies to: 73-73


74-79: LGTM! Improved error handling with proper exception propagation.

The enhanced XPathException handling properly logs the error and sets mException to ensure the error is propagated to onPostExecute for proper error delivery to the listener.

try {
return entityLoaderHelper.loadEntities(nodeset[0], this);
Trace trace = FirebasePerformance.getInstance().newTrace(CCPerfMonitoring.SYNC_ENTITY_LIST_LOADING);
if (entityLoaderHelper != null && !entityLoaderHelper.isAsyncNodeEntityFactory()) {
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify that entityLoaderHelper null check is redundant.

The null check for entityLoaderHelper appears redundant since it's initialized in the constructor and should never be null during execution.

Also applies to: 68-68


🏁 Script executed:

#!/bin/bash
# Description: Verify that entityLoaderHelper is always non-null in EntityLoaderTask
# Expected: entityLoaderHelper should be initialized in constructor and never set to null

# Search for entityLoaderHelper field assignments
ast-grep --pattern 'class EntityLoaderTask {
  $$$
  entityLoaderHelper = $_;
  $$$
}'

# Search for any null assignments to entityLoaderHelper
rg -A 5 "entityLoaderHelper\s*=\s*null"

# Search for constructor to confirm initialization
ast-grep --pattern 'EntityLoaderTask($$$) {
  $$$
  entityLoaderHelper = $_;
  $$$
}'

Length of output: 724


🏁 Script executed:

#!/bin/bash
# Locate the EntityLoaderTask source file and inspect entityLoaderHelper usage

# 1. Find the file containing the class declaration
FILE=$(rg -l "class EntityLoaderTask" --max-count 1)
echo "Found file: $FILE"
echo

# 2. List all references to entityLoaderHelper in that file
echo "=== entityLoaderHelper references ==="
rg -n "entityLoaderHelper" "$FILE" || echo "No occurrences found"
echo

# 3. Show the constructor signatures for EntityLoaderTask
echo "=== Constructors in EntityLoaderTask ==="
rg -n "EntityLoaderTask\(" -C2 "$FILE" || echo "No constructors found"

Length of output: 1664


Remove redundant null checks for entityLoaderHelper

Both null checks around entityLoaderHelper are unnecessary because it’s a final field initialized in every constructor. You can simplify the two if statements as follows:

  • app/src/org/commcare/tasks/EntityLoaderTask.java:60
  • app/src/org/commcare/tasks/EntityLoaderTask.java:68

Suggested diff:

-    if (entityLoaderHelper != null && !entityLoaderHelper.isAsyncNodeEntityFactory()) {
+    if (!entityLoaderHelper.isAsyncNodeEntityFactory()) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (entityLoaderHelper != null && !entityLoaderHelper.isAsyncNodeEntityFactory()) {
if (!entityLoaderHelper.isAsyncNodeEntityFactory()) {
🤖 Prompt for AI Agents
In app/src/org/commcare/tasks/EntityLoaderTask.java around lines 60 and 68,
remove the redundant null checks for the final field entityLoaderHelper (it is
initialized in every constructor) by simplifying the two if statements to only
check isAsyncNodeEntityFactory() (i.e., drop "entityLoaderHelper != null &&"),
leaving just the necessary boolean condition so the code is clearer and avoids
unnecessary null checks.

Comment on lines 68 to 71
if (entityLoaderHelper != null && !entityLoaderHelper.isAsyncNodeEntityFactory()) {
trace.putAttribute(CCPerfMonitoring.NUM_CASES_LOADED,
String.valueOf((entities == null || entities.first == null ? 0 : entities.first.size())));
trace.stop();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null safety check for trace object.

The trace stopping logic assumes the trace was initialized, but it could be null if the condition wasn't met during initialization.

-        if (entityLoaderHelper != null && !entityLoaderHelper.isAsyncNodeEntityFactory()) {
+        if (trace != null && entityLoaderHelper != null && !entityLoaderHelper.isAsyncNodeEntityFactory()) {
             trace.putAttribute(CCPerfMonitoring.NUM_CASES_LOADED,
                     String.valueOf((entities == null || entities.first == null ? 0 : entities.first.size())));
             trace.stop();
         }

Note: This change should be applied together with the trace initialization optimization suggested above.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (entityLoaderHelper != null && !entityLoaderHelper.isAsyncNodeEntityFactory()) {
trace.putAttribute(CCPerfMonitoring.NUM_CASES_LOADED,
String.valueOf((entities == null || entities.first == null ? 0 : entities.first.size())));
trace.stop();
}
if (trace != null && entityLoaderHelper != null && !entityLoaderHelper.isAsyncNodeEntityFactory()) {
trace.putAttribute(CCPerfMonitoring.NUM_CASES_LOADED,
String.valueOf((entities == null || entities.first == null ? 0 : entities.first.size())));
trace.stop();
}
🤖 Prompt for AI Agents
In app/src/org/commcare/tasks/EntityLoaderTask.java around lines 68-72, the code
calls trace.putAttribute(...) and trace.stop() without ensuring trace is
non-null; add a null check (e.g., if (trace != null) { ... }) around both
operations so you only putAttribute and stop when trace was actually
initialized, and ensure this change is coordinated with the trace initialization
optimization mentioned in the other review.

@avazirna avazirna force-pushed the add-case-list-loading-perf-monitoring-trace branch from 509eeef to 58d9765 Compare August 15, 2025 08:53
@avazirna avazirna marked this pull request as ready for review August 15, 2025 08:55
@avazirna avazirna force-pushed the add-case-list-loading-perf-monitoring-trace branch from 58d9765 to a6dc921 Compare August 15, 2025 09:22
@avazirna
Copy link
Contributor Author

@damagatchi retest this please

shubham1g5
shubham1g5 previously approved these changes Aug 25, 2025
Comment on lines 61 to 63
trace.putAttribute(CCAnalyticsParam.CCHQ_DOMAIN, ReportingUtils.getDomain());
trace.putAttribute(CCAnalyticsParam.USERNAME, ReportingUtils.getUser());
trace.start();
Copy link
Contributor

Choose a reason for hiding this comment

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

can we put all of this in CCPerfMonitoring. createTrace instead ? (maybe directly call it 1startTracing`). (the idea being one should not repeat this code for every trace we add)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point, let me do that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

object CCPerfMonitoring {
// Traces
// Measures the duration of synchronous case list loading
const val SYNC_ENTITY_LIST_LOADING = "sync_case_list_loading"
Copy link
Contributor

Choose a reason for hiding this comment

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

opt: would be nice to adapt a naming conventions here that distinguishes between the trace and attributes like - TRACE_ SYNC_ENTITY_LIST_LOADING and ATTR_ NUM_CASES_LOADED

trace.stop();
CCPerfMonitoring.INSTANCE.stopTracing(trace);
Copy link
Contributor

Choose a reason for hiding this comment

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

seems like we are calling stop twice now

@avazirna avazirna force-pushed the add-case-list-loading-perf-monitoring-trace branch from e31df7a to 304ece5 Compare August 26, 2025 09:32
try {
val trace = FirebasePerformance.getInstance().newTrace(traceName)
trace.putAttribute(CCAnalyticsParam.CCHQ_DOMAIN, ReportingUtils.getDomain())
trace.putAttribute(CCAnalyticsParam.USERNAME, ReportingUtils.getUser())
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually i think we should be using the same list of attributes as we send to analytics user properties here, think app id is definitely a big one that we should be including here.

@avazirna avazirna force-pushed the add-case-list-loading-perf-monitoring-trace branch from 304ece5 to 1cf88be Compare August 26, 2025 18:06
@avazirna avazirna requested a review from shubham1g5 August 27, 2025 08:48
Copy link
Contributor

@shubham1g5 shubham1g5 left a comment

Choose a reason for hiding this comment

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

Left a suggestion as nice to have but nothing blocking

Comment on lines +65 to +67
trace.putAttribute(CCPerfMonitoring.ATTR_NUM_CASES_LOADED,
String.valueOf((entities == null || entities.first == null ? 0 : entities.first.size())));
CCPerfMonitoring.INSTANCE.stopTracing(trace);
Copy link
Contributor

Choose a reason for hiding this comment

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

think all trace handling should be as fail safe as possible, as such think we should pass the attributes as a dictionary to the stopTracing method as well and have it call the trace.putAttribute on that dictionary in the catch all block itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I made this change on a different branch (for another trace), I will push PR this separately

@avazirna avazirna merged commit a7833fb into master Aug 27, 2025
5 of 9 checks passed
@avazirna avazirna deleted the add-case-list-loading-perf-monitoring-trace branch August 27, 2025 09:17
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.

3 participants