| // | |
| // Copyright 2015 The ANGLE Project Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // | |
| // test_utils_posix.cpp: Implementation of OS-specific functions for Posix systems | |
| #include "util/test_utils.h" | |
| #include <dlfcn.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <sched.h> | |
| #include <signal.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #include <time.h> | |
| #include <unistd.h> | |
| #include <cstdarg> | |
| #include <cstring> | |
| #include <iostream> | |
| #include "common/debug.h" | |
| #include "common/platform.h" | |
| #if !defined(ANGLE_PLATFORM_FUCHSIA) | |
| # include <sys/resource.h> | |
| #endif | |
| namespace angle | |
| { | |
| namespace | |
| { | |
| struct ScopedPipe | |
| { | |
| ~ScopedPipe() | |
| { | |
| closeEndPoint(0); | |
| closeEndPoint(1); | |
| } | |
| void closeEndPoint(int index) | |
| { | |
| if (fds[index] >= 0) | |
| { | |
| close(fds[index]); | |
| fds[index] = -1; | |
| } | |
| } | |
| bool valid() const { return fds[0] != -1 || fds[1] != -1; } | |
| int fds[2] = { | |
| -1, | |
| -1, | |
| }; | |
| }; | |
| bool ReadFromFile(int fd, std::string *out) | |
| { | |
| char buffer[256]; | |
| ssize_t bytesRead = read(fd, buffer, sizeof(buffer)); | |
| // If interrupted, retry. | |
| if (bytesRead < 0 && errno == EINTR) | |
| { | |
| return true; | |
| } | |
| // If failed, or nothing to read, we are done. | |
| if (bytesRead <= 0) | |
| { | |
| return false; | |
| } | |
| out->append(buffer, bytesRead); | |
| return true; | |
| } | |
| void ReadEntireFile(int fd, std::string *out) | |
| { | |
| while (true) | |
| { | |
| if (!ReadFromFile(fd, out)) | |
| break; | |
| } | |
| } | |
| class PosixProcess : public Process | |
| { | |
| public: | |
| PosixProcess(const std::vector<const char *> &commandLineArgs, | |
| bool captureStdOut, | |
| bool captureStdErr) | |
| { | |
| if (commandLineArgs.empty()) | |
| { | |
| return; | |
| } | |
| // Create pipes for stdout and stderr. | |
| if (captureStdOut) | |
| { | |
| if (pipe(mStdoutPipe.fds) != 0) | |
| { | |
| std::cerr << "Error calling pipe: " << errno << "\n"; | |
| return; | |
| } | |
| if (fcntl(mStdoutPipe.fds[0], F_SETFL, O_NONBLOCK) == -1) | |
| { | |
| std::cerr << "Error calling fcntl: " << errno << "\n"; | |
| return; | |
| } | |
| } | |
| if (captureStdErr) | |
| { | |
| if (pipe(mStderrPipe.fds) != 0) | |
| { | |
| std::cerr << "Error calling pipe: " << errno << "\n"; | |
| return; | |
| } | |
| if (fcntl(mStderrPipe.fds[0], F_SETFL, O_NONBLOCK) == -1) | |
| { | |
| std::cerr << "Error calling fcntl: " << errno << "\n"; | |
| return; | |
| } | |
| } | |
| mPID = fork(); | |
| if (mPID < 0) | |
| { | |
| return; | |
| } | |
| mStarted = true; | |
| mTimer.start(); | |
| if (mPID == 0) | |
| { | |
| // Child. Execute the application. | |
| // Redirect stdout and stderr to the pipe fds. | |
| if (captureStdOut) | |
| { | |
| if (dup2(mStdoutPipe.fds[1], STDOUT_FILENO) < 0) | |
| { | |
| _exit(errno); | |
| } | |
| mStdoutPipe.closeEndPoint(1); | |
| } | |
| if (captureStdErr) | |
| { | |
| if (dup2(mStderrPipe.fds[1], STDERR_FILENO) < 0) | |
| { | |
| _exit(errno); | |
| } | |
| mStderrPipe.closeEndPoint(1); | |
| } | |
| // Execute the application, which doesn't return unless failed. Note: execv takes argv | |
| // as `char * const *` for historical reasons. It is safe to const_cast it: | |
| // | |
| // http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html | |
| // | |
| // > The statement about argv[] and envp[] being constants is included to make explicit | |
| // to future writers of language bindings that these objects are completely constant. | |
| // Due to a limitation of the ISO C standard, it is not possible to state that idea in | |
| // standard C. Specifying two levels of const- qualification for the argv[] and envp[] | |
| // parameters for the exec functions may seem to be the natural choice, given that these | |
| // functions do not modify either the array of pointers or the characters to which the | |
| // function points, but this would disallow existing correct code. Instead, only the | |
| // array of pointers is noted as constant. | |
| std::vector<char *> args; | |
| for (const char *arg : commandLineArgs) | |
| { | |
| args.push_back(const_cast<char *>(arg)); | |
| } | |
| args.push_back(nullptr); | |
| execv(commandLineArgs[0], args.data()); | |
| std::cerr << "Error calling evecv: " << errno; | |
| _exit(errno); | |
| } | |
| // Parent continues execution. | |
| mStdoutPipe.closeEndPoint(1); | |
| mStderrPipe.closeEndPoint(1); | |
| } | |
| ~PosixProcess() override {} | |
| bool started() override { return mStarted; } | |
| bool finish() override | |
| { | |
| if (!mStarted) | |
| { | |
| return false; | |
| } | |
| if (mFinished) | |
| { | |
| return true; | |
| } | |
| while (!finished()) | |
| { | |
| angle::Sleep(1); | |
| } | |
| return true; | |
| } | |
| bool finished() override | |
| { | |
| if (!mStarted) | |
| { | |
| return false; | |
| } | |
| if (mFinished) | |
| { | |
| return true; | |
| } | |
| int status = 0; | |
| pid_t returnedPID = ::waitpid(mPID, &status, WNOHANG); | |
| if (returnedPID == -1 && errno != ECHILD) | |
| { | |
| std::cerr << "Error calling waitpid: " << ::strerror(errno) << "\n"; | |
| return true; | |
| } | |
| if (returnedPID == mPID) | |
| { | |
| mFinished = true; | |
| mTimer.stop(); | |
| readPipes(); | |
| mExitCode = WEXITSTATUS(status); | |
| return true; | |
| } | |
| if (mStdoutPipe.valid()) | |
| { | |
| ReadFromFile(mStdoutPipe.fds[0], &mStdout); | |
| } | |
| if (mStderrPipe.valid()) | |
| { | |
| ReadFromFile(mStderrPipe.fds[0], &mStderr); | |
| } | |
| return false; | |
| } | |
| int getExitCode() override { return mExitCode; } | |
| bool kill() override | |
| { | |
| if (!mStarted) | |
| { | |
| return false; | |
| } | |
| if (finished()) | |
| { | |
| return true; | |
| } | |
| return (::kill(mPID, SIGTERM) == 0); | |
| } | |
| private: | |
| void readPipes() | |
| { | |
| // Close the write end of the pipes, so EOF can be generated when child exits. | |
| // Then read back the output of the child. | |
| if (mStdoutPipe.valid()) | |
| { | |
| ReadEntireFile(mStdoutPipe.fds[0], &mStdout); | |
| } | |
| if (mStderrPipe.valid()) | |
| { | |
| ReadEntireFile(mStderrPipe.fds[0], &mStderr); | |
| } | |
| } | |
| bool mStarted = false; | |
| bool mFinished = false; | |
| ScopedPipe mStdoutPipe; | |
| ScopedPipe mStderrPipe; | |
| int mExitCode = 0; | |
| pid_t mPID = -1; | |
| }; | |
| std::string TempFileName() | |
| { | |
| return std::string(".angle.XXXXXX"); | |
| } | |
| } // anonymous namespace | |
| void Sleep(unsigned int milliseconds) | |
| { | |
| // On Windows Sleep(0) yields while it isn't guaranteed by Posix's sleep | |
| // so we replicate Windows' behavior with an explicit yield. | |
| if (milliseconds == 0) | |
| { | |
| sched_yield(); | |
| } | |
| else | |
| { | |
| timespec sleepTime = { | |
| .tv_sec = milliseconds / 1000, | |
| .tv_nsec = (milliseconds % 1000) * 1000000, | |
| }; | |
| nanosleep(&sleepTime, nullptr); | |
| } | |
| } | |
| void SetLowPriorityProcess() | |
| { | |
| #if !defined(ANGLE_PLATFORM_FUCHSIA) | |
| setpriority(PRIO_PROCESS, getpid(), 10); | |
| #endif | |
| } | |
| void WriteDebugMessage(const char *format, ...) | |
| { | |
| va_list vararg; | |
| va_start(vararg, format); | |
| vfprintf(stderr, format, vararg); | |
| va_end(vararg); | |
| } | |
| bool StabilizeCPUForBenchmarking() | |
| { | |
| #if !defined(ANGLE_PLATFORM_FUCHSIA) | |
| bool success = true; | |
| errno = 0; | |
| setpriority(PRIO_PROCESS, getpid(), -20); | |
| if (errno) | |
| { | |
| // A friendly warning in case the test was run without appropriate permission. | |
| perror( | |
| "Warning: setpriority failed in StabilizeCPUForBenchmarking. Process will retain " | |
| "default priority"); | |
| success = false; | |
| } | |
| # if ANGLE_PLATFORM_LINUX | |
| cpu_set_t affinity; | |
| CPU_SET(0, &affinity); | |
| errno = 0; | |
| if (sched_setaffinity(getpid(), sizeof(affinity), &affinity)) | |
| { | |
| perror( | |
| "Warning: sched_setaffinity failed in StabilizeCPUForBenchmarking. Process will retain " | |
| "default affinity"); | |
| success = false; | |
| } | |
| # else | |
| // TODO(jmadill): Implement for non-linux. http://anglebug.com/2923 | |
| # endif | |
| return success; | |
| #else // defined(ANGLE_PLATFORM_FUCHSIA) | |
| return false; | |
| #endif | |
| } | |
| bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen) | |
| { | |
| const char *tmp = getenv("TMPDIR"); | |
| if (tmp) | |
| { | |
| strncpy(tempDirOut, tmp, maxDirNameLen); | |
| return true; | |
| } | |
| #if defined(ANGLE_PLATFORM_ANDROID) | |
| // TODO(jmadill): Android support. http://anglebug.com/3162 | |
| // return PathService::Get(DIR_CACHE, path); | |
| return false; | |
| #else | |
| strncpy(tempDirOut, "/tmp", maxDirNameLen); | |
| return true; | |
| #endif | |
| } | |
| bool CreateTemporaryFileInDir(const char *dir, char *tempFileNameOut, uint32_t maxFileNameLen) | |
| { | |
| std::string tempFile = TempFileName(); | |
| sprintf(tempFileNameOut, "%s/%s", dir, tempFile.c_str()); | |
| int fd = mkstemp(tempFileNameOut); | |
| close(fd); | |
| return fd != -1; | |
| } | |
| bool DeleteFile(const char *path) | |
| { | |
| return unlink(path) == 0; | |
| } | |
| Process *LaunchProcess(const std::vector<const char *> &args, | |
| bool captureStdout, | |
| bool captureStderr) | |
| { | |
| return new PosixProcess(args, captureStdout, captureStderr); | |
| } | |
| int NumberOfProcessors() | |
| { | |
| // sysconf returns the number of "logical" (not "physical") processors on both | |
| // Mac and Linux. So we get the number of max available "logical" processors. | |
| // | |
| // Note that the number of "currently online" processors may be fewer than the | |
| // returned value of NumberOfProcessors(). On some platforms, the kernel may | |
| // make some processors offline intermittently, to save power when system | |
| // loading is low. | |
| // | |
| // One common use case that needs to know the processor count is to create | |
| // optimal number of threads for optimization. It should make plan according | |
| // to the number of "max available" processors instead of "currently online" | |
| // ones. The kernel should be smart enough to make all processors online when | |
| // it has sufficient number of threads waiting to run. | |
| long res = sysconf(_SC_NPROCESSORS_CONF); | |
| if (res == -1) | |
| { | |
| return 1; | |
| } | |
| return static_cast<int>(res); | |
| } | |
| } // namespace angle |