chromium/services/resource_coordinator/memory_instrumentation/aggregate_metrics_processor.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/resource_coordinator/memory_instrumentation/aggregate_metrics_processor.h"

#include <set>
#include <string>
#include <vector>

#include "base/android/library_loader/anchor_functions.h"
#include "base/android/library_loader/anchor_functions_buildflags.h"
#include "base/bits.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/files/file.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/memory/page_size.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/global_memory_dump.h"

#if BUILDFLAG(SUPPORTS_CODE_ORDERING)

namespace {

void LogNativeCodeResidentPages(const std::set<size_t>& accessed_pages_set) {
  // |SUPPORTS_CODE_ORDERING| can only be enabled on Android.
  const auto kResidentPagesPath = base::FilePath(
      "/data/local/tmp/chrome/native-library-resident-pages.txt");

  auto file = base::File(kResidentPagesPath, base::File::FLAG_CREATE_ALWAYS |
                                                 base::File::FLAG_WRITE);

  if (!file.IsValid()) {
    DLOG(ERROR) << "Could not open " << kResidentPagesPath;
    return;
  }

  for (size_t page : accessed_pages_set) {
    std::string page_str = base::StringPrintf("%" PRIuS "\n", page);

    if (UNSAFE_TODO(file.WriteAtCurrentPos(
            page_str.c_str(), static_cast<int>(page_str.size()))) < 0) {
      DLOG(WARNING) << "Error while dumping Resident pages";
      return;
    }
  }
}

}  // namespace

namespace memory_instrumentation {

mojom::AggregatedMetricsPtr ComputeGlobalNativeCodeResidentMemoryKb(
    const std::map<base::ProcessId, mojom::RawOSMemDump*>& pid_to_pmd) {
  std::vector<uint8_t> common_map;
  auto metrics = mojom::AggregatedMetricsPtr(mojom::AggregatedMetrics::New());

  for (const auto& pmd : pid_to_pmd) {
    if (!pmd.second || pmd.second->native_library_pages_bitmap.empty()) {
      DLOG(WARNING) << "No process pagemap entry for " << pmd.first;
      return metrics;
    }

    if (common_map.size() < pmd.second->native_library_pages_bitmap.size()) {
      common_map.resize(pmd.second->native_library_pages_bitmap.size());
    }
    for (size_t i = 0; i < pmd.second->native_library_pages_bitmap.size();
         ++i) {
      common_map[i] |= pmd.second->native_library_pages_bitmap[i];
    }
  }

  // |accessed_pages_set| will be ~40kB on 32 bit mode and ~80kB on 64 bit mode.
  std::set<size_t> accessed_pages_set;
  for (size_t i = 0; i < common_map.size(); i++) {
    for (int j = 0; j < 8; j++) {
      if (common_map[i] & (1 << j))
        accessed_pages_set.insert(i * 8 + j);
    }
  }

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          "log-native-library-residency")) {
    LogNativeCodeResidentPages(accessed_pages_set);
  }

  const size_t kPageSize = base::GetPageSize();
  const size_t kb_per_page = kPageSize / 1024;

  int32_t native_library_resident_not_ordered_kb =
      GlobalMemoryDump::AggregatedMetrics::kInvalid;
  int32_t native_library_not_resident_ordered_kb =
      GlobalMemoryDump::AggregatedMetrics::kInvalid;
  int32_t native_library_resident_kb =
      static_cast<int32_t>(accessed_pages_set.size() * kb_per_page);

  // Reporting the resident code data only requires |AreAnchorsSane()|, not
  // |IsOrderingSane()| which is a stronger guarantee.
  if (base::android::IsOrderingSane()) {
    // Start and end markers are not necessarily aligned with page boundaries.
    size_t start_of_text_page_index = base::android::kStartOfText / kPageSize;
    // Range is [start_of_ordered_section_page_offset,
    //           end_of_ordered_section_page_offset)
    size_t start_of_ordered_section_page_offset =
        base::android::kStartOfOrderedText / kPageSize -
        start_of_text_page_index;
    size_t end_of_ordered_section_page_offset =
        base::bits::AlignUp(base::android::kEndOfOrderedText, kPageSize) /
            kPageSize -
        start_of_text_page_index;

    size_t resident_pages_in_ordered_section = 0;
    size_t resident_pages_outside_ordered_section = 0;
    for (size_t page : accessed_pages_set) {
      if (page >= start_of_ordered_section_page_offset &&
          page < end_of_ordered_section_page_offset) {
        resident_pages_in_ordered_section++;
      } else {
        resident_pages_outside_ordered_section++;
      }
    }
    size_t ordered_section_pages = end_of_ordered_section_page_offset -
                                   start_of_ordered_section_page_offset;
    size_t not_resident_ordered_pages =
        ordered_section_pages - resident_pages_in_ordered_section;

    native_library_resident_not_ordered_kb = static_cast<int32_t>(
        resident_pages_outside_ordered_section * kb_per_page);
    native_library_not_resident_ordered_kb =
        static_cast<int32_t>(not_resident_ordered_pages * kb_per_page);
  }

  // TODO(crbug.com/41455053) replace adding |NativeCodeResidentMemory| to trace
  // this way by adding it through |tracing_observer| in Finalize().
  TRACE_EVENT_INSTANT1(base::trace_event::MemoryDumpManager::kTraceCategory,
                       "ReportGlobalNativeCodeResidentMemoryKb",
                       TRACE_EVENT_SCOPE_GLOBAL, "NativeCodeResidentMemory",
                       native_library_resident_kb);

  metrics->native_library_resident_kb = native_library_resident_kb;
  metrics->native_library_resident_not_ordered_kb =
      native_library_resident_not_ordered_kb;
  metrics->native_library_not_resident_ordered_kb =
      native_library_not_resident_ordered_kb;

  // 0 is not a valid value, as at the very least the current page (the one
  // containing code for the collection function) should be resident.
  if (!native_library_resident_kb) {
    metrics->native_library_resident_kb =
        GlobalMemoryDump::AggregatedMetrics::kInvalid;
  }

  return metrics;
}

}  // namespace memory_instrumentation

#else

namespace memory_instrumentation {

mojom::AggregatedMetricsPtr ComputeGlobalNativeCodeResidentMemoryKb(
    const std::map<base::ProcessId, mojom::RawOSMemDump*>& pid_to_pmd) {}

}  // namespace memory_instrumentation

#endif  // #if BUILDFLAG(SUPPORTS_CODE_ORDERING)