// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in
//    the documentation and/or other materials provided with the
//    distribution.
//  * Neither the name of Google, Inc. nor the names of its contributors
//    may be used to endorse or promote products derived from this
//    software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.

#ifndef TOOLS_PLATFORM_H_
#define TOOLS_PLATFORM_H_

#include "PlatformMacros.h"

#include "json/json.h"
#include "mozilla/Atomics.h"
#include "mozilla/BaseProfilerDetail.h"
#include "mozilla/Logging.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/ProfileBufferEntrySerialization.h"
#include "mozilla/ProfileJSONWriter.h"
#include "mozilla/ProfilerUtils.h"
#include "mozilla/ProgressLogger.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Vector.h"
#include "nsString.h"
#include "shared-libraries.h"

#include <cstddef>
#include <cstdint>
#include <functional>

class ProfilerCodeAddressService;

namespace mozilla {
struct SymbolTable;
}

extern mozilla::LazyLogModule gProfilerLog;

// These are for MOZ_LOG="prof:3" or higher. It's the default logging level for
// the profiler, and should be used sparingly.
#define LOG_TEST MOZ_LOG_TEST(gProfilerLog, mozilla::LogLevel::Info)
#define LOG(arg, ...)                            \
  MOZ_LOG(gProfilerLog, mozilla::LogLevel::Info, \
          ("[%" PRIu64 "] " arg,                 \
           uint64_t(profiler_current_process_id().ToNumber()), ##__VA_ARGS__))

// These are for MOZ_LOG="prof:4" or higher. It should be used for logging that
// is somewhat more verbose than LOG.
#define DEBUG_LOG_TEST MOZ_LOG_TEST(gProfilerLog, mozilla::LogLevel::Debug)
#define DEBUG_LOG(arg, ...)                       \
  MOZ_LOG(gProfilerLog, mozilla::LogLevel::Debug, \
          ("[%" PRIu64 "] " arg,                  \
           uint64_t(profiler_current_process_id().ToNumber()), ##__VA_ARGS__))

typedef uint8_t* Address;

// Stringify the given JSON value, in the most compact format.
// Note: Numbers are limited to a precision of 6 decimal digits, so that
// timestamps in ms have a precision in ns.
Json::String ToCompactString(const Json::Value& aJsonValue);

// Profiling log stored in a Json::Value. The actual log only exists while the
// profiler is running, and will be inserted at the end of the JSON profile.
class ProfilingLog {
 public:
  // These will be called by ActivePS when the profiler starts/stops.
  static void Init();
  static void Destroy();

  // Access the profiling log JSON object, in order to modify it.
  // Only calls the given function if the profiler is active.
  // Thread-safe. But `aF` must not call other locking profiler functions.
  // This is intended to capture some internal logging that doesn't belong in
  // other places like markers. The log is accessible through the JS console on
  // profiler.firefox.com, in the `profile.profilingLog` object; the data format
  // is intentionally not defined, and not intended to be shown in the
  // front-end.
  // Please use caution not to output too much data.
  template <typename F>
  static void Access(F&& aF) {
    mozilla::baseprofiler::detail::BaseProfilerAutoLock lock{gMutex};
    if (gLog) {
      std::forward<F>(aF)(*gLog);
    }
  }

#define DURATION_JSON_SUFFIX "_ms"

  // Convert a TimeDuration to the value to be stored in the log.
  // Use DURATION_JSON_SUFFIX as suffix in the property name.
  static Json::Value Duration(const mozilla::TimeDuration& aDuration) {
    return Json::Value{aDuration.ToMilliseconds()};
  }

#define TIMESTAMP_JSON_SUFFIX "_TSms"

  // Convert a TimeStamp to the value to be stored in the log.
  // Use TIMESTAMP_JSON_SUFFIX as suffix in the property name.
  static Json::Value Timestamp(
      const mozilla::TimeStamp& aTimestamp = mozilla::TimeStamp::Now()) {
    if (aTimestamp.IsNull()) {
      return Json::Value{0.0};
    }
    return Duration(aTimestamp - mozilla::TimeStamp::ProcessCreation());
  }

  static bool IsLockedOnCurrentThread();

 private:
  static mozilla::baseprofiler::detail::BaseProfilerMutex gMutex;
  static mozilla::UniquePtr<Json::Value> gLog;
};

// ----------------------------------------------------------------------------
// Miscellaneous

// If positive, skip stack-sampling in the sampler thread loop.
// Users should increment it atomically when samplings should be avoided, and
// later decrement it back. Multiple uses can overlap.
// There could be a sampling in progress when this is first incremented, so if
// it is critical to prevent any sampling, lock the profiler mutex instead.
// Relaxed ordering, because it's used to request that the profiler pause
// future sampling; this is not time critical, nor dependent on anything else.
extern mozilla::Atomic<int, mozilla::MemoryOrdering::Relaxed> gSkipSampling;

void AppendSharedLibraries(mozilla::JSONWriter& aWriter,
                           const SharedLibraryInfo& aInfo);

// Convert the array of strings to a bitfield.
uint32_t ParseFeaturesFromStringArray(const char** aFeatures,
                                      uint32_t aFeatureCount,
                                      bool aIsStartup = false);

// Add the begin/end 'Awake' markers for the thread.
void profiler_mark_thread_awake();

void profiler_mark_thread_asleep();

[[nodiscard]] bool profiler_get_profile_json(
    SpliceableChunkedJSONWriter& aSpliceableChunkedJSONWriter,
    double aSinceTime, bool aIsShuttingDown,
    mozilla::ProgressLogger aProgressLogger);

// Flags to conveniently track various JS instrumentations.
enum class JSInstrumentationFlags {
  StackSampling = 0x1,
  Allocations = 0x2,
};

// Write out the information of the active profiling configuration.
void profiler_write_active_configuration(mozilla::JSONWriter& aWriter);

// Extract all received exit profiles that have not yet expired (i.e., they
// still intersect with this process' buffer range).
mozilla::Vector<nsCString> profiler_move_exit_profiles();

// If the "MOZ_PROFILER_SYMBOLICATE" env-var is set, we return a new
// ProfilerCodeAddressService object to use for local symbolication of profiles.
// This is off by default, and mainly intended for local development.
mozilla::UniquePtr<ProfilerCodeAddressService>
profiler_code_address_service_for_presymbolication();

extern "C" {
// This function is defined in the profiler rust module at
// tools/profiler/rust-helper. mozilla::SymbolTable and CompactSymbolTable
// have identical memory layout.
bool profiler_get_symbol_table(const char* debug_path, const char* breakpad_id,
                               mozilla::SymbolTable* symbol_table);

bool profiler_demangle_rust(const char* mangled, char* buffer, size_t len);
}

// For each running times value, call MACRO(index, name, unit, jsonProperty)
#define PROFILER_FOR_EACH_RUNNING_TIME(MACRO) \
  MACRO(0, ThreadCPU, Delta, threadCPUDelta)

// This class contains all "running times" such as CPU usage measurements.
// All measurements are listed in `PROFILER_FOR_EACH_RUNNING_TIME` above.
// Each measurement is optional and only takes a value when explicitly set.
// Two RunningTimes object may be subtracted, to get the difference between
// known values.
class RunningTimes {
 public:
  constexpr RunningTimes() = default;

  // Constructor with only a timestamp, useful when no measurements will be
  // taken.
  constexpr explicit RunningTimes(const mozilla::TimeStamp& aTimeStamp)
      : mPostMeasurementTimeStamp(aTimeStamp) {}

  constexpr void Clear() { *this = RunningTimes{}; }

  constexpr bool IsEmpty() const { return mKnownBits == 0; }

  // This should be called right after CPU measurements have been taken.
  void SetPostMeasurementTimeStamp(const mozilla::TimeStamp& aTimeStamp) {
    mPostMeasurementTimeStamp = aTimeStamp;
  }

  const mozilla::TimeStamp& PostMeasurementTimeStamp() const {
    return mPostMeasurementTimeStamp;
  }

  // Should be filled for any registered thread.

#define RUNNING_TIME_MEMBER(index, name, unit, jsonProperty)          \
  constexpr bool Is##name##unit##Known() const {                      \
    return (mKnownBits & mGot##name##unit) != 0;                      \
  }                                                                   \
                                                                      \
  constexpr void Clear##name##unit() {                                \
    m##name##unit = 0;                                                \
    mKnownBits &= ~mGot##name##unit;                                  \
  }                                                                   \
                                                                      \
  constexpr void Reset##name##unit(uint64_t a##name##unit) {          \
    m##name##unit = a##name##unit;                                    \
    mKnownBits |= mGot##name##unit;                                   \
  }                                                                   \
                                                                      \
  constexpr void Set##name##unit(uint64_t a##name##unit) {            \
    MOZ_ASSERT(!Is##name##unit##Known(), #name #unit " already set"); \
    Reset##name##unit(a##name##unit);                                 \
  }                                                                   \
                                                                      \
  constexpr mozilla::Maybe<uint64_t> Get##name##unit() const {        \
    if (Is##name##unit##Known()) {                                    \
      return mozilla::Some(m##name##unit);                            \
    }                                                                 \
    return mozilla::Nothing{};                                        \
  }                                                                   \
                                                                      \
  constexpr mozilla::Maybe<uint64_t> GetJson##name##unit() const {    \
    if (Is##name##unit##Known()) {                                    \
      return mozilla::Some(ConvertRawToJson(m##name##unit));          \
    }                                                                 \
    return mozilla::Nothing{};                                        \
  }

  PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_MEMBER)

#undef RUNNING_TIME_MEMBER

  // Take values from another RunningTimes.
  RunningTimes& TakeFrom(RunningTimes& aOther) {
    if (!aOther.IsEmpty()) {
#define RUNNING_TIME_TAKE(index, name, unit, jsonProperty)   \
  if (aOther.Is##name##unit##Known()) {                      \
    Set##name##unit(std::exchange(aOther.m##name##unit, 0)); \
  }

      PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_TAKE)

#undef RUNNING_TIME_TAKE

      aOther.mKnownBits = 0;
    }
    return *this;
  }

  // Difference from `aBefore` to `this`. Any unknown makes the result unknown.
  // PostMeasurementTimeStamp set to `this` PostMeasurementTimeStamp, to keep
  // the most recent timestamp associated with the end of the interval over
  // which the difference applies.
  RunningTimes operator-(const RunningTimes& aBefore) const {
    RunningTimes diff;
    diff.mPostMeasurementTimeStamp = mPostMeasurementTimeStamp;
#define RUNNING_TIME_SUB(index, name, unit, jsonProperty)           \
  if (Is##name##unit##Known() && aBefore.Is##name##unit##Known()) { \
    diff.Set##name##unit(m##name##unit - aBefore.m##name##unit);    \
  }

    PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_SUB)

#undef RUNNING_TIME_SUB
    return diff;
  }

 private:
  friend mozilla::ProfileBufferEntryWriter::Serializer<RunningTimes>;
  friend mozilla::ProfileBufferEntryReader::Deserializer<RunningTimes>;

  // Platform-dependent.
  static uint64_t ConvertRawToJson(uint64_t aRawValue);

  mozilla::TimeStamp mPostMeasurementTimeStamp;

  uint32_t mKnownBits = 0u;

#define RUNNING_TIME_MEMBER(index, name, unit, jsonProperty) \
  static constexpr uint32_t mGot##name##unit = 1u << index;  \
  uint64_t m##name##unit = 0;

  PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_MEMBER)

