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

#include "base/metrics/persistent_histogram_storage.h"

#include <cinttypes>
#include <string_view>

#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/logging.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/persistent_memory_allocator.h"
#include "base/process/memory.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "build/build_config.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>
// Must be after <windows.h>
#include <memoryapi.h>
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
#include <sys/mman.h>
#endif

namespace {

constexpr size_t kAllocSize = 1 << 20;  // 1 MiB

void* AllocateLocalMemory(size_t size) {
  void* address;

#if BUILDFLAG(IS_WIN)
  address =
      ::VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
  if (address)
    return address;
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
  // MAP_ANON is deprecated on Linux but MAP_ANONYMOUS is not universal on Mac.
  // MAP_SHARED is not available on Linux <2.4 but required on Mac.
  address = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
                   -1, 0);
  if (address != MAP_FAILED)
    return address;
#else
#error This architecture is not (yet) supported.
#endif

  // As a last resort, just allocate the memory from the heap. This will
  // achieve the same basic result but the acquired memory has to be
  // explicitly zeroed and thus realized immediately (i.e. all pages are
  // added to the process now instead of only when first accessed).
  if (!base::UncheckedMalloc(size, &address))
    return nullptr;
  DCHECK(address);
  memset(address, 0, size);
  return address;
}

}  // namespace

namespace base {

PersistentHistogramStorage::PersistentHistogramStorage(
    std::string_view allocator_name,
    StorageDirManagement storage_dir_management)
    : storage_dir_management_(storage_dir_management) {
  DCHECK(!allocator_name.empty());
  DCHECK(IsStringASCII(allocator_name));

  // This code may be executed before crash handling and/or OOM handling has
  // been initialized for the process. Silently ignore a failed allocation
  // (no metric persistence) rather that generating a crash that won't be
  // caught/reported.
  void* memory = AllocateLocalMemory(kAllocSize);
  if (!memory)
    return;

  GlobalHistogramAllocator::CreateWithPersistentMemory(memory, kAllocSize, 0,
                                                       0,  // No identifier.
                                                       allocator_name);
  GlobalHistogramAllocator::Get()->CreateTrackingHistograms(allocator_name);
}

PersistentHistogramStorage::~PersistentHistogramStorage() {
  PersistentHistogramAllocator* allocator = GlobalHistogramAllocator::Get();
  if (!allocator)
    return;

  allocator->UpdateTrackingHistograms();

  if (disabled_)
    return;

  // Stop if the storage base directory has not been properly set.
  if (storage_base_dir_.empty()) {
    LOG(ERROR)
        << "Could not write \"" << allocator->Name()
        << "\" persistent histograms to file as the storage base directory "
           "is not properly set.";
    return;
  }

  FilePath storage_dir = storage_base_dir_.AppendASCII(allocator->Name());

  switch (storage_dir_management_) {
    case StorageDirManagement::kCreate:
      if (!CreateDirectory(storage_dir)) {
        LOG(ERROR)
            << "Could not write \"" << allocator->Name()
            << "\" persistent histograms to file as the storage directory "
               "cannot be created.";
        return;
      }
      break;
    case StorageDirManagement::kUseExisting:
      if (!DirectoryExists(storage_dir)) {
        // When the consumer of this class decides to use an existing storage
        // directory, it should ensure the directory's existence if it's
        // essential.
        LOG(ERROR)
            << "Could not write \"" << allocator->Name()
            << "\" persistent histograms to file as the storage directory "
               "does not exist.";
        return;
      }
      break;
  }

  // Save data using the process ID and microseconds since Windows Epoch for the
  // filename with the correct extension. Using this format prevents collisions
  // between multiple processes using the same provider name.
  const FilePath file_path =
      storage_dir
          .AppendASCII(StringPrintf(
              "%" CrPRIdPid "_%" PRId64, GetCurrentProcId(),
              Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds()))
          .AddExtension(PersistentMemoryAllocator::kFileExtension);

  std::string_view contents(static_cast<const char*>(allocator->data()),
                            allocator->used());
  if (!ImportantFileWriter::WriteFileAtomically(file_path, contents)) {
    LOG(ERROR) << "Persistent histograms fail to write to file: "
               << file_path.value();
  }
}

}  // namespace base
