// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
// Copyright (c) 2007, Google Inc.
// 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.
//
// ---
// Author: Sanjay Ghemawat
//         Chris Demetriou (refactoring)
//
// Collect profiling data.

#include <config.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/time.h>
#include <string.h>
#include <fcntl.h>

#include "profiledata.h"

#include "base/logging.h"
#include "base/proc_maps_iterator.h"
#include "base/sysinfo.h"

// All of these are initialized in profiledata.h.
const int ProfileData::kMaxStackDepth;
const int ProfileData::kAssociativity;
const int ProfileData::kBuckets;
const int ProfileData::kBufferLength;

ProfileData::Options::Options()
    : frequency_(1) {
}

// This function is safe to call from asynchronous signals (but is not
// re-entrant).  However, that's not part of its public interface.
void ProfileData::Evict(const Entry& entry) {
  const int d = entry.depth;
  const int nslots = d + 2;     // Number of slots needed in eviction buffer
  if (num_evicted_ + nslots > kBufferLength) {
    FlushEvicted();
    assert(num_evicted_ == 0);
    assert(nslots <= kBufferLength);
  }
  evict_[num_evicted_++] = entry.count;
  evict_[num_evicted_++] = d;
  memcpy(&evict_[num_evicted_], entry.stack, d * sizeof(Slot));
  num_evicted_ += d;
}

ProfileData::ProfileData()
    : hash_(0),
      evict_(0),
      num_evicted_(0),
      out_(-1),
      count_(0),
      evictions_(0),
      total_bytes_(0),
      fname_(0),
      start_time_(0) {
}

bool ProfileData::Start(const char* fname,
                        const ProfileData::Options& options) {
  if (enabled()) {
    return false;
  }

  // Open output file and initialize various data structures
  int fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, 0666);
  if (fd < 0) {
    // Can't open outfile for write
    return false;
  }

  start_time_ = time(nullptr);
  fname_ = strdup(fname);

  // Reset counters
  num_evicted_ = 0;
  count_       = 0;
  evictions_   = 0;
  total_bytes_ = 0;

  hash_ = new Bucket[kBuckets];
  evict_ = new Slot[kBufferLength];
  memset(hash_, 0, sizeof(hash_[0]) * kBuckets);

  // Record special entries
  evict_[num_evicted_++] = 0;                     // count for header
  evict_[num_evicted_++] = 3;                     // depth for header
  evict_[num_evicted_++] = 0;                     // Version number
  CHECK_NE(0, options.frequency());
  int period = 1000000 / options.frequency();
  evict_[num_evicted_++] = period;                // Period (microseconds)
  evict_[num_evicted_++] = 0;                     // Padding

  out_ = fd;

  return true;
}

ProfileData::~ProfileData() {
  Stop();
}

// Dump /proc/maps data to fd.  Copied from heap-profile-table.cc.
#define NO_INTR(fn)  do {} while ((fn) < 0 && errno == EINTR)

static void FDWrite(int fd, const char* buf, size_t len) {
  while (len > 0) {
    ssize_t r;
    NO_INTR(r = write(fd, buf, len));
    RAW_CHECK(r >= 0, "write failed");
    buf += r;
    len -= r;
  }
}

void ProfileData::Stop() {
  if (!enabled()) {
    return;
  }

  // Move data from hash table to eviction buffer
  for (int b = 0; b < kBuckets; b++) {
    Bucket* bucket = &hash_[b];
    for (int a = 0; a < kAssociativity; a++) {
      if (bucket->entry[a].count > 0) {
        Evict(bucket->entry[a]);
      }
    }
  }

  if (num_evicted_ + 3 > kBufferLength) {
    // Ensure there is enough room for end of data marker
    FlushEvicted();
  }

  // Write end of data marker
  evict_[num_evicted_++] = 0;         // count
  evict_[num_evicted_++] = 1;         // depth
  evict_[num_evicted_++] = 0;         // end of data marker
  FlushEvicted();

  // Dump "/proc/self/maps" so we get list of mapped shared libraries
  tcmalloc::SaveProcSelfMapsToRawFD(static_cast<RawFD>(out_));

  Reset();
  fprintf(stderr, "PROFILE: interrupts/evictions/bytes = %d/%d/%zu\n",
          count_, evictions_, total_bytes_);
}

