| /* |
| * Copyright (C) 2018 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 <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <cctype> |
| #include <cstdio> |
| #include <fstream> |
| #include <iterator> |
| #if defined(__ANDROID__) && !defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__) |
| #include "bpf/BpfMap.h" |
| #endif |
| #include <sstream> |
| #include <string> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/parseint.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| #include <android-base/unique_fd.h> |
| #include <dmabufinfo/dmabuf_sysfs_stats.h> |
| |
| #include "meminfo_private.h" |
| |
| namespace android { |
| namespace meminfo { |
| |
| bool SysMemInfo::ReadMemInfo(const char* path) { |
| return ReadMemInfo(path, SysMemInfo::kDefaultSysMemInfoTags.size(), |
| &*SysMemInfo::kDefaultSysMemInfoTags.begin(), |
| [&](std::string_view tag, uint64_t val) { |
| // Safe to store the string_view in the map |
| // because the tags from |
| // kDefaultSysMemInfoTags are all |
| // statically-allocated. |
| mem_in_kb_[tag] = val; |
| }); |
| } |
| |
| bool SysMemInfo::ReadMemInfo(std::vector<uint64_t>* out, const char* path) { |
| out->clear(); |
| out->resize(SysMemInfo::kDefaultSysMemInfoTags.size()); |
| return ReadMemInfo(SysMemInfo::kDefaultSysMemInfoTags.size(), |
| &*SysMemInfo::kDefaultSysMemInfoTags.begin(), out->data(), path); |
| } |
| |
| bool SysMemInfo::ReadMemInfo(size_t ntags, const std::string_view* tags, uint64_t* out, |
| const char* path) { |
| return ReadMemInfo(path, ntags, tags, [&]([[maybe_unused]] std::string_view tag, uint64_t val) { |
| auto it = std::find(tags, tags + ntags, tag); |
| if (it == tags + ntags) { |
| LOG(ERROR) << "Tried to store invalid tag: " << tag; |
| return; |
| } |
| auto index = std::distance(tags, it); |
| // store the values in the same order as the tags |
| out[index] = val; |
| }); |
| } |
| |
| uint64_t SysMemInfo::ReadVmallocInfo() { |
| return ::android::meminfo::ReadVmallocInfo(); |
| } |
| |
| bool SysMemInfo::ReadMemInfo(const char* path, size_t ntags, const std::string_view* tags, |
| std::function<void(std::string_view, uint64_t)> store_val) { |
| char buffer[4096]; |
| int fd = open(path, O_RDONLY | O_CLOEXEC); |
| if (fd < 0) { |
| PLOG(ERROR) << "Failed to open file :" << path; |
| return false; |
| } |
| |
| const int len = read(fd, buffer, sizeof(buffer) - 1); |
| close(fd); |
| if (len < 0) { |
| return false; |
| } |
| |
| buffer[len] = '\0'; |
| char* p = buffer; |
| uint32_t found = 0; |
| uint32_t lineno = 0; |
| bool zram_tag_found = false; |
| while (*p && found < ntags) { |
| for (size_t tagno = 0; tagno < ntags; ++tagno) { |
| const std::string_view& tag = tags[tagno]; |
| // Special case for "Zram:" tag that android_os_Debug and friends look |
| // up along with the rest of the numbers from /proc/meminfo |
| if (!zram_tag_found && tag == "Zram:") { |
| store_val(tag, mem_zram_kb()); |
| zram_tag_found = true; |
| found++; |
| continue; |
| } |
| |
| if (strncmp(p, tag.data(), tag.size()) == 0) { |
| p += tag.size(); |
| while (*p == ' ') p++; |
| char* endptr = nullptr; |
| uint64_t val = strtoull(p, &endptr, 10); |
| if (p == endptr) { |
| PLOG(ERROR) << "Failed to parse line:" << lineno + 1 << " in file: " << path; |
| return false; |
| } |
| store_val(tag, val); |
| p = endptr; |
| found++; |
| break; |
| } |
| } |
| |
| while (*p && *p != '\n') { |
| p++; |
| } |
| if (*p) p++; |
| lineno++; |
| } |
| |
| return true; |
| } |
| |
| uint64_t SysMemInfo::mem_zram_kb(const char* zram_dev_cstr) { |
| uint64_t mem_zram_total = 0; |
| if (zram_dev_cstr) { |
| if (!MemZramDevice(zram_dev_cstr, &mem_zram_total)) { |
| return 0; |
| } |
| return mem_zram_total / 1024; |
| } |
| |
| constexpr uint32_t kMaxZramDevices = 256; |
| for (uint32_t i = 0; i < kMaxZramDevices; i++) { |
| std::string zram_dev_abspath = ::android::base::StringPrintf("/sys/block/zram%u/", i); |
| if (access(zram_dev_abspath.c_str(), F_OK)) { |
| // We assume zram devices appear in range 0-255 and appear always in sequence |
| // under /sys/block. So, stop looking for them once we find one is missing. |
| break; |
| } |
| |
| uint64_t mem_zram_dev; |
| if (!MemZramDevice(zram_dev_abspath.c_str(), &mem_zram_dev)) { |
| return 0; |
| } |
| |
| mem_zram_total += mem_zram_dev; |
| } |
| |
| return mem_zram_total / 1024; |
| } |
| |
| bool SysMemInfo::MemZramDevice(const char* zram_dev, uint64_t* mem_zram_dev) { |
| std::string mmstat = ::android::base::StringPrintf("%s/%s", zram_dev, "mm_stat"); |
| auto mmstat_fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(mmstat.c_str(), "re"), fclose}; |
| if (mmstat_fp != nullptr) { |
| // only if we do have mmstat, use it. Otherwise, fall through to trying out the old |
| // 'mem_used_total' |
| if (fscanf(mmstat_fp.get(), "%*" SCNu64 " %*" SCNu64 " %" SCNu64, mem_zram_dev) != 1) { |
| PLOG(ERROR) << "Malformed mm_stat file in: " << zram_dev; |
| return false; |
| } |
| return true; |
| } |
| |
| std::string content; |
| if (::android::base::ReadFileToString( |
| ::android::base::StringPrintf("%s/mem_used_total", zram_dev), &content)) { |
| *mem_zram_dev = strtoull(content.c_str(), NULL, 10); |
| if (*mem_zram_dev == ULLONG_MAX) { |
| PLOG(ERROR) << "Malformed mem_used_total file for zram dev: " << zram_dev |
| << " content: " << content; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| LOG(ERROR) << "Can't find memory status under: " << zram_dev; |
| return false; |
| } |
| |
| // Public methods |
| uint64_t ReadVmallocInfo(const char* path) { |
| uint64_t vmalloc_total = 0; |
| auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path, "re"), fclose}; |
| if (fp == nullptr) { |
| return vmalloc_total; |
| } |
| |
| char* line = nullptr; |
| size_t line_alloc = 0; |
| while (getline(&line, &line_alloc, fp.get()) > 0) { |
| // We are looking for lines like |
| // |
| // 0x0000000000000000-0x0000000000000000 12288 drm_property_create_blob+0x44/0xec pages=2 vmalloc |
| // 0x0000000000000000-0x0000000000000000 8192 wlan_logging_sock_init_svc+0xf8/0x4f0 [wlan] pages=1 vmalloc |
| // |
| // Notice that if the caller is coming from a module, the kernel prints and extra |
| // "[module_name]" after the address and the symbol of the call site. This means we can't |
| // use the old sscanf() method of getting the # of pages. |
| char* p_start = strstr(line, "pages="); |
| if (p_start == nullptr) { |
| // we didn't find anything |
| continue; |
| } |
| |
| uint64_t nr_pages; |
| if (sscanf(p_start, "pages=%" SCNu64 "", &nr_pages) == 1) { |
| vmalloc_total += (nr_pages * getpagesize()); |
| } |
| } |
| |
| free(line); |
| |
| return vmalloc_total; |
| } |
| |
| static bool ReadSysfsFile(const std::string& path, uint64_t* value) { |
| std::string content; |
| if (!::android::base::ReadFileToString(path, &content)) { |
| LOG(ERROR) << "Can't open file: " << path; |
| return false; |
| } |
| |
| *value = strtoull(content.c_str(), NULL, 10); |
| if (*value == ULLONG_MAX) { |
| PLOG(ERROR) << "Invalid file format: " << path; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ReadIonHeapsSizeKb(uint64_t* size, const std::string& path) { |
| return ReadSysfsFile(path, size); |
| } |
| |
| bool ReadIonPoolsSizeKb(uint64_t* size, const std::string& path) { |
| return ReadSysfsFile(path, size); |
| } |
| |
| bool ReadDmabufHeapPoolsSizeKb(uint64_t* size, const std::string& dma_heap_pool_size_path) { |
| static bool support_dmabuf_heap_pool_size = [dma_heap_pool_size_path]() -> bool { |
| bool ret = (access(dma_heap_pool_size_path.c_str(), R_OK) == 0); |
| if (!ret) |
| LOG(ERROR) << "Unable to read DMA-BUF heap total pool size, read ION total pool " |
| "size instead."; |
| return ret; |
| }(); |
| |
| if (!support_dmabuf_heap_pool_size) return ReadIonPoolsSizeKb(size); |
| |
| return ReadSysfsFile(dma_heap_pool_size_path, size); |
| } |
| |
| bool ReadDmabufHeapTotalExportedKb(uint64_t* size, const std::string& dma_heap_root_path, |
| const std::string& dmabuf_sysfs_stats_path) { |
| static bool support_dmabuf_heaps = [dma_heap_root_path]() -> bool { |
| bool ret = (access(dma_heap_root_path.c_str(), R_OK) == 0); |
| if (!ret) LOG(ERROR) << "DMA-BUF heaps not supported, read ION heap total instead."; |
| return ret; |
| }(); |
| |
| if (!support_dmabuf_heaps) return ReadIonHeapsSizeKb(size); |
| |
| std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(dma_heap_root_path.c_str()), closedir); |
| |
| if (!dir) { |
| return false; |
| } |
| |
| std::unordered_set<std::string> heap_list; |
| struct dirent* dent; |
| while ((dent = readdir(dir.get()))) { |
| if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) continue; |
| |
| heap_list.insert(dent->d_name); |
| } |
| |
| if (heap_list.empty()) return false; |
| |
| android::dmabufinfo::DmabufSysfsStats stats; |
| if (!android::dmabufinfo::GetDmabufSysfsStats(&stats, dmabuf_sysfs_stats_path)) return false; |
| |
| auto exporter_info = stats.exporter_info(); |
| |
| *size = 0; |
| for (const auto& heap : heap_list) { |
| auto iter = exporter_info.find(heap); |
| if (iter != exporter_info.end()) *size += iter->second.size; |
| } |
| |
| *size = *size / 1024; |
| |
| return true; |
| } |
| |
| bool ReadPerProcessGpuMem([[maybe_unused]] std::unordered_map<uint32_t, uint64_t>* out) { |
| #if defined(__ANDROID__) && !defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__) |
| static constexpr const char kBpfGpuMemTotalMap[] = "/sys/fs/bpf/map_gpu_mem_gpu_mem_total_map"; |
| |
| // Use the read-only wrapper BpfMapRO to properly retrieve the read-only map. |
| auto map = bpf::BpfMapRO<uint64_t, uint64_t>(kBpfGpuMemTotalMap); |
| if (!map.isValid()) { |
| LOG(ERROR) << "Can't open file: " << kBpfGpuMemTotalMap; |
| return false; |
| } |
| |
| if (!out) { |
| LOG(ERROR) << "ReadPerProcessGpuMem: out param is null"; |
| return false; |
| } |
| out->clear(); |
| |
| auto map_key = map.getFirstKey(); |
| if (!map_key.ok()) { |
| return true; |
| } |
| |
| do { |
| uint64_t key = map_key.value(); |
| uint32_t pid = key; // BPF Key [32-bits GPU ID | 32-bits PID] |
| |
| auto gpu_mem = map.readValue(key); |
| if (!gpu_mem.ok()) { |
| LOG(ERROR) << "Invalid file format: " << kBpfGpuMemTotalMap; |
| return false; |
| } |
| |
| const auto& iter = out->find(pid); |
| if (iter == out->end()) { |
| out->insert({pid, gpu_mem.value() / 1024}); |
| } else { |
| iter->second += gpu_mem.value() / 1024; |
| } |
| |
| map_key = map.getNextKey(key); |
| } while (map_key.ok()); |
| |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool ReadProcessGpuUsageKb([[maybe_unused]] uint32_t pid, [[maybe_unused]] uint32_t gpu_id, |
| uint64_t* size) { |
| #if defined(__ANDROID__) && !defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__) |
| static constexpr const char kBpfGpuMemTotalMap[] = "/sys/fs/bpf/map_gpu_mem_gpu_mem_total_map"; |
| |
| uint64_t gpu_mem; |
| |
| // BPF Key [32-bits GPU ID | 32-bits PID] |
| uint64_t kBpfKeyGpuUsage = ((uint64_t)gpu_id << 32) | pid; |
| |
| // Use the read-only wrapper BpfMapRO to properly retrieve the read-only map. |
| auto map = bpf::BpfMapRO<uint64_t, uint64_t>(kBpfGpuMemTotalMap); |
| if (!map.isValid()) { |
| LOG(ERROR) << "Can't open file: " << kBpfGpuMemTotalMap; |
| return false; |
| } |
| |
| auto res = map.readValue(kBpfKeyGpuUsage); |
| |
| if (res.ok()) { |
| gpu_mem = res.value(); |
| } else if (res.error().code() == ENOENT) { |
| gpu_mem = 0; |
| } else { |
| LOG(ERROR) << "Invalid file format: " << kBpfGpuMemTotalMap; |
| return false; |
| } |
| |
| if (size) { |
| *size = gpu_mem / 1024; |
| } |
| return true; |
| #else |
| if (size) { |
| *size = 0; |
| } |
| return false; |
| #endif |
| } |
| |
| bool ReadGpuTotalUsageKb(uint64_t* size) { |
| // gpu_mem_total tracepoint defines PID 0 as global total |
| // GPU ID 0 suffices for current android devices. |
| // This will need to check all GPU IDs in future if more than |
| // one is GPU device is present on the device. |
| return ReadProcessGpuUsageKb(0, 0, size); |
| } |
| |
| } // namespace meminfo |
| } // namespace android |