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

Commit 2f618d1b authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Add LongArrayMultiStateCounter

Bug: 197162116
Test: atest FrameworksCoreTests:LongArrayMultiStateCounterTest
Test: atest CorePerfTests:LongArrayMultiStateCounterPerfTest

Change-Id: I3e20aa1a64b6bb89658c57c74487f95681d4f0ff
parent 6fd7e85c
Loading
Loading
Loading
Loading
+172 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 android.os;

import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;

import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.os.LongArrayMultiStateCounter;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class LongArrayMultiStateCounterPerfTest {

    @Rule
    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

    /**
     * A complete line-for-line reimplementation of
     * {@link }com.android.internal.os.CpuTimeInFreqMultiStateCounter}, only in Java instead of
     * native.
     */
    private static class TestLongArrayMultiStateCounter {
        private final int mStateCount;
        private final int mArrayLength;
        private int mCurrentState;
        private long mLastStateChangeTimestampMs;
        private long mLastUpdateTimestampMs;

        private static class State {
            private long mTimeInStateSinceUpdate;
            private long[] mCounter;
        }

        private final State[] mStates;
        private final long[] mLastTimeInFreq;
        private final long[] mDelta;

        TestLongArrayMultiStateCounter(int stateCount, int arrayLength, int initialState,
                long timestampMs) {
            mStateCount = stateCount;
            mArrayLength = arrayLength;
            mCurrentState = initialState;
            mLastStateChangeTimestampMs = timestampMs;
            mLastUpdateTimestampMs = timestampMs;
            mStates = new State[stateCount];
            for (int i = 0; i < mStateCount; i++) {
                mStates[i] = new State();
                mStates[i].mCounter = new long[mArrayLength];
            }
            mLastTimeInFreq = new long[mArrayLength];
            mDelta = new long[mArrayLength];
        }

        public void setState(int state, long timestampMs) {
            if (timestampMs >= mLastStateChangeTimestampMs) {
                mStates[mCurrentState].mTimeInStateSinceUpdate +=
                        timestampMs - mLastStateChangeTimestampMs;
            } else {
                for (int i = 0; i < mStateCount; i++) {
                    mStates[i].mTimeInStateSinceUpdate = 0;
                }
            }
            mCurrentState = state;
            mLastStateChangeTimestampMs = timestampMs;
        }

        public void updateValue(long[] timeInFreq, long timestampMs) {
            setState(mCurrentState, timestampMs);

            if (timestampMs > mLastUpdateTimestampMs) {
                if (delta(mLastTimeInFreq, timeInFreq, mDelta)) {
                    long timeSinceUpdate = timestampMs - mLastUpdateTimestampMs;
                    for (int i = 0; i < mStateCount; i++) {
                        long timeInState = mStates[i].mTimeInStateSinceUpdate;
                        if (timeInState > 0) {
                            add(mStates[i].mCounter, mDelta, timeInState, timeSinceUpdate);
                            mStates[i].mTimeInStateSinceUpdate = 0;
                        }
                    }
                } else {
                    throw new RuntimeException();
                }
            } else if (timestampMs < mLastUpdateTimestampMs) {
                throw new RuntimeException();
            }
            System.arraycopy(timeInFreq, 0, mLastTimeInFreq, 0, mArrayLength);
            mLastUpdateTimestampMs = timestampMs;
        }

        private boolean delta(long[] timeInFreq1, long[] timeInFreq2, long[] delta) {
            if (delta.length != mArrayLength) {
                throw new RuntimeException();
            }

            boolean is_delta_valid = true;
            for (int i = 0; i < mStateCount; i++) {
                if (timeInFreq2[i] >= timeInFreq1[i]) {
                    delta[i] = timeInFreq2[i] - timeInFreq1[i];
                } else {
                    delta[i] = 0;
                    is_delta_valid = false;
                }
            }

            return is_delta_valid;
        }

        private void add(long[] counter, long[] delta, long numerator, long denominator) {
            if (numerator != denominator) {
                for (int i = 0; i < mArrayLength; i++) {
                    counter[i] += delta[i] * numerator / denominator;
                }
            } else {
                for (int i = 0; i < mArrayLength; i++) {
                    counter[i] += delta[i];
                }
            }
        }
    }

    @Test
    public void javaImplementation() {
        TestLongArrayMultiStateCounter counter =
                new TestLongArrayMultiStateCounter(2, 4, 0, 1000);
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        long time = 1000;
        long[] timeInFreq = {100, 200, 300, 400};
        while (state.keepRunning()) {
            counter.setState(1, time);
            counter.setState(0, time + 1000);
            counter.updateValue(timeInFreq, time + 2000);
            time += 10000;
        }
    }

    @Test
    public void nativeImplementation() {
        LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4, 0, 1000);
        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
        long time = 1000;
        LongArrayMultiStateCounter.LongArrayContainer timeInFreq =
                new LongArrayMultiStateCounter.LongArrayContainer(4);
        timeInFreq.setValues(new long[]{100, 200, 300, 400});
        while (state.keepRunning()) {
            counter.setState(1, time);
            counter.setState(0, time + 1000);
            counter.updateValues(timeInFreq, time + 2000);
            time += 10000;
        }
    }
}
+181 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.internal.os;

