|  | // | 
|  | // 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 defined(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 |