| /* |
| * 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. |
| */ |
| |
| #include <aidl/android/system/virtualmachineservice/IVirtualMachineService.h> |
| #include <aidl/com/android/microdroid/testservice/BnBenchmarkService.h> |
| #include <android-base/logging.h> |
| #include <android-base/parseint.h> |
| #include <android-base/result.h> |
| #include <android-base/strings.h> |
| #include <android-base/unique_fd.h> |
| #include <fcntl.h> |
| #include <linux/vm_sockets.h> |
| #include <stdio.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <binder_rpc_unstable.hpp> |
| #include <fstream> |
| #include <random> |
| #include <string> |
| |
| #include "io_vsock.h" |
| |
| using aidl::android::system::virtualmachineservice::IVirtualMachineService; |
| using android::base::ErrnoError; |
| using android::base::Error; |
| using android::base::Result; |
| using android::base::unique_fd; |
| |
| namespace { |
| constexpr uint64_t kBlockSizeBytes = 4096; |
| |
| template <typename T> |
| static ndk::ScopedAStatus resultStatus(const T& result) { |
| if (!result.ok()) { |
| std::stringstream error; |
| error << result.error(); |
| return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT, |
| error.str().c_str()); |
| } |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| class IOBenchmarkService : public aidl::com::android::microdroid::testservice::BnBenchmarkService { |
| public: |
| ndk::ScopedAStatus readFile(const std::string& filename, int64_t fileSizeBytes, bool isRand, |
| double* out) override { |
| auto res = read_file(filename, fileSizeBytes, isRand); |
| if (res.ok()) { |
| *out = res.value(); |
| } |
| return resultStatus(res); |
| } |
| |
| ndk::ScopedAStatus getMemInfoEntry(const std::string& name, int64_t* out) override { |
| auto value = read_meminfo_entry(name); |
| if (!value.ok()) { |
| return resultStatus(value); |
| } |
| |
| *out = (int64_t)value.value(); |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus initVsockServer(int32_t port, int32_t* out) override { |
| auto res = io_vsock::init_vsock_server(port); |
| if (res.ok()) { |
| *out = res.value(); |
| } |
| return resultStatus(res); |
| } |
| |
| ndk::ScopedAStatus runVsockServerAndReceiveData(int32_t server_fd, |
| int32_t num_bytes_to_receive) override { |
| auto res = io_vsock::run_vsock_server_and_receive_data(server_fd, num_bytes_to_receive); |
| return resultStatus(res); |
| } |
| |
| private: |
| /** Returns the elapsed seconds for reading the file. */ |
| Result<double> read_file(const std::string& filename, int64_t fileSizeBytes, bool is_rand) { |
| const int64_t block_count = fileSizeBytes / kBlockSizeBytes; |
| std::vector<uint64_t> offsets; |
| if (is_rand) { |
| std::mt19937 rd{std::random_device{}()}; |
| offsets.reserve(block_count); |
| for (auto i = 0; i < block_count; ++i) offsets.push_back(i * kBlockSizeBytes); |
| std::shuffle(offsets.begin(), offsets.end(), rd); |
| } |
| char buf[kBlockSizeBytes]; |
| |
| clock_t start = clock(); |
| unique_fd fd(open(filename.c_str(), O_RDONLY | O_CLOEXEC)); |
| if (fd.get() == -1) { |
| return ErrnoError() << "Read: opening " << filename << " failed"; |
| } |
| for (auto i = 0; i < block_count; ++i) { |
| if (is_rand) { |
| if (lseek(fd.get(), offsets[i], SEEK_SET) == -1) { |
| return ErrnoError() << "failed to lseek"; |
| } |
| } |
| auto bytes = read(fd.get(), buf, kBlockSizeBytes); |
| if (bytes == 0) { |
| return Error() << "unexpected end of file"; |
| } else if (bytes == -1) { |
| return ErrnoError() << "failed to read"; |
| } |
| } |
| return {((double)clock() - start) / CLOCKS_PER_SEC}; |
| } |
| |
| Result<size_t> read_meminfo_entry(const std::string& stat) { |
| std::ifstream fs("/proc/meminfo"); |
| if (!fs.is_open()) { |
| return Error() << "could not open /proc/meminfo"; |
| } |
| |
| std::string line; |
| while (std::getline(fs, line)) { |
| auto elems = android::base::Split(line, ":"); |
| if (elems[0] != stat) continue; |
| |
| std::string str = android::base::Trim(elems[1]); |
| if (android::base::EndsWith(str, " kB")) { |
| str = str.substr(0, str.length() - 3); |
| } |
| |
| size_t value; |
| if (!android::base::ParseUint(str, &value)) { |
| return ErrnoError() << "failed to parse \"" << str << "\" as size_t"; |
| } |
| return {value}; |
| } |
| |
| return Error() << "entry \"" << stat << "\" not found"; |
| } |
| }; |
| |
| Result<void> run_io_benchmark_tests() { |
| auto test_service = ndk::SharedRefBase::make<IOBenchmarkService>(); |
| auto callback = []([[maybe_unused]] void* param) { |
| // Tell microdroid_manager that we're ready. |
| // If we can't, abort in order to fail fast - the host won't proceed without |
| // receiving the onReady signal. |
| ndk::SpAIBinder binder( |
| RpcClient(VMADDR_CID_HOST, IVirtualMachineService::VM_BINDER_SERVICE_PORT)); |
| auto vm_service = IVirtualMachineService::fromBinder(binder); |
| if (vm_service == nullptr) { |
| LOG(ERROR) << "failed to connect VirtualMachineService\n"; |
| abort(); |
| } |
| if (auto status = vm_service->notifyPayloadReady(); !status.isOk()) { |
| LOG(ERROR) << "failed to notify payload ready to virtualizationservice: " |
| << status.getDescription(); |
| abort(); |
| } |
| }; |
| |
| if (!RunRpcServerCallback(test_service->asBinder().get(), test_service->SERVICE_PORT, callback, |
| nullptr)) { |
| return Error() << "RPC Server failed to run"; |
| } |
| return {}; |
| } |
| } // Anonymous namespace |
| |
| extern "C" int android_native_main([[maybe_unused]] int argc, char* argv[]) { |
| if (strcmp(argv[1], "no_io") == 0) { |
| // do nothing for now; just leave it alive. good night. |
| for (;;) { |
| sleep(1000); |
| } |
| } else if (strcmp(argv[1], "io") == 0) { |
| if (auto res = run_io_benchmark_tests(); !res.ok()) { |
| LOG(ERROR) << "IO benchmark test failed: " << res.error() << "\n"; |
| return EXIT_FAILURE; |
| } |
| } |
| return EXIT_SUCCESS; |
| } |