//===--- TracingSignpost.h - Tracing with the signpost API ---------*- C++ -*-//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Concurrency tracing implemented with the os_signpost API.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_CONCURRENCY_TRACINGSIGNPOST_H
#define SWIFT_CONCURRENCY_TRACINGSIGNPOST_H

#include "TaskPrivate.h"
#include "Tracing.h"
#include "swift/ABI/Task.h"
#include "swift/Basic/Lazy.h"
#include "swift/Runtime/Casting.h"
#include "swift/Runtime/HeapObject.h"
#include "swift/Runtime/TracingCommon.h"
#include <inttypes.h>
#include <os/log.h>
#include <os/signpost.h>

#if defined(__arm64__) && __has_include(<os/apt_private.h>)
#include <os/apt_private.h>
#endif

// Compatibility notes:
//
// These signposts can be read by external software that isn't synced with the
// Swift runtime build. Changes here must be considered carefully to avoid
// breaking users of these signposts.
//
// We may:
// * Add new signpost calls with new names. (Keeping in mind that they won't be
//   picked up by software that doesn't know about them.)
// * Remove existing calls if the given event is somehow no longer relevant.
// * Change format strings.
// * Add format string arguments.
//
// We may NOT:
// * Change the order of existing format string arguments.
// * Change event names.
// * Change subsystem names.

#define SWIFT_LOG_ACTOR_LIFETIME_NAME "actor_lifetime"
#define SWIFT_LOG_ACTOR_DEALLOCATE_NAME "actor_deallocate"
#define SWIFT_LOG_ACTOR_ENQUEUE_NAME "actor_enqueue"
#define SWIFT_LOG_ACTOR_DEQUEUE_NAME "actor_dequeue"
#define SWIFT_LOG_ACTOR_STATE_CHANGED_NAME "actor_state_changed"
#define SWIFT_LOG_ACTOR_JOB_QUEUE_NAME "actor_job_queue"
#define SWIFT_LOG_TASK_LIFETIME_NAME "task_lifetime"
#define SWIFT_LOG_TASK_FLAGS_CHANGED_NAME "task_flags_changed"
#define SWIFT_LOG_TASK_STATUS_CHANGED_NAME "task_status_changed"
#define SWIFT_LOG_TASK_WAIT_NAME "task_wait"
#define SWIFT_LOG_TASK_CONTINUATION "task_continuation"
#define SWIFT_LOG_TASK_CONTINUATION_AWAIT "task_continuation_await"
#define SWIFT_LOG_JOB_ENQUEUE_GLOBAL_NAME "job_enqueue_global"
#define SWIFT_LOG_JOB_ENQUEUE_GLOBAL_WITH_DELAY_NAME                           \
  "job_enqueue_global_with_delay"
#define SWIFT_LOG_JOB_ENQUEUE_EXECUTOR_NAME "job_enqueue_executor"
#define SWIFT_LOG_JOB_RUN_NAME "job_run"
#define SWIFT_LOG_TASK_SWITCH_EXECUTOR_NAME "task_switch_executor"

