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

Commit 0d1c5725 authored by Siim Sammul's avatar Siim Sammul Committed by Automerger Merge Worker
Browse files

Merge "Rate limit dropbox entries in ActivityManagerService per process and...

Merge "Rate limit dropbox entries in ActivityManagerService per process and add a test for the rate limiting." into tm-dev am: 3735778a

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17567803



Change-Id: I456ff81d19ff512a8bf6e42d5c86473eb90fbb30
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 73511876 3735778a
Loading
Loading
Loading
Loading
+3 −18
Original line number Diff line number Diff line
@@ -310,7 +310,6 @@ import android.sysprop.InitProperties;
import android.sysprop.VoldProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -8685,7 +8684,7 @@ public class ActivityManagerService extends IActivityManager.Stub
        }
    }
    private final ArrayMap<String, long[]> mErrorClusterRecords = new ArrayMap<>();
    private final DropboxRateLimiter mDropboxRateLimiter = new DropboxRateLimiter();
    /**
     * Write a description of an error (crash, WTF, ANR) to the drop box.
@@ -8720,22 +8719,8 @@ public class ActivityManagerService extends IActivityManager.Stub
        final String dropboxTag = processClass(process) + "_" + eventType;
        if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
        // Rate-limit how often we're willing to do the heavy lifting below to
        // collect and record logs; currently 5 logs per 10 second period per eventType.
        final long now = SystemClock.elapsedRealtime();
        synchronized (mErrorClusterRecords) {
            long[] errRecord = mErrorClusterRecords.get(eventType);
            if (errRecord == null) {
                errRecord = new long[2]; // [0]: startTime, [1]: count
                mErrorClusterRecords.put(eventType, errRecord);
            }
            if (now - errRecord[0] > 10 * DateUtils.SECOND_IN_MILLIS) {
                errRecord[0] = now;
                errRecord[1] = 1L;
            } else {
                if (errRecord[1]++ >= 5) return;
            }
        }
        // Check if we should rate limit and abort early if needed.
        if (mDropboxRateLimiter.shouldRateLimit(eventType, processName)) return;
        final StringBuilder sb = new StringBuilder(1024);
        appendDropBoxProcessHeaders(process, processName, sb);
+125 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.am;

import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.ArrayMap;

import com.android.internal.annotations.GuardedBy;

/** Rate limiter for adding errors into dropbox. */
public class DropboxRateLimiter {
    private static final long RATE_LIMIT_BUFFER_EXPIRY = 15 * DateUtils.SECOND_IN_MILLIS;
    private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
    private static final int RATE_LIMIT_ALLOWED_ENTRIES = 5;

    @GuardedBy("mErrorClusterRecords")
    private final ArrayMap<String, ErrorRecord> mErrorClusterRecords = new ArrayMap<>();
    private final Clock mClock;

    private long mLastMapCleanUp = 0L;

    public DropboxRateLimiter() {
        this(new DefaultClock());
    }

    public DropboxRateLimiter(Clock clock) {
        mClock = clock;
    }

    /** The interface clock to use for tracking the time elapsed. */
    public interface Clock {
        /** How long in millis has passed since the device came online. */
        long uptimeMillis();
    }

    /** Determines whether dropbox entries of a specific tag and process should be rate limited. */
    public boolean shouldRateLimit(String eventType, String processName) {
        // Rate-limit how often we're willing to do the heavy lifting to collect and record logs.
        final long now = mClock.uptimeMillis();
        synchronized (mErrorClusterRecords) {
            // Remove expired records if enough time has passed since the last cleanup.
            maybeRemoveExpiredRecords(now);

            ErrorRecord errRecord = mErrorClusterRecords.get(errorKey(eventType, processName));
            if (errRecord == null) {
                errRecord = new ErrorRecord(now, 1);
                mErrorClusterRecords.put(errorKey(eventType, processName), errRecord);
            } else if (now - errRecord.getStartTime() > RATE_LIMIT_BUFFER_DURATION) {
                errRecord.setStartTime(now);
                errRecord.setCount(1);
            } else {
                errRecord.incrementCount();
                if (errRecord.getCount() > RATE_LIMIT_ALLOWED_ENTRIES) return true;
            }
        }
        return false;
    }

    private void maybeRemoveExpiredRecords(long now) {
        if (now - mLastMapCleanUp <= RATE_LIMIT_BUFFER_EXPIRY) return;

        for (int i = mErrorClusterRecords.size() - 1; i >= 0; i--) {
            if (now - mErrorClusterRecords.valueAt(i).getStartTime() > RATE_LIMIT_BUFFER_EXPIRY) {
                mErrorClusterRecords.removeAt(i);
            }
        }

        mLastMapCleanUp = now;
    }

    String errorKey(String eventType, String processName) {
        return eventType + processName;
    }

    private class ErrorRecord {
        long mStartTime;
        int mCount;

        ErrorRecord(long startTime, int count) {
            mStartTime = startTime;
            mCount = count;
        }

        public void setStartTime(long startTime) {
            mStartTime = startTime;
        }

        public void setCount(int count) {
            mCount = count;
        }

        public void incrementCount() {
            mCount++;
        }

        public long getStartTime() {
            return mStartTime;
        }

        public int getCount() {
            return mCount;
        }
    }

    private static class DefaultClock implements Clock {
        public long uptimeMillis() {
            return SystemClock.uptimeMillis();
        }
    }
}
+87 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.am;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.os.SystemClock;

import org.junit.Before;
import org.junit.Test;

/**
 * Test class for {@link DropboxRateLimiter}.
 *
 * Build/Install/Run:
 *  atest DropboxRateLimiterTest
 */
public class DropboxRateLimiterTest {
    private DropboxRateLimiter mRateLimiter;
    private TestClock mClock;

    @Before
    public void setUp() {
        mClock = new TestClock();
        mRateLimiter = new DropboxRateLimiter(mClock);
    }

    @Test
    public void testMultipleProcesses() {
        // The first 5 entries should not be rate limited.
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
        // Different processes and tags should not get rate limited either.
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process2"));
        assertFalse(mRateLimiter.shouldRateLimit("tag2", "process"));
        // The 6th entry of the same process should be rate limited.
        assertTrue(mRateLimiter.shouldRateLimit("tag", "process"));
    }

    @Test
    public void testBufferClearing() throws Exception {
        // The first 5 entries should not be rate limited.
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
        // The 6th entry of the same process should be rate limited.
        assertTrue(mRateLimiter.shouldRateLimit("tag", "process"));

        // After 11 seconds there should be nothing left in the buffer and the same type of entry
        // should not get rate limited anymore.
        mClock.setOffsetMillis(11000);

        assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
    }

    private static class TestClock implements DropboxRateLimiter.Clock {
        long mOffsetMillis = 0L;

        public long uptimeMillis() {
            return mOffsetMillis + SystemClock.uptimeMillis();
        }

        public void setOffsetMillis(long millis) {
            mOffsetMillis = millis;
        }
    }
}