import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;

import libcore.util.NativeAllocationRegistry;

/**
 * Performs per-state counting of multi-element values over time. The class' behavior is illustrated
 * by this example:
 * <pre>
 *   // At 0 ms, the state of the tracked object is 0
 *   counter.setState(0, 0);
 *
 *   // At 1000 ms, the state changes to 1
 *   counter.setState(1, 1000);
 *
 *   // At 3000 ms, the tracked values are updated to {100, 200}
 *   arrayContainer.setValues(new long[]{{30, 300}};
 *   counter.updateValues(arrayContainer, 3000);
 *
 *   // The values are distributed between states 0 and 1 according to the time
 *   // spent in those respective states. In this specific case, 1000 and 2000 ms.
 *   counter.getValues(arrayContainer, 0);
 *   // arrayContainer now has values {10, 100}
 *   counter.getValues(arrayContainer, 1);
 *   // arrayContainer now has values {20, 200}
 * </pre>
 *
 * The tracked values are expected to increase monotonically.
 *
 * @hide
 */
public class LongArrayMultiStateCounter {

    /**
     * Container for a native equivalent of a long[].
     */
    public static class LongArrayContainer {
        private static final NativeAllocationRegistry sRegistry =
                NativeAllocationRegistry.createMalloced(
                        LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());

        private final long mNativeObject;
        private final int mLength;

        public LongArrayContainer(int length) {
            mLength = length;
            mNativeObject = native_init(length);
            sRegistry.registerNativeAllocation(this, mNativeObject);
        }

        /**
         * Copies the supplied values into the underlying native array.
         */
        public void setValues(long[] array) {
            if (array.length != mLength) {
                throw new IllegalArgumentException(
                        "Invalid array length: " + mLength + ", expected: " + mLength);
            }
            native_setValues(mNativeObject, array);
        }

        /**
         * Copies the underlying native array values to the supplied array.
         */
        public void getValues(long[] array) {
            if (array.length != mLength) {
                throw new IllegalArgumentException(
                        "Invalid array length: " + mLength + ", expected: " + mLength);
            }
            native_getValues(mNativeObject, array);
        }

        @CriticalNative
        private static native long native_init(int length);

        @CriticalNative
        private static native long native_getReleaseFunc();

        @FastNative
        private native void native_setValues(long nativeObject, long[] array);

        @FastNative
        private native void native_getValues(long nativeObject, long[] array);
    }

    private static final NativeAllocationRegistry sRegistry =
            NativeAllocationRegistry.createMalloced(
                    LongArrayMultiStateCounter.class.getClassLoader(), native_getReleaseFunc());

