Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fe1ab98b authored by Steven Terrell's avatar Steven Terrell
Browse files

Add Merging Logic to Jank Processor

This change will take JankStats that are collected from outside the
platform and merge them into the list of pending stats. When a stat is
being merged, it will either be merged into an existing stat that is
currently waiting to be logged or create a net new stat. The
determination to merge into an existing stat or create a new one is
based off of a key which is a concatenated string of widget category,
widget id and widget state.

Bug: 377572463
Flag: android.app.jank.detailed_app_jank_metrics_api
Test: atest CoreAppJankTestCases
Change-Id: I15b261076107cf7192652eabaf037da46a750d12
parent 29dae0b6
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ public class FrameOverrunHistogram {
     * Create a new instance of FrameOverrunHistogram.
     */
    public FrameOverrunHistogram() {
        mBucketCounts = new int[sBucketEndpoints.length - 1];
        mBucketCounts = new int[sBucketEndpoints.length];
    }

    /**
+69 −5
Original line number Diff line number Diff line
@@ -59,7 +59,6 @@ public class JankDataProcessor {
     * @param appUid the uid of the app.
     */
    public void processJankData(List<JankData> jankData, String activityName, int appUid) {
        mCurrentBatchCount++;
        // add all the previous and active states to the pending states list.
        mStateTracker.retrieveAllStates(mPendingStates);

@@ -79,9 +78,8 @@ public class JankDataProcessor {
            }
        }
        // At this point we have attributed all frames to a state.
        if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) {
            logMetricCounts();
        }
        incrementBatchCountAndMaybeLogStats();

        // return the StatData object back to the pool to be reused.
        jankDataProcessingComplete();
    }