void ProfileData::Reset() {
  if (!enabled()) {
    return;
  }

  // Don't reset count_, evictions_, or total_bytes_ here.  They're used
  // by Stop to print information about the profile after reset, and are
  // cleared by Start when starting a new profile.
  close(out_);
  delete[] hash_;
  hash_ = 0;
  delete[] evict_;
  evict_ = 0;
  num_evicted_ = 0;
  free(fname_);
  fname_ = 0;
  start_time_ = 0;

  out_ = -1;
}

// This function is safe to call from asynchronous signals (but is not
// re-entrant).  However, that's not part of its public interface.
void ProfileData::GetCurrentState(State* state) const {
  if (enabled()) {
    state->enabled = true;
    state->start_time = start_time_;
    state->samples_gathered = count_;
    int buf_size = sizeof(state->profile_name);
    strncpy(state->profile_name, fname_ ? fname_ : "", buf_size);
    state->profile_name[buf_size-1] = '\0';
  } else {
    state->enabled = false;
    state->start_time = 0;
    state->samples_gathered = 0;
    state->profile_name[0] = '\0';
  }
}

// This function is safe to call from asynchronous signals (but is not
// re-entrant).  However, that's not part of its public interface.
void ProfileData::FlushTable() {
  if (!enabled()) {
    return;
  }

  // Move data from hash table to eviction buffer
  for (int b = 0; b < kBuckets; b++) {
    Bucket* bucket = &hash_[b];
    for (int a = 0; a < kAssociativity; a++) {
      if (bucket->entry[a].count > 0) {
        Evict(bucket->entry[a]);
        bucket->entry[a].depth = 0;
        bucket->entry[a].count = 0;
      }
    }
  }

  // Write out all pending data
  FlushEvicted();
}

void ProfileData::Add(int depth, const void* const* stack) {
  if (!enabled()) {
    return;
  }

  if (depth > kMaxStackDepth) depth = kMaxStackDepth;
  RAW_CHECK(depth > 0, "ProfileData::Add depth <= 0");

  // Make hash-value
  Slot h = 0;
  for (int i = 0; i < depth; i++) {
    Slot slot = reinterpret_cast<Slot>(stack[i]);
    h = (h << 8) | (h >> (8*(sizeof(h)-1)));
    h += (slot * 31) + (slot * 7) + (slot * 3);
  }

  count_++;

  // See if table already has an entry for this trace
  bool done = false;
  Bucket* bucket = &hash_[h % kBuckets];
  for (int a = 0; a < kAssociativity; a++) {
    Entry* e = &bucket->entry[a];
    if (e->depth == depth) {
      bool match = true;
      for (int i = 0; i < depth; i++) {
        if (e->stack[i] != reinterpret_cast<Slot>(stack[i])) {
          match = false;
          break;
        }
      }
      if (match) {
        e->count++;
        done = true;
        break;
      }
    }
  }

  if (!done) {
    // Evict entry with smallest count
    Entry* e = &bucket->entry[0];
    for (int a = 1; a < kAssociativity; a++) {
      if (bucket->entry[a].count < e->count) {
        e = &bucket->entry[a];
      }
    }
    if (e->count > 0) {
      evictions_++;
      Evict(*e);
    }

    // Use the newly evicted entry
    e->depth = depth;
    e->count = 1;
    for (int i = 0; i < depth; i++) {
      e->stack[i] = reinterpret_cast<Slot>(stack[i]);
    }
  }
}

// This function is safe to call from asynchronous signals (but is not
// re-entrant).  However, that's not part of its public interface.
void ProfileData::FlushEvicted() {
  if (num_evicted_ > 0) {
    const char* buf = reinterpret_cast<char*>(evict_);
    size_t bytes = sizeof(evict_[0]) * num_evicted_;
    total_bytes_ += bytes;
    FDWrite(out_, buf, bytes);
  }
  num_evicted_ = 0;
}