    private final int mStateCount;
    private final int mLength;
    private final long mNativeObject;

    public LongArrayMultiStateCounter(int stateCount, int arrayLength, int initialState,
            long timestampMs) {
        mStateCount = stateCount;
        mLength = arrayLength;
        mNativeObject = native_init(stateCount, arrayLength, initialState, timestampMs);
        sRegistry.registerNativeAllocation(this, mNativeObject);
    }

    /**
     * Sets the current state to the supplied value.
     */
    public void setState(int state, long timestampMs) {
        if (state < 0 || state >= mStateCount) {
            throw new IllegalArgumentException(
                    "State: " + state + ", outside the range: [0-" + mStateCount + "]");
        }
        native_setState(mNativeObject, state, timestampMs);
    }

    /**
     * Sets the new values.  The delta between the previously set values and these values
     * is distributed among the state according to the time the object spent in those states
     * since the previous call to updateValues.
     */
    public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) {
        if (longArrayContainer.mLength != mLength) {
            throw new IllegalArgumentException(
                    "Invalid array length: " + longArrayContainer.mLength + ", expected: "
                            + mLength);
        }
        native_updateValues(mNativeObject, longArrayContainer.mNativeObject, timestampMs);
    }

    /**
     * Populates longArrayContainer with the accumulated counts for the specified state.
     */
    public void getCounts(LongArrayContainer longArrayContainer, int state) {
        if (state < 0 || state >= mStateCount) {
            throw new IllegalArgumentException(
                    "State: " + state + ", outside the range: [0-" + mStateCount + "]");
        }
        native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state);
    }

    @Override
    public String toString() {
        return native_toString(mNativeObject);
    }

    @CriticalNative
    private static native long native_init(int stateCount, int arrayLength, int initialState,
            long timestampMs);

    @CriticalNative
    private static native long native_getReleaseFunc();

    @CriticalNative
    private static native void native_setState(long nativeObject, int state, long timestampMs);

    @CriticalNative
    private static native void native_updateValues(long nativeObject,
            long longArrayContainerNativeObject, long timestampMs);

    @CriticalNative
    private static native void native_getCounts(long nativeObject,
            long longArrayContainerNativeObject, int state);

    @FastNative
    private native String native_toString(long nativeObject);
}
+2 −0
Original line number Diff line number Diff line
@@ -216,6 +216,7 @@ cc_library_shared {
                "com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
                "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp",
                "com_android_internal_os_KernelSingleUidTimeReader.cpp",
                "com_android_internal_os_LongArrayMultiStateCounter.cpp",
                "com_android_internal_os_Zygote.cpp",
                "com_android_internal_os_ZygoteCommandBuffer.cpp",
                "com_android_internal_os_ZygoteInit.cpp",
@@ -244,6 +245,7 @@ cc_library_shared {
                "av-types-aidl-cpp",
                "android.hardware.camera.device@3.2",
                "libandroidicu",
                "libbattery",
                "libbpf_android",
                "libnetdbpf",
                "libnetdutils",
+2 −0
Original line number Diff line number Diff line
@@ -203,6 +203,7 @@ extern int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv* e
extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env);
extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env);
extern int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv* env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
@@ -1584,6 +1585,7 @@ static const RegJNIRec gRegJNI[] = {
        REG_JNI(register_com_android_internal_content_om_OverlayConfig),
        REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
        REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
        REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
        REG_JNI(register_com_android_internal_os_Zygote),
        REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer),
        REG_JNI(register_com_android_internal_os_ZygoteInit),
+144 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.
 */

#include <nativehelper/ScopedPrimitiveArray.h>
#include <cstring>
#include "LongArrayMultiStateCounter.h"
#include "core_jni_helpers.h"

