Merge "aconfig: update java read api for performance" into main

This commit is contained in:
Treehugger Robot 2024-06-04 23:29:18 +00:00 committed by Gerrit Code Review
commit 910ad04d2f
6 changed files with 158 additions and 212 deletions

View file

@ -19,13 +19,13 @@ package android.aconfig.storage;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer; import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode; import java.nio.channels.FileChannel.MapMode;
import android.aconfig.storage.PackageReadContext; import android.aconfig.storage.PackageReadContext;
import android.aconfig.storage.FlagReadContext; import android.aconfig.storage.FlagReadContext;
import android.aconfig.storage.BooleanFlagValue;
import dalvik.annotation.optimization.FastNative; import dalvik.annotation.optimization.FastNative;
@ -68,19 +68,55 @@ public class AconfigStorageReadAPI {
} }
// JNI interface to get package read context // JNI interface to get package read context
// @param mappedFile: memory mapped package map file
// @param packageName: package name
// @throws IOException if the passed in file is not a valid package map file
@FastNative @FastNative
public static native PackageReadContext getPackageReadContext( private static native ByteBuffer getPackageReadContextImpl(
ByteBuffer mappedFile, String packageName); ByteBuffer mappedFile, String packageName) throws IOException;
// API to get package read context
// @param mappedFile: memory mapped package map file
// @param packageName: package name
// @throws IOException if the passed in file is not a valid package map file
static public PackageReadContext getPackageReadContext (
ByteBuffer mappedFile, String packageName) throws IOException {
ByteBuffer buffer = getPackageReadContextImpl(mappedFile, packageName);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return new PackageReadContext(buffer.getInt(), buffer.getInt(4));
}
// JNI interface to get flag read context // JNI interface to get flag read context
// @param mappedFile: memory mapped flag map file
// @param packageId: package id to represent a specific package, obtained from
// package map file
// @param flagName: flag name
// @throws IOException if the passed in file is not a valid flag map file
@FastNative @FastNative
public static native FlagReadContext getFlagReadContext( private static native ByteBuffer getFlagReadContextImpl(
ByteBuffer mappedFile, int packageId, String flagName); ByteBuffer mappedFile, int packageId, String flagName) throws IOException;
// API to get flag read context
// @param mappedFile: memory mapped flag map file
// @param packageId: package id to represent a specific package, obtained from
// package map file
// @param flagName: flag name
// @throws IOException if the passed in file is not a valid flag map file
public static FlagReadContext getFlagReadContext(
ByteBuffer mappedFile, int packageId, String flagName) throws IOException {
ByteBuffer buffer = getFlagReadContextImpl(mappedFile, packageId, flagName);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return new FlagReadContext(buffer.getInt(), buffer.getInt(4));
}
// JNI interface to get boolean flag value // JNI interface to get boolean flag value
// @param mappedFile: memory mapped flag value file
// @param flagIndex: flag global index in the flag value array
// @throws IOException if the passed in file is not a valid flag value file or the
// flag index went over the file boundary.
@FastNative @FastNative
public static native BooleanFlagValue getBooleanFlagValue( public static native boolean getBooleanFlagValue(
ByteBuffer mappedFile, int flagIndex); ByteBuffer mappedFile, int flagIndex) throws IOException;
static { static {
System.loadLibrary("aconfig_storage_read_api_rust_jni"); System.loadLibrary("aconfig_storage_read_api_rust_jni");

View file

@ -1,30 +0,0 @@
package android.aconfig.storage;
/*
* Copyright (C) 2024 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.
*/
public class BooleanFlagValue {
public boolean mQuerySuccess;
public String mErrorMessage;
public boolean mFlagValue;
public BooleanFlagValue(boolean querySuccess,
String errorMessage,
boolean value) {
mQuerySuccess = querySuccess;
mErrorMessage = errorMessage;
mFlagValue = value;
}
}

View file

@ -16,20 +16,11 @@ package android.aconfig.storage;
*/ */
public class FlagReadContext { public class FlagReadContext {
public boolean mQuerySuccess;
public String mErrorMessage;
public boolean mFlagExists;
public StoredFlagType mFlagType; public StoredFlagType mFlagType;
public int mFlagIndex; public int mFlagIndex;
public FlagReadContext(boolean querySuccess, public FlagReadContext(int flagType,
String errorMessage,
boolean flagExists,
int flagType,
int flagIndex) { int flagIndex) {
mQuerySuccess = querySuccess;
mErrorMessage = errorMessage;
mFlagExists = flagExists;
mFlagType = StoredFlagType.fromInteger(flagType); mFlagType = StoredFlagType.fromInteger(flagType);
mFlagIndex = flagIndex; mFlagIndex = flagIndex;
} }

View file

@ -16,20 +16,11 @@ package android.aconfig.storage;
*/ */
public class PackageReadContext { public class PackageReadContext {
public boolean mQuerySuccess;
public String mErrorMessage;
public boolean mPackageExists;
public int mPackageId; public int mPackageId;
public int mBooleanStartIndex; public int mBooleanStartIndex;
public PackageReadContext(boolean querySuccess, public PackageReadContext(int packageId,
String errorMessage,
boolean packageExists,
int packageId,
int booleanStartIndex) { int booleanStartIndex) {
mQuerySuccess = querySuccess;
mErrorMessage = errorMessage;
mPackageExists = packageExists;
mPackageId = packageId; mPackageId = packageId;
mBooleanStartIndex = booleanStartIndex; mBooleanStartIndex = booleanStartIndex;
} }

View file

@ -6,8 +6,8 @@ use aconfig_storage_read_api::package_table_query::find_package_read_context;
use aconfig_storage_read_api::{FlagReadContext, PackageReadContext}; use aconfig_storage_read_api::{FlagReadContext, PackageReadContext};
use anyhow::Result; use anyhow::Result;
use jni::objects::{JByteBuffer, JClass, JString, JValue}; use jni::objects::{JByteBuffer, JClass, JString};
use jni::sys::{jint, jobject}; use jni::sys::{jboolean, jint};
use jni::JNIEnv; use jni::JNIEnv;
/// Call rust find package read context /// Call rust find package read context
@ -28,55 +28,42 @@ fn get_package_read_context_java(
Ok(find_package_read_context(buffer, &package_name)?) Ok(find_package_read_context(buffer, &package_name)?)
} }
/// Create java package read context return
fn create_java_package_read_context(
env: &mut JNIEnv,
success_query: bool,
error_message: String,
pkg_found: bool,
pkg_id: u32,
start_index: u32,
) -> jobject {
let query_success = JValue::Bool(success_query as u8);
let errmsg = env.new_string(error_message).expect("failed to create JString");
let package_exists = JValue::Bool(pkg_found as u8);
let package_id = JValue::Int(pkg_id as i32);
let boolean_start_index = JValue::Int(start_index as i32);
let context = env.new_object(
"android/aconfig/storage/PackageReadContext",
"(ZLjava/lang/String;ZII)V",
&[query_success, (&errmsg).into(), package_exists, package_id, boolean_start_index],
);
context.expect("failed to call PackageReadContext constructor").into_raw()
}
/// Get package read context JNI /// Get package read context JNI
#[no_mangle] #[no_mangle]
#[allow(unused)] #[allow(unused)]
pub extern "system" fn Java_android_aconfig_storage_AconfigStorageReadAPI_getPackageReadContext< pub extern "system" fn Java_android_aconfig_storage_AconfigStorageReadAPI_getPackageReadContextImpl<
'local, 'local,
>( >(
mut env: JNIEnv<'local>, mut env: JNIEnv<'local>,
class: JClass<'local>, class: JClass<'local>,
file: JByteBuffer<'local>, file: JByteBuffer<'local>,
package: JString<'local>, package: JString<'local>,
) -> jobject { ) -> JByteBuffer<'local> {
let mut package_id = -1;
let mut boolean_start_index = -1;
match get_package_read_context_java(&mut env, file, package) { match get_package_read_context_java(&mut env, file, package) {
Ok(context_opt) => match context_opt { Ok(context_opt) => {
Some(context) => create_java_package_read_context( if let Some(context) = context_opt {
&mut env, package_id = context.package_id as i32;
true, boolean_start_index = context.boolean_start_index as i32;
String::from(""), }
true, }
context.package_id,
context.boolean_start_index,
),
None => create_java_package_read_context(&mut env, true, String::from(""), false, 0, 0),
},
Err(errmsg) => { Err(errmsg) => {
create_java_package_read_context(&mut env, false, format!("{:?}", errmsg), false, 0, 0) env.throw(("java/io/IOException", errmsg.to_string())).expect("failed to throw");
} }
} }
let mut bytes = Vec::new();
bytes.extend_from_slice(&package_id.to_le_bytes());
bytes.extend_from_slice(&boolean_start_index.to_le_bytes());
let (addr, len) = {
let buf = bytes.leak();
(buf.as_mut_ptr(), buf.len())
};
// SAFETY:
// The safety here is ensured as the content is ensured to be valid
unsafe { env.new_direct_byte_buffer(addr, len).expect("failed to create byte buffer") }
} }
/// Call rust find flag read context /// Call rust find flag read context
@ -98,32 +85,10 @@ fn get_flag_read_context_java(
Ok(find_flag_read_context(buffer, package_id as u32, &flag_name)?) Ok(find_flag_read_context(buffer, package_id as u32, &flag_name)?)
} }
/// Create java flag read context return
fn create_java_flag_read_context(
env: &mut JNIEnv,
success_query: bool,
error_message: String,
flg_found: bool,
flg_type: u32,
flg_index: u32,
) -> jobject {
let query_success = JValue::Bool(success_query as u8);
let errmsg = env.new_string(error_message).expect("failed to create JString");
let flag_exists = JValue::Bool(flg_found as u8);
let flag_type = JValue::Int(flg_type as i32);
let flag_index = JValue::Int(flg_index as i32);
let context = env.new_object(
"android/aconfig/storage/FlagReadContext",
"(ZLjava/lang/String;ZII)V",
&[query_success, (&errmsg).into(), flag_exists, flag_type, flag_index],
);
context.expect("failed to call FlagReadContext constructor").into_raw()
}
/// Get flag read context JNI /// Get flag read context JNI
#[no_mangle] #[no_mangle]
#[allow(unused)] #[allow(unused)]
pub extern "system" fn Java_android_aconfig_storage_AconfigStorageReadAPI_getFlagReadContext< pub extern "system" fn Java_android_aconfig_storage_AconfigStorageReadAPI_getFlagReadContextImpl<
'local, 'local,
>( >(
mut env: JNIEnv<'local>, mut env: JNIEnv<'local>,
@ -131,41 +96,32 @@ pub extern "system" fn Java_android_aconfig_storage_AconfigStorageReadAPI_getFla
file: JByteBuffer<'local>, file: JByteBuffer<'local>,
package_id: jint, package_id: jint,
flag: JString<'local>, flag: JString<'local>,
) -> jobject { ) -> JByteBuffer<'local> {
let mut flag_type = -1;
let mut flag_index = -1;
match get_flag_read_context_java(&mut env, file, package_id, flag) { match get_flag_read_context_java(&mut env, file, package_id, flag) {
Ok(context_opt) => match context_opt { Ok(context_opt) => {
Some(context) => create_java_flag_read_context( if let Some(context) = context_opt {
&mut env, flag_type = context.flag_type as i32;
true, flag_index = context.flag_index as i32;
String::from(""), }
true, }
context.flag_type as u32,
context.flag_index as u32,
),
None => create_java_flag_read_context(&mut env, true, String::from(""), false, 9999, 0),
},
Err(errmsg) => { Err(errmsg) => {
create_java_flag_read_context(&mut env, false, format!("{:?}", errmsg), false, 9999, 0) env.throw(("java/io/IOException", errmsg.to_string())).expect("failed to throw");
} }
} }
}
/// Create java boolean flag value return let mut bytes = Vec::new();
fn create_java_boolean_flag_value( bytes.extend_from_slice(&flag_type.to_le_bytes());
env: &mut JNIEnv, bytes.extend_from_slice(&flag_index.to_le_bytes());
success_query: bool, let (addr, len) = {
error_message: String, let buf = bytes.leak();
value: bool, (buf.as_mut_ptr(), buf.len())
) -> jobject { };
let query_success = JValue::Bool(success_query as u8); // SAFETY:
let errmsg = env.new_string(error_message).expect("failed to create JString"); // The safety here is ensured as the content is ensured to be valid
let flag_value = JValue::Bool(value as u8); unsafe { env.new_direct_byte_buffer(addr, len).expect("failed to create byte buffer") }
let context = env.new_object(
"android/aconfig/storage/BooleanFlagValue",
"(ZLjava/lang/String;Z)V",
&[query_success, (&errmsg).into(), flag_value],
);
context.expect("failed to call BooleanFlagValue constructor").into_raw()
} }
/// Call rust find boolean flag value /// Call rust find boolean flag value
@ -193,11 +149,12 @@ pub extern "system" fn Java_android_aconfig_storage_AconfigStorageReadAPI_getBoo
class: JClass<'local>, class: JClass<'local>,
file: JByteBuffer<'local>, file: JByteBuffer<'local>,
flag_index: jint, flag_index: jint,
) -> jobject { ) -> jboolean {
match get_boolean_flag_value_java(&mut env, file, flag_index) { match get_boolean_flag_value_java(&mut env, file, flag_index) {
Ok(value) => create_java_boolean_flag_value(&mut env, true, String::from(""), value), Ok(value) => value as u8,
Err(errmsg) => { Err(errmsg) => {
create_java_boolean_flag_value(&mut env, false, format!("{:?}", errmsg), false) env.throw(("java/io/IOException", errmsg.to_string())).expect("failed to throw");
0u8
} }
} }
} }

