/* * Copyright (C) 2014 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 "tests/common/TestScene.h" #include "protos/hwui.pb.h" #include "Properties.h" #include <getopt.h> #include <stdio.h> #include <string> #include <unistd.h> #include <unordered_map> #include <vector> #include <pthread.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> using namespace android; using namespace android::uirenderer; using namespace android::uirenderer::test; static int gRepeatCount = 1; static std::vector<TestScene::Info> gRunTests; static TestScene::Options gOpts; void run(const TestScene::Info& info, const TestScene::Options& opts); static void printHelp() { printf(R"( USAGE: hwuitest [OPTIONS] <TESTNAME> OPTIONS: -c, --count=NUM NUM loops a test should run (example, number of frames) -r, --runs=NUM Repeat the test(s) NUM times -h, --help Display this help --list List all tests --wait-for-gpu Set this to wait for the GPU before producing the next frame. Note that without locked clocks this will pathologically bad performance due to large idle time --report-frametime[=weight] If set, the test will print to stdout the moving average frametime. Weight is optional, default is 10 --cpuset=name Adds the test to the specified cpuset before running Not supported on all devices and needs root )"); } static void listTests() { printf("Tests: \n"); for (auto&& test : TestScene::testMap()) { auto&& info = test.second; const char* col1 = info.name.c_str(); int dlen = info.description.length(); const char* col2 = info.description.c_str(); // World's best line breaking algorithm. do { int toPrint = dlen; if (toPrint > 50) { char* found = (char*) memrchr(col2, ' ', 50); if (found) { toPrint = found - col2; } else { toPrint = 50; } } printf("%-20s %.*s\n", col1, toPrint, col2); col1 = ""; col2 += toPrint; dlen -= toPrint; while (*col2 == ' ') { col2++; dlen--; } } while (dlen > 0); printf("\n"); } } static void moveToCpuSet(const char* cpusetName) { if (access("/dev/cpuset/tasks", F_OK)) { fprintf(stderr, "don't have access to cpusets, skipping...\n"); return; } static const int BUF_SIZE = 100; char buffer[BUF_SIZE]; if (snprintf(buffer, BUF_SIZE, "/dev/cpuset/%s/tasks", cpusetName) >= BUF_SIZE) { fprintf(stderr, "Error, cpusetName too large to fit in buffer '%s'\n", cpusetName); return; } int fd = open(buffer, O_WRONLY | O_CLOEXEC); if (fd == -1) { fprintf(stderr, "Error opening file %d\n", errno); return; } pid_t pid = getpid(); int towrite = snprintf(buffer, BUF_SIZE, "%ld", (long) pid); if (towrite >= BUF_SIZE) { fprintf(stderr, "Buffer wasn't large enough?\n"); } else { if (write(fd, buffer, towrite) != towrite) { fprintf(stderr, "Failed to write, errno=%d", errno); } } close(fd); } // For options that only exist in long-form. Anything in the // 0-255 range is reserved for short options (which just use their ASCII value) namespace LongOpts { enum { Reserved = 255, List, WaitForGpu, ReportFrametime, CpuSet, }; } static const struct option LONG_OPTIONS[] = { { "frames", required_argument, nullptr, 'f' }, { "repeat", required_argument, nullptr, 'r' }, { "help", no_argument, nullptr, 'h' }, { "list", no_argument, nullptr, LongOpts::List }, { "wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu }, { "report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime }, { "cpuset", required_argument, nullptr, LongOpts::CpuSet }, { 0, 0, 0, 0 } }; static const char* SHORT_OPTIONS = "c:r:h"; void parseOptions(int argc, char* argv[]) { int c; bool error = false; opterr = 0; while (true) { /* getopt_long stores the option index here. */ int option_index = 0; c = getopt_long(argc, argv, SHORT_OPTIONS, LONG_OPTIONS, &option_index); if (c == -1) break; switch (c) { case 0: // Option set a flag, don't need to do anything // (although none of the current LONG_OPTIONS do this...) break; case LongOpts::List: listTests(); exit(EXIT_SUCCESS); break; case 'c': gOpts.count = atoi(optarg); if (!gOpts.count) { fprintf(stderr, "Invalid frames argument '%s'\n", optarg); error = true; } break; case 'r': gRepeatCount = atoi(optarg); if (!gRepeatCount) { fprintf(stderr, "Invalid repeat argument '%s'\n", optarg); error = true; } else { gRepeatCount = (gRepeatCount > 0 ? gRepeatCount : INT_MAX); } break; case LongOpts::ReportFrametime: if (optarg) { gOpts.reportFrametimeWeight = atoi(optarg); if (!gOpts.reportFrametimeWeight) { fprintf(stderr, "Invalid report frametime weight '%s'\n", optarg); error = true; } } else { gOpts.reportFrametimeWeight = 10; } break; case LongOpts::WaitForGpu: Properties::waitForGpuCompletion = true; break; case LongOpts::CpuSet: if (!optarg) { error = true; break; } moveToCpuSet(optarg); break; case 'h': printHelp(); exit(EXIT_SUCCESS); break; case '?': fprintf(stderr, "Unrecognized option '%s'\n", argv[optind - 1]); // fall-through default: error = true; break; } } if (error) { fprintf(stderr, "Try 'hwuitest --help' for more information.\n"); exit(EXIT_FAILURE); } /* Print any remaining command line arguments (not options). */ if (optind < argc) { do { const char* test = argv[optind++]; auto pos = TestScene::testMap().find(test); if (pos == TestScene::testMap().end()) { fprintf(stderr, "Unknown test '%s'\n", test); exit(EXIT_FAILURE); } else { gRunTests.push_back(pos->second); } } while (optind < argc); } else { gRunTests.push_back(TestScene::testMap()["shadowgrid"]); } } int main(int argc, char* argv[]) { // set defaults gOpts.count = 150; parseOptions(argc, argv); for (int i = 0; i < gRepeatCount; i++) { for (auto&& test : gRunTests) { run(test, gOpts); } } printf("Success!\n"); return 0; }