// Copyright 2024 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/tracing/public/cpp/perfetto/metadata_data_source.h"

#include "base/check.h"
#include "base/command_line.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/time/time_override.h"
#include "base/trace_event/common/trace_event_common.h"
#include "base/tracing/trace_time.h"
#include "components/variations/active_field_trials.h"
#include "services/tracing/public/cpp/perfetto/perfetto_data_source_names.h"
#include "third_party/perfetto/protos/perfetto/common/data_source_descriptor.gen.h"
#include "third_party/perfetto/protos/perfetto/config/chrome/chrome_config.gen.h"
#include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/extension_descriptor.pbzero.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/apk_info.h"
#endif

namespace tracing {
namespace {

inline constexpr char kCommandLineKey[] = "command_line";
inline constexpr char kClockDomainMetadataKey[] = "clock-domain";
inline constexpr char kTraceCaptureDatetimeKey[] = "trace-capture-datetime";

inline constexpr char kCpuCoresMetadataKey[] = "cpu-num-cores";
inline constexpr char kOSNameMetadataKey[] = "os-name";
inline constexpr char kOSVersionMetadataKey[] = "os-version";

#if BUILDFLAG(IS_ANDROID)
inline constexpr char kPlayStorePackage[] = "com.android.vending";
#endif

}  // namespace

std::string_view GetClockString(base::TimeTicks::Clock clock) {
  switch (clock) {
    case base::TimeTicks::Clock::FUCHSIA_ZX_CLOCK_MONOTONIC:
      return "FUCHSIA_ZX_CLOCK_MONOTONIC";
    case base::TimeTicks::Clock::LINUX_CLOCK_MONOTONIC:
      return "LINUX_CLOCK_MONOTONIC";
    case base::TimeTicks::Clock::IOS_CF_ABSOLUTE_TIME_MINUS_KERN_BOOTTIME:
      return "IOS_CF_ABSOLUTE_TIME_MINUS_KERN_BOOTTIME";
    case base::TimeTicks::Clock::MAC_MACH_ABSOLUTE_TIME:
      return "MAC_MACH_ABSOLUTE_TIME";
    case base::TimeTicks::Clock::WIN_QPC:
      return "WIN_QPC";
    case base::TimeTicks::Clock::WIN_ROLLOVER_PROTECTED_TIME_GET_TIME:
      return "WIN_ROLLOVER_PROTECTED_TIME_GET_TIME";
  }

  NOTREACHED();
}

struct MetadataDataSourceTlsState {
  explicit MetadataDataSourceTlsState(
      const MetadataDataSource::TraceContext& trace_context) {
    auto locked_ds = trace_context.GetDataSourceLocked();
    if (locked_ds.valid()) {
      privacy_filtering_enabled = locked_ds->privacy_filtering_enabled();
      instance = reinterpret_cast<uintptr_t>(&(*locked_ds));
    }
  }
  bool privacy_filtering_enabled = false;
  uintptr_t instance = 0;
};

void MetadataDataSource::Register(
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    std::vector<BundleRecorder> bundle_recorders,
    std::vector<PacketRecorder> packet_recorders,
    ChromeMetadataRecorder chrome_metadata_recorder) {
  perfetto::DataSourceDescriptor desc;
  desc.set_name(kMetaData2SourceName);
  perfetto::DataSource<MetadataDataSource, MetadataDataSourceTraits>::Register(
      desc, std::move(task_runner), std::move(bundle_recorders),
      std::move(packet_recorders), std::move(chrome_metadata_recorder));
}

MetadataDataSource::MetadataDataSource(
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    std::vector<BundleRecorder> bundle_recorders,
    std::vector<PacketRecorder> packet_recorders,
    ChromeMetadataRecorder chrome_metadata_recorder)
    : task_runner_(std::move(task_runner)),
      bundle_recorders_(std::move(bundle_recorders)),
      packet_recorders_(std::move(packet_recorders)),
      chrome_metadata_recorder_(std::move(chrome_metadata_recorder)) {}

MetadataDataSource::~MetadataDataSource() = default;

void MetadataDataSource::OnSetup(const SetupArgs& args) {
  const perfetto::protos::gen::ChromeConfig& chrome_config =
      args.config->chrome_config();
  privacy_filtering_enabled_ = chrome_config.privacy_filtering_enabled();
}

void MetadataDataSource::OnStart(const StartArgs&) {
  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&MetadataDataSource::WriteMetadata,
                     reinterpret_cast<uintptr_t>(this),
                     std::move(bundle_recorders_), std::move(packet_recorders_),
                     std::move(chrome_metadata_recorder_)));
}

void MetadataDataSource::OnFlush(const FlushArgs&) {}

void MetadataDataSource::OnStop(const StopArgs&) {}

#if BUILDFLAG(IS_ANDROID)
// static
void MetadataDataSource::RecordAndroidMetadata(
    perfetto::protos::pbzero::ChromeMetadataPacket* chrome_metadata,
    bool is_system_app,
    const std::string& installer_package_name,
    const std::string& host_package_name) {
  if (is_system_app || installer_package_name == kPlayStorePackage) {
    if (!host_package_name.empty()) {
      chrome_metadata->set_app_package_name(host_package_name);
    }
  }
#if defined(OFFICIAL_BUILD)
  // Version code is only set for official builds on Android.
  const std::string& version_code_str =
      base::android::apk_info::package_version_code();
  if (!version_code_str.empty()) {
    int version_code = 0;
    bool res = base::StringToInt(version_code_str, &version_code);
    DCHECK(res);
    chrome_metadata->set_chrome_version_code(version_code);
  }
#endif  // defined(OFFICIAL_BUILD)
}
#endif  // BUILDFLAG(IS_ANDROID)