View file

@ -18,6 +18,8 @@ package android.aconfig.storage.test;
import java.io.IOException; import java.io.IOException;
import java.nio.MappedByteBuffer; import java.nio.MappedByteBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
@ -33,7 +35,6 @@ import android.aconfig.storage.AconfigStorageReadAPI;
import android.aconfig.storage.PackageReadContext; import android.aconfig.storage.PackageReadContext;
import android.aconfig.storage.FlagReadContext; import android.aconfig.storage.FlagReadContext;
import android.aconfig.storage.FlagReadContext.StoredFlagType; import android.aconfig.storage.FlagReadContext.StoredFlagType;
import android.aconfig.storage.BooleanFlagValue;
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public class AconfigStorageReadAPITest{ public class AconfigStorageReadAPITest{
@ -51,29 +52,24 @@ public class AconfigStorageReadAPITest{
} }
assertTrue(packageMap != null); assertTrue(packageMap != null);
PackageReadContext context = AconfigStorageReadAPI.getPackageReadContext( try {
packageMap, "com.android.aconfig.storage.test_1"); PackageReadContext context = AconfigStorageReadAPI.getPackageReadContext(
assertTrue(context.mQuerySuccess); packageMap, "com.android.aconfig.storage.test_1");
assertTrue(context.mErrorMessage, context.mErrorMessage.equals("")); assertEquals(context.mPackageId, 0);
assertTrue(context.mPackageExists); assertEquals(context.mBooleanStartIndex, 0);
assertEquals(context.mPackageId, 0);
assertEquals(context.mBooleanStartIndex, 0);
context = AconfigStorageReadAPI.getPackageReadContext( context = AconfigStorageReadAPI.getPackageReadContext(
packageMap, "com.android.aconfig.storage.test_2"); packageMap, "com.android.aconfig.storage.test_2");
assertTrue(context.mQuerySuccess); assertEquals(context.mPackageId, 1);
assertTrue(context.mErrorMessage, context.mErrorMessage.equals("")); assertEquals(context.mBooleanStartIndex, 3);
assertTrue(context.mPackageExists);
assertEquals(context.mPackageId, 1);
assertEquals(context.mBooleanStartIndex, 3);
context = AconfigStorageReadAPI.getPackageReadContext( context = AconfigStorageReadAPI.getPackageReadContext(
packageMap, "com.android.aconfig.storage.test_4"); packageMap, "com.android.aconfig.storage.test_4");
assertTrue(context.mQuerySuccess); assertEquals(context.mPackageId, 2);
assertTrue(context.mErrorMessage, context.mErrorMessage.equals("")); assertEquals(context.mBooleanStartIndex, 6);
assertTrue(context.mPackageExists); } catch (IOException ex) {
assertEquals(context.mPackageId, 2); assertTrue(ex.toString(), false);
assertEquals(context.mBooleanStartIndex, 6); }
} }
@Test @Test
@ -87,13 +83,14 @@ public class AconfigStorageReadAPITest{
} }
assertTrue(packageMap != null); assertTrue(packageMap != null);
PackageReadContext context = AconfigStorageReadAPI.getPackageReadContext( try {
packageMap, "unknown"); PackageReadContext context = AconfigStorageReadAPI.getPackageReadContext(
assertTrue(context.mQuerySuccess); packageMap, "unknown");
assertTrue(context.mErrorMessage, context.mErrorMessage.equals("")); assertEquals(context.mPackageId, -1);
assertFalse(context.mPackageExists); assertEquals(context.mBooleanStartIndex, -1);
assertEquals(context.mPackageId, 0); } catch(IOException ex){
assertEquals(context.mBooleanStartIndex, 0); assertTrue(ex.toString(), false);
}
} }
@Test @Test
@ -134,14 +131,15 @@ public class AconfigStorageReadAPITest{
baselines.add(new Baseline(2, "enabled_fixed_ro", StoredFlagType.FixedReadOnlyBoolean, 0)); baselines.add(new Baseline(2, "enabled_fixed_ro", StoredFlagType.FixedReadOnlyBoolean, 0));
baselines.add(new Baseline(0, "disabled_rw", StoredFlagType.ReadWriteBoolean, 0)); baselines.add(new Baseline(0, "disabled_rw", StoredFlagType.ReadWriteBoolean, 0));
for (Baseline baseline : baselines) { try {
FlagReadContext context = AconfigStorageReadAPI.getFlagReadContext( for (Baseline baseline : baselines) {
flagMap, baseline.mPackageId, baseline.mFlagName); FlagReadContext context = AconfigStorageReadAPI.getFlagReadContext(
assertTrue(context.mQuerySuccess); flagMap, baseline.mPackageId, baseline.mFlagName);
assertTrue(context.mErrorMessage, context.mErrorMessage.equals("")); assertEquals(context.mFlagType, baseline.mFlagType);
assertTrue(context.mFlagExists); assertEquals(context.mFlagIndex, baseline.mFlagIndex);
assertEquals(context.mFlagType, baseline.mFlagType); }
assertEquals(context.mFlagIndex, baseline.mFlagIndex); } catch (IOException ex) {
assertTrue(ex.toString(), false);
} }
} }
@ -156,21 +154,19 @@ public class AconfigStorageReadAPITest{
} }
assertTrue(flagMap!= null); assertTrue(flagMap!= null);
FlagReadContext context = AconfigStorageReadAPI.getFlagReadContext( try {
flagMap, 0, "unknown"); FlagReadContext context = AconfigStorageReadAPI.getFlagReadContext(
assertTrue(context.mQuerySuccess); flagMap, 0, "unknown");
assertTrue(context.mErrorMessage, context.mErrorMessage.equals("")); assertEquals(context.mFlagType, null);
assertFalse(context.mFlagExists); assertEquals(context.mFlagIndex, -1);
assertEquals(context.mFlagType, null);
assertEquals(context.mFlagIndex, 0);
context = AconfigStorageReadAPI.getFlagReadContext( context = AconfigStorageReadAPI.getFlagReadContext(
flagMap, 3, "enabled_ro"); flagMap, 3, "enabled_ro");
assertTrue(context.mQuerySuccess); assertEquals(context.mFlagType, null);
assertTrue(context.mErrorMessage, context.mErrorMessage.equals("")); assertEquals(context.mFlagIndex, -1);
assertFalse(context.mFlagExists); } catch (IOException ex) {
assertEquals(context.mFlagType, null); assertTrue(ex.toString(), false);
assertEquals(context.mFlagIndex, 0); }
} }
@Test @Test
@ -179,17 +175,19 @@ public class AconfigStorageReadAPITest{
try { try {
flagVal = AconfigStorageReadAPI.mapStorageFile( flagVal = AconfigStorageReadAPI.mapStorageFile(
mStorageDir + "/boot/mockup.val"); mStorageDir + "/boot/mockup.val");
} catch(IOException ex){ } catch (IOException ex) {
assertTrue(ex.toString(), false); assertTrue(ex.toString(), false);
} }
assertTrue(flagVal!= null); assertTrue(flagVal!= null);
boolean[] baselines = {false, true, true, false, true, true, true, true}; boolean[] baselines = {false, true, true, false, true, true, true, true};
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
BooleanFlagValue value = AconfigStorageReadAPI.getBooleanFlagValue(flagVal, i); try {
assertTrue(value.mQuerySuccess); Boolean value = AconfigStorageReadAPI.getBooleanFlagValue(flagVal, i);
assertTrue(value.mErrorMessage, value.mErrorMessage.equals("")); assertEquals(value, baselines[i]);
assertEquals(value.mFlagValue, baselines[i]); } catch (IOException ex) {
assertTrue(ex.toString(), false);
}
} }
} }
@ -199,14 +197,17 @@ public class AconfigStorageReadAPITest{
try { try {
flagVal = AconfigStorageReadAPI.mapStorageFile( flagVal = AconfigStorageReadAPI.mapStorageFile(
mStorageDir + "/boot/mockup.val"); mStorageDir + "/boot/mockup.val");
} catch(IOException ex){ } catch (IOException ex) {
assertTrue(ex.toString(), false); assertTrue(ex.toString(), false);
} }
assertTrue(flagVal!= null); assertTrue(flagVal!= null);
BooleanFlagValue value = AconfigStorageReadAPI.getBooleanFlagValue(flagVal, 9); try {
String expectedErrmsg = "Flag value offset goes beyond the end of the file"; Boolean value = AconfigStorageReadAPI.getBooleanFlagValue(flagVal, 9);
assertFalse(value.mQuerySuccess); assertTrue("should throw", false);
assertTrue(value.mErrorMessage, value.mErrorMessage.contains(expectedErrmsg)); } catch (IOException ex) {
String expectedErrmsg = "invalid storage file byte offset";
assertTrue(ex.toString(), ex.toString().contains(expectedErrmsg));
}
} }
} }