// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/developer/memory/metrics/digest.h"

#include <lib/trace/event.h>

#include <regex>

namespace memory {

bool BlobfsIsActiveVmo(const Vmo& vmo) {
  if (std::regex_match(vmo.name, std::regex("blob-[0-9a-f]+"))) {
    // Data VMOs are only active when they have clients
    return vmo.num_children > 0;
  }
  // Any other VMO is considered active
  return true;
}

// VMOs are bucketed into the first matching entry.
const std::vector<const BucketMatch> Digester::kDefaultBucketMatches = {
    {"ZBI Buffer", ".*", "uncompressed-bootfs"},
    // Memory used with the GPU or display hardware.
    {"Graphics", ".*", "magma_create_buffer|Mali .*|Magma.*|ImagePipe2Surface.*"},
    // Unused protected pool memory.
    {"ProtectedPool", "driver_host:.*", "SysmemAmlogicProtectedPool"},
    // Unused contiguous pool memory.
    {"ContiguousPool", "driver_host:.*", "SysmemContiguousPool"},
    {"Fshost", "fshost.cm", ".*"},
    {"Minfs", ".*minfs", ".*"},
    {"Blobfs", ".*blobfs", ".*", [](const Vmo& vmo) { return BlobfsIsActiveVmo(vmo); }},
    {"BlobfsInactive", ".*blobfs", ".*", [](const Vmo& vmo) { return !BlobfsIsActiveVmo(vmo); }},
    {"FlutterApps", "io\\.flutter\\..*", "dart.*"},
    {"Flutter", "io\\.flutter\\..*", ".*"},
    {"Web", "web_engine_exe:.*", ".*"},
    {"Kronk", "kronk.cmx|kronk_for_testing.cmx", ".*"},
    {"Scenic", "scenic.cmx", ".*"},
    {"Amlogic", "driver_host:pdev:05:00:f", ".*"},
    {"Netstack", "netstack.cmx", ".*"},
    {"Pkgfs", "pkgfs", ".*"},
    {"Cast", "cast_agent.cmx", ".*"},
    {"Archivist", "archivist.cm", ".*"},
    {"Cobalt", "cobalt.cmx", ".*"},
    {"Audio", "audio_core.cmx", ".*"},
    {"Context", "context_provider.cmx", ".*"},
};

BucketMatch::BucketMatch(const std::string& name, const std::string& process,
                         const std::string& vmo)
    : name_(name), process_(process), vmo_(vmo) {}

BucketMatch::BucketMatch(const std::string& name, const std::string& process,
                         const std::string& vmo, VmoMatcher vmo_matcher)
    : name_(name), process_(process), vmo_(vmo), vmo_matcher_(vmo_matcher) {}

bool BucketMatch::ProcessMatch(const std::string& process) {
  const auto& pi = process_match_.find(process);
  if (pi != process_match_.end()) {
    return pi->second;
  }
  bool match = std::regex_match(process, process_);
  process_match_.emplace(process, match);
  return match;
}

bool BucketMatch::VmoMatch(const Vmo& vmo) {
  const auto& vi = vmo_match_.find(vmo.name);
  if (vi != vmo_match_.end()) {
    return vi->second;
  }
  bool match = std::regex_match(vmo.name, vmo_);
  if (vmo_matcher_) {
    match &= (*vmo_matcher_)(vmo);
  }
  vmo_match_.emplace(vmo.name, match);
  return match;
}

Digest::Digest(const Capture& capture, Digester* digester) { digester->Digest(capture, this); }

Digester::Digester(const std::vector<const BucketMatch>& bucket_matches) {
  for (const auto& bucket_match : bucket_matches) {
    bucket_matches_.emplace_back(bucket_match);
  }
}

void Digester::Digest(const Capture& capture, class Digest* digest) {
  TRACE_DURATION("memory_metrics", "Digester::Digest");
  digest->time_ = capture.time();
  digest->undigested_vmos_.reserve(capture.koid_to_vmo().size());
  for (const auto& [koid, vmo] : capture.koid_to_vmo()) {
    digest->undigested_vmos_.emplace(koid);
  }

  digest->buckets_.reserve(bucket_matches_.size());
  for (auto& bucket_match : bucket_matches_) {
    auto& bucket = digest->buckets_.emplace_back(bucket_match.name(), 0);
    for (const auto& [koid, process] : capture.koid_to_process()) {
      if (!bucket_match.ProcessMatch(process.name)) {
        continue;
      }
      for (const auto& v : process.vmos) {
        if (digest->undigested_vmos_.count(v) == 0) {
          continue;
        }
        const auto& vmo = capture.vmo_for_koid(v);
        if (!bucket_match.VmoMatch(vmo)) {
          continue;
        }
        bucket.size_ += vmo.committed_bytes;
        digest->undigested_vmos_.erase(v);
      }
    }
  }

  std::sort(digest->buckets_.begin(), digest->buckets_.end(),
            [](const Bucket& a, const Bucket& b) { return a.size() > b.size(); });
  uint64_t undigested_size = 0;
  for (auto v : digest->undigested_vmos_) {
    undigested_size += capture.vmo_for_koid(v).committed_bytes;
  }
  if (undigested_size > 0) {
    digest->buckets_.emplace_back("Undigested", undigested_size);
  }

  const auto& kmem = capture.kmem();
  if (kmem.total_bytes > 0) {
    uint64_t vmo_size = 0;
    for (const auto& bucket : digest->buckets_) {
      vmo_size += bucket.size_;
    }
    if (vmo_size < kmem.vmo_bytes) {
      digest->buckets_.emplace_back("Orphaned", kmem.vmo_bytes - vmo_size);
    }

    if (kmem.vmo_pager_total_bytes > 0) {
      digest->buckets_.emplace_back("PagerTotal", kmem.vmo_pager_total_bytes);
      digest->buckets_.emplace_back("PagerNewest", kmem.vmo_pager_newest_bytes);
      digest->buckets_.emplace_back("PagerOldest", kmem.vmo_pager_oldest_bytes);
    }

    if (kmem.vmo_discardable_locked_bytes > 0 || kmem.vmo_discardable_unlocked_bytes > 0) {
      digest->buckets_.emplace_back("DiscardableLocked", kmem.vmo_discardable_locked_bytes);
      digest->buckets_.emplace_back("DiscardableUnlocked", kmem.vmo_discardable_unlocked_bytes);
    }

    digest->buckets_.emplace_back("Kernel", kmem.wired_bytes + kmem.total_heap_bytes +
                                                kmem.mmu_overhead_bytes + kmem.ipc_bytes +
                                                kmem.other_bytes);
    digest->buckets_.emplace_back("Free", kmem.free_bytes);
  }
}

}  // namespace memory
