diff options
-rw-r--r-- | libs/binder/ndk/include_ndk/android/binder_ibinder.h | 33 | ||||
-rw-r--r-- | services/gpuservice/tests/unittests/Android.bp | 6 | ||||
-rw-r--r-- | services/gpuservice/tests/unittests/GpuMemTracerTest.cpp | 196 | ||||
-rw-r--r-- | services/gpuservice/tracing/GpuMemTracer.cpp | 38 | ||||
-rw-r--r-- | services/gpuservice/tracing/include/tracing/GpuMemTracer.h | 29 |
5 files changed, 284 insertions, 18 deletions
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h index 0ca3a0760c..bb70588b8a 100644 --- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h +++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h @@ -660,13 +660,15 @@ const char* AIBinder_Class_getDescriptor(const AIBinder_Class* clazz) __INTRODUC /** * Whether AIBinder is less than another. * - * This provides a per-process-unique total ordering of binders determined by - * an underlying allocation address where a null AIBinder* is considered to be - * ordered before all other binders. + * This provides a per-process-unique total ordering of binders where a null + * AIBinder* object is considered to be before all other binder objects. + * For instance, two binders refer to the same object in a local or remote + * process when both AIBinder_lt(a, b) and AIBinder(b, a) are false. This API + * might be used to insert and lookup binders in binary search trees. * * AIBinder* pointers themselves actually also create a per-process-unique total * ordering. However, this ordering is inconsistent with AIBinder_Weak_lt for - * remote binders. + * remote binders. So, in general, this function should be preferred. * * Available since API level 31. * @@ -698,14 +700,21 @@ AIBinder_Weak* AIBinder_Weak_clone(const AIBinder_Weak* weak); * the same as AIBinder_lt. Similarly, a null AIBinder_Weak* is considered to be * ordered before all other weak references. * - * If you have many AIBinder_Weak* objects which are all references to distinct - * binder objects which happen to have the same underlying address (as ordered - * by AIBinder_lt), these AIBinder_Weak* objects will retain the same order with - * respect to all other AIBinder_Weak* pointers with different underlying - * addresses and are also guaranteed to have a per-process-unique ordering. That - * is, even though multiple AIBinder* instances may happen to be allocated at - * the same underlying address, this function will still correctly distinguish - * that these are weak pointers to different binder objects. + * This function correctly distinguishes binders even if one is deallocated. So, + * for instance, an AIBinder_Weak* entry representing a deleted binder will + * never compare as equal to an AIBinder_Weak* entry which represents a + * different allocation of a binder, even if the two binders were originally + * allocated at the same address. That is: + * + * AIBinder* a = ...; // imagine this has address 0x8 + * AIBinder_Weak* bWeak = AIBinder_Weak_new(a); + * AIBinder_decStrong(a); // a may be deleted, if this is the last reference + * AIBinder* b = ...; // imagine this has address 0x8 (same address as b) + * AIBinder_Weak* bWeak = AIBinder_Weak_new(b); + * + * Then when a/b are compared with other binders, their order will be preserved, + * and it will either be the case that AIBinder_Weak_lt(aWeak, bWeak) OR + * AIBinder_Weak_lt(bWeak, aWeak), but not both. * * Unlike AIBinder*, the AIBinder_Weak* addresses themselves have nothing to do * with the underlying binder. diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp index 940a26b3f5..9606daacec 100644 --- a/services/gpuservice/tests/unittests/Android.bp +++ b/services/gpuservice/tests/unittests/Android.bp @@ -20,6 +20,7 @@ cc_test { }, srcs: [ "GpuMemTest.cpp", + "GpuMemTracerTest.cpp", "GpuStatsTest.cpp", ], shared_libs: [ @@ -29,14 +30,19 @@ cc_test { "libcutils", "libgfxstats", "libgpumem", + "libgpumemtracer", "libgraphicsenv", "liblog", + "libprotobuf-cpp-lite", + "libprotoutil", "libstatslog", "libstatspull", "libutils", ], static_libs: [ "libgmock", + "libperfetto_client_experimental", + "perfetto_trace_protos", ], require_root: true, } diff --git a/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp b/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp new file mode 100644 index 0000000000..d76f039a6d --- /dev/null +++ b/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp @@ -0,0 +1,196 @@ +/* + * Copyright 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. + */ + +#undef LOG_TAG +#define LOG_TAG "gpuservice_unittest" + +#include <bpf/BpfMap.h> +#include <gpumem/GpuMem.h> +#include <gtest/gtest.h> +#include <perfetto/trace/trace.pb.h> +#include <tracing/GpuMemTracer.h> + +#include "TestableGpuMem.h" + +namespace android { + +constexpr uint32_t TEST_MAP_SIZE = 10; +constexpr uint64_t TEST_GLOBAL_KEY = 0; +constexpr uint32_t TEST_GLOBAL_PID = 0; +constexpr uint64_t TEST_GLOBAL_VAL = 123; +constexpr uint32_t TEST_GLOBAL_GPU_ID = 0; +constexpr uint64_t TEST_PROC_KEY_1 = 1; +constexpr uint32_t TEST_PROC_PID_1 = 1; +constexpr uint64_t TEST_PROC_VAL_1 = 234; +constexpr uint32_t TEST_PROC_1_GPU_ID = 0; +constexpr uint64_t TEST_PROC_KEY_2 = 4294967298; // (1 << 32) + 2 +constexpr uint32_t TEST_PROC_PID_2 = 2; +constexpr uint64_t TEST_PROC_VAL_2 = 345; +constexpr uint32_t TEST_PROC_2_GPU_ID = 1; + +class GpuMemTracerTest : public testing::Test { +public: + GpuMemTracerTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); + } + + ~GpuMemTracerTest() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); + } + + void SetUp() override { + bpf::setrlimitForTest(); + + mGpuMem = std::make_shared<GpuMem>(); + mGpuMemTracer = std::make_unique<GpuMemTracer>(); + mGpuMemTracer->initializeForTest(mGpuMem); + mTestableGpuMem = TestableGpuMem(mGpuMem.get()); + + errno = 0; + mTestMap = bpf::BpfMap<uint64_t, uint64_t>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, + BPF_F_NO_PREALLOC); + + EXPECT_EQ(0, errno); + EXPECT_LE(0, mTestMap.getMap().get()); + EXPECT_TRUE(mTestMap.isValid()); + } + + int getTracerThreadCount() { return mGpuMemTracer->tracerThreadCount; } + + std::vector<perfetto::protos::TracePacket> readGpuMemTotalPacketsBlocking( + perfetto::TracingSession* tracingSession) { + std::vector<char> raw_trace = tracingSession->ReadTraceBlocking(); + perfetto::protos::Trace trace; + trace.ParseFromArray(raw_trace.data(), int(raw_trace.size())); + + std::vector<perfetto::protos::TracePacket> packets; + for (const auto& packet : trace.packet()) { + if (!packet.has_gpu_mem_total_event()) { + continue; + } + packets.emplace_back(packet); + } + return packets; + } + + std::shared_ptr<GpuMem> mGpuMem; + TestableGpuMem mTestableGpuMem; + std::unique_ptr<GpuMemTracer> mGpuMemTracer; + bpf::BpfMap<uint64_t, uint64_t> mTestMap; +}; + +static constexpr uint64_t getSizeForPid(uint32_t pid) { + switch (pid) { + case TEST_GLOBAL_PID: + return TEST_GLOBAL_VAL; + case TEST_PROC_PID_1: + return TEST_PROC_VAL_1; + case TEST_PROC_PID_2: + return TEST_PROC_VAL_2; + } + return 0; +} + +static constexpr uint32_t getGpuIdForPid(uint32_t pid) { + switch (pid) { + case TEST_GLOBAL_PID: + return TEST_GLOBAL_GPU_ID; + case TEST_PROC_PID_1: + return TEST_PROC_1_GPU_ID; + case TEST_PROC_PID_2: + return TEST_PROC_2_GPU_ID; + } + return 0; +} + +TEST_F(GpuMemTracerTest, traceInitialCountersAfterGpuMemInitialize) { + ASSERT_RESULT_OK(mTestMap.writeValue(TEST_GLOBAL_KEY, TEST_GLOBAL_VAL, BPF_ANY)); + ASSERT_RESULT_OK(mTestMap.writeValue(TEST_PROC_KEY_1, TEST_PROC_VAL_1, BPF_ANY)); + ASSERT_RESULT_OK(mTestMap.writeValue(TEST_PROC_KEY_2, TEST_PROC_VAL_2, BPF_ANY)); + mTestableGpuMem.setGpuMemTotalMap(mTestMap); + mTestableGpuMem.setInitialized(); + + // Only 1 tracer thread should be existing for test. + EXPECT_EQ(getTracerThreadCount(), 1); + auto tracingSession = mGpuMemTracer->getTracingSessionForTest(); + + tracingSession->StartBlocking(); + // Sleep for a short time to let the tracer thread finish its work + sleep(1); + tracingSession->StopBlocking(); + + // The test tracer thread should have finished its execution by now. + EXPECT_EQ(getTracerThreadCount(), 0); + + auto packets = readGpuMemTotalPacketsBlocking(tracingSession.get()); + EXPECT_EQ(packets.size(), 3); + + const auto& packet0 = packets[0]; + ASSERT_TRUE(packet0.has_timestamp()); + ASSERT_TRUE(packet0.has_gpu_mem_total_event()); + const auto& gpuMemEvent0 = packet0.gpu_mem_total_event(); + ASSERT_TRUE(gpuMemEvent0.has_pid()); + const auto& pid0 = gpuMemEvent0.pid(); + ASSERT_TRUE(gpuMemEvent0.has_size()); + EXPECT_EQ(gpuMemEvent0.size(), getSizeForPid(pid0)); + ASSERT_TRUE(gpuMemEvent0.has_gpu_id()); + EXPECT_EQ(gpuMemEvent0.gpu_id(), getGpuIdForPid(pid0)); + + const auto& packet1 = packets[1]; + ASSERT_TRUE(packet1.has_timestamp()); + ASSERT_TRUE(packet1.has_gpu_mem_total_event()); + const auto& gpuMemEvent1 = packet1.gpu_mem_total_event(); + ASSERT_TRUE(gpuMemEvent1.has_pid()); + const auto& pid1 = gpuMemEvent1.pid(); + ASSERT_TRUE(gpuMemEvent1.has_size()); + EXPECT_EQ(gpuMemEvent1.size(), getSizeForPid(pid1)); + ASSERT_TRUE(gpuMemEvent1.has_gpu_id()); + EXPECT_EQ(gpuMemEvent1.gpu_id(), getGpuIdForPid(pid1)); + + const auto& packet2 = packets[2]; + ASSERT_TRUE(packet2.has_timestamp()); + ASSERT_TRUE(packet2.has_gpu_mem_total_event()); + const auto& gpuMemEvent2 = packet2.gpu_mem_total_event(); + ASSERT_TRUE(gpuMemEvent2.has_pid()); + const auto& pid2 = gpuMemEvent2.pid(); + ASSERT_TRUE(gpuMemEvent2.has_size()); + EXPECT_EQ(gpuMemEvent2.size(), getSizeForPid(pid2)); + ASSERT_TRUE(gpuMemEvent2.has_gpu_id()); + EXPECT_EQ(gpuMemEvent2.gpu_id(), getGpuIdForPid(pid2)); +} + +TEST_F(GpuMemTracerTest, noTracingWithoutGpuMemInitialize) { + // Only 1 tracer thread should be existing for test. + EXPECT_EQ(getTracerThreadCount(), 1); + + auto tracingSession = mGpuMemTracer->getTracingSessionForTest(); + + tracingSession->StartBlocking(); + // Sleep for a short time to let the tracer thread finish its work + sleep(1); + tracingSession->StopBlocking(); + + // The test tracer thread should have finished its execution by now. + EXPECT_EQ(getTracerThreadCount(), 0); + + auto packets = readGpuMemTotalPacketsBlocking(tracingSession.get()); + EXPECT_EQ(packets.size(), 0); +} +} // namespace android diff --git a/services/gpuservice/tracing/GpuMemTracer.cpp b/services/gpuservice/tracing/GpuMemTracer.cpp index 6366e1d8e2..44a30eae13 100644 --- a/services/gpuservice/tracing/GpuMemTracer.cpp +++ b/services/gpuservice/tracing/GpuMemTracer.cpp @@ -44,9 +44,35 @@ void GpuMemTracer::initialize(std::shared_ptr<GpuMem> gpuMem) { args.backends = perfetto::kSystemBackend; perfetto::Tracing::Initialize(args); registerDataSource(); - std::thread tracerThread(&GpuMemTracer::threadLoop, this); + std::thread tracerThread(&GpuMemTracer::threadLoop, this, true); pthread_setname_np(tracerThread.native_handle(), "GpuMemTracerThread"); tracerThread.detach(); + tracerThreadCount++; +} + +void GpuMemTracer::initializeForTest(std::shared_ptr<GpuMem> gpuMem) { + mGpuMem = gpuMem; + perfetto::TracingInitArgs args; + args.backends = perfetto::kInProcessBackend; + perfetto::Tracing::Initialize(args); + registerDataSource(); + std::thread tracerThread(&GpuMemTracer::threadLoop, this, false); + pthread_setname_np(tracerThread.native_handle(), "GpuMemTracerThreadForTest"); + tracerThread.detach(); + tracerThreadCount++; +} + +// Each tracing session can be used for a single block of Start -> Stop. +std::unique_ptr<perfetto::TracingSession> GpuMemTracer::getTracingSessionForTest() { + perfetto::TraceConfig cfg; + cfg.set_duration_ms(500); + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name(GpuMemTracer::kGpuMemDataSource); + + auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend); + tracingSession->Setup(cfg); + return tracingSession; } void GpuMemTracer::registerDataSource() { @@ -55,8 +81,8 @@ void GpuMemTracer::registerDataSource() { GpuMemDataSource::Register(dsd); } -void GpuMemTracer::threadLoop() { - while (true) { +void GpuMemTracer::threadLoop(bool infiniteLoop) { + do { { std::unique_lock<std::mutex> lock(GpuMemTracer::sTraceMutex); while (!sTraceStarted) { @@ -68,7 +94,11 @@ void GpuMemTracer::threadLoop() { std::lock_guard<std::mutex> lock(GpuMemTracer::sTraceMutex); sTraceStarted = false; } - } + } while (infiniteLoop); + + // Thread loop is exiting. Reduce the tracerThreadCount to reflect the number of active threads + // in the wait loop. + tracerThreadCount--; } void GpuMemTracer::traceInitialCounters() { diff --git a/services/gpuservice/tracing/include/tracing/GpuMemTracer.h b/services/gpuservice/tracing/include/tracing/GpuMemTracer.h index 40deb4c212..ae871f11cb 100644 --- a/services/gpuservice/tracing/include/tracing/GpuMemTracer.h +++ b/services/gpuservice/tracing/include/tracing/GpuMemTracer.h @@ -20,6 +20,10 @@ #include <mutex> +namespace perfetto::protos { +class TracePacket; +} + namespace android { class GpuMem; @@ -45,16 +49,37 @@ public: // perfetto::kInProcessBackend in tests. void registerDataSource(); + // TODO(b/175904796): Refactor gpuservice lib to include perfetto lib and move the test + // functions into the unittests. + // Functions only used for testing with in-process backend. These functions require the static + // perfetto lib to be linked. If the tests have a perfetto linked, while libgpumemtracer.so also + // has one linked, they will both use different static states maintained in perfetto. Since the + // static perfetto states are not shared, tracing sessions created in the unit test are not + // recognized by GpuMemTracer. As a result, we cannot use any of the perfetto functions from + // this class, which defeats the purpose of the unit test. To solve this, we restrict all + // tracing functionality to this class, while the unit test validates the data. + // Sets up the perfetto in-process backend and calls into registerDataSource. + void initializeForTest(std::shared_ptr<GpuMem>); + // Creates a tracing session with in process backend, for testing. + std::unique_ptr<perfetto::TracingSession> getTracingSessionForTest(); + // Read and filter the gpu memory packets from the created trace. + std::vector<perfetto::protos::TracePacket> readGpuMemTotalPacketsForTestBlocking( + perfetto::TracingSession* tracingSession); + static constexpr char kGpuMemDataSource[] = "android.gpu.memory"; static std::condition_variable sCondition; static std::mutex sTraceMutex; static bool sTraceStarted; private: - void traceInitialCounters(); - void threadLoop(); + // Friend class for testing + friend class GpuMemTracerTest; + void threadLoop(bool infiniteLoop); + void traceInitialCounters(); std::shared_ptr<GpuMem> mGpuMem; + // Count of how many tracer threads are currently active. Useful for testing. + std::atomic<int32_t> tracerThreadCount = 0; }; } // namespace android |