namespace swift {
namespace concurrency {
namespace trace {

extern os_log_t ActorLog;
extern os_log_t TaskLog;
extern swift::once_t LogsToken;
extern bool TracingEnabled;

void setupLogs(void *unused);

// Check a representative os_signpost function for NULL rather than doing a
// standard availability check, for better performance if the check doesn't get
// optimized out.
#define ENSURE_LOGS(...)                                                       \
  do {                                                                         \
    if (!runtime::trace::tracingReady())                                       \
      return __VA_ARGS__;                                                      \
    swift::once(LogsToken, setupLogs, nullptr);                                \
    if (!TracingEnabled)                                                       \
      return __VA_ARGS__;                                                      \
  } while (0)

// Every function does ENSURE_LOGS() before making any os_signpost calls, so
// we can skip availability checking on all the individual calls.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
#pragma clang diagnostic ignored "-Wunguarded-availability-new"

// Actor trace calls.

inline void actor_create(HeapObject *actor) {
  ENSURE_LOGS();

  // Do an explicit enabled check here to avoid the cost of swift_getTypeName in
  // the common case.
  if (!os_signpost_enabled(ActorLog))
    return;

  auto typeName = swift_getTypeName(swift_getObjectType(actor), true);

  auto id = os_signpost_id_make_with_pointer(ActorLog, actor);
  os_signpost_interval_begin(ActorLog, id, SWIFT_LOG_ACTOR_LIFETIME_NAME,
                             "actor=%p typeName=%.*s", actor,
                             (int)typeName.length, typeName.data);
}

inline void actor_deallocate(HeapObject *actor) {
  ENSURE_LOGS();
  auto id = os_signpost_id_make_with_pointer(ActorLog, actor);
  os_signpost_interval_end(ActorLog, id, SWIFT_LOG_ACTOR_LIFETIME_NAME,
                           "actor=%p", actor);
  os_signpost_event_emit(ActorLog, id, SWIFT_LOG_ACTOR_DEALLOCATE_NAME,
                         "actor=%p", actor);
}

inline void actor_enqueue(HeapObject *actor, Job *job) {
  if (AsyncTask *task = dyn_cast<AsyncTask>(job)) {
    ENSURE_LOGS();
    auto metadata = swift_getObjectType(actor);
    auto descriptor = (const void *)metadata->getTypeContextDescriptor();
    auto id = os_signpost_id_make_with_pointer(ActorLog, actor);
    os_signpost_event_emit(
        ActorLog, id, SWIFT_LOG_ACTOR_ENQUEUE_NAME,
        "actor=%p task=%" PRId64 " taskPointer=%p metadata=%p descriptor=%p",
        actor, task->getTaskId(), task, metadata, descriptor);
  }
}

inline void actor_dequeue(HeapObject *actor, Job *job) {
  if (AsyncTask *task = dyn_cast_or_null<AsyncTask>(job)) {
    ENSURE_LOGS();
    auto metadata = swift_getObjectType(actor);
    auto descriptor = (const void *)metadata->getTypeContextDescriptor();
    auto id = os_signpost_id_make_with_pointer(ActorLog, actor);
    os_signpost_event_emit(
        ActorLog, id, SWIFT_LOG_ACTOR_DEQUEUE_NAME,
        "actor=%p task=%" PRId64 " taskPointer=%p metadata=%p descriptor=%p",
        actor, task->getTaskId(), task, metadata, descriptor);
  }
}

inline void actor_state_changed(HeapObject *actor, Job *firstJob, uint8_t state,
                                bool isDistributedRemote,
                                bool isPriorityEscalated, uint8_t maxPriority) {
  ENSURE_LOGS();
  auto id = os_signpost_id_make_with_pointer(ActorLog, actor);
  os_signpost_event_emit(ActorLog, id, SWIFT_LOG_ACTOR_STATE_CHANGED_NAME,
                         "actor=%p needsPreprocessing=%d "
                         "state=%u isDistributedRemote=%{bool}d "
                         "isPriorityEscalated=%{bool}d, maxPriority=%u",
                         actor, (firstJob != nullptr), state,
                         isDistributedRemote, isPriorityEscalated, maxPriority);
}

inline void actor_note_job_queue(HeapObject *actor, Job *first,
                                 Job *(*getNext)(Job *)) {
  ENSURE_LOGS();

  // Do an explicit enabled check here, since the loop is potentially expensive.
  if (!os_signpost_enabled(ActorLog))
    return;

  // Count the number of pending jobs. We may want to track this separately
  // rather than looping to count, but this gets the job done for now.

  unsigned jobCount = 0;
  for (Job *job = first; job; job = getNext(job))
    if (isa<AsyncTask>(job))
      jobCount++;

  auto id = os_signpost_id_make_with_pointer(ActorLog, actor);
  os_signpost_event_emit(ActorLog, id, SWIFT_LOG_ACTOR_JOB_QUEUE_NAME,
                         "actor=%p jobCount=%u", actor, jobCount);
}

// Task trace calls.

inline void task_create(AsyncTask *task, AsyncTask *parent, TaskGroup *group,
                        AsyncLet *asyncLet, uint8_t jobPriority,
                        bool isChildTask, bool isFuture, bool isGroupChildTask,
                        bool isAsyncLetTask, bool isDiscardingTask,
                        bool hasInitialTaskExecutorPreference,
                        const char* taskName) {
  ENSURE_LOGS();
  auto id = os_signpost_id_make_with_pointer(TaskLog, task);
  auto parentID = parent ? parent->getTaskId() : 0;
  os_signpost_interval_begin(
      TaskLog, id, SWIFT_LOG_TASK_LIFETIME_NAME,
      "task=%" PRId64
      " resumefn=%p jobPriority=%u isChildTask=%{bool}d, isFuture=%{bool}d "
      "isGroupChildTask=%{bool}d isAsyncLetTask=%{bool}d parent=%" PRId64
      " group=%p asyncLet=%p "
      "isDiscardingTask=%{bool}d hasInitialTaskExecutorPreference=%{bool}d "
      "taskName=%{public}s taskPointer=%p",
      task->getTaskId(), task->getResumeFunctionForLogging(true), jobPriority,
      isChildTask, isFuture, isGroupChildTask, isAsyncLetTask, parentID, group,
      asyncLet, isDiscardingTask, hasInitialTaskExecutorPreference, taskName,
      task);
}

inline void task_destroy(AsyncTask *task) {
  ENSURE_LOGS();
  auto id = os_signpost_id_make_with_pointer(TaskLog, task);
  os_signpost_interval_end(TaskLog, id, SWIFT_LOG_TASK_LIFETIME_NAME,
                           "task=%" PRId64 " taskPointer=%p",
                           task->getTaskId(), task);
}

inline void task_status_changed(AsyncTask *task, uint8_t maxPriority,
                                bool isCancelled, bool isEscalated,
                                bool isStarting, bool isRunning,
                                bool isEnqueued, bool wasRunning) {
#if !TARGET_OS_SIMULATOR
#if OS_APT_SPI_VERSION >= 20241023
  uint64_t taskId = task->getTaskId();
  if (isRunning) {
    if (SWIFT_RUNTIME_WEAK_CHECK(os_apt_msg_async_task_running_4swift)) {
      SWIFT_RUNTIME_WEAK_USE(os_apt_msg_async_task_running_4swift(taskId));
    }
  } else if (!isRunning && wasRunning) {
#if OS_APT_SPI_VERSION >= 20250710
    if (SWIFT_RUNTIME_WEAK_CHECK(os_apt_msg_async_task_stopped_4swift)) {
      SWIFT_RUNTIME_WEAK_USE(os_apt_msg_async_task_stopped_4swift(taskId));
    }
#endif // OS_APT_SPI_VERSION >= 20250710
  }
#endif // OS_APT_SPI_VERSION >= 20241023
#endif // !TARGET_OS_SIMULATOR
  ENSURE_LOGS();
  auto id = os_signpost_id_make_with_pointer(TaskLog, task);
  const char *taskName = task->getTaskName();
  os_signpost_event_emit(
      TaskLog, id, SWIFT_LOG_TASK_STATUS_CHANGED_NAME,
      "task=%" PRId64 " resumefn=%p "
      "maxPriority=%u, isCancelled=%{bool}d "
      "isEscalated=%{bool}d, isRunning=%{bool}d, isEnqueued=%{bool}d "
      "taskName=%{public}s taskPointer=%p",
      task->getTaskId(), task->getResumeFunctionForLogging(isStarting),
      maxPriority, isCancelled, isEscalated, isRunning, isEnqueued, taskName,
      task);
}

inline void task_flags_changed(AsyncTask *task, uint8_t jobPriority,
                               bool isChildTask, bool isFuture,
                               bool isGroupChildTask, bool isAsyncLetTask) {
  ENSURE_LOGS();
  auto id = os_signpost_id_make_with_pointer(TaskLog, task);
  os_signpost_event_emit(
      TaskLog, id, SWIFT_LOG_TASK_FLAGS_CHANGED_NAME,
      "task=%" PRId64
      " jobPriority=%u isChildTask=%{bool}d, isFuture=%{bool}d "
      "isGroupChildTask=%{bool}d isAsyncLetTask=%{bool}d taskPointer=%p",
      task->getTaskId(), jobPriority, isChildTask, isFuture,
      isGroupChildTask, isAsyncLetTask, task);
}

inline void task_wait(AsyncTask *task, AsyncTask *waitingOn, uintptr_t status) {
  ENSURE_LOGS();
  auto id = os_signpost_id_make_with_pointer(TaskLog, task);
  auto waitingID = waitingOn ? waitingOn->getTaskId() : 0;
  os_signpost_interval_begin(TaskLog, id, SWIFT_LOG_TASK_WAIT_NAME,
                             "task=%" PRId64 " waitingOnTask=%" PRId64
                             " status=0x%" PRIxPTR " taskPointer=%p",
                             task->getTaskId(), waitingID, status, task);
}

inline void task_resume(AsyncTask *task) {
  auto id = os_signpost_id_make_with_pointer(TaskLog, task);
  os_signpost_interval_end(TaskLog, id, SWIFT_LOG_TASK_WAIT_NAME,
                           "task=%" PRId64 " taskPointer=%p",
                           task->getTaskId(), task);
}

inline void task_continuation_init(AsyncTask *task,
                                   ContinuationAsyncContext *context) {
  ENSURE_LOGS();
  auto id = os_signpost_id_make_with_pointer(TaskLog, context);
  os_signpost_interval_begin(TaskLog, id, SWIFT_LOG_TASK_CONTINUATION,
                             "task=%" PRId64 " context=%p taskPointer=%p",
                             task->getTaskId(), context, task);
}

inline void task_continuation_await(ContinuationAsyncContext *context) {
  ENSURE_LOGS();
  auto id = os_signpost_id_make_with_pointer(TaskLog, context);
  os_signpost_event_emit(TaskLog, id, SWIFT_LOG_TASK_CONTINUATION_AWAIT,
                         "context=%p", context);
}

inline void task_continuation_resume(ContinuationAsyncContext *context,
                                     bool error) {
  ENSURE_LOGS();
  auto id = os_signpost_id_make_with_pointer(TaskLog, context);
  os_signpost_interval_end(TaskLog, id, SWIFT_LOG_TASK_CONTINUATION,
                           "context=%p error=%{bool}d", context, error);
}

inline void job_enqueue_global(Job *job) {
  if (AsyncTask *task = dyn_cast<AsyncTask>(job)) {
    ENSURE_LOGS();
    auto id = os_signpost_id_make_with_pointer(TaskLog, job);
    os_signpost_event_emit(TaskLog, id, SWIFT_LOG_JOB_ENQUEUE_GLOBAL_NAME,
                           "task=%" PRId64 " taskPointer=%p",
                           task->getTaskId(), task);
  }
}

inline void job_enqueue_global_with_delay(unsigned long long delay, Job *job) {
  if (AsyncTask *task = dyn_cast<AsyncTask>(job)) {
    ENSURE_LOGS();
    auto id = os_signpost_id_make_with_pointer(TaskLog, job);
    os_signpost_event_emit(
        TaskLog, id, SWIFT_LOG_JOB_ENQUEUE_GLOBAL_WITH_DELAY_NAME,
        "task=%" PRId64 " delay=%llu taskPointer=%p",
        task->getTaskId(), delay, task);
  }
}

inline TracingExecutorKind classifyExecutor(SerialExecutorRef serialExecutor,
                                            TaskExecutorRef taskExecutor) {
  if (serialExecutor.isMainExecutor())
    return TracingExecutorKind::MainActor;
  if (serialExecutor.isDefaultActor())
    return TracingExecutorKind::DefaultActor;
  if (!serialExecutor.isGeneric())
    return TracingExecutorKind::CustomSerialExecutor;
  if (taskExecutor.isDefined())
    return TracingExecutorKind::TaskExecutor;
  return TracingExecutorKind::GlobalConcurrent;
}

inline void job_enqueue_executor(Job *job, SerialExecutorRef serialExecutor,
                                  TaskExecutorRef taskExecutor) {
  if (AsyncTask *task = dyn_cast<AsyncTask>(job)) {
    ENSURE_LOGS();
    HeapObject *executorIdentity =
        serialExecutor.isGeneric() ? nullptr : serialExecutor.getIdentity();
    auto metadata =
        executorIdentity ? swift_getObjectType(executorIdentity) : nullptr;
    auto descriptor = metadata
        ? (const void *)metadata->getTypeContextDescriptor() : nullptr;
    auto executorKind = classifyExecutor(serialExecutor, taskExecutor);
    auto id = os_signpost_id_make_with_pointer(TaskLog, job);
    os_signpost_event_emit(TaskLog, id, SWIFT_LOG_JOB_ENQUEUE_EXECUTOR_NAME,
                           "task=%" PRId64 " taskPointer=%p"
                           " executor=%p metadata=%p descriptor=%p"
                           " executorKind=%u",
                           task->getTaskId(), task, executorIdentity, metadata,
                           descriptor, (unsigned)executorKind);
  }
}

inline job_run_info job_run_begin(Job *job, SerialExecutorRef serialExecutor,
                                  TaskExecutorRef taskExecutor) {
  auto invalidInfo = []{
    return job_run_info{ 0, OS_SIGNPOST_ID_INVALID };
  };

  if (AsyncTask *task = dyn_cast<AsyncTask>(job)) {
    ENSURE_LOGS(invalidInfo());
    auto handle = os_signpost_id_generate(TaskLog);
    auto taskId = task->getTaskId();
    const char *taskName = task->getTaskName();

    HeapObject *actor = serialExecutor.isDefaultActor()
                            ? serialExecutor.getDefaultActor()
                            : nullptr;
    HeapObject *executorIdentity =
        serialExecutor.isGeneric() ? nullptr : serialExecutor.getIdentity();
    auto metadata =
        executorIdentity ? swift_getObjectType(executorIdentity) : nullptr;
    auto descriptor = metadata
        ? (const void *)metadata->getTypeContextDescriptor() : nullptr;
    auto executorKind = classifyExecutor(serialExecutor, taskExecutor);

    os_signpost_interval_begin(
        TaskLog, handle, SWIFT_LOG_JOB_RUN_NAME,
        "task=%" PRId64 " taskPointer=%p"
        " actor=%p executor=%p metadata=%p descriptor=%p "
        "taskName=%{public}s executorKind=%u",
        taskId, task, actor, executorIdentity, metadata, descriptor, taskName,
        (unsigned)executorKind);
    return { taskId, handle };
  }
  return invalidInfo();
}

inline void job_run_end(job_run_info info) {
  if (info.handle != OS_SIGNPOST_ID_INVALID) {
    ENSURE_LOGS();
    os_signpost_interval_end(TaskLog, info.handle, SWIFT_LOG_JOB_RUN_NAME,
                             "task=%" PRId64, info.taskId);
  }
}

inline void task_switch_executor(AsyncTask *task,
                                 SerialExecutorRef serialExecutor,
                                 TaskExecutorRef taskExecutor) {
  ENSURE_LOGS();
  HeapObject *executorIdentity =
      serialExecutor.isGeneric() ? nullptr : serialExecutor.getIdentity();
  auto metadata =
      executorIdentity ? swift_getObjectType(executorIdentity) : nullptr;
  auto descriptor =
      metadata ? (const void *)metadata->getTypeContextDescriptor() : nullptr;
  auto executorKind = classifyExecutor(serialExecutor, taskExecutor);
  auto id = os_signpost_id_make_with_pointer(TaskLog, task);
  os_signpost_event_emit(TaskLog, id, SWIFT_LOG_TASK_SWITCH_EXECUTOR_NAME,
                         "task=%" PRId64 " taskPointer=%p"
                         " executor=%p metadata=%p descriptor=%p"
                         " executorKind=%u",
                         task->getTaskId(), task, executorIdentity, metadata,
                         descriptor, (unsigned)executorKind);
}

#pragma clang diagnostic pop

} // namespace trace
} // namespace concurrency
} // namespace swift

#endif