namespace android {

static jlong native_init(jint stateCount, jint arrayLength, jint initialState, jlong timestamp) {
    battery::LongArrayMultiStateCounter *counter =
            new battery::LongArrayMultiStateCounter(stateCount, initialState,
                                                    std::vector<uint64_t>(arrayLength), timestamp);
    return reinterpret_cast<jlong>(counter);
}

static void native_dispose(void *nativePtr) {
    battery::LongArrayMultiStateCounter *counter =
            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
    delete counter;
}

static jlong native_getReleaseFunc() {
    return reinterpret_cast<jlong>(native_dispose);
}

static void native_setState(jlong nativePtr, jint state, jlong timestamp) {
    battery::LongArrayMultiStateCounter *counter =
            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
    counter->setState(state, timestamp);
}

static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
                                jlong timestamp) {
    battery::LongArrayMultiStateCounter *counter =
            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
    std::vector<uint64_t> *vector =
            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);

    counter->updateValue(*vector, timestamp);
}

static void native_getCounts(jlong nativePtr, jlong longArrayContainerNativePtr, jint state) {
    battery::LongArrayMultiStateCounter *counter =
            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
    std::vector<uint64_t> *vector =
            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);

    *vector = counter->getCount(state);
}

static jobject native_toString(JNIEnv *env, jlong nativePtr, jobject self) {
    battery::LongArrayMultiStateCounter *counter =
            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
    return env->NewStringUTF(counter->toString().c_str());
}

static jlong native_init_LongArrayContainer(jint length) {
    return reinterpret_cast<jlong>(new std::vector<uint64_t>(length));
}

static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
        // @CriticalNative
        {"native_init", "(IIIJ)J", (void *)native_init},
        // @CriticalNative
        {"native_getReleaseFunc", "()J", (void *)native_getReleaseFunc},
        // @CriticalNative
        {"native_setState", "(JIJ)V", (void *)native_setState},
        // @CriticalNative
        {"native_updateValues", "(JJJ)V", (void *)native_updateValues},
        // @CriticalNative
        {"native_getCounts", "(JJI)V", (void *)native_getCounts},
        // @FastNative
        {"native_toString", "(J)Ljava/lang/String;", (void *)native_toString},
};

/////////////////////// LongArrayMultiStateCounter.LongArrayContainer ////////////////////////

static void native_dispose_LongArrayContainer(jlong nativePtr) {
    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
    delete vector;
}

static jlong native_getReleaseFunc_LongArrayContainer() {
    return reinterpret_cast<jlong>(native_dispose_LongArrayContainer);
}

static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
                                                jlongArray jarray) {
    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
    ScopedLongArrayRO scopedArray(env, jarray);
    const uint64_t *array = reinterpret_cast<const uint64_t *>(scopedArray.get());
    uint8_t size = scopedArray.size();

    // Boundary checks are performed in the Java layer
    std::copy(array, array + size, vector->data());
}

static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
                                                jlongArray jarray) {
    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
    ScopedLongArrayRW scopedArray(env, jarray);

    // Boundary checks are performed in the Java layer
    std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get());
}

static const JNINativeMethod g_LongArrayContainer_methods[] = {
        // @CriticalNative
        {"native_init", "(I)J", (void *)native_init_LongArrayContainer},
        // @CriticalNative
        {"native_getReleaseFunc", "()J", (void *)native_getReleaseFunc_LongArrayContainer},
        // @FastNative
        {"native_setValues", "(J[J)V", (void *)native_setValues_LongArrayContainer},
        // @FastNative
        {"native_getValues", "(J[J)V", (void *)native_getValues_LongArrayContainer},
};

int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv *env) {
    // 0 represents success, thus "|" and not "&"
    return RegisterMethodsOrDie(env, "com/android/internal/os/LongArrayMultiStateCounter",
                                g_LongArrayMultiStateCounter_methods,
                                NELEM(g_LongArrayMultiStateCounter_methods)) |
            RegisterMethodsOrDie(env,
                                 "com/android/internal/os/LongArrayMultiStateCounter"
                                 "$LongArrayContainer",
                                 g_LongArrayContainer_methods, NELEM(g_LongArrayContainer_methods));
}

} // namespace android
Loading