Merge "Store metrics for each run of a benchmark." into main
diff --git a/tools/aconfig/aconfig_storage_file/Cargo.toml b/tools/aconfig/aconfig_storage_file/Cargo.toml
index e65b1bf..9d3ee6c 100644
--- a/tools/aconfig/aconfig_storage_file/Cargo.toml
+++ b/tools/aconfig/aconfig_storage_file/Cargo.toml
@@ -9,7 +9,9 @@
[dependencies]
anyhow = "1.0.69"
+memmap2 = "0.8.0"
protobuf = "3.2.0"
+once_cell = "1.19.0"
[build-dependencies]
protobuf-codegen = "3.2.0"
diff --git a/tools/aconfig/aconfig_storage_file/src/lib.rs b/tools/aconfig/aconfig_storage_file/src/lib.rs
index f5aecff..a9f5e21 100644
--- a/tools/aconfig/aconfig_storage_file/src/lib.rs
+++ b/tools/aconfig/aconfig_storage_file/src/lib.rs
@@ -21,6 +21,9 @@
pub mod flag_value;
pub mod package_table;
+#[cfg(feature = "cargo")]
+pub mod mapped_file;
+
mod protos;
#[cfg(test)]
mod test_utils;
diff --git a/tools/aconfig/aconfig_storage_file/src/mapped_file.rs b/tools/aconfig/aconfig_storage_file/src/mapped_file.rs
new file mode 100644
index 0000000..84a3558
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/src/mapped_file.rs
@@ -0,0 +1,315 @@
+/*
+ * 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.
+ */
+
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::{BufReader, Read};
+use std::sync::{Arc, Mutex};
+
+use anyhow::{bail, ensure, Result};
+use memmap2::Mmap;
+use once_cell::sync::Lazy;
+
+use crate::protos::{
+ storage_files::try_from_binary_proto, ProtoStorageFileInfo, ProtoStorageFiles,
+};
+use crate::StorageFileSelection;
+
+/// Cache for already mapped files
+static ALL_MAPPED_FILES: Lazy<Mutex<HashMap<String, MappedStorageFileSet>>> =
+ Lazy::new(|| {
+ let mapped_files = HashMap::new();
+ Mutex::new(mapped_files)
+ });
+
+/// Mapped storage files for a particular container
+#[derive(Debug)]
+struct MappedStorageFileSet {
+ package_map: Arc<Mmap>,
+ flag_map: Arc<Mmap>,
+ flag_val: Arc<Mmap>,
+}
+
+/// Find where storage files are stored for a particular container
+fn find_container_storage_location(
+ location_pb_file: &str,
+ container: &str,
+) -> Result<ProtoStorageFileInfo> {
+ let file = File::open(location_pb_file)?;
+ let mut reader = BufReader::new(file);
+ let mut bytes = Vec::new();
+ reader.read_to_end(&mut bytes)?;
+
+ let storage_locations: ProtoStorageFiles = try_from_binary_proto(&bytes)?;
+ for location_info in storage_locations.files.iter() {
+ if location_info.container() == container {
+ return Ok(location_info.clone());
+ }
+ }
+ bail!("Storage file does not exist for {}", container)
+}
+
+/// Map all storage files for a particular container
+fn map_container_storage_files(
+ location_pb_file: &str,
+ container: &str,
+) -> Result<MappedStorageFileSet> {
+ let files_location = find_container_storage_location(location_pb_file, container)?;
+
+ let package_map_file = File::open(files_location.package_map())?;
+ let metadata = package_map_file.metadata()?;
+ ensure!(
+ metadata.permissions().readonly(),
+ "Cannot mmap file {} as it is not read only",
+ files_location.package_map()
+ );
+ // SAFETY:
+ //
+ // Mmap constructors are unsafe as it would have undefined behaviors if the file
+ // is modified after mapped (https://docs.rs/memmap2/latest/memmap2/struct.Mmap.html).
+ //
+ // We either have to make this api unsafe or ensure that the file will not be modified
+ // which means it is read only. Here in the code, we check explicitly that the file
+ // being mapped must only have read permission, otherwise, error out, thus making sure
+ // it is safe.
+ //
+ // We should remove this restriction if we need to support mmap non read only file in
+ // the future (by making this api unsafe). But for now, all flags are boot stable, so
+ // the boot flag file copy should be readonly.
+ let package_map = Arc::new(unsafe { Mmap::map(&package_map_file)? });
+
+ let flag_map_file = File::open(files_location.flag_map())?;
+ let metadata = flag_map_file.metadata()?;
+ ensure!(
+ metadata.permissions().readonly(),
+ "Cannot mmap file {} as it is not read only",
+ files_location.flag_map()
+ );
+ // SAFETY: Refer to the previous safety statement
+ let flag_map = Arc::new(unsafe { Mmap::map(&flag_map_file)? });
+
+ let flag_val_file = File::open(files_location.flag_val())?;
+ let metadata = flag_val_file.metadata()?;
+ ensure!(
+ metadata.permissions().readonly(),
+ "Cannot mmap file {} as it is not read only",
+ files_location.flag_val()
+ );
+ // SAFETY: Refer to the previous safety statement
+ let flag_val = Arc::new(unsafe { Mmap::map(&flag_val_file)? });
+
+ Ok(MappedStorageFileSet { package_map, flag_map, flag_val })
+}
+
+/// Get a mapped storage file given the container and file type
+pub fn get_mapped_file(
+ location_pb_file: &str,
+ container: &str,
+ file_selection: StorageFileSelection,
+) -> Result<Arc<Mmap>> {
+ let mut all_mapped_files = ALL_MAPPED_FILES.lock().unwrap();
+ match all_mapped_files.get(container) {
+ Some(mapped_files) => Ok(match file_selection {
+ StorageFileSelection::PackageMap => Arc::clone(&mapped_files.package_map),
+ StorageFileSelection::FlagMap => Arc::clone(&mapped_files.flag_map),
+ StorageFileSelection::FlagVal => Arc::clone(&mapped_files.flag_val),
+ }),
+ None => {
+ let mapped_files = map_container_storage_files(location_pb_file, container)?;
+ let file_ptr = match file_selection {
+ StorageFileSelection::PackageMap => Arc::clone(&mapped_files.package_map),
+ StorageFileSelection::FlagMap => Arc::clone(&mapped_files.flag_map),
+ StorageFileSelection::FlagVal => Arc::clone(&mapped_files.flag_val),
+ };
+ all_mapped_files.insert(container.to_string(), mapped_files);
+ Ok(file_ptr)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::get_binary_storage_proto_bytes;
+ use std::io::Write;
+
+ #[test]
+ fn test_find_storage_file_location() {
+ let text_proto = r#"
+files {
+ version: 0
+ container: "system"
+ package_map: "/system/etc/package.map"
+ flag_map: "/system/etc/flag.map"
+ flag_val: "/metadata/aconfig/system.val"
+ timestamp: 12345
+}
+files {
+ version: 1
+ container: "product"
+ package_map: "/product/etc/package.map"
+ flag_map: "/product/etc/flag.map"
+ flag_val: "/metadata/aconfig/product.val"
+ timestamp: 54321
+}
+"#;
+ let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
+ let file_full_path = "./tests/temp_location_file_1.pb";
+ let mut file = File::create(&file_full_path).unwrap();
+ file.write_all(&binary_proto_bytes).unwrap();
+
+ let file_info = find_container_storage_location(file_full_path, "system").unwrap();
+ assert_eq!(file_info.version(), 0);
+ assert_eq!(file_info.container(), "system");
+ assert_eq!(file_info.package_map(), "/system/etc/package.map");
+ assert_eq!(file_info.flag_map(), "/system/etc/flag.map");
+ assert_eq!(file_info.flag_val(), "/metadata/aconfig/system.val");
+ assert_eq!(file_info.timestamp(), 12345);
+
+ let file_info = find_container_storage_location(file_full_path, "product").unwrap();
+ assert_eq!(file_info.version(), 1);
+ assert_eq!(file_info.container(), "product");
+ assert_eq!(file_info.package_map(), "/product/etc/package.map");
+ assert_eq!(file_info.flag_map(), "/product/etc/flag.map");
+ assert_eq!(file_info.flag_val(), "/metadata/aconfig/product.val");
+ assert_eq!(file_info.timestamp(), 54321);
+
+ let err = find_container_storage_location(file_full_path, "vendor").unwrap_err();
+ assert_eq!(format!("{:?}", err), "Storage file does not exist for vendor");
+ }
+
+ fn map_and_verify(
+ location_pb_file: &str,
+ file_selection: StorageFileSelection,
+ actual_file: &str,
+ ) {
+ let mut opened_file = File::open(actual_file).unwrap();
+ let mut content = Vec::new();
+ opened_file.read_to_end(&mut content).unwrap();
+
+ let mmaped_file = get_mapped_file(location_pb_file, "system", file_selection).unwrap();
+ assert_eq!(mmaped_file[..], content[..]);
+ }
+
+ #[test]
+ fn test_mapped_file_contents() {
+ let text_proto = r#"
+files {
+ version: 0
+ container: "system"
+ package_map: "./tests/package.map"
+ flag_map: "./tests/flag.map"
+ flag_val: "./tests/flag.val"
+ timestamp: 12345
+}
+"#;
+ let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
+ let location_file_full_path = "./tests/temp_location_file_2.pb";
+ let mut file = File::create(&location_file_full_path).unwrap();
+ file.write_all(&binary_proto_bytes).unwrap();
+
+ map_and_verify(
+ location_file_full_path,
+ StorageFileSelection::PackageMap,
+ "./tests/package.map",
+ );
+
+ map_and_verify(
+ location_file_full_path,
+ StorageFileSelection::FlagMap,
+ "./tests/flag.map");
+
+ map_and_verify(
+ location_file_full_path,
+ StorageFileSelection::FlagVal,
+ "./tests/flag.val");
+ }
+
+ #[test]
+ fn test_map_non_read_only_file() {
+ let text_proto = r#"
+files {
+ version: 0
+ container: "system"
+ package_map: "./tests/rw.package.map"
+ flag_map: "./tests/rw.flag.map"
+ flag_val: "./tests/rw.flag.val"
+ timestamp: 12345
+}
+"#;
+ let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
+ let location_file_full_path = "./tests/temp_location_file_3.pb";
+ let mut file = File::create(&location_file_full_path).unwrap();
+ file.write_all(&binary_proto_bytes).unwrap();
+
+ let error = map_container_storage_files(
+ location_file_full_path,
+ "system",
+ ).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ "Cannot mmap file ./tests/rw.package.map as it is not read only"
+ );
+
+ let text_proto = r#"
+files {
+ version: 0
+ container: "system"
+ package_map: "./tests/package.map"
+ flag_map: "./tests/rw.flag.map"
+ flag_val: "./tests/rw.flag.val"
+ timestamp: 12345
+}
+"#;
+ let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
+ let location_file_full_path = "./tests/temp_location_file_3.pb";
+ let mut file = File::create(&location_file_full_path).unwrap();
+ file.write_all(&binary_proto_bytes).unwrap();
+
+ let error = map_container_storage_files(
+ location_file_full_path,
+ "system",
+ ).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ "Cannot mmap file ./tests/rw.flag.map as it is not read only"
+ );
+
+ let text_proto = r#"
+files {
+ version: 0
+ container: "system"
+ package_map: "./tests/package.map"
+ flag_map: "./tests/flag.map"
+ flag_val: "./tests/rw.flag.val"
+ timestamp: 12345
+}
+"#;
+ let binary_proto_bytes = get_binary_storage_proto_bytes(text_proto).unwrap();
+ let location_file_full_path = "./tests/temp_location_file_3.pb";
+ let mut file = File::create(&location_file_full_path).unwrap();
+ file.write_all(&binary_proto_bytes).unwrap();
+
+ let error = map_container_storage_files(
+ location_file_full_path,
+ "system",
+ ).unwrap_err();
+ assert_eq!(
+ format!("{:?}", error),
+ "Cannot mmap file ./tests/rw.flag.val as it is not read only"
+ );
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.map b/tools/aconfig/aconfig_storage_file/tests/flag.map
new file mode 100644
index 0000000..43b6f9a
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/flag.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/flag.val b/tools/aconfig/aconfig_storage_file/tests/flag.val
new file mode 100644
index 0000000..f39f8d3
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/flag.val
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/package.map b/tools/aconfig/aconfig_storage_file/tests/package.map
new file mode 100644
index 0000000..8ed4767
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/package.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/rw.flag.map b/tools/aconfig/aconfig_storage_file/tests/rw.flag.map
new file mode 100644
index 0000000..43b6f9a
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/rw.flag.map
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/rw.flag.val b/tools/aconfig/aconfig_storage_file/tests/rw.flag.val
new file mode 100644
index 0000000..f39f8d3
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/rw.flag.val
Binary files differ
diff --git a/tools/aconfig/aconfig_storage_file/tests/rw.package.map b/tools/aconfig/aconfig_storage_file/tests/rw.package.map
new file mode 100644
index 0000000..8ed4767
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/rw.package.map
Binary files differ