/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_Perfetto_h
#define mozilla_Perfetto_h

#ifdef MOZ_PERFETTO
#  include "mozilla/BaseProfilerMarkers.h"
#  include "mozilla/Flow.h"
#  include "mozilla/TimeStamp.h"
#  include "nsString.h"
#  include "nsPrintfCString.h"
#  include "perfetto/perfetto.h"

// Initialization is called when a content process is created.
// This can be called multiple times.
extern void InitPerfetto();

/* Perfetto Tracing:
 *
 * This file provides an interface to the perfetto tracing API.  The API from
 * the perfetto sdk can be used directly, but an additional set of macros
 * prefixed with PERFETTO_* have been defined to help minimize the use of
 * ifdef's.
 *
 * The common perfetto macros require a category and name at the very least.
 * These must be static strings, or wrapped with perfetto::DynamicString if
 * dynamic. If the string is static, but provided through a runtime pointer,
 * then it must be wrapped with perfetto::StaticString.
 *
 * You can also provide additional parameters such as a timestamp,
 * or a lambda to add additional information to the trace marker.
 * For more info, see https://perfetto.dev/docs/instrumentation/tracing-sdk
 *
 * Examples:
 *
 *  // Add a trace event to measure work inside a block,
 *  // using static strings only.
 *
 *  {
 *    PERFETTO_TRACE_EVENT("js", "JS::RunScript");
 *    run_script();
 *  }
 *
 *  // Add a trace event to measure work inside a block,
 *  // using a dynamic string.
 *
 *  void runScript(nsCString& scriptName)
 *  {
 *    PERFETTO_TRACE_EVENT("js", perfetto::DynamicString{scriptName.get()});
 *    run_script();
 *  }
 *
 *  // Add a trace event using a dynamic category and name.
 *
 *  void runScript(nsCString& categoryName, nsCString& scriptName)
 *  {
 *    perfetto::DynamicCategory category{category.get()};
 *    PERFETTO_TRACE_EVENT(category, perfetto::DynamicString{scriptName.get()});
 *    run_script();
 *  }
 *
 *  // Add a trace event to measure two arbitrary points of code.
 *  // Events in the same category must always be nested.
 *
 *  void startWork() {
 *    PERFETTO_TRACE_EVENT_BEGIN("js", "StartWork");
 *    ...
 *    PERFETTO_TRACE_EVENT_END("js");
 *  }
 *
 *  // Create a trace marker for an event that has already occurred
 *  // using previously saved timestamps.
 *
 *  void record_event(TimeStamp startTimeStamp, TimeStamp endTimeStamp)
 *  {
 *    PERFETTO_TRACE_EVENT_BEGIN("js", "Some Event", startTimeStamp);
 *    PERFETTO_TRACE_EVENT_END("js", endTimeStamp);
 *  }
 */

// Wrap the common trace event macros from perfetto so
// they can be called without #ifdef's.
#  define PERFETTO_TRACE_EVENT(...) TRACE_EVENT(__VA_ARGS__)
#  define PERFETTO_TRACE_EVENT_BEGIN(...) TRACE_EVENT_BEGIN(__VA_ARGS__)
#  define PERFETTO_TRACE_EVENT_END(...) TRACE_EVENT_END(__VA_ARGS__)
#  define PERFETTO_TRACE_EVENT_INSTANT(...) TRACE_EVENT_INSTANT(__VA_ARGS__)

namespace perfetto {
// Specialize custom timestamps for mozilla::TimeStamp.
template <>
struct TraceTimestampTraits<mozilla::TimeStamp> {
  static inline TraceTimestamp ConvertTimestampToTraceTimeNs(
      const mozilla::TimeStamp& timestamp) {
    return {protos::gen::BuiltinClock::BUILTIN_CLOCK_MONOTONIC,
            timestamp.RawClockMonotonicNanosecondsSinceBoot()};
  }
};
}  // namespace perfetto

// Categories can be added dynamically, but to minimize overhead
// all categories should be pre-defined here whenever possible.
PERFETTO_DEFINE_CATEGORIES(perfetto::Category("task"),
                           perfetto::Category("usertiming"));

using MS = mozilla::MarkerSchema;

// Primary template.  Assert if a payload type has not been specialized so we
// don't miss payload information.
template <typename T, typename Enable = void>
struct AddDebugAnnotationImpl {
  static void call(perfetto::EventContext& ctx, const char* const aKey,
                   const T& aValue) {
    static_assert(false,
                  "Unsupported payload type for perfetto debug annotations.");
  }
};

// Do nothing for these types.
template <>
struct AddDebugAnnotationImpl<mozilla::Nothing> {
  static void call(perfetto::EventContext& ctx, const char* const aKey,
                   const mozilla::Nothing& aValue) {}
};

template <>
struct AddDebugAnnotationImpl<std::nullptr_t> {
  static void call(perfetto::EventContext& ctx, const char* const aKey,
                   const std::nullptr_t& aValue) {}
};

