blob: 10cd1fe63dbb6d493ec12ba62dc9816633f79efb [file] [log] [blame]
/*
* 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 "src/profiling/memory/page_idle_checker.h"
#include "perfetto/ext/base/utils.h"
#include "src/profiling/memory/utils.h"
#include <inttypes.h>
#include <vector>
namespace perfetto {
namespace profiling {
namespace {
constexpr uint64_t kIsInRam = 1ULL << 63;
constexpr uint64_t kRamPhysicalPageMask = ~(~0ULL << 55);
} // namespace
int64_t PageIdleChecker::OnIdlePage(uint64_t addr, size_t size) {
uint64_t page_nr = addr / base::kPageSize;
uint64_t end_page_nr = (addr + size) / base::kPageSize;
// The trailing division will have rounded down, unless the end is at a page
// boundary. Add one page if we rounded down.
if ((addr + size) % base::kPageSize != 0)
end_page_nr++;
size_t pages = end_page_nr - page_nr;
std::vector<uint64_t> virt_page_infos(pages);
off64_t virt_off = static_cast<off64_t>(page_nr * sizeof(virt_page_infos[0]));
size_t virt_rd_size = pages * sizeof(virt_page_infos[0]);
ssize_t rd = ReadAtOffsetClobberSeekPos(*pagemap_fd_, &(virt_page_infos[0]),
virt_rd_size, virt_off);
if (rd != static_cast<ssize_t>(virt_rd_size)) {
PERFETTO_ELOG("Invalid read from pagemap: %zd", rd);
return -1;
}
int64_t idle_mem = 0;
for (size_t i = 0; i < pages; ++i) {
if (!virt_page_infos[i]) {
PERFETTO_DLOG("Empty pageinfo.");
continue;
}
if (!(virt_page_infos[i] & kIsInRam)) {
PERFETTO_DLOG("Page is not in RAM.");
continue;
}
uint64_t phys_page_nr = virt_page_infos[i] & kRamPhysicalPageMask;
if (!phys_page_nr) {
PERFETTO_ELOG("Failed to get physical page number.");
continue;
}
int idle = IsPageIdle(phys_page_nr);
if (idle == -1)
continue;
if (idle) {
if (i == 0)
idle_mem += GetFirstPageShare(addr, size);
else if (i == pages - 1)
idle_mem += GetLastPageShare(addr, size);
else
idle_mem += base::kPageSize;
} else {
touched_phys_page_nrs_.emplace(phys_page_nr);
}
}
return idle_mem;
}
void PageIdleChecker::MarkPagesIdle() {
for (uint64_t phys_page_nr : touched_phys_page_nrs_)
MarkPageIdle(phys_page_nr);
touched_phys_page_nrs_.clear();
}
void PageIdleChecker::MarkPageIdle(uint64_t phys_page_nr) {
// The file implements a bitmap where each bit corresponds to a memory page.
// The bitmap is represented by an array of 8-byte integers, and the page at
// PFN #i is mapped to bit #i%64 of array element #i/64, byte order i
// native. When a bit is set, the corresponding page is idle.
//
// The kernel ORs the value written with the existing bitmap, so we do not
// override previously written values.
// See https://www.kernel.org/doc/Documentation/vm/idle_page_tracking.txt
off64_t offset = 8 * (phys_page_nr / 64);
size_t bit_offset = phys_page_nr % 64;
uint64_t bit_pattern = 1 << bit_offset;
if (WriteAtOffsetClobberSeekPos(*bitmap_fd_, &bit_pattern,
sizeof(bit_pattern), offset) !=
static_cast<ssize_t>(sizeof(bit_pattern))) {
PERFETTO_PLOG("Failed to write bit pattern at %" PRIi64 ".", offset);
}
}
int PageIdleChecker::IsPageIdle(uint64_t phys_page_nr) {
off64_t offset = 8 * (phys_page_nr / 64);
size_t bit_offset = phys_page_nr % 64;
uint64_t bit_pattern;
if (ReadAtOffsetClobberSeekPos(*bitmap_fd_, &bit_pattern, sizeof(bit_pattern),
offset) !=
static_cast<ssize_t>(sizeof(bit_pattern))) {
PERFETTO_PLOG("Failed to read bit pattern at %" PRIi64 ".", offset);
return -1;
}
return static_cast<int>(bit_pattern & (1 << bit_offset));
}
uint64_t GetFirstPageShare(uint64_t addr, size_t size) {
// Our allocation is xxxx in this illustration:
// +----------------------------------------------+
// | xxxxxxxxxx|xxxxxx |
// | xxxxxxxxxx|xxxxxx |
// | xxxxxxxxxx|xxxxxx |
// +-------------+---------------+----------------+
// ^ ^ ^ ^
// + + + +
// page_aligned_addr addr end addr + size
uint64_t page_aligned_addr = (addr / base::kPageSize) * base::kPageSize;
uint64_t end = page_aligned_addr + base::kPageSize;
if (end > addr + size) {
// The whole allocation is on the first page.
return size;
}
return base::kPageSize - (addr - page_aligned_addr);
}
uint64_t GetLastPageShare(uint64_t addr, size_t size) {
uint64_t last_page_size = (addr + size) % base::kPageSize;
if (last_page_size == 0) {
// Address ends at a page boundary, the whole last page is idle.
return base::kPageSize;
} else {
// Address does not end at a page boundary, only a subset of the last
// page should be attributed to this allocation.
return last_page_size;
}
}
} // namespace profiling
} // namespace perfetto