#undef RUNNING_TIME_MEMBER
};

template <>
struct mozilla::ProfileBufferEntryWriter::Serializer<RunningTimes> {
  static Length Bytes(const RunningTimes& aRunningTimes) {
    Length bytes = 0;

#define RUNNING_TIME_SERIALIZATION_BYTES(index, name, unit, jsonProperty) \
  if (aRunningTimes.Is##name##unit##Known()) {                            \
    bytes += ULEB128Size(aRunningTimes.m##name##unit);                    \
  }

    PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_SERIALIZATION_BYTES)

#undef RUNNING_TIME_SERIALIZATION_BYTES
    return ULEB128Size(aRunningTimes.mKnownBits) + bytes;
  }

  static void Write(ProfileBufferEntryWriter& aEW,
                    const RunningTimes& aRunningTimes) {
    aEW.WriteULEB128(aRunningTimes.mKnownBits);

#define RUNNING_TIME_SERIALIZE(index, name, unit, jsonProperty) \
  if (aRunningTimes.Is##name##unit##Known()) {                  \
    aEW.WriteULEB128(aRunningTimes.m##name##unit);              \
  }

    PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_SERIALIZE)

#undef RUNNING_TIME_SERIALIZE
  }
};

template <>
struct mozilla::ProfileBufferEntryReader::Deserializer<RunningTimes> {
  static void ReadInto(ProfileBufferEntryReader& aER,
                       RunningTimes& aRunningTimes) {
    aRunningTimes = Read(aER);
  }

  static RunningTimes Read(ProfileBufferEntryReader& aER) {
    // Start with empty running times, everything is cleared.
    RunningTimes times;

    // This sets all the bits into mKnownBits, we don't need to modify it
    // further.
    times.mKnownBits = aER.ReadULEB128<uint32_t>();

    // For each member that should be known, read its value.
#define RUNNING_TIME_DESERIALIZE(index, name, unit, jsonProperty)           \
  if (times.Is##name##unit##Known()) {                                      \
    times.m##name##unit = aER.ReadULEB128<decltype(times.m##name##unit)>(); \
  }

    PROFILER_FOR_EACH_RUNNING_TIME(RUNNING_TIME_DESERIALIZE)

#undef RUNNING_TIME_DESERIALIZE

    return times;
  }
};

#endif /* ndef TOOLS_PLATFORM_H_ */