@@ -91,7 +89,73 @@ public class JankDataProcessor {
     * stats
     */
    public void mergeJankStats(AppJankStats jankStats, String activityName) {
        // TODO b/377572463 Add Merging Logic
        // Each state has a key which is a combination of widget category, widget id and widget
        // state, this key is also used to identify pending stats, a pending stat is essentially a
        // state with frames associated with it.
        String stateKey = mStateTracker.getStateKey(jankStats.getWidgetCategory(),
                jankStats.getWidgetId(), jankStats.getWidgetState());

        if (mPendingJankStats.containsKey(stateKey)) {
            mergeExistingStat(stateKey, jankStats);
        } else {
            mergeNewStat(stateKey, activityName, jankStats);
        }

        incrementBatchCountAndMaybeLogStats();
    }

    private void mergeExistingStat(String stateKey, AppJankStats jankStat) {
        PendingJankStat pendingStat = mPendingJankStats.get(stateKey);

        pendingStat.mJankyFrames += jankStat.getJankyFrameCount();
        pendingStat.mTotalFrames += jankStat.getTotalFrameCount();

        mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets,
                jankStat.getFrameOverrunHistogram().getBucketCounters());
    }

    private void mergeNewStat(String stateKey, String activityName, AppJankStats jankStats) {
        // Check if we have space for a new stat
        if (mPendingJankStats.size() > MAX_IN_MEMORY_STATS) {
            return;
        }

        PendingJankStat pendingStat = mPendingJankStatsPool.acquire();
        if (pendingStat == null) {
            pendingStat = new PendingJankStat();

        }
        pendingStat.clearStats();

        pendingStat.mActivityName = activityName;
        pendingStat.mUid = jankStats.getUid();
        pendingStat.mWidgetId = jankStats.getWidgetId();
        pendingStat.mWidgetCategory = jankStats.getWidgetCategory();
        pendingStat.mWidgetState = jankStats.getWidgetState();
        pendingStat.mTotalFrames = jankStats.getTotalFrameCount();
        pendingStat.mJankyFrames = jankStats.getJankyFrameCount();

        mergeOverrunHistograms(pendingStat.mFrameOverrunBuckets,
                jankStats.getFrameOverrunHistogram().getBucketCounters());

        mPendingJankStats.put(stateKey, pendingStat);
    }

    private void mergeOverrunHistograms(int[] mergeTarget, int[] mergeSource) {
        // The length of each histogram should be identical, if they are not then its possible the
        // buckets are not in sync, these records should not be recorded.
        if (mergeTarget.length != mergeSource.length) return;

        for (int i = 0; i < mergeTarget.length; i++) {
            mergeTarget[i] += mergeSource[i];
        }
    }

    private void incrementBatchCountAndMaybeLogStats() {
        mCurrentBatchCount++;
        if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) {
            logMetricCounts();
        }
    }

    /**
+6 −1
Original line number Diff line number Diff line
@@ -89,8 +89,13 @@ public class JankTracker {
     * stats
     */
    public void mergeAppJankStats(AppJankStats appJankStats) {
        getHandler().post(new Runnable() {
            @Override
            public void run() {
                mJankDataProcessor.mergeJankStats(appJankStats, mActivityName);
            }
        });
    }

    public void setActivityName(@NonNull String activityName) {
        mActivityName = activityName;
+5 −1
Original line number Diff line number Diff line
@@ -180,7 +180,11 @@ public class StateTracker {
        }
    }

    private String getStateKey(String widgetCategory, String widgetId, String widgetState) {
    /**
     * Returns a concatenated string of the inputs. This key can be used to retrieve both pending
     * stats and the state that was used to create the pending stat.
     */
    public String getStateKey(String widgetCategory, String widgetId, String widgetState) {
        return widgetCategory + widgetId + widgetState;
    }

+92 −0
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@ package android.app.jank.tests;

import static org.junit.Assert.assertEquals;

import android.app.jank.AppJankStats;
import android.app.jank.Flags;
import android.app.jank.FrameOverrunHistogram;
import android.app.jank.JankDataProcessor;
import android.app.jank.StateTracker;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -39,6 +41,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@RunWith(AndroidJUnit4.class)
@@ -154,6 +157,73 @@ public class JankDataProcessorTest {
        assertEquals(totalFrames, histogramFrames);
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
    public void mergeAppJankStats_confirmStatAddedToPendingStats() {
        HashMap<String, JankDataProcessor.PendingJankStat> pendingStats =
                mJankDataProcessor.getPendingJankStats();

        assertEquals(pendingStats.size(), 0);

        AppJankStats jankStats = getAppJankStats();
        mJankDataProcessor.mergeJankStats(jankStats, sActivityName);

        pendingStats = mJankDataProcessor.getPendingJankStats();

        assertEquals(pendingStats.size(), 1);
    }

    /**
     * This test confirms matching states are combined into one pending stat.  When JankStats are
     * merged from outside the platform they will contain widget category, widget id and widget
     * state. If an incoming JankStats matches a pending stat on all those fields the incoming
     * JankStat will be merged into the existing stat.
     */
    @Test
    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
    public void mergeAppJankStats_confirmStatsWithMatchingStatesAreCombinedIntoOnePendingStat() {
        AppJankStats jankStats = getAppJankStats();
        mJankDataProcessor.mergeJankStats(jankStats, sActivityName);

        HashMap<String, JankDataProcessor.PendingJankStat> pendingStats =
                mJankDataProcessor.getPendingJankStats();
        assertEquals(pendingStats.size(), 1);

        AppJankStats secondJankStat = getAppJankStats();
        mJankDataProcessor.mergeJankStats(secondJankStat, sActivityName);

        pendingStats = mJankDataProcessor.getPendingJankStats();

        assertEquals(pendingStats.size(), 1);
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
    public void mergeAppJankStats_whenStatsWithMatchingStatesMerge_confirmFrameCountsAdded() {
        AppJankStats jankStats = getAppJankStats();
        mJankDataProcessor.mergeJankStats(jankStats, sActivityName);
        mJankDataProcessor.mergeJankStats(jankStats, sActivityName);

        HashMap<String, JankDataProcessor.PendingJankStat> pendingStats =
                mJankDataProcessor.getPendingJankStats();

        String statKey = pendingStats.keySet().iterator().next();
        JankDataProcessor.PendingJankStat pendingStat = pendingStats.get(statKey);

        assertEquals(pendingStats.size(), 1);
        // The same jankStats objects are merged twice, this should result in the frame counts being
        // doubled.
        assertEquals(jankStats.getJankyFrameCount() * 2, pendingStat.getJankyFrames());
        assertEquals(jankStats.getTotalFrameCount() * 2, pendingStat.getTotalFrames());

        int[] originalHistogramBuckets = jankStats.getFrameOverrunHistogram().getBucketCounters();
        int[] frameOverrunBuckets = pendingStat.getFrameOverrunBuckets();

        for (int i = 0; i < frameOverrunBuckets.length; i++) {
            assertEquals(originalHistogramBuckets[i] * 2, frameOverrunBuckets[i]);
        }
    }

    // TODO b/375005277 add tests that cover logging and releasing resources back to pool.

    private long getTotalFramesCounted() {
@@ -276,4 +346,26 @@ public class JankDataProcessorTest {
        return mockData;
    }

    private AppJankStats getAppJankStats() {
        AppJankStats jankStats = new AppJankStats(
                /*App Uid*/APP_ID,
                /*Widget Id*/"test widget id",
                /*Widget Category*/AppJankStats.SCROLL,
                /*Widget State*/AppJankStats.SCROLLING,
                /*Total Frames*/100,
                /*Janky Frames*/25,
                getOverrunHistogram()
        );
        return jankStats;
    }

    private FrameOverrunHistogram getOverrunHistogram() {
        FrameOverrunHistogram overrunHistogram = new FrameOverrunHistogram();
        overrunHistogram.addFrameOverrunMillis(-2);
        overrunHistogram.addFrameOverrunMillis(1);
        overrunHistogram.addFrameOverrunMillis(5);
        overrunHistogram.addFrameOverrunMillis(25);
        return overrunHistogram;
    }

}