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

Commit 3709d3ef authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge changes from topic "sep23"

* changes:
  Improve performance of small CursorWindows.
  Benchmarks to measure CursorWindow overheads.
parents 1f7549e4 539fdff8
Loading
Loading
Loading
Loading
+11 −1
Original line number Original line Diff line number Diff line
@@ -20,7 +20,17 @@
            <action android:name="com.android.perftests.core.PERFTEST" />
            <action android:name="com.android.perftests.core.PERFTEST" />
          </intent-filter>
          </intent-filter>
        </activity>
        </activity>
        <service android:name="android.os.SomeService" android:exported="false" android:process=":some_service" />

        <service
            android:name="android.os.SomeService"
            android:exported="false"
            android:process=":some_service" />

        <provider
            android:name="android.os.SomeProvider"
            android:authorities="android.os.SomeProvider"
            android:exported="false"
            android:process=":some_provider" />


        <service
        <service
            android:name="android.view.autofill.MyAutofillService"
            android:name="android.view.autofill.MyAutofillService"
+89 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 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.database;

import static org.junit.Assert.assertEquals;

import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;

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

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

@RunWith(AndroidJUnit4.class)
@LargeTest
public class CrossProcessCursorPerfTest {
    @Rule
    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();

    /**
     * Measure transporting a small {@link Cursor}, roughly 1KB in size.
     */
    @Test
    public void timeSmall() throws Exception {
        time(1);
    }

    /**
     * Measure transporting a small {@link Cursor}, roughly 54KB in size.
     */
    @Test
    public void timeMedium() throws Exception {
        time(100);
    }

    /**
     * Measure transporting a small {@link Cursor}, roughly 5.4MB in size.
     */
    @Test
    public void timeLarge() throws Exception {
        time(10_000);
    }

    private static final Uri TEST_URI = Uri.parse("content://android.os.SomeProvider/");

    private void time(int count) throws Exception {
        try (ContentProviderClient client = InstrumentationRegistry.getTargetContext()
                .getContentResolver().acquireContentProviderClient(TEST_URI)) {
            // Configure remote side once with data size to return
            final ContentValues values = new ContentValues();
            values.put(Intent.EXTRA_INDEX, count);
            client.update(TEST_URI, values, null);

            // Repeatedly query that data until we reach convergence
            final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
            while (state.keepRunning()) {
                try (Cursor c = client.query(TEST_URI, null, null, null)) {
                    // Actually walk the returned values to ensure we pull all
                    // data from the remote side
                    while (c.moveToNext()) {
                        assertEquals(c.getPosition(), c.getInt(0));
                    }
                }
            }
        }
    }
}
+73 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 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.content.ContentProvider;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;

import java.util.Arrays;

public class SomeProvider extends ContentProvider {
    private Cursor mCursor;

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        return mCursor;
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        final char[] valueRaw = new char[512];
        Arrays.fill(valueRaw, '!');
        final String value = new String(valueRaw);

        final int count = values.getAsInteger(Intent.EXTRA_INDEX);
        final MatrixCursor cursor = new MatrixCursor(new String[] { "_id", "value" });
        for (int i = 0; i < count; i++) {
            MatrixCursor.RowBuilder row = cursor.newRow();
            row.add(0, i);
            row.add(1, value);
        }
        mCursor = cursor;
        return 1;
    }
}
+15 −7
Original line number Original line Diff line number Diff line
@@ -84,23 +84,31 @@ static int getFdCount() {
}
}


static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
    status_t status;
    String8 name;
    String8 name;
    CursorWindow* window;

    const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
    const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
    name.setTo(nameStr);
    name.setTo(nameStr);
    env->ReleaseStringUTFChars(nameObj, nameStr);
    env->ReleaseStringUTFChars(nameObj, nameStr);


    CursorWindow* window;
    if (cursorWindowSize < 0) {
    status_t status = CursorWindow::create(name, cursorWindowSize, &window);
        status = INVALID_OPERATION;
        goto fail;
    }
    status = CursorWindow::create(name, cursorWindowSize, &window);
    if (status || !window) {
    if (status || !window) {
        jniThrowExceptionFmt(env,
        goto fail;
                "android/database/CursorWindowAllocationException",
                "Could not allocate CursorWindow '%s' of size %d due to error %d.",
                name.string(), cursorWindowSize, status);
        return 0;
    }
    }


    LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
    LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
    return reinterpret_cast<jlong>(window);
    return reinterpret_cast<jlong>(window);

