// Copyright 2020 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/trace_event/interned_args_helper.h"

#include "third_party/perfetto/include/perfetto/tracing/track_event_interned_data_index.h"
#include "third_party/perfetto/protos/perfetto/trace/interned_data/interned_data.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/profiling/profile_common.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/log_message.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/source_location.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/task_execution.pbzero.h"

namespace base {
namespace trace_event {

namespace {

const void* const kModuleCacheForTracingKey = &kModuleCacheForTracingKey;

class ModuleCacheForTracing : public perfetto::TrackEventTlsStateUserData {
 public:
  ModuleCacheForTracing() = default;
  ~ModuleCacheForTracing() override = default;

  base::ModuleCache& GetModuleCache() { return module_cache_; }

 private:
  base::ModuleCache module_cache_;
};

}  // namespace

//  static
void InternedSourceLocation::Add(
    perfetto::protos::pbzero::InternedData* interned_data,
    size_t iid,
    const TraceSourceLocation& location) {
  auto* msg = interned_data->add_source_locations();
  msg->set_iid(iid);
  if (location.file_name != nullptr)
    msg->set_file_name(location.file_name);
  if (location.function_name != nullptr)
    msg->set_function_name(location.function_name);
  // TODO(ssid): Add line number once it is allowed in internal proto.
  // TODO(ssid): Add program counter to the proto fields when
  // !BUILDFLAG(ENABLE_LOCATION_SOURCE).
  // TODO(http://crbug.com760702) remove file name and just pass the program
  // counter to the heap profiler macro.
  // TODO(ssid): Consider writing the program counter of the current task
  // (from the callback function pointer) instead of location that posted the
  // task.
}

// static
void InternedLogMessage::Add(
    perfetto::protos::pbzero::InternedData* interned_data,
    size_t iid,
    const std::string& log_message) {
  auto* msg = interned_data->add_log_message_body();
  msg->set_iid(iid);
  msg->set_body(log_message);
}

// static
void InternedBuildId::Add(perfetto::protos::pbzero::InternedData* interned_data,
                          size_t iid,
                          const std::string& build_id) {
  auto* msg = interned_data->add_build_ids();
  msg->set_iid(iid);
  msg->set_str(build_id);
}

// static
void InternedMappingPath::Add(
    perfetto::protos::pbzero::InternedData* interned_data,
    size_t iid,
    const std::string& mapping_path) {
  auto* msg = interned_data->add_mapping_paths();
  msg->set_iid(iid);
  msg->set_str(mapping_path);
}

// static
size_t InternedMapping::Get(perfetto::EventContext* ctx,
                            const base::ModuleCache::Module* module) {
  auto* index_for_field = GetOrCreateIndexForField(ctx->GetIncrementalState());
  size_t iid;
  if (index_for_field->index_.LookUpOrInsert(&iid, module)) {
    return iid;
  }
  InternedMapping::Add(ctx, iid, module);
  return iid;
}

// static
void InternedMapping::Add(perfetto::EventContext* ctx,
                          size_t iid,
                          const base::ModuleCache::Module* module) {
  // TODO(b/270470700): Remove TransformModuleIDToSymbolServerFormat on all
  // platforms once tools/tracing is fixed.
  const auto build_id = InternedBuildId::Get(
      ctx, base::TransformModuleIDToSymbolServerFormat(module->GetId()));
  const auto path_id =
      InternedMappingPath::Get(ctx, module->GetDebugBasename().MaybeAsASCII());

  auto* msg =
      ctx->GetIncrementalState()->serialized_interned_data->add_mappings();
  msg->set_iid(iid);
  msg->set_build_id(build_id);
  msg->add_path_string_ids(path_id);
}

// static
std::optional<size_t> InternedUnsymbolizedSourceLocation::Get(
    perfetto::EventContext* ctx,
    uintptr_t address) {
  auto* index_for_field = GetOrCreateIndexForField(ctx->GetIncrementalState());
#if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
  ModuleCacheForTracing* module_cache = static_cast<ModuleCacheForTracing*>(
      ctx->GetTlsUserData(kModuleCacheForTracingKey));
  if (!module_cache) {
    auto new_module_cache = std::make_unique<ModuleCacheForTracing>();
    module_cache = new_module_cache.get();
    ctx->SetTlsUserData(kModuleCacheForTracingKey, std::move(new_module_cache));
  }
  const base::ModuleCache::Module* module =
      module_cache->GetModuleCache().GetModuleForAddress(address);
#else
  const base::ModuleCache::Module* module =
      index_for_field->module_cache_.GetModuleForAddress(address);
#endif
  if (!module) {
    return std::nullopt;
  }
  size_t iid;
  if (index_for_field->index_.LookUpOrInsert(&iid, address)) {
    return iid;
  }
  const auto mapping_id = InternedMapping::Get(ctx, module);
  const uintptr_t rel_pc = address - module->GetBaseAddress();
  InternedUnsymbolizedSourceLocation::Add(
      ctx->GetIncrementalState()->serialized_interned_data.get(), iid,
      base::trace_event::UnsymbolizedSourceLocation(mapping_id, rel_pc));
  return iid;
}

// static
void InternedUnsymbolizedSourceLocation::Add(
    perfetto::protos::pbzero::InternedData* interned_data,
    size_t iid,
    const UnsymbolizedSourceLocation& location) {
  auto* msg = interned_data->add_unsymbolized_source_locations();
  msg->set_iid(iid);
  msg->set_mapping_id(location.mapping_id);
  msg->set_rel_pc(location.rel_pc);
}

}  // namespace trace_event
}  // namespace base
