/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef tools_profiler_MemoryProfiler_h
#define tools_profiler_MemoryProfiler_h

#include "nsIMemoryProfiler.h"

#include "mozilla/StaticPtr.h"
#include "mozilla/TimeStamp.h"

#include "CompactTraceTable.h"
#include "nsTArray.h"
#include "prlock.h"

#define MEMORY_PROFILER_CID                                     \
  { 0xf976eaa2, 0xcc1f, 0x47ee,                                 \
    { 0x81, 0x29, 0xb8, 0x26, 0x2a, 0x3d, 0xb6, 0xb2 } }

#define MEMORY_PROFILER_CONTRACT_ID "@mozilla.org/tools/memory-profiler;1"

struct PRLock;

namespace mozilla {

class NativeProfilerImpl;
class GCHeapProfilerImpl;

struct ProfilerForJSContext
{
  ProfilerForJSContext()
    : mProfiler(nullptr)
    , mEnabled(false)
  {}
  GCHeapProfilerImpl* mProfiler;
  bool mEnabled;
};
using JSContextProfilerMap =
  nsDataHashtable<nsClearingPtrHashKey<JSContext>, ProfilerForJSContext>;

class MemoryProfiler final : public nsIMemoryProfiler
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIMEMORYPROFILER

private:
  static void InitOnce();
  ~MemoryProfiler() {}

  // The accesses to other static member are guarded by sLock and
  // sProfileContextCount.
  static PRLock* sLock;
  static uint32_t sProfileContextCount;

  static StaticAutoPtr<NativeProfilerImpl> sNativeProfiler;
  static StaticAutoPtr<JSContextProfilerMap> sJSContextProfilerMap;
  static TimeStamp sStartTime;
};

// Allocation events to be reported.
struct AllocEvent {
  TimeStamp mTimestamp;
  // index to a stacktrace singleton.
  uint32_t mTraceIdx;
  // Allocation size
  int32_t mSize;

  AllocEvent(uint32_t aTraceIdx, int32_t aSize, TimeStamp aTimestamp)
    : mTimestamp(aTimestamp)
    , mTraceIdx(aTraceIdx)
    , mSize(aSize)
  {}
};

// Index to allocation events but also a mark bit to be GC-able.
struct AllocEntry {
  uint32_t mEventIdx : 31;
  bool mMarked : 1;

  // Default constructor for uninitialized stack value required by
  // getter methods.
  AllocEntry()
    : mEventIdx(0)
    , mMarked(false)
  {}
  explicit AllocEntry(int aEventIdx)
    : mEventIdx(aEventIdx)
    , mMarked(false)
  {}
};

using AllocMap = nsDataHashtable<nsClearingVoidPtrHashKey, AllocEntry>;

class ProfilerImpl
{
public:
  static nsTArray<nsCString> GetStacktrace();
  static double DRandom();

  ProfilerImpl();
  virtual nsTArray<nsCString> GetNames() const = 0;
  virtual nsTArray<TrieNode> GetTraces() const = 0;
  virtual const nsTArray<AllocEvent>& GetEvents() const = 0;

protected:
  /**
   * The sampler generates a random variable which conforms to a geometric
   * distribution of probability p = 1 / mSampleSize to calculate the
   * next-to-be-sampled byte directly; It avoids rolling a dice on each byte.
   *
   * Let Bn denote a Bernoulli process with first success on n-th trial, the
   * cumulative distribution function of Bn is Cn = 1 - (1 - p) ^ n.
   * Let U denote a uniformly distributed random variable in [0, 1).
   * A geometric random variable can be generated by Cn's reverse function:
   * G = floor(log(1 - U) / log(1 - p)).
   *
   * @param aSize the number of bytes seen
   * @return the number of events sampled
   */
  size_t AddBytesSampled(uint32_t aBytes);

  uint32_t mSampleSize;

private:
  uint32_t mRemainingBytes;
  double mLog1minusP;
};

/*
 * This class is used to make sure the profile data is only accessed
 * on one thread at a time. Don't use mozilla::Mutex because we don't
 * want to allocate memory.
 */
class AutoMPLock
{
public:
  explicit AutoMPLock(PRLock* aLock)
  {
    MOZ_ASSERT(aLock);
    mLock = aLock;
    PR_Lock(mLock);
  }

  ~AutoMPLock()
  {
    PR_Unlock(mLock);
  }

private:
  PRLock* mLock;
};

} // namespace mozilla

#endif