fail:
    jniThrowExceptionFmt(env, "android/database/CursorWindowAllocationException",
                         "Could not allocate CursorWindow '%s' of size %d due to error %d.",
                         name.string(), cursorWindowSize, status);
    return 0;
}
}


static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
+155 −38
Original line number Original line Diff line number Diff line
@@ -30,23 +30,62 @@


namespace android {
namespace android {


CursorWindow::CursorWindow(const String8& name, int ashmemFd,
/**
        void* data, size_t size, bool readOnly) :
 * By default windows are lightweight inline allocations of this size;
        mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size), mReadOnly(readOnly) {
 * they're only inflated to ashmem regions when more space is needed.
 */
static constexpr const size_t kInlineSize = 16384;

CursorWindow::CursorWindow(const String8& name, int ashmemFd, void* data, size_t size,
                           size_t inflatedSize, bool readOnly) :
        mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size),
        mInflatedSize(inflatedSize), mReadOnly(readOnly) {
    mHeader = static_cast<Header*>(mData);
    mHeader = static_cast<Header*>(mData);
}
}


CursorWindow::~CursorWindow() {
CursorWindow::~CursorWindow() {
    if (mAshmemFd != -1) {
        ::munmap(mData, mSize);
        ::munmap(mData, mSize);
        ::close(mAshmemFd);
        ::close(mAshmemFd);
    } else {
        free(mData);
    }
}

status_t CursorWindow::create(const String8& name, size_t inflatedSize,
                              CursorWindow** outCursorWindow) {
    *outCursorWindow = nullptr;

    size_t size = std::min(kInlineSize, inflatedSize);
    void* data = calloc(size, 1);
    if (!data) return NO_MEMORY;

    CursorWindow* window = new CursorWindow(name, -1, data, size,
                                            inflatedSize, false /*readOnly*/);
    status_t result = window->clear();
    if (!result) {
        LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
                "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
                window->mHeader->freeOffset,
                window->mHeader->numRows,
                window->mHeader->numColumns,
                window->mSize, window->mData);
        *outCursorWindow = window;
        return OK;
    }
    delete window;
    return result;
}
}


status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** outCursorWindow) {
status_t CursorWindow::inflate() {
    // Shortcut when we can't expand any further
    if (mSize == mInflatedSize) return INVALID_OPERATION;

    String8 ashmemName("CursorWindow: ");
    String8 ashmemName("CursorWindow: ");
    ashmemName.append(name);
    ashmemName.append(mName);


    status_t result;
    status_t result;
    int ashmemFd = ashmem_create_region(ashmemName.string(), size);
    int ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize);
    if (ashmemFd < 0) {
    if (ashmemFd < 0) {
        result = -errno;
        result = -errno;
        ALOGE("CursorWindow: ashmem_create_region() failed: errno=%d.", errno);
        ALOGE("CursorWindow: ashmem_create_region() failed: errno=%d.", errno);
@@ -55,7 +94,8 @@ status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** o
        if (result < 0) {
        if (result < 0) {
            ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d",errno);
            ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d",errno);
        } else {
        } else {
            void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
            void* data = ::mmap(NULL, mInflatedSize, PROT_READ | PROT_WRITE,
                                MAP_SHARED, ashmemFd, 0);
            if (data == MAP_FAILED) {
            if (data == MAP_FAILED) {
                result = -errno;
                result = -errno;
                ALOGE("CursorWindow: mmap() failed: errno=%d.", errno);
                ALOGE("CursorWindow: mmap() failed: errno=%d.", errno);
@@ -64,33 +104,49 @@ status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** o
                if (result < 0) {
                if (result < 0) {
                    ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d.", errno);
                    ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d.", errno);
                } else {
                } else {
                    CursorWindow* window = new CursorWindow(name, ashmemFd,
                    // Move inline contents into new ashmem region
                            data, size, false /*readOnly*/);
                    memcpy(data, mData, mSize);
                    result = window->clear();
                    free(mData);
                    if (!result) {
                    mAshmemFd = ashmemFd;
                        LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
                    mData = data;
                    mHeader = static_cast<Header*>(mData);
                    mSize = mInflatedSize;
                    LOG_WINDOW("Inflated CursorWindow: freeOffset=%d, "
                            "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
                            "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
                                window->mHeader->freeOffset,
                            mHeader->freeOffset,
                                window->mHeader->numRows,
                            mHeader->numRows,
                                window->mHeader->numColumns,
                            mHeader->numColumns,
                                window->mSize, window->mData);
                            mSize, mData);
                        *outCursorWindow = window;
                    return OK;
                    return OK;
                }
                }
                    delete window;
                }
            }
            }
            ::munmap(data, size);
            ::munmap(data, mInflatedSize);
        }
        }
        ::close(ashmemFd);
        ::close(ashmemFd);
    }
    }
    *outCursorWindow = NULL;
    return result;
    return result;
}
}


