From d2918fe2181f1cc2113fbb5398886a1e6e68aa2d Mon Sep 17 00:00:00 2001 From: Erick Reyes Date: Thu, 24 Jan 2019 15:26:23 -0800 Subject: [PATCH] meminfo: Add memtrack dmabufinfo library This is the userspace component for the improved shared memory buffer tracking. A new static library called libdmabufinfo will provide APIs to parse dmabuf debugfs entries. The library can also map and read the file descriptors per process to cross-reference the global data per process. This change only adds a global API to read dmabuf stats from debugfs. Bug: 63860998 Test: dmabufinfo_test Change-Id: Ib0deedc73337a70875e03e07d3e9a692c4c33e63 Signed-off-by: Greg Hackmann Signed-off-by: Erick Reyes Signed-off-by: Sandeep Patil --- libmeminfo/libdmabufinfo/Android.bp | 55 ++++ libmeminfo/libdmabufinfo/dmabufinfo.cpp | 241 +++++++++++++++++ libmeminfo/libdmabufinfo/dmabufinfo_test.cpp | 252 ++++++++++++++++++ .../include/dmabufinfo/dmabufinfo.h | 73 +++++ 4 files changed, 621 insertions(+) create mode 100644 libmeminfo/libdmabufinfo/Android.bp create mode 100644 libmeminfo/libdmabufinfo/dmabufinfo.cpp create mode 100644 libmeminfo/libdmabufinfo/dmabufinfo_test.cpp create mode 100644 libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h diff --git a/libmeminfo/libdmabufinfo/Android.bp b/libmeminfo/libdmabufinfo/Android.bp new file mode 100644 index 000000000..3d5f2e707 --- /dev/null +++ b/libmeminfo/libdmabufinfo/Android.bp @@ -0,0 +1,55 @@ +// +// Copyright (C) 2019 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. + +cc_defaults { + name: "dmabufinfo_defaults", + static_libs: [ + "libbase", + "liblog", + "libprocinfo", + ], + + cflags: [ + "-Wall", + "-Werror", + "-Wextra", + ], +} + +cc_library_static { + name: "libdmabufinfo", + defaults: ["dmabufinfo_defaults"], + export_include_dirs: ["include"], + static_libs: ["libc++fs"], + + srcs: [ + "dmabufinfo.cpp", + ], +} + +cc_test { + name: "dmabufinfo_test", + defaults: ["dmabufinfo_defaults"], + srcs: [ + "dmabufinfo_test.cpp" + ], + + static_libs: [ + "libc++fs", + "libdmabufinfo", + "libion", + "libmeminfo", + ], +} diff --git a/libmeminfo/libdmabufinfo/dmabufinfo.cpp b/libmeminfo/libdmabufinfo/dmabufinfo.cpp new file mode 100644 index 000000000..41ecc5195 --- /dev/null +++ b/libmeminfo/libdmabufinfo/dmabufinfo.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2019 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 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace dmabufinfo { + +static bool FileIsDmaBuf(const std::string& path) { + return ::android::base::StartsWith(path, "/dmabuf"); +} + +static bool ReadDmaBufFdInfo(pid_t pid, int fd, std::string* name, std::string* exporter, + uint64_t* count) { + std::string fdinfo = ::android::base::StringPrintf("/proc/%d/fdinfo/%d", pid, fd); + auto fp = std::unique_ptr{fopen(fdinfo.c_str(), "re"), fclose}; + if (fp == nullptr) { + LOG(ERROR) << "Failed to open dmabuf info from debugfs"; + return false; + } + + char* line = nullptr; + size_t len = 0; + while (getline(&line, &len, fp.get()) > 0) { + switch (line[0]) { + case 'c': + if (strncmp(line, "count:", 6) == 0) { + char* c = line + 6; + *count = strtoull(c, nullptr, 10); + } + break; + case 'e': + if (strncmp(line, "exp_name:", 9) == 0) { + char* c = line + 9; + *exporter = ::android::base::Trim(c); + } + break; + case 'n': + if (strncmp(line, "name:", 5) == 0) { + char* c = line + 5; + *name = ::android::base::Trim(std::string(c)); + } + break; + } + } + + free(line); + return true; +} + +static bool ReadDmaBufFdRefs(pid_t pid, std::vector* dmabufs) { + std::string fdpath = ::android::base::StringPrintf("/proc/%d/fd", pid); + for (auto& de : std::filesystem::directory_iterator(fdpath)) { + if (!std::filesystem::is_symlink(de.path())) { + continue; + } + + std::string target; + if (!::android::base::Readlink(de.path().string(), &target)) { + LOG(ERROR) << "Failed to find target for symlink: " << de.path().string(); + return false; + } + + if (!FileIsDmaBuf(target)) { + continue; + } + + int fd; + if (!::android::base::ParseInt(de.path().filename().string(), &fd)) { + LOG(ERROR) << "Dmabuf fd: " << de.path().string() << " is invalid"; + return false; + } + + // Set defaults in case the kernel doesn't give us the information + // we need in fdinfo + std::string name = ""; + std::string exporter = ""; + uint64_t count = 0; + if (!ReadDmaBufFdInfo(pid, fd, &name, &exporter, &count)) { + LOG(ERROR) << "Failed to read fdinfo for: " << de.path().string(); + return false; + } + + struct stat sb; + if (stat(de.path().c_str(), &sb) < 0) { + PLOG(ERROR) << "Failed to stat: " << de.path().string(); + return false; + } + + DmaBuffer& buf = + dmabufs->emplace_back(sb.st_ino, sb.st_blocks * 512, count, exporter, name); + buf.AddFdRef(pid); + } + + return true; +} + +static bool ReadDmaBufMapRefs(pid_t pid, std::vector* dmabufs) { + std::string mapspath = ::android::base::StringPrintf("/proc/%d/maps", pid); + auto fp = std::unique_ptr{fopen(mapspath.c_str(), "re"), fclose}; + if (fp == nullptr) { + LOG(ERROR) << "Failed to open maps for pid: " << pid; + return false; + } + + char* line = nullptr; + size_t len = 0; + + // Process the map if it is dmabuf. Add map reference to existing object in 'dmabufs' + // if it was already found. If it wasn't create a new one and append it to 'dmabufs' + auto account_dmabuf = [&](uint64_t start, uint64_t end, uint16_t /* flags */, + uint64_t /* pgoff */, const char* name) { + // no need to look into this mapping if it is not dmabuf + if (!FileIsDmaBuf(std::string(name))) { + return; + } + + // TODO (b/123532375) : Add inode number to the callback of ReadMapFileContent. + // + // Workaround: we know 'name' points to the name at the end of 'line'. + // We use that to backtrack and pick up the inode number from the line as well. + // start end flag pgoff mj:mn inode name + // 00400000-00409000 r-xp 00000000 00:00 426998 /dmabuf (deleted) + const char* p = name; + p--; + // skip spaces + while (p != line && *p == ' ') { + p--; + } + // walk backwards to the beginning of inode number + while (p != line && isdigit(*p)) { + p--; + } + uint64_t inode = strtoull(p, nullptr, 10); + auto buf = std::find_if(dmabufs->begin(), dmabufs->end(), + [&inode](const DmaBuffer& dbuf) { return dbuf.inode() == inode; }); + if (buf != dmabufs->end()) { + buf->AddMapRef(pid); + return; + } + + // We have a new buffer, but unknown count and name + DmaBuffer& dbuf = dmabufs->emplace_back(inode, end - start, 0, "", ""); + dbuf.AddMapRef(pid); + }; + + while (getline(&line, &len, fp.get()) > 0) { + if (!::android::procinfo::ReadMapFileContent(line, account_dmabuf)) { + LOG(ERROR) << "Failed t parse maps for pid: " << pid; + return false; + } + } + + free(line); + return true; +} + +// Public methods +bool ReadDmaBufInfo(std::vector* dmabufs, const std::string& path) { + auto fp = std::unique_ptr{fopen(path.c_str(), "re"), fclose}; + if (fp == nullptr) { + LOG(ERROR) << "Failed to open dmabuf info from debugfs"; + return false; + } + + char* line = nullptr; + size_t len = 0; + dmabufs->clear(); + while (getline(&line, &len, fp.get()) > 0) { + // The new dmabuf bufinfo format adds inode number and a name at the end + // We are looking for lines as follows: + // size flags mode count exp_name ino name + // 01048576 00000002 00000007 00000001 ion 00018758 CAMERA + // 01048576 00000002 00000007 00000001 ion 00018758 + uint64_t size, count; + char* exporter_name = nullptr; + ino_t inode; + char* name = nullptr; + int matched = sscanf(line, "%" SCNu64 "%*x %*x %" SCNu64 " %ms %lu %ms", &size, &count, + &exporter_name, &inode, &name); + if (matched < 4) { + continue; + } + dmabufs->emplace_back(inode, size, count, exporter_name, matched > 4 ? name : ""); + free(exporter_name); + free(name); + } + + free(line); + + return true; +} + +bool ReadDmaBufInfo(pid_t pid, std::vector* dmabufs) { + dmabufs->clear(); + if (!ReadDmaBufFdRefs(pid, dmabufs)) { + LOG(ERROR) << "Failed to read dmabuf fd references"; + return false; + } + + if (!ReadDmaBufMapRefs(pid, dmabufs)) { + LOG(ERROR) << "Failed to read dmabuf map references"; + return false; + } + return true; +} + +} // namespace dmabufinfo +} // namespace android diff --git a/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp b/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp new file mode 100644 index 000000000..aa5f16c22 --- /dev/null +++ b/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp @@ -0,0 +1,252 @@ +/* Copyright (C) 2019 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 +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +using namespace ::android::dmabufinfo; +using namespace ::android::base; + +#define MAX_HEAP_NAME 32 +#define ION_HEAP_ANY_MASK (0x7fffffff) + +struct ion_heap_data { + char name[MAX_HEAP_NAME]; + __u32 type; + __u32 heap_id; + __u32 reserved0; + __u32 reserved1; + __u32 reserved2; +}; + +#ifndef DMA_BUF_SET_NAME +#define DMA_BUF_SET_NAME _IOW(DMA_BUF_BASE, 5, const char*) +#endif + +#define EXPECT_ONE_BUF_EQ(_bufptr, _name, _fdrefs, _maprefs, _expname, _count, _size) \ + do { \ + EXPECT_EQ(_bufptr->name(), _name); \ + EXPECT_EQ(_bufptr->fdrefs().size(), _fdrefs); \ + EXPECT_EQ(_bufptr->maprefs().size(), _maprefs); \ + EXPECT_EQ(_bufptr->exporter(), _expname); \ + EXPECT_EQ(_bufptr->count(), _count); \ + EXPECT_EQ(_bufptr->size(), _size); \ + } while (0) + +#define EXPECT_PID_IN_FDREFS(_bufptr, _pid, _expect) \ + do { \ + const std::vector& _fdrefs = _bufptr->fdrefs(); \ + auto _ref = std::find_if(_fdrefs.begin(), _fdrefs.end(), \ + [&](const pid_t& p) { return p == _pid; }); \ + EXPECT_EQ((_ref == _fdrefs.end()), _expect); \ + } while (0) + +#define EXPECT_PID_IN_MAPREFS(_bufptr, _pid, _expect) \ + do { \ + const std::vector& _maprefs = _bufptr->maprefs(); \ + auto _ref = std::find_if(_maprefs.begin(), _maprefs.end(), \ + [&](const pid_t& p) { return p == _pid; }); \ + EXPECT_EQ((_ref == _maprefs.end()), _expect); \ + } while (0) + +TEST(DmaBufInfoParser, TestReadDmaBufInfo) { + std::string bufinfo = R"bufinfo(00045056 00000002 00000007 00000002 ion 00022069 + Attached Devices: +Total 0 devices attached +01048576 00000002 00000007 00000001 ion 00019834 CAMERA + Attached Devices: + soc:qcom,cam_smmu:msm_cam_smmu_icp +Total 1 devices attached)bufinfo"; + + TemporaryFile tf; + ASSERT_TRUE(tf.fd != -1); + ASSERT_TRUE(::android::base::WriteStringToFd(bufinfo, tf.fd)); + std::string path = std::string(tf.path); + + std::vector dmabufs; + EXPECT_TRUE(ReadDmaBufInfo(&dmabufs, path)); + + EXPECT_EQ(dmabufs.size(), 2UL); + + EXPECT_EQ(dmabufs[0].size(), 45056UL); + EXPECT_EQ(dmabufs[0].inode(), 22069UL); + EXPECT_EQ(dmabufs[0].count(), 2UL); + EXPECT_EQ(dmabufs[0].exporter(), "ion"); + EXPECT_TRUE(dmabufs[0].name().empty()); + EXPECT_EQ(dmabufs[0].total_refs(), 0ULL); + EXPECT_TRUE(dmabufs[0].fdrefs().empty()); + EXPECT_TRUE(dmabufs[0].maprefs().empty()); + + EXPECT_EQ(dmabufs[1].size(), 1048576UL); + EXPECT_EQ(dmabufs[1].inode(), 19834UL); + EXPECT_EQ(dmabufs[1].count(), 1UL); + EXPECT_EQ(dmabufs[1].exporter(), "ion"); + EXPECT_FALSE(dmabufs[1].name().empty()); + EXPECT_EQ(dmabufs[1].name(), "CAMERA"); + EXPECT_EQ(dmabufs[1].total_refs(), 0ULL); + EXPECT_TRUE(dmabufs[1].fdrefs().empty()); + EXPECT_TRUE(dmabufs[1].maprefs().empty()); +} + +class DmaBufTester : public ::testing::Test { + public: + DmaBufTester() : ion_fd(ion_open()), ion_heap_mask(get_ion_heap_mask()) {} + + ~DmaBufTester() { + if (is_valid()) { + ion_close(ion_fd); + } + } + + bool is_valid() { return (ion_fd >= 0 && ion_heap_mask > 0); } + + unique_fd allocate(uint64_t size, const std::string& name) { + int fd; + int err = ion_alloc_fd(ion_fd, size, 0, ion_heap_mask, 0, &fd); + if (err < 0) { + return unique_fd{err}; + } + + if (!name.empty()) { + err = ioctl(fd, DMA_BUF_SET_NAME, name.c_str()); + if (err < 0) return unique_fd{-errno}; + } + + return unique_fd{fd}; + } + + private: + int get_ion_heap_mask() { + if (ion_fd < 0) { + return 0; + } + + if (ion_is_legacy(ion_fd)) { + // Since ION is still in staging, we've seen that the heap mask ids are also + // changed across kernels for some reason. So, here we basically ask for a buffer + // from _any_ heap. + return ION_HEAP_ANY_MASK; + } + + int cnt; + int err = ion_query_heap_cnt(ion_fd, &cnt); + if (err < 0) { + return err; + } + + std::vector heaps; + heaps.resize(cnt); + err = ion_query_get_heaps(ion_fd, cnt, &heaps[0]); + if (err < 0) { + return err; + } + + unsigned int ret = 0; + for (auto& it : heaps) { + if (!strcmp(it.name, "ion_system_heap")) { + ret |= (1 << it.heap_id); + } + } + + return ret; + } + + unique_fd ion_fd; + const int ion_heap_mask; +}; + +TEST_F(DmaBufTester, TestFdRef) { + // Test if a dma buffer is found while the corresponding file descriptor + // is open + ASSERT_TRUE(is_valid()); + pid_t pid = getpid(); + std::vector dmabufs; + { + // Allocate one buffer and make sure the library can see it + unique_fd buf = allocate(4096, "dmabuftester-4k"); + ASSERT_GT(buf, 0) << "Allocated buffer is invalid"; + ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs)); + + EXPECT_EQ(dmabufs.size(), 1UL); + EXPECT_ONE_BUF_EQ(dmabufs.begin(), "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL); + + // Make sure the buffer has the right pid too. + EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, false); + } + + // Now make sure the buffer has disappeared + ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs)); + EXPECT_TRUE(dmabufs.empty()); +} + +TEST_F(DmaBufTester, TestMapRef) { + // Test to make sure we can find a buffer if the fd is closed but the buffer + // is mapped + ASSERT_TRUE(is_valid()); + pid_t pid = getpid(); + std::vector dmabufs; + { + // Allocate one buffer and make sure the library can see it + unique_fd buf = allocate(4096, "dmabuftester-4k"); + ASSERT_GT(buf, 0) << "Allocated buffer is invalid"; + auto ptr = mmap(0, 4096, PROT_READ, MAP_SHARED, buf, 0); + ASSERT_NE(ptr, MAP_FAILED); + ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs)); + + EXPECT_EQ(dmabufs.size(), 1UL); + EXPECT_ONE_BUF_EQ(dmabufs.begin(), "dmabuftester-4k", 1UL, 1UL, "ion", 2UL, 4096ULL); + + // Make sure the buffer has the right pid too. + EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, false); + EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, false); + + // close the file descriptor and re-read the stats + buf.reset(-1); + ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs)); + + EXPECT_EQ(dmabufs.size(), 1UL); + EXPECT_ONE_BUF_EQ(dmabufs.begin(), "", 0UL, 1UL, "", 0UL, 4096ULL); + + EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, true); + EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, false); + + // unmap the bufer and lose all references + munmap(ptr, 4096); + } + + // Now make sure the buffer has disappeared + ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs)); + EXPECT_TRUE(dmabufs.empty()); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::android::base::InitLogging(argv, android::base::StderrLogger); + return RUN_ALL_TESTS(); +} diff --git a/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h b/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h new file mode 100644 index 000000000..29ce4d0bd --- /dev/null +++ b/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 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. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace android { +namespace dmabufinfo { + +struct DmaBuffer { + public: + DmaBuffer(ino_t inode, uint64_t size, uint64_t count, const std::string& exporter, + const std::string& name) + : inode_(inode), size_(size), count_(count), exporter_(exporter), name_(name) {} + ~DmaBuffer() = default; + + // Adds one file descriptor reference for the given pid + void AddFdRef(pid_t pid) { fdrefs_.emplace_back(pid); } + + // Adds one map reference for the given pid + void AddMapRef(pid_t pid) { maprefs_.emplace_back(pid); } + + // Getters for each property + uint64_t size() { return size_; } + const std::vector& fdrefs() const { return fdrefs_; } + const std::vector& maprefs() const { return maprefs_; } + ino_t inode() const { return inode_; } + uint64_t total_refs() const { return fdrefs_.size() + maprefs_.size(); } + uint64_t count() const { return count_; }; + const std::string& name() const { return name_; } + const std::string& exporter() const { return exporter_; } + + private: + ino_t inode_; + uint64_t size_; + uint64_t count_; + std::string exporter_; + std::string name_; + std::vector fdrefs_; + std::vector maprefs_; +}; + +// Read and return current dma buf objects from +// DEBUGFS/dma_buf/bufinfo. The references to each dma buffer are not +// populated here and will return an empty vector. +// Returns false if something went wrong with the function, true otherwise. +bool ReadDmaBufInfo(std::vector* dmabufs, + const std::string& path = "/sys/kernel/debug/dma_buf/bufinfo"); + +// Read and return dmabuf objects for a given process without the help +// of DEBUGFS +bool ReadDmaBufInfo(pid_t pid, std::vector* dmabufs); + +} // namespace dmabufinfo +} // namespace android