// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/globals.h"
#if defined(DART_HOST_OS_LINUX) && defined(SUPPORT_TIMELINE)

#include <errno.h>
#include <fcntl.h>
#include <cstdlib>

#include "platform/atomic.h"
#include "platform/signal_blocker.h"
#include "vm/isolate.h"
#include "vm/json_stream.h"
#include "vm/lockers.h"
#include "vm/log.h"
#include "vm/object.h"
#include "vm/service_event.h"
#include "vm/thread.h"
#include "vm/timeline.h"

namespace dart {

DECLARE_FLAG(bool, trace_timeline);

static int OpenTraceFD() {
  const char* kSystraceDebugPath = "/sys/kernel/debug/tracing/trace_marker";
  const char* kSystracePath = "/sys/kernel/tracing/trace_marker";

  int fd = TEMP_FAILURE_RETRY(::open(kSystracePath, O_WRONLY));
  if (fd < 0) {
    fd = TEMP_FAILURE_RETRY(::open(kSystraceDebugPath, O_WRONLY));
  }

  if (fd < 0 && FLAG_trace_timeline) {
    OS::PrintErr("TimelineEventSystraceRecorder: Could not open `%s` or `%s`\n",
                 kSystraceDebugPath, kSystracePath);
  }
  return fd;
}

TimelineEventSystraceRecorder::TimelineEventSystraceRecorder()
    : TimelineEventPlatformRecorder(), systrace_fd_(OpenTraceFD()) {
  Timeline::set_recorder_discards_clock_values(true);
}

TimelineEventSystraceRecorder::~TimelineEventSystraceRecorder() {
  if (systrace_fd_ >= 0) {
    close(systrace_fd_);
  }
}

intptr_t TimelineEventSystraceRecorder::PrintSystrace(TimelineEvent* event,
                                                      char* buffer,
                                                      intptr_t buffer_size) {
  ASSERT(buffer != nullptr);
  ASSERT(buffer_size > 0);
  buffer[0] = '\0';
  intptr_t length = 0;
  int64_t pid = OS::ProcessId();
  switch (event->event_type()) {
    case TimelineEvent::kBegin: {
      length = Utils::SNPrint(buffer, buffer_size, "B|%" Pd64 "|%s", pid,
                              event->label());
      break;
    }
    case TimelineEvent::kEnd: {
      length = Utils::SNPrint(buffer, buffer_size, "E");
      break;
    }
    case TimelineEvent::kCounter: {
      if (event->arguments_length() > 0) {
        // We only report the first counter value.
        length = Utils::SNPrint(buffer, buffer_size, "C|%" Pd64 "|%s|%s", pid,
                                event->label(), event->arguments()[0].value);
      }
      break;
    }
    case TimelineEvent::kAsyncBegin: {
      length = Utils::SNPrint(buffer, buffer_size, "S|%" Pd64 "|%s|%" Pd64 "",
                              pid, event->label(), event->Id());
      break;
    }
    case TimelineEvent::kAsyncEnd: {
      length = Utils::SNPrint(buffer, buffer_size, "F|%" Pd64 "|%s|%" Pd64 "",
                              pid, event->label(), event->Id());
      break;
    }
    default:
      // Ignore event types that we cannot serialize to the Systrace format.
      break;
  }
  return length;
}

void TimelineEventSystraceRecorder::OnEvent(TimelineEvent* event) {
  if (event == nullptr) {
    return;
  }
  if (systrace_fd_ < 0) {
    return;
  }

  // Serialize to the systrace format.
  const intptr_t kBufferLength = 1024;
  char buffer[kBufferLength];
  const intptr_t event_length = PrintSystrace(event, &buffer[0], kBufferLength);
  if (event_length > 0) {
    ssize_t result;
    // Repeatedly attempt the write while we are being interrupted.
    do {
      result = write(systrace_fd_, buffer, event_length);
    } while ((result == -1L) && (errno == EINTR));
  }
}

}  // namespace dart

#endif  // defined(DART_HOST_OS_LINUX) && !defined(PRODUCT)