// Specialize mozilla::Maybe<>
template <typename T>
struct AddDebugAnnotationImpl<mozilla::Maybe<T>> {
  static void call(perfetto::EventContext& ctx, const char* const aKey,
                   const mozilla::Maybe<T>& aValue) {
    if (aValue.isNothing()) {
      return;
    }
    AddDebugAnnotationImpl<T>::call(ctx, aKey, *aValue);
  }
};

// Specialize integral types.
template <typename T>
struct AddDebugAnnotationImpl<T, std::enable_if_t<std::is_integral_v<T>>> {
  static void call(perfetto::EventContext& ctx, const char* const aKey,
                   const T& aValue) {
    auto* arg = ctx.event()->add_debug_annotations();
    arg->set_name(aKey);

    if constexpr (std::is_same_v<T, bool>) {
      arg->set_bool_value(static_cast<uint64_t>(aValue));
    } else if constexpr (std::is_signed_v<T>) {
      arg->set_int_value(static_cast<uint64_t>(aValue));
    } else {
      static_assert(std::is_unsigned_v<T>);
      arg->set_uint_value(static_cast<uint64_t>(aValue));
    }
  }
};

// Specialize time durations.
template <>
struct AddDebugAnnotationImpl<
    mozilla::BaseTimeDuration<mozilla::TimeDurationValueCalculator>> {
  static void call(
      perfetto::EventContext& ctx, const char* const aKey,
      const mozilla::BaseTimeDuration<mozilla::TimeDurationValueCalculator>&
          aValue) {
    auto* arg = ctx.event()->add_debug_annotations();
    arg->set_name(aKey);
    arg->set_uint_value(static_cast<uint64_t>(aValue.ToMilliseconds()));
  }
};

template <>
struct AddDebugAnnotationImpl<Flow> {
  static void call(perfetto::EventContext& ctx, const char* const aKey,
                   const Flow& aValue) {
    AddDebugAnnotationImpl<uint64_t>::call(ctx, aKey, aValue.Id());
  }
};

// Specialize the various string representations.
#  define ADD_DEBUG_STRING_ANNOTATION_IMPL(templatetype, stringtype,        \
                                           paramtype, getter)               \
    template <templatetype>                                                 \
    struct AddDebugAnnotationImpl<stringtype> {                             \
      static void call(perfetto::EventContext& ctx, const char* const aKey, \
                       const paramtype aValue) {                            \
        auto* arg = ctx.event()->add_debug_annotations();                   \
        arg->set_name(aKey);                                                \
        arg->set_string_value(getter);                                      \
      }                                                                     \
    };

#  define ADD_DEBUG_STRING_ANNOTATION(type, getter) \
    ADD_DEBUG_STRING_ANNOTATION_IMPL(, type, type&, getter)

// ProfilerString8View may not be null-terminated, use the length-specified
// overload.
template <>
struct AddDebugAnnotationImpl<mozilla::ProfilerString8View> {
  static void call(perfetto::EventContext& ctx, const char* const aKey,
                   const mozilla::ProfilerString8View& aValue) {
    auto* arg = ctx.event()->add_debug_annotations();
    arg->set_name(aKey);
    auto sv = aValue.StringView();
    arg->set_string_value(sv.data(), sv.length());
  }
};
ADD_DEBUG_STRING_ANNOTATION_IMPL(size_t N, nsAutoCStringN<N>,
                                 nsAutoCStringN<N>&, aValue.get())
ADD_DEBUG_STRING_ANNOTATION(nsCString, aValue.get())
ADD_DEBUG_STRING_ANNOTATION(nsAutoCString, aValue.get())
ADD_DEBUG_STRING_ANNOTATION(nsTLiteralString<char>, aValue.get())
ADD_DEBUG_STRING_ANNOTATION(nsPrintfCString, aValue.get())
ADD_DEBUG_STRING_ANNOTATION(NS_ConvertUTF16toUTF8, aValue.get())
ADD_DEBUG_STRING_ANNOTATION(nsTDependentString<char>, aValue.get())

ADD_DEBUG_STRING_ANNOTATION(nsACString, nsAutoCString(aValue).get())
ADD_DEBUG_STRING_ANNOTATION(std::string, aValue)
ADD_DEBUG_STRING_ANNOTATION_IMPL(size_t N, char[N], char*, aValue)
ADD_DEBUG_STRING_ANNOTATION(mozilla::ProfilerString16View,
                            NS_ConvertUTF16toUTF8(aValue).get())
ADD_DEBUG_STRING_ANNOTATION(nsAString, NS_ConvertUTF16toUTF8(aValue).get())
ADD_DEBUG_STRING_ANNOTATION_IMPL(size_t N, nsAutoStringN<N>, nsAutoStringN<N>&,
                                 NS_ConvertUTF16toUTF8(aValue).get())