status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow) {
status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow) {
    String8 name = parcel->readString8();
    *outCursorWindow = nullptr;

    String8 name;
    status_t result = parcel->readString8(&name);
    if (result) return result;


    bool isAshmem;
    result = parcel->readBool(&isAshmem);
    if (result) return result;

    if (isAshmem) {
        return createFromParcelAshmem(parcel, name, outCursorWindow);
    } else {
        return createFromParcelInline(parcel, name, outCursorWindow);
    }
}

status_t CursorWindow::createFromParcelAshmem(Parcel* parcel, String8& name,
                                              CursorWindow** outCursorWindow) {
    status_t result;
    status_t result;
    int actualSize;
    int actualSize;
    int ashmemFd = parcel->readFileDescriptor();
    int ashmemFd = parcel->readFileDescriptor();
@@ -122,8 +178,8 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursor
                            actualSize, (int) size, errno);
                            actualSize, (int) size, errno);
                } else {
                } else {
                    CursorWindow* window = new CursorWindow(name, dupAshmemFd,
                    CursorWindow* window = new CursorWindow(name, dupAshmemFd,
                            data, size, true /*readOnly*/);
                            data, size, size, true /*readOnly*/);
                    LOG_WINDOW("Created CursorWindow from parcel: freeOffset=%d, "
                    LOG_WINDOW("Created CursorWindow from ashmem parcel: freeOffset=%d, "
                            "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
                            "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
                            window->mHeader->freeOffset,
                            window->mHeader->freeOffset,
                            window->mHeader->numRows,
                            window->mHeader->numRows,
@@ -140,12 +196,62 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursor
    return result;
    return result;
}
}


status_t CursorWindow::createFromParcelInline(Parcel* parcel, String8& name,
                                              CursorWindow** outCursorWindow) {
    uint32_t sentSize;
    status_t result = parcel->readUint32(&sentSize);
    if (result) return result;
    if (sentSize > kInlineSize) return NO_MEMORY;

    void* data = calloc(sentSize, 1);
    if (!data) return NO_MEMORY;

    result = parcel->read(data, sentSize);
    if (result) return result;

    CursorWindow* window = new CursorWindow(name, -1, data, sentSize,
                                            sentSize, true /*readOnly*/);
    LOG_WINDOW("Created CursorWindow from inline parcel: freeOffset=%d, "
            "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
            window->mHeader->freeOffset,
            window->mHeader->numRows,
            window->mHeader->numColumns,
            window->mSize, window->mData);
    *outCursorWindow = window;
    return OK;
}

status_t CursorWindow::writeToParcel(Parcel* parcel) {
status_t CursorWindow::writeToParcel(Parcel* parcel) {
    status_t status = parcel->writeString8(mName);
        LOG_WINDOW("Writing CursorWindow: freeOffset=%d, "
    if (!status) {
                "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
        status = parcel->writeDupFileDescriptor(mAshmemFd);
                mHeader->freeOffset,
                mHeader->numRows,
                mHeader->numColumns,
                mSize, mData);

    status_t result = parcel->writeString8(mName);
    if (result) return result;

    if (mAshmemFd != -1) {
        result = parcel->writeBool(true);
        if (result) return result;
        return writeToParcelAshmem(parcel);
    } else {
        result = parcel->writeBool(false);
        if (result) return result;
        return writeToParcelInline(parcel);
    }
    }
    return status;
}

status_t CursorWindow::writeToParcelAshmem(Parcel* parcel) {
    return parcel->writeDupFileDescriptor(mAshmemFd);
}

status_t CursorWindow::writeToParcelInline(Parcel* parcel) {
    status_t result = parcel->writeUint32(mHeader->freeOffset);
    if (result) return result;

    return parcel->write(mData, mHeader->freeOffset);
}
}


status_t CursorWindow::clear() {
status_t CursorWindow::clear() {
@@ -187,6 +293,7 @@ status_t CursorWindow::allocRow() {
    if (rowSlot == NULL) {
    if (rowSlot == NULL) {
        return NO_MEMORY;
        return NO_MEMORY;
    }
    }
    uint32_t rowSlotOffset = offsetFromPtr(rowSlot);


    // Allocate the slots for the field directory
    // Allocate the slots for the field directory
    size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot);
    size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot);
@@ -201,7 +308,8 @@ status_t CursorWindow::allocRow() {
    memset(fieldDir, 0, fieldDirSize);
    memset(fieldDir, 0, fieldDirSize);


    LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %zu bytes at offset %u\n",
    LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %zu bytes at offset %u\n",
            mHeader->numRows - 1, offsetFromPtr(rowSlot), fieldDirSize, fieldDirOffset);
            mHeader->numRows - 1, rowSlotOffset, fieldDirSize, fieldDirOffset);
    rowSlot = static_cast<RowSlot*>(offsetToPtr(rowSlotOffset));
    rowSlot->offset = fieldDirOffset;
    rowSlot->offset = fieldDirOffset;
    return OK;
    return OK;
}
}
@@ -228,12 +336,16 @@ uint32_t CursorWindow::alloc(size_t size, bool aligned) {


    uint32_t offset = mHeader->freeOffset + padding;
    uint32_t offset = mHeader->freeOffset + padding;
    uint32_t nextFreeOffset = offset + size;
    uint32_t nextFreeOffset = offset + size;
    if (nextFreeOffset > mSize) {
        // Try inflating to ashmem before finally giving up
        inflate();
        if (nextFreeOffset > mSize) {
        if (nextFreeOffset > mSize) {
            ALOGW("Window is full: requested allocation %zu bytes, "
            ALOGW("Window is full: requested allocation %zu bytes, "
                    "free space %zu bytes, window size %zu bytes",
                    "free space %zu bytes, window size %zu bytes",
                    size, freeSpace(), mSize);
                    size, freeSpace(), mSize);
            return 0;
            return 0;
        }
        }
    }


    mHeader->freeOffset = nextFreeOffset;
    mHeader->freeOffset = nextFreeOffset;
    return offset;
    return offset;
@@ -260,7 +372,10 @@ CursorWindow::RowSlot* CursorWindow::allocRowSlot() {
    }
    }
    if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) {
    if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) {
        if (!chunk->nextChunkOffset) {
        if (!chunk->nextChunkOffset) {
            chunk->nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/);
            uint32_t chunkOffset = offsetFromPtr(chunk);
            uint32_t newChunk = alloc(sizeof(RowSlotChunk), true /*aligned*/);
            chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunkOffset));
            chunk->nextChunkOffset = newChunk;
            if (!chunk->nextChunkOffset) {
            if (!chunk->nextChunkOffset) {
                return NULL;
                return NULL;
            }
            }
@@ -308,6 +423,7 @@ status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column,
    if (!fieldSlot) {
    if (!fieldSlot) {
        return BAD_VALUE;
        return BAD_VALUE;
    }
    }
    uint32_t fieldSlotOffset = offsetFromPtr(fieldSlot);


    uint32_t offset = alloc(size);
    uint32_t offset = alloc(size);
    if (!offset) {
    if (!offset) {
@@ -316,6 +432,7 @@ status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column,


    memcpy(offsetToPtr(offset), value, size);
    memcpy(offsetToPtr(offset), value, size);


    fieldSlot = static_cast<FieldSlot*>(offsetToPtr(fieldSlotOffset));
    fieldSlot->type = type;
    fieldSlot->type = type;
    fieldSlot->data.buffer.offset = offset;
    fieldSlot->data.buffer.offset = offset;
    fieldSlot->data.buffer.size = size;
    fieldSlot->data.buffer.size = size;
Loading