| /* | |
| * 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. | |
| */ | |
| #include <android/log.h> | |
| #include <fcntl.h> | |
| #include <jni.h> | |
| #include <signal.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #include <time.h> | |
| #include <unistd.h> | |
| #include <fstream> | |
| #include <memory> | |
| #include <string> | |
| #include <sstream> | |
| #include <thread> | |
| #include <vector> | |
| #include "run_tflite.h" | |
| #include "tensorflow/lite/interpreter.h" | |
| #include "tensorflow/lite/model.h" | |
| #include "tensorflow/lite/nnapi/nnapi_implementation.h" | |
| #define LOG_TAG "NN_MPROC_STRESS" | |
| constexpr int kInvalidArguments = -1; | |
| enum Arguments : int { | |
| kArgModelPath = 1, | |
| kArgInputDataPath, | |
| kArgInputShape, | |
| kArgInputElementSize, | |
| kArgProcessCount, | |
| kArgThreadCount, | |
| kArgDurationSeconds, | |
| kArgTestName, | |
| kArgJustCompileModel, | |
| kArgProcessFailureRatePercent, | |
| kArgNnApiDeviceName, | |
| kArgMmapModel | |
| }; | |
| constexpr int kMandatoryArgsCount = 9; | |
| const char* kUsage = | |
| R"""(%s modelFileName inputDataFile inputShape inputElementByteSize procCount threadCount durationInSeconds testName justCompileModel [processFailureRate] [nnapiDeviceName] [mmapModel] | |
| where: | |
| inputShape comma separated list of integers (e.g. '1,224,224,3') | |
| justCompileModel: true/false) | |
| processFailureRate: 0 to 100 percent probability of having one of the client processes failing. Defaults to 0.) | |
| mmapModel: true/false select if the TFLite model should be memory mapped to the given file or created from program memory)"""; | |
| bool canReadInputFile(const char* path) { | |
| std::string modelFileName(path); | |
| std::ifstream fstream(modelFileName); | |
| std::stringstream readBuffer; | |
| readBuffer << fstream.rdbuf(); | |
| return fstream.good(); | |
| } | |
| bool readInputData(const char* inputDataFileName, std::vector<int> input_shape, | |
| int inputElementSize, | |
| std::vector<InferenceInOutSequence>* result) { | |
| int inputElementCount = 1; | |
| std::for_each( | |
| input_shape.begin(), input_shape.end(), | |
| [&inputElementCount](int dimSize) { inputElementCount *= dimSize; }); | |
| size_t inputDataSizeBytes = inputElementCount * inputElementSize; | |
| std::ifstream dataFile; | |
| dataFile.open(inputDataFileName); | |
| if (!dataFile) { | |
| return false; | |
| } | |
| std::function<bool(uint8_t*, size_t)> failToGenerateData = | |
| [](uint8_t*, size_t) { return false; }; | |
| while (!dataFile.eof()) { | |
| std::unique_ptr<uint8_t[]> dataBuffer = | |
| std::make_unique<uint8_t[]>(inputDataSizeBytes); | |
| if (!dataFile.read(reinterpret_cast<char*>(dataBuffer.get()), | |
| inputDataSizeBytes)) { | |
| break; | |
| } | |
| InferenceInOut entry{ | |
| dataBuffer.release(), inputDataSizeBytes, {}, failToGenerateData}; | |
| result->push_back({entry}); | |
| } | |
| return result; | |
| } | |
| bool runModel(const char* modelFileName, | |
| const std::vector<InferenceInOutSequence>& data, | |
| int durationSeconds, const std::string& nnApiDeviceName, | |
| bool justCompileModel, bool mmapModel) { | |
| if (justCompileModel) { | |
| std::time_t startTime = std::time(nullptr); | |
| while (std::difftime(std::time(nullptr), startTime) < durationSeconds) { | |
| int nnapiErrno = 0; | |
| std::unique_ptr<BenchmarkModel> model(BenchmarkModel::create( | |
| modelFileName, /*useNnApi=*/true, | |
| /*enableIntermediateTensorsDump=*/false, | |
| &nnapiErrno, | |
| nnApiDeviceName.empty() ? nullptr : nnApiDeviceName.c_str(), mmapModel, | |
| /*nnapi_cache_dir=*/nullptr)); | |
| if (!model) { | |
| __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error creating model"); | |
| return false; | |
| } | |
| // sleeping from 300ms to 800ms | |
| constexpr int kMinPauseMs = 300; | |
| constexpr int kMaxPauseMs = 800; | |
| int sleepForMs = kMinPauseMs + (drand48() * (kMaxPauseMs - kMinPauseMs)); | |
| usleep(sleepForMs * 1000); | |
| } | |
| return true; | |
| } else { | |
| int nnapiErrno = 0; | |
| std::unique_ptr<BenchmarkModel> model(BenchmarkModel::create( | |
| modelFileName, /*useNnApi=*/true, | |
| /*enableIntermediateTensorsDump=*/false, | |
| &nnapiErrno, | |
| nnApiDeviceName.empty() ? nullptr : nnApiDeviceName.c_str(), mmapModel, | |
| /*nnapi_cache_dir=*/nullptr)); | |
| if (!model) { | |
| __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error creating model"); | |
| return false; | |
| } | |
| std::vector<InferenceResult> result; | |
| constexpr int flags = | |
| FLAG_DISCARD_INFERENCE_OUTPUT | FLAG_IGNORE_GOLDEN_OUTPUT; | |
| return model->benchmark(data, std::numeric_limits<int>::max(), | |
| durationSeconds, flags, &result); | |
| } | |
| } | |
| bool getBooleanArg(int argc, char* argv[], int argIndex, bool defaultValue) { | |
| if (argc > argIndex) { | |
| std::string argAsString(argv[argIndex]); | |
| return argAsString == "true"; | |
| } else { | |
| return defaultValue; | |
| } | |
| } | |
| int getIntArg(int argc, char* argv[], int argIndex, int defaultValue) { | |
| if (argc > argIndex) { | |
| return std::atoi(argv[argIndex]); | |
| } else { | |
| return defaultValue; | |
| } | |
| } | |
| int main(int argc, char* argv[]) { | |
| if (argc < kMandatoryArgsCount) { | |
| __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, kUsage, kMandatoryArgsCount, | |
| argc, argv[0]); | |
| return kInvalidArguments; | |
| } | |
| const char* modelFileName = argv[kArgModelPath]; | |
| const char* inputDataFileName = argv[kArgInputDataPath]; | |
| const char* testName = argv[kArgTestName]; | |
| std::string nnApiDeviceName{ | |
| argc > kArgNnApiDeviceName ? argv[kArgNnApiDeviceName] : ""}; | |
| int numProcesses = getIntArg(argc, argv, kArgProcessCount, 0); | |
| int numThreads = getIntArg(argc, argv, kArgThreadCount, 0); | |
| int durationSeconds = getIntArg(argc, argv, kArgDurationSeconds, 0); | |
| bool justCompileModel = | |
| getBooleanArg(argc, argv, kArgJustCompileModel, false); | |
| std::vector<int> inputShape; | |
| std::istringstream inputShapeStream(argv[kArgInputShape]); | |
| std::string currSizeToken; | |
| while (std::getline(inputShapeStream, currSizeToken, ',')) { | |
| inputShape.push_back(std::stoi(currSizeToken)); | |
| } | |
| int inputElementSize = getIntArg(argc, argv, kArgInputElementSize, 0); | |
| int processFailureRate = getIntArg(argc, argv, kArgProcessFailureRatePercent, 0); | |
| bool mmapModel = getBooleanArg(argc, argv, kArgMmapModel, true); | |
| // Validate params | |
| if (!canReadInputFile(modelFileName)) { | |
| __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, | |
| "Error reading model file '%s'", modelFileName); | |
| return kInvalidArguments; | |
| } | |
| std::vector<InferenceInOutSequence> inputData; | |
| if (!justCompileModel) { | |
| if (!readInputData(inputDataFileName, inputShape, inputElementSize, | |
| &inputData)) { | |
| __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, | |
| "Error reading input data file '%s'", | |
| inputDataFileName); | |
| return kInvalidArguments; | |
| } | |
| } | |
| if (numProcesses <= 0 || numThreads <= 0 || durationSeconds <= 0 || | |
| inputElementSize <= 0) { | |
| __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Invalid arguments"); | |
| return kInvalidArguments; | |
| } | |
| __android_log_print( | |
| ANDROID_LOG_INFO, LOG_TAG, | |
| "Test '%s': running %s of model at path '%s' with input shape [%s] " | |
| "(element data size %d)," | |
| " %d processes of %d threads each using device '%s' for %d seconds", | |
| testName, justCompileModel ? "compilation only" : "full inference", | |
| modelFileName, argv[kArgInputShape], inputElementSize, numProcesses, | |
| numThreads, | |
| nnApiDeviceName.empty() ? "no-device" : nnApiDeviceName.c_str(), | |
| durationSeconds); | |
| srand48(time(NULL) + getpid()); | |
| std::vector<pid_t> children; | |
| pid_t pid = 1; | |
| bool forkSucceeded = true; | |
| bool isSubprocess = false; | |
| for (int i = 0; i < numProcesses; i++) { | |
| if (pid != 0) { | |
| pid = fork(); | |
| if (pid > 0) { | |
| children.push_back(pid); | |
| __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Forked child pid %d", | |
| pid); | |
| } else if (pid < 0) { | |
| forkSucceeded = false; | |
| break; | |
| } else { | |
| isSubprocess = true; | |
| } | |
| } | |
| } | |
| if (isSubprocess) { | |
| __android_log_print( | |
| ANDROID_LOG_INFO, LOG_TAG, | |
| "%s model '%s': for %d seconds on device '%s' on %d threads", | |
| justCompileModel ? "Compiling" : "Running", modelFileName, | |
| durationSeconds, | |
| nnApiDeviceName.empty() ? "no-device" : nnApiDeviceName.c_str(), | |
| numThreads); | |
| bool shouldKillProcess = (drand48() * 100) <= (double)processFailureRate; | |
| if (shouldKillProcess) { | |
| float killAfter = durationSeconds * drand48(); | |
| __android_log_print(ANDROID_LOG_INFO, LOG_TAG, | |
| "This process will be killed in %f seconds", | |
| killAfter); | |
| std::thread killer = std::thread([killAfter]() { | |
| usleep(killAfter * 1000.0 * 1000); | |
| __android_log_print(ANDROID_LOG_INFO, LOG_TAG, | |
| "Killing current test process."); | |
| kill(getpid(), 9); | |
| }); | |
| killer.detach(); | |
| } | |
| std::vector<std::thread> threads; | |
| threads.reserve(numThreads); | |
| for (int i = 0; i < numThreads; i++) { | |
| threads.push_back(std::thread([&]() { | |
| runModel(modelFileName, inputData, durationSeconds, nnApiDeviceName, | |
| justCompileModel, mmapModel); | |
| })); | |
| } | |
| std::for_each(threads.begin(), threads.end(), | |
| [](std::thread& t) { t.join(); }); | |
| } else { | |
| for (auto pid : children) { | |
| waitpid(pid, nullptr, 0); | |
| } | |
| } | |
| __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Test '%s': %s returning ", | |
| testName, isSubprocess ? "Test process" : "Main process"); | |
| return 0; | |
| } |