/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Should match the definitions in: frameworks/native/cmds/atrace/atrace.cpp
#define FRAMEWORK_CATEGORIES(C)                                  \
  C(always, "always", "Always category")                         \
  C(graphics, "gfx", "Graphics category")                        \
  C(input, "input", "Input category")                            \
  C(view, "view", "View category")                               \
  C(webview, "webview", "WebView category")                      \
  C(windowmanager, "wm", "WindowManager category")               \
  C(activitymanager, "am", "ActivityManager category")           \
  C(syncmanager, "sm", "SyncManager category")                   \
  C(audio, "audio", "Audio category")                            \
  C(video, "video", "Video category")                            \
  C(camera, "camera", "Camera category")                         \
  C(hal, "hal", "HAL category")                                  \
  C(app, "app", "App category")                                  \
  C(resources, "res", "Resources category")                      \
  C(dalvik, "dalvik", "Dalvik category")                         \
  C(rs, "rs", "RS category")                                     \
  C(bionic, "bionic", "Bionic category")                         \
  C(power, "power", "Power category")                            \
  C(packagemanager, "pm", "PackageManager category")             \
  C(systemserver, "ss", "System Server category")                \
  C(database, "database", "Database category")                   \
  C(network, "network", "Network category")                      \
  C(adb, "adb", "ADB category")                                  \
  C(vibrator, "vibrator", "Vibrator category")                   \
  C(aidl, "aidl", "AIDL category")                               \
  C(nnapi, "nnapi", "NNAPI category")                            \
  C(rro, "rro", "RRO category")                                  \
  C(thermal, "thermal", "Thermal category")

#include <atomic>
#include <mutex>

#include <android-base/properties.h>
#include <cutils/trace.h>
#include <inttypes.h>

#include "perfetto/public/compiler.h"
#include "perfetto/public/producer.h"
#include "perfetto/public/te_category_macros.h"
#include "perfetto/public/te_macros.h"
#include "perfetto/public/track_event.h"
#include "trace_categories.h"
#include "tracing_perfetto_internal.h"

#ifdef __BIONIC__
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
#include <sys/_system_properties.h>
#endif

namespace tracing_perfetto {

namespace internal {

namespace {
PERFETTO_TE_CATEGORIES_DECLARE(FRAMEWORK_CATEGORIES);

PERFETTO_TE_CATEGORIES_DEFINE(FRAMEWORK_CATEGORIES);

static constexpr char kPreferFlagProperty[] = "debug.atrace.prefer_sdk";
static std::atomic<const prop_info*> prefer_property_info = nullptr;
static std::atomic_uint32_t last_prefer_seq_num = 0;
static std::atomic_uint64_t prefer_flags = 0;

static const prop_info* system_property_find(const char* name [[maybe_unused]]) {
  #ifdef __BIONIC__
  return __system_property_find(name);
  #endif

  return nullptr;
}

static uint32_t system_property_serial(const prop_info* pi [[maybe_unused]]) {
  #ifdef __BIONIC__
  return __system_property_serial(pi);
  #endif

  return last_prefer_seq_num;
}

struct PerfettoTeCategory* toCategory(uint64_t inCategory) {
  switch (inCategory) {
    case TRACE_CATEGORY_ALWAYS:
      return &always;
    case TRACE_CATEGORY_GRAPHICS:
      return &graphics;
    case TRACE_CATEGORY_INPUT:
      return &input;
    case TRACE_CATEGORY_VIEW:
      return &view;
    case TRACE_CATEGORY_WEBVIEW:
      return &webview;
    case TRACE_CATEGORY_WINDOW_MANAGER:
      return &windowmanager;
    case TRACE_CATEGORY_ACTIVITY_MANAGER:
      return &activitymanager;
    case TRACE_CATEGORY_SYNC_MANAGER:
      return &syncmanager;
    case TRACE_CATEGORY_AUDIO:
      return &audio;
    case TRACE_CATEGORY_VIDEO:
      return &video;
    case TRACE_CATEGORY_CAMERA:
      return &camera;
    case TRACE_CATEGORY_HAL:
      return &hal;
    case TRACE_CATEGORY_APP:
      return &app;
    case TRACE_CATEGORY_RESOURCES:
      return &resources;
    case TRACE_CATEGORY_DALVIK:
      return &dalvik;
    case TRACE_CATEGORY_RS:
      return &rs;
    case TRACE_CATEGORY_BIONIC:
      return &bionic;
    case TRACE_CATEGORY_POWER:
      return &power;
    case TRACE_CATEGORY_PACKAGE_MANAGER:
      return &packagemanager;
    case TRACE_CATEGORY_SYSTEM_SERVER:
      return &systemserver;
    case TRACE_CATEGORY_DATABASE:
      return &database;
    case TRACE_CATEGORY_NETWORK:
      return &network;
    case TRACE_CATEGORY_ADB:
      return &adb;
    case TRACE_CATEGORY_VIBRATOR:
      return &vibrator;
    case TRACE_CATEGORY_AIDL:
      return &aidl;
    case TRACE_CATEGORY_NNAPI:
      return &nnapi;
    case TRACE_CATEGORY_RRO:
      return &rro;
    case TRACE_CATEGORY_THERMAL:
      return &thermal;
    default:
      return nullptr;
  }
}

}  // namespace

bool isPerfettoCategoryEnabled(PerfettoTeCategory* category) {
  return category != nullptr;
}

/**
 * Updates the cached |prefer_flags|.
 *
 * We cache the prefer_flags because reading it on every trace event is expensive.
 * The cache is invalidated when a sys_prop sequence number changes.
 */
void updatePreferFlags() {
  if (!prefer_property_info.load(std::memory_order_acquire)) {
    auto* new_prefer_property_info = system_property_find(kPreferFlagProperty);
    prefer_flags.store(android::base::GetIntProperty(kPreferFlagProperty, 0),
                       std::memory_order_relaxed);

    if (!new_prefer_property_info) {
      // This should never happen. If it does, we fail gracefully and end up reading the property
      // traced event.
      return;
    }

    last_prefer_seq_num = system_property_serial(new_prefer_property_info);
    prefer_property_info.store(new_prefer_property_info, std::memory_order_release);
  }

  uint32_t prefer_seq_num =  system_property_serial(prefer_property_info);
  if (prefer_seq_num != last_prefer_seq_num.load(std::memory_order_acquire)) {
    prefer_flags.store(android::base::GetIntProperty(kPreferFlagProperty, 0),
                       std::memory_order_relaxed);
    last_prefer_seq_num.store(prefer_seq_num, std::memory_order_release);
  }
}

bool shouldPreferAtrace(PerfettoTeCategory *perfettoCategory, uint64_t atraceCategory) {
  // There are 3 cases:
  // 1. Atrace is not enabled.
  if (!atrace_is_tag_enabled(atraceCategory)) {
    return false;
  }

  // 2. Atrace is enabled but perfetto is not enabled.
  if (!isPerfettoCategoryEnabled(perfettoCategory)) {
    return true;
  }

  // Update prefer_flags before checking it below
  updatePreferFlags();

  // 3. Atrace and perfetto are enabled.
  // Even though this category is enabled for track events, the config mandates that we downgrade
  // it to atrace if the same atrace category is currently enabled. This prevents missing the
  // event from a concurrent session that needs the same category in atrace.
  return (atraceCategory & prefer_flags.load(std::memory_order_relaxed)) == 0;
}

struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) {
  struct PerfettoTeCategory* perfettoCategory = toCategory(category);
  if (perfettoCategory == nullptr) {
    return nullptr;
  }

  bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT(
       (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED));
  return enabled ? perfettoCategory : nullptr;
}

void registerWithPerfetto(bool test) {
  static std::once_flag registration;
  std::call_once(registration, [test]() {
    struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
    args.backends = test ? PERFETTO_BACKEND_IN_PROCESS : PERFETTO_BACKEND_SYSTEM;
    args.shmem_size_hint_kb = 1024;
    PerfettoProducerInit(args);
    PerfettoTeInit();
    PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES);
  });
}

void perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name) {
  PERFETTO_TE(category, PERFETTO_TE_SLICE_BEGIN(name));
}

void perfettoTraceEnd(const struct PerfettoTeCategory& category) {
  PERFETTO_TE(category, PERFETTO_TE_SLICE_END());
}

void perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
                                       const char* trackName, uint64_t cookie) {
  PERFETTO_TE(
        category, PERFETTO_TE_SLICE_BEGIN(name),
        PERFETTO_TE_PROTO_TRACK(
            PerfettoTeNamedTrackUuid(trackName, cookie,
                                     PerfettoTeProcessTrackUuid()),
            PERFETTO_TE_PROTO_FIELD_CSTR(
                perfetto_protos_TrackDescriptor_atrace_name_field_number,
                trackName),
            PERFETTO_TE_PROTO_FIELD_VARINT(
                perfetto_protos_TrackDescriptor_parent_uuid_field_number,
                PerfettoTeProcessTrackUuid())));
}

void perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
                                     const char* trackName, uint64_t cookie) {
    PERFETTO_TE(
        category, PERFETTO_TE_SLICE_END(),
        PERFETTO_TE_PROTO_TRACK(
            PerfettoTeNamedTrackUuid(trackName, cookie,
                                     PerfettoTeProcessTrackUuid()),
            PERFETTO_TE_PROTO_FIELD_CSTR(
                perfetto_protos_TrackDescriptor_atrace_name_field_number,
                trackName),
            PERFETTO_TE_PROTO_FIELD_VARINT(
                perfetto_protos_TrackDescriptor_parent_uuid_field_number,
                PerfettoTeProcessTrackUuid())));
}

void perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
                               uint64_t cookie) {
  perfettoTraceAsyncBeginForTrack(category, name, name, cookie);
}

void perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
                             uint64_t cookie) {
  perfettoTraceAsyncEndForTrack(category, name, cookie);
}

void perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name) {
  PERFETTO_TE(category, PERFETTO_TE_INSTANT(name));
}

void perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
                                    const char* trackName, const char* name) {
  PERFETTO_TE(
        category, PERFETTO_TE_INSTANT(name),
        PERFETTO_TE_PROTO_TRACK(
            PerfettoTeNamedTrackUuid(trackName, 1,
                                     PerfettoTeProcessTrackUuid()),
            PERFETTO_TE_PROTO_FIELD_CSTR(
                perfetto_protos_TrackDescriptor_atrace_name_field_number,
                trackName),
            PERFETTO_TE_PROTO_FIELD_VARINT(
                perfetto_protos_TrackDescriptor_parent_uuid_field_number,
                PerfettoTeProcessTrackUuid())));
}

void perfettoTraceCounter(const struct PerfettoTeCategory& category,
                          const char* name, int64_t value) {
  PERFETTO_TE(
        category, PERFETTO_TE_COUNTER(),
        PERFETTO_TE_PROTO_TRACK(
            PerfettoTeCounterTrackUuid(name,
                                       PerfettoTeProcessTrackUuid()),
            PERFETTO_TE_PROTO_FIELD_CSTR(
                perfetto_protos_TrackDescriptor_atrace_name_field_number,
                name),
            PERFETTO_TE_PROTO_FIELD_VARINT(
                perfetto_protos_TrackDescriptor_parent_uuid_field_number,
                PerfettoTeProcessTrackUuid()),
            PERFETTO_TE_PROTO_FIELD_BYTES(
                perfetto_protos_TrackDescriptor_counter_field_number,
                PERFETTO_NULL, 0)),
        PERFETTO_TE_INT_COUNTER(value));
}
}  // namespace internal

}  // namespace tracing_perfetto