// static
void MetadataDataSource::RecordTraceCaptureDatetime(
    base::Time time,
    perfetto::protos::pbzero::ChromeEventBundle* bundle) {
  base::Time::Exploded exploded;
  time.UTCExplode(&exploded);
  CHECK(exploded.HasValidValues());
  AddMetadataToBundle(
      kTraceCaptureDatetimeKey,
      base::StringPrintf("%d-%d-%d %d:%d:%d", exploded.year, exploded.month,
                         exploded.day_of_month, exploded.hour, exploded.minute,
                         exploded.second),
      bundle);
}

void MetadataDataSource::WriteMetadata(
    uintptr_t instance,
    std::vector<BundleRecorder> bundle_recorders,
    std::vector<PacketRecorder> packet_recorders,
    ChromeMetadataRecorder chrome_metadata_recorder) {
  MetadataDataSource::Trace([&](TraceContext ctx) {
    if (instance != ctx.GetCustomTlsState()->instance) {
      return;
    }
    bool privacy_filtering_enabled =
        ctx.GetCustomTlsState()->privacy_filtering_enabled;

    auto now = TRACE_TIME_TICKS_NOW().since_origin().InNanoseconds();

    if (!privacy_filtering_enabled) {
      auto packet = ctx.NewTracePacket();
      packet->set_timestamp(now);
      packet->set_timestamp_clock_id(base::tracing::kTraceClockId);
      auto* bundle = packet->set_chrome_events();
      base::CommandLine::StringType command_line =
          base::CommandLine::ForCurrentProcess()->GetCommandLineString();
#if BUILDFLAG(IS_WIN)
      AddMetadataToBundle(kCommandLineKey, base::WideToUTF8(command_line),
                          bundle);
#else
      AddMetadataToBundle(kCommandLineKey, command_line, bundle);
#endif
      AddMetadataToBundle(kClockDomainMetadataKey,
                          GetClockString(base::TimeTicks::GetClock()), bundle);
      RecordTraceCaptureDatetime(TRACE_TIME_NOW(), bundle);
      for (auto& recorder : bundle_recorders) {
        if (recorder.is_null()) {
          continue;
        }
        recorder.Run(bundle);
      }
    }
    for (auto& recorder : packet_recorders) {
      if (recorder.is_null()) {
        continue;
      }
      auto packet = ctx.NewTracePacket();
      packet->set_timestamp(now);
      packet->set_timestamp_clock_id(base::tracing::kTraceClockId);

      recorder.Run(packet.get(), privacy_filtering_enabled);
    }

    auto packet = ctx.NewTracePacket();
    packet->set_timestamp(now);
    packet->set_timestamp_clock_id(base::tracing::kTraceClockId);
    auto* chrome_metadata = packet->set_chrome_metadata();
    if (chrome_metadata_recorder) {
      chrome_metadata_recorder.Run(chrome_metadata);
    }

#if BUILDFLAG(IS_ANDROID)
    RecordAndroidMetadata(chrome_metadata, base::android::apk_info::is_system_app(),
                          base::android::apk_info::installer_package_name(),
                          base::android::apk_info::host_package_name());
#endif  // BUILDFLAG(IS_ANDROID)

    // Do not include low anonymity field trials, to prevent them from being
    // included in chrometto reports.
    std::vector<variations::ActiveGroupId> active_group_ids;
    variations::GetFieldTrialActiveGroupIds(std::string_view(),
                                            &active_group_ids);

    for (const auto& active_group_id : active_group_ids) {
      perfetto::protos::pbzero::ChromeMetadataPacket::FinchHash* finch_hash =
          chrome_metadata->add_field_trial_hashes();
      finch_hash->set_name(active_group_id.name);
      finch_hash->set_group(active_group_id.group);
    }
    packet->Finalize();
    ctx.Flush();
  });
}

void MetadataDataSource::AddMetadataToBundle(
    std::string_view name,
    std::string_view value,
    perfetto::protos::pbzero::ChromeEventBundle* bundle) {
  auto* metadata = bundle->add_metadata();
  metadata->set_name(name.data(), name.size());
  metadata->set_string_value(value.data(), value.size());
}

void MetadataDataSource::AddMetadataToBundle(
    std::string_view name,
    const base::Value& value,
    perfetto::protos::pbzero::ChromeEventBundle* bundle) {
  auto* metadata = bundle->add_metadata();
  metadata->set_name(name.data(), name.size());

  if (value.is_int()) {
    metadata->set_int_value(value.GetInt());
  } else if (value.is_bool()) {
    metadata->set_bool_value(value.GetBool());
  } else if (value.is_string()) {
    metadata->set_string_value(value.GetString());
  } else {
    metadata->set_json_value(base::WriteJson(value).value_or(""));
  }
}

void MetadataDataSource::RecordDefaultBundleMetadata(
    perfetto::protos::pbzero::ChromeEventBundle* bundle) {
  MetadataDataSource::AddMetadataToBundle(
      kOSNameMetadataKey, base::SysInfo::OperatingSystemName(), bundle);
  MetadataDataSource::AddMetadataToBundle(
      kOSVersionMetadataKey, base::SysInfo::OperatingSystemVersion(), bundle);

  MetadataDataSource::AddMetadataToBundle(
      kCpuCoresMetadataKey,
      base::Value(static_cast<int>(base::SysInfo::NumberOfProcessors())),
      bundle);
}

}  // namespace tracing

PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS(
    COMPONENT_EXPORT(TRACING_CPP),
    tracing::MetadataDataSource,
    tracing::MetadataDataSourceTraits);