ADD_DEBUG_STRING_ANNOTATION_IMPL(, const nsAString&, nsAString&,
                                 NS_ConvertUTF16toUTF8(aValue).get())
ADD_DEBUG_STRING_ANNOTATION(nsString, NS_ConvertUTF16toUTF8(aValue).get())

// Main helper call that dispatches to proper specialization.
template <typename T>
void AddDebugAnnotation(perfetto::EventContext& ctx, const char* const aKey,
                        const T& aValue) {
  AddDebugAnnotationImpl<T>::call(ctx, aKey, aValue);
}

extern const char* ProfilerCategoryNames[];

// Main entry point from the gecko profiler for each marker.
template <typename MarkerType, typename... PayloadArguments>
void EmitPerfettoTrackEvent(const mozilla::ProfilerString8View& aName,
                            const mozilla::MarkerCategory& aCategory,
                            const mozilla::MarkerOptions& aOptions,
                            MarkerType aMarkerType,
                            const PayloadArguments&... aPayloadArguments) {
  mozilla::TimeStamp startTime, endTime;
  mozilla::MarkerTiming::Phase phase;

  if (aOptions.IsTimingUnspecified()) {
    startTime = mozilla::TimeStamp::Now();
    phase = mozilla::MarkerTiming::Phase::Instant;
  } else {
    startTime = aOptions.Timing().StartTime();
    endTime = aOptions.Timing().EndTime();
    phase = aOptions.Timing().MarkerPhase();
  }

  auto nameSv = aName.StringView();
  const char* nameStr = nameSv.data();
  if (!nameStr) {
    return;
  }

  // Create a dynamic category for the marker.
  const char* categoryName =
      ProfilerCategoryNames[static_cast<uint32_t>(aCategory.GetCategory())];
  perfetto::DynamicCategory category{categoryName};

  // If the Marker has payload fields, we can annotate them in the perfetto
  // track event. Otherwise, we define an empty lambda which does nothing.
  std::function<void(perfetto::EventContext)> annotateTrackEvent =
      [&](perfetto::EventContext ctx) {};
  if constexpr (mozilla::MarkerHasTranslator<MarkerType>::value) {
    // NOP
    // TODO - Bug 2037782
  } else if constexpr (mozilla::MarkerHasPayloadFields<MarkerType>::value) {
    annotateTrackEvent = [&](perfetto::EventContext ctx) {
      static_assert(
          sizeof...(PayloadArguments) ==
              std::extent_v<decltype(MarkerType::PayloadFields)>,
          "Number and type of fields must be equal to number and type of "
          "payload arguments. If this is not the case a "
          "TranslateMarkerInputToSchema function must be defined.");
      size_t i = 0;
      auto processArgument = [&](const auto& payloadArg) {
        AddDebugAnnotation(ctx, MarkerType::PayloadFields[i++].Key, payloadArg);
      };
      (processArgument(aPayloadArguments), ...);
    };
  }

  // Create a unique id for each marker so it has it's own track.
  mozilla::HashNumber hash = mozilla::HashString(nameStr, nameSv.length());

  switch (phase) {
    case mozilla::MarkerTiming::Phase::Interval: {
      hash = mozilla::AddToHash(
          hash, startTime.RawClockMonotonicNanosecondsSinceBoot());
      hash = mozilla::AddToHash(
          hash, endTime.RawClockMonotonicNanosecondsSinceBoot());
      perfetto::Track track(hash);

      PERFETTO_TRACE_EVENT_BEGIN(
          category, (perfetto::DynamicString{nameStr, nameSv.length()}), track,
          startTime);
      PERFETTO_TRACE_EVENT_END(category, track, endTime, annotateTrackEvent);
    } break;
    case mozilla::MarkerTiming::Phase::Instant: {
      PERFETTO_TRACE_EVENT_INSTANT(
          category, (perfetto::DynamicString{nameStr, nameSv.length()}),
          startTime);
    } break;
    case mozilla::MarkerTiming::Phase::IntervalStart: {
      PERFETTO_TRACE_EVENT_BEGIN(
          category, (perfetto::DynamicString{nameStr, nameSv.length()}),
          perfetto::Track(hash), startTime);
    } break;
    case mozilla::MarkerTiming::Phase::IntervalEnd: {
      PERFETTO_TRACE_EVENT_END(category, perfetto::Track(hash), endTime,
                               annotateTrackEvent);
    } break;
  }
}

#else  // !defined(MOZ_PERFETTO)
#  define PERFETTO_TRACE_EVENT(...) \
    do {                            \
    } while (0)
#  define PERFETTO_TRACE_EVENT_BEGIN(...) \
    do {                                  \
    } while (0)
#  define PERFETTO_TRACE_EVENT_END(...) \
    do {                                \
    } while (0)
#  define PERFETTO_TRACE_EVENT_INSTANT(...) \
    do {                                    \
    } while (0)
inline void InitPerfetto() {}
#endif  // MOZ_PERFETTO

#endif  // mozilla_Perfetto_h
