1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <aidl/com/android/microdroid/testservice/BnBenchmarkService.h>
18 #include <android-base/logging.h>
19 #include <android-base/parseint.h>
20 #include <android-base/result.h>
21 #include <android-base/strings.h>
22 #include <android-base/unique_fd.h>
23 #include <fcntl.h>
24 #include <linux/vm_sockets.h>
25 #include <stdio.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <time.h>
29 #include <unistd.h>
30 #include <vm_main.h>
31 #include <vm_payload.h>
32 
33 #include <fstream>
34 #include <random>
35 #include <string>
36 
37 #include "io_vsock.h"
38 
39 using android::base::ErrnoError;
40 using android::base::Error;
41 using android::base::Result;
42 using android::base::unique_fd;
43 
44 namespace {
45 constexpr uint64_t kBlockSizeBytes = 4096;
46 constexpr uint64_t kNumBytesPerMB = 1024 * 1024;
47 
48 template <typename T>
resultStatus(const T & result)49 static ndk::ScopedAStatus resultStatus(const T& result) {
50     if (!result.ok()) {
51         std::stringstream error;
52         error << result.error();
53         return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
54                                                                 error.str().c_str());
55     }
56     return ndk::ScopedAStatus::ok();
57 }
58 
59 class IOBenchmarkService : public aidl::com::android::microdroid::testservice::BnBenchmarkService {
60 public:
measureReadRate(const std::string & filename,bool isRand,double * out)61     ndk::ScopedAStatus measureReadRate(const std::string& filename, bool isRand,
62                                        double* out) override {
63         auto res = measure_read_rate(filename, isRand);
64         if (res.ok()) {
65             *out = res.value();
66         }
67         return resultStatus(res);
68     }
69 
getMemInfoEntry(const std::string & name,int64_t * out)70     ndk::ScopedAStatus getMemInfoEntry(const std::string& name, int64_t* out) override {
71         auto value = read_meminfo_entry(name);
72         if (!value.ok()) {
73             return resultStatus(value);
74         }
75 
76         *out = (int64_t)value.value();
77         return ndk::ScopedAStatus::ok();
78     }
79 
allocAnonMemory(int64_t mb,int64_t * out)80     ndk::ScopedAStatus allocAnonMemory(int64_t mb, int64_t* out) override {
81         *out = (int64_t)(long)alloc_anon_memory((long)mb);
82         return ndk::ScopedAStatus::ok();
83     }
84 
initVsockServer(int32_t port,int32_t * out)85     ndk::ScopedAStatus initVsockServer(int32_t port, int32_t* out) override {
86         auto res = io_vsock::init_vsock_server(port);
87         if (res.ok()) {
88             *out = res.value();
89         }
90         return resultStatus(res);
91     }
92 
runVsockServerAndReceiveData(int32_t server_fd,int32_t num_bytes_to_receive)93     ndk::ScopedAStatus runVsockServerAndReceiveData(int32_t server_fd,
94                                                     int32_t num_bytes_to_receive) override {
95         auto res = io_vsock::run_vsock_server_and_receive_data(server_fd, num_bytes_to_receive);
96         return resultStatus(res);
97     }
98 
99 private:
100     /**
101      * Measures the read rate for reading the given file.
102      * @return The read rate in MB/s.
103      */
measure_read_rate(const std::string & filename,bool is_rand)104     Result<double> measure_read_rate(const std::string& filename, bool is_rand) {
105         struct stat file_stats;
106         if (stat(filename.c_str(), &file_stats) == -1) {
107             return Error() << "failed to get file stats";
108         }
109         const int64_t file_size_bytes = file_stats.st_size;
110         const int64_t block_count = file_size_bytes / kBlockSizeBytes;
111         std::vector<uint64_t> offsets(block_count);
112         for (auto i = 0; i < block_count; ++i) {
113             offsets[i] = i * kBlockSizeBytes;
114         }
115         if (is_rand) {
116             std::mt19937 rd{std::random_device{}()};
117             std::shuffle(offsets.begin(), offsets.end(), rd);
118         }
119         char buf[kBlockSizeBytes];
120 
121         struct timespec start;
122         if (clock_gettime(CLOCK_MONOTONIC, &start) == -1) {
123             return ErrnoError() << "failed to clock_gettime";
124         }
125         unique_fd fd(open(filename.c_str(), O_RDONLY | O_CLOEXEC));
126         if (fd.get() == -1) {
127             return ErrnoError() << "Read: opening " << filename << " failed";
128         }
129         for (auto i = 0; i < block_count; ++i) {
130             auto bytes = pread(fd, buf, kBlockSizeBytes, offsets[i]);
131             if (bytes == 0) {
132                 return Error() << "unexpected end of file";
133             } else if (bytes == -1) {
134                 return ErrnoError() << "failed to read";
135             }
136         }
137         struct timespec finish;
138         if (clock_gettime(CLOCK_MONOTONIC, &finish) == -1) {
139             return ErrnoError() << "failed to clock_gettime";
140         }
141         double elapsed_seconds =
142                 finish.tv_sec - start.tv_sec + (finish.tv_nsec - start.tv_nsec) / 1e9;
143         double file_size_mb = (double)file_size_bytes / kNumBytesPerMB;
144         return {file_size_mb / elapsed_seconds};
145     }
146 
alloc_anon_memory(long mb)147     void* alloc_anon_memory(long mb) {
148         long bytes = mb << 20;
149         void* p = malloc(bytes);
150         /*
151          * Heap memory is demand allocated. Dirty all pages to ensure
152          * all are allocated.
153          */
154         memset(p, 0x55, bytes);
155         return p;
156     }
157 
read_meminfo_entry(const std::string & stat)158     Result<size_t> read_meminfo_entry(const std::string& stat) {
159         std::ifstream fs("/proc/meminfo");
160         if (!fs.is_open()) {
161             return Error() << "could not open /proc/meminfo";
162         }
163 
164         std::string line;
165         while (std::getline(fs, line)) {
166             auto elems = android::base::Split(line, ":");
167             if (elems[0] != stat) continue;
168 
169             std::string str = android::base::Trim(elems[1]);
170             if (android::base::EndsWith(str, " kB")) {
171                 str = str.substr(0, str.length() - 3);
172             }
173 
174             size_t value;
175             if (!android::base::ParseUint(str, &value)) {
176                 return ErrnoError() << "failed to parse \"" << str << "\" as size_t";
177             }
178             return {value};
179         }
180 
181         return Error() << "entry \"" << stat << "\" not found";
182     }
183 };
184 
run_io_benchmark_tests()185 Result<void> run_io_benchmark_tests() {
186     auto test_service = ndk::SharedRefBase::make<IOBenchmarkService>();
187     auto callback = []([[maybe_unused]] void* param) { AVmPayload_notifyPayloadReady(); };
188     AVmPayload_runVsockRpcServer(test_service->asBinder().get(), test_service->SERVICE_PORT,
189                                  callback, nullptr);
190     return {};
191 }
192 } // Anonymous namespace
193 
AVmPayload_main()194 extern "C" int AVmPayload_main() {
195     if (auto res = run_io_benchmark_tests(); !res.ok()) {
196         LOG(ERROR) << "IO benchmark test failed: " << res.error() << "\n";
197         return EXIT_FAILURE;
198     }
199     return EXIT_SUCCESS;
200 }
201