// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "tools/cygprofile/cygprofile.h"

#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>

#include <cstdio>
#include <fstream>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/containers/hash_tables.h"
#include "base/files/scoped_file.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"

namespace cygprofile {
namespace {

// Allow 8 MBytes of data for each thread log.
const int kMaxBufferSize = 8 * 1024 * 1024 / sizeof(LogEntry);

// Have the background internal thread do its flush every 15 sec.
const int kFlushThreadIdleTimeSec = 15;

const char kLogFileNamePrefix[] = "/data/local/tmp/chrome/cyglog/";

// "cyglog.PID.LWP.PPID"
const char kLogFilenameFormat[] = "%scyglog.%d.%d-%d";

// Magic value of above to prevent instrumentation. Used when ThreadLog is being
// constructed (to prevent reentering by malloc, for example) and by the flush
// log thread (to prevent it from being logged0.
ThreadLog* const kMagicBeingConstructed = reinterpret_cast<ThreadLog*>(1);

// Per-thread pointer to the current log object.
static __thread ThreadLog* g_tls_log = NULL;

// Returns light-weight process ID. On Linux, this is a system-wide unique
// thread id.
pid_t GetTID() {
  return syscall(__NR_gettid);
}

timespec GetCurrentTime() {
  timespec timestamp;
  clock_gettime(CLOCK_MONOTONIC, &timestamp);
  return timestamp;
}

// Sleeps for |sec| seconds.
void SleepSec(int sec) {
  for (int secs_to_sleep = sec; secs_to_sleep != 0;)
    secs_to_sleep = sleep(secs_to_sleep);
}

// Exposes the string header that will appear at the top of every trace file.
// This string contains memory mapping information for the mapped
// library/executable which is used offline during symbolization. Note that
// this class is meant to be instantiated once per process and lazily (during
// the first flush).
struct ImmutableFileHeaderLine {
  ImmutableFileHeaderLine() : value(MakeFileHeaderLine()) {}

  const std::string value;

 private:
  // Returns whether the integer representation of the hexadecimal address
  // stored in |line| at position |start_offset| was successfully stored in
  // |result|.
  static bool ParseAddress(const std::string& line,
                           off_t start_offset,
                           size_t length,
                           uint64* result) {
    if (start_offset >= line.length())
      return false;

    uint64 address;
    const bool ret = HexStringToUInt64(
        base::StringPiece(line.c_str() + start_offset, length), &address);
    if (!ret)
      return false;

    *result = address;
    return true;
  }

  // Parses /proc/self/maps and returns a two line string such as:
  // 758c6000-79f4b000 r-xp 00000000 b3:17 309475 libchrome.2009.0.so
  // secs    usecs   pid:threadid    func
  static std::string MakeFileHeaderLine() {
    std::ifstream mapsfile("/proc/self/maps");
    CHECK(mapsfile.good());
    std::string result;

    for (std::string line; std::getline(mapsfile, line); ) {
      if (line.find("r-xp") == std::string::npos)
        continue;

      const size_t address_length = line.find('-');
      uint64 start_address = 0;
      CHECK(ParseAddress(line, 0, address_length, &start_address));

      uint64 end_address = 0;
      CHECK(ParseAddress(line, address_length + 1, address_length,
                         &end_address));

      const uintptr_t current_func_addr = reinterpret_cast<uintptr_t>(
          &MakeFileHeaderLine);
      if (current_func_addr >= start_address &&
          current_func_addr < end_address) {
        result.swap(line);
        break;
      }
    }
    CHECK(!result.empty());
    result.append("\nsecs\tusecs\tpid:threadid\tfunc\n");
    return result;
  }
};

base::LazyInstance<ThreadLogsManager>::Leaky g_logs_manager =
    LAZY_INSTANCE_INITIALIZER;

base::LazyInstance<ImmutableFileHeaderLine>::Leaky g_file_header_line =
    LAZY_INSTANCE_INITIALIZER;

}  // namespace

// Custom thread implementation that joins on destruction. Note that
// base::Thread has non-trivial dependencies on e.g. AtExitManager which makes
// it hard to use it early.
class Thread {
 public:
  Thread(const base::Closure& thread_callback)
      : thread_callback_(thread_callback) {
    CHECK_EQ(0, pthread_create(&handle_, NULL, &Thread::EntryPoint, this));
  }

  ~Thread() {
    CHECK_EQ(0, pthread_join(handle_, NULL));
  }

 private:
  static void* EntryPoint(void* data) {
    // Disable logging on this thread. Although this routine is not instrumented
    // (cygprofile.gyp provides that), the called routines are and thus will
    // call instrumentation.
    CHECK(g_tls_log == NULL);  // Must be 0 as this is a new thread.
    g_tls_log = kMagicBeingConstructed;

    Thread* const instance = reinterpret_cast<Thread*>(data);
    instance->thread_callback_.Run();
    return NULL;
  }

  const base::Closure thread_callback_;
  pthread_t handle_;

  DISALLOW_COPY_AND_ASSIGN(Thread);
};

// Single log entry recorded for each function call.
LogEntry::LogEntry(const void* address)
    : time(GetCurrentTime()),
      pid(getpid()),
      tid(GetTID()),
      address(address) {
}

ThreadLog::ThreadLog()
  : tid_(GetTID()),
    in_use_(false),
    flush_callback_(
        base::Bind(&ThreadLog::FlushInternal, base::Unretained(this))) {
}

ThreadLog::ThreadLog(const FlushCallback& flush_callback)
  : tid_(GetTID()),
    in_use_(false),
    flush_callback_(flush_callback) {
}

ThreadLog::~ThreadLog() {
  g_tls_log = NULL;
}

void ThreadLog::AddEntry(void* address) {
  if (in_use_)
    return;
  in_use_ = true;

  CHECK_EQ(tid_, GetTID());
  const std::pair<std::hash_set<void*>::iterator, bool> pair =
      called_functions_.insert(address);
  const bool did_insert = pair.second;

  if (did_insert) {
    base::AutoLock auto_lock(lock_);
    entries_.push_back(LogEntry(address));
    // Crash in a quickly understandable way instead of crashing (or maybe not
    // though) due to OOM.
    CHECK_LE(entries_.size(), kMaxBufferSize);
  }

  in_use_ = false;
}

void ThreadLog::TakeEntries(std::vector<LogEntry>* destination) {
  base::AutoLock auto_lock(lock_);
  destination->swap(entries_);
  STLClearObject(&entries_);
}

void ThreadLog::Flush(std::vector<LogEntry>* entries) const {
  flush_callback_.Run(entries);
}

void ThreadLog::FlushInternal(std::vector<LogEntry>* entries) const {
  const std::string log_filename(
      base::StringPrintf(
          kLogFilenameFormat, kLogFileNamePrefix, getpid(), tid_, getppid()));
  const base::ScopedFILE file(fopen(log_filename.c_str(), "a"));
  CHECK(file.get());

  const long offset = ftell(file.get());
  if (offset == 0)
    fprintf(file.get(), "%s", g_file_header_line.Get().value.c_str());

  for (std::vector<LogEntry>::const_iterator it = entries->begin();
       it != entries->end(); ++it) {
    fprintf(file.get(), "%ld %ld\t%d:%ld\t%p\n", it->time.tv_sec,
            it->time.tv_nsec / 1000, it->pid, it->tid, it->address);
  }

  STLClearObject(entries);
}

ThreadLogsManager::ThreadLogsManager()
    : wait_callback_(base::Bind(&SleepSec, kFlushThreadIdleTimeSec)) {
}

ThreadLogsManager::ThreadLogsManager(const base::Closure& wait_callback,
                                     const base::Closure& notify_callback)

    : wait_callback_(wait_callback),
      notify_callback_(notify_callback) {
}

ThreadLogsManager::~ThreadLogsManager() {
  // Note that the internal thread does some work until it sees |flush_thread_|
  // = NULL.
  scoped_ptr<Thread> flush_thread;
  {
    base::AutoLock auto_lock(lock_);
    flush_thread_.swap(flush_thread);
  }
  flush_thread.reset();  // Joins the flush thread.

  STLDeleteContainerPointers(logs_.begin(), logs_.end());
}

void ThreadLogsManager::AddLog(scoped_ptr<ThreadLog> new_log) {
  base::AutoLock auto_lock(lock_);

  if (logs_.empty())
    StartInternalFlushThread_Locked();

  logs_.push_back(new_log.release());
}

void ThreadLogsManager::StartInternalFlushThread_Locked() {
  lock_.AssertAcquired();
  CHECK(!flush_thread_);
  // Note that the |flush_thread_| joins at destruction which guarantees that it
  // will never outlive |this|, i.e. it's safe not to use ref-counting.
  flush_thread_.reset(
      new Thread(base::Bind(&ThreadLogsManager::FlushAllLogsOnFlushThread,
                            base::Unretained(this))));
}

// Type used below for flushing.
struct LogData {
  LogData(ThreadLog* thread_log) : thread_log(thread_log) {}

  ThreadLog* const thread_log;
  std::vector<LogEntry> entries;
};

void ThreadLogsManager::FlushAllLogsOnFlushThread() {
  while (true) {
    {
      base::AutoLock auto_lock(lock_);
      // The |flush_thread_| field is reset during destruction.
      if (!flush_thread_)
        return;
    }
    // Sleep for a few secs and then flush all thread's buffers. There is a
    // danger that, when quitting Chrome, this thread may see unallocated data
    // and segfault. We do not care because we need logs when Chrome is working.
    wait_callback_.Run();

    // Copy the ThreadLog pointers to avoid acquiring both the logs manager's
    // lock and the one for individual thread logs.
    std::vector<ThreadLog*> thread_logs_copy;
    {
      base::AutoLock auto_lock(lock_);
      thread_logs_copy = logs_;
    }

    // Move the logs' data before flushing them so that the mutexes are not
    // acquired for too long.
    std::vector<LogData> logs;
    for (std::vector<ThreadLog*>::const_iterator it =
             thread_logs_copy.begin();
         it != thread_logs_copy.end(); ++it) {
      ThreadLog* const thread_log = *it;
      LogData log_data(thread_log);
      logs.push_back(log_data);
      thread_log->TakeEntries(&logs.back().entries);
    }

    for (std::vector<LogData>::iterator it = logs.begin();
         it != logs.end(); ++it) {
      if (!it->entries.empty())
        it->thread_log->Flush(&it->entries);
    }

    if (!notify_callback_.is_null())
      notify_callback_.Run();
  }
}

extern "C" {

// The GCC compiler callbacks, called on every function invocation providing
// addresses of caller and callee codes.
void __cyg_profile_func_enter(void* this_fn, void* call_site)
    __attribute__((no_instrument_function));
void __cyg_profile_func_exit(void* this_fn, void* call_site)
    __attribute__((no_instrument_function));

void __cyg_profile_func_enter(void* this_fn, void* callee_unused) {
  if (g_tls_log == NULL) {
    g_tls_log = kMagicBeingConstructed;
    ThreadLog* new_log = new ThreadLog();
    CHECK(new_log);
    g_logs_manager.Pointer()->AddLog(make_scoped_ptr(new_log));
    g_tls_log = new_log;
  }

  if (g_tls_log != kMagicBeingConstructed)
    g_tls_log->AddEntry(this_fn);
}

void __cyg_profile_func_exit(void* this_fn, void* call_site) {}

}  // extern "C"
}  // namespace cygprofile