Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 134332b6 authored by Adithya Srinivasan's avatar Adithya Srinivasan
Browse files

Add unittests for GpuMemTracer

This change adds some unittests for the GpuMemTracer module to verify
that the producer indeed works.

Bug: 164194338
Test: atest gpuservice_unittest:GpuMemTracerTest
Change-Id: I9f570567310f23261b43c85ab8cdc3ede8717f09
Merged-In: I9f570567310f23261b43c85ab8cdc3ede8717f09
parent a0b95c80
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ cc_test {
    },
    srcs: [
        "GpuMemTest.cpp",
        "GpuMemTracerTest.cpp",
        "GpuStatsTest.cpp",
    ],
    shared_libs: [
@@ -29,14 +30,18 @@ cc_test {
        "libcutils",
        "libgfxstats",
        "libgpumem",
        "libgpumemtracer",
        "libgraphicsenv",
        "liblog",
        "libprotobuf-cpp-lite",
        "libprotoutil",
        "libstatslog",
        "libstatspull",
        "libutils",
    ],
    static_libs: [
        "libgmock",
        "perfetto_trace_protos",
    ],
    require_root: true,
}
+182 −0
Original line number Diff line number Diff line
/*
 * 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 {
        SKIP_IF_BPF_NOT_SUPPORTED;
        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::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) {
    SKIP_IF_BPF_NOT_SUPPORTED;
    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 = mGpuMemTracer->readGpuMemTotalPacketsForTestBlocking(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 = mGpuMemTracer->readGpuMemTotalPacketsForTestBlocking(tracingSession.get());
    EXPECT_EQ(packets.size(), 0);
}
} // namespace android
+3 −0
Original line number Diff line number Diff line
@@ -21,10 +21,13 @@ cc_library_shared {
        "libgpumem",
        "libbase",
        "liblog",
        "libprotobuf-cpp-lite",
        "libprotoutil",
        "libutils",
    ],
    static_libs: [
        "libperfetto_client_experimental",
        "perfetto_trace_protos",
    ],
    export_include_dirs: ["include"],
    export_static_lib_headers: [
+51 −4
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@

#include <gpumem/GpuMem.h>
#include <perfetto/trace/android/gpu_mem_event.pbzero.h>
#include <perfetto/trace/trace.pb.h>
#include <unistd.h>

#include <thread>
@@ -44,9 +45,51 @@ 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++;
}

std::vector<perfetto::protos::TracePacket> GpuMemTracer::readGpuMemTotalPacketsForTestBlocking(
        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;
}

// 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 +98,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 +111,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() {
+27 −2
Original line number Diff line number Diff line
@@ -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