/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */


#ifndef PAGESPEED_KERNEL_BASE_STATISTICS_H_
#define PAGESPEED_KERNEL_BASE_STATISTICS_H_

#include <map>

#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/thread_annotations.h"

namespace net_instaweb {

class MessageHandler;
class Statistics;
class StatisticsLogger;
class Writer;

// Variables can normally only be increased, not decreased.  However, for
// testing, They can also be Cleared.
//
// TODO(jmarantz): consider renaming this to Counter or maybe UpCounter.
class Variable {
 public:
  virtual ~Variable();

  virtual int64 Get() const = 0;
  // Return some name representing the variable, provided that the specific
  // implementation has some sensible way of doing so.
  virtual StringPiece GetName() const = 0;

  // Adds 'delta' to the variable's value, returning the result.
  int64 Add(int64 non_negative_delta) {
    DCHECK_LE(0, non_negative_delta);
    return AddHelper(non_negative_delta);
  }

  virtual void Clear() = 0;

 protected:
  // This is virtual so that subclasses can add platform-specific atomicity.
  virtual int64 AddHelper(int64 delta) = 0;
};

// UpDownCounters are variables that can also be decreased (e.g. Add
// of a negative number) or Set to an arbitrary value.
//
// TODO(jmarantz): Make this not inherit from Variable, which will simplify the
// 'CheckNotNegative' and its ifndefs, but will require us to do more accurate
// type bookkeeping in tests, etc.
//
// TODO(jmarantz): consider renaming Variable->Counter, UpDownCounter->Variable.
class UpDownCounter {
 public:
  virtual ~UpDownCounter();

  virtual int64 Get() const = 0;
  // Return some name representing the variable, provided that the specific
  // implementation has some sensible way of doing so.
  virtual StringPiece GetName() const = 0;

  // Sets the specified value, returning the previous value.  This can be
  // used to by two competing threads/processes to determine which thread
  // modified the value first.  The default implementation is non-atomic,
  // but implementations can override to provide an atomic version.
  //
  // Non-atomic implementations may result in multiple concurrent updates
  // each returning the old value.  In an atomic implementation, only one
  // concurrent update will return the old value.
  virtual int64 SetReturningPreviousValue(int64 value);

  virtual void Set(int64 value) = 0;
  void Clear() { Set(0); }
  int64 Add(int64 delta) { return AddHelper(delta); }

 protected:
  // This is virtual so that subclasses can add platform-specific atomicity.
  virtual int64 AddHelper(int64 delta) = 0;
};

// Scalar value protected by a mutex. Mutex must fully protect access
// to underlying scalar. For example, in mod_pagespeed and
// ngx_pagespeed, variables are stored in shared memory and accessible
// from any process on a machine, so the mutex must provide protection
// across separate processes.
//
// StatisticsLogger depends upon these mutexes being cross-process so that
// several processes using the same file system don't clobber each others logs.
//
// TODO(jmarantz): this could be done using templates rather than using virtual
// methods, as MutexedScalar does not implement an abstract interface.
class MutexedScalar {
 public:
  virtual ~MutexedScalar();

  // Subclasses should not define these methods, instead define the *LockHeld()
  // methods below.
  int64 Get() const;
  void Set(int64 value);
  int64 SetReturningPreviousValue(int64 value);
  int64 AddHelper(int64 delta);

 protected:
  friend class StatisticsLogger;

  virtual AbstractMutex* mutex() const = 0;

  // Get/Setters that may only be called if you already hold the mutex.
  virtual int64 GetLockHeld() const = 0;
  virtual int64 SetReturningPreviousValueLockHeld(int64 value) = 0;

  // These are implemented based on GetLockHeld() and
  // SetReturningPreviousLockHeld().
  void SetLockHeld(int64 value);
  int64 AddLockHeld(int64 delta);
};

class Histogram {
 public:
  virtual ~Histogram();
  // Record a value in its bucket.
  virtual void Add(double value) = 0;
  // Throw away all data.
  virtual void Clear() = 0;
  // True if the histogram is empty.
  bool Empty() {
    ScopedMutex hold(lock());
    return CountInternal() == 0;
  }
  // Write Histogram Data to the writer.
  // Default implementation does not include histogram graph, but only raw
  // histogram data table. It looks like:
  // ________________________________________
  // |  TITLE String                         |
  // |  Avg: StdDev: Median: 90%: 95%: 99%   |
  // |  Raw Histogram Data:                  |
  // |  [0,1] 1 25% 25%  |||||               |
  // |  [2,3] 1 25% 50%  |||||               |
  // |  [4,5] 2 50% 100% ||||||||||          |
  // |_______________________________________|
  virtual void Render(int index, Writer* writer, MessageHandler* handler);

  // Returns number of buckets the histogram actually has.
  virtual int NumBuckets() = 0;
  // Allow histogram have negative values.
  virtual void EnableNegativeBuckets() = 0;
  // Set the minimum value allowed in histogram.
  virtual void SetMinValue(double value) = 0;
  // Set the value upper-bound of a histogram,
  // the value range in histogram is [MinValue, MaxValue) or
  // [-MaxValue, MaxValue) if enabled negative buckets.
  virtual void SetMaxValue(double value) = 0;

  // Set the suggested number of buckets for the histogram. The implementation
  // may chose to use a somewhat different number.
  virtual void SetSuggestedNumBuckets(int i) = 0;

  // Returns average of the values added.
  double Average() {
    ScopedMutex hold(lock());
    return AverageInternal();
  }
  // Return estimated value that is greater than perc% of all data.
  // e.g. Percentile(20) returns the value which is greater than
  // 20% of data.
  double Percentile(const double perc) {
    ScopedMutex hold(lock());
    return PercentileInternal(perc);
  }
  double StandardDeviation() {
    ScopedMutex hold(lock());
    return StandardDeviationInternal();
  }
  double Count() {
    ScopedMutex hold(lock());
    return CountInternal();
  }
  double Maximum() {
    ScopedMutex hold(lock());
    return MaximumInternal();
  }
  double Minimum() {
    ScopedMutex hold(lock());
    return MinimumInternal();
  }
  double Median() {
    return Percentile(50);
  }

  // Formats the histogram statistics as an HTML table row.  This
  // is intended for use in Statistics::RenderHistograms.
  //
  // The <tr> element id is given id=hist_row_%d where %d is from the index.
  // Included in the row an input radio button which is initiated in state
  // 'selected' for index==0.
  GoogleString HtmlTableRow(const GoogleString& title, int index);

  // Lower bound of a bucket. If index == NumBuckets() + 1, returns the
  // upper bound of the histogram. DCHECK if index is in the range of
  // [0, NumBuckets()+1].
  virtual double BucketStart(int index) = 0;
  // Upper bound of a bucket.
  virtual double BucketLimit(int index) {
    return BucketStart(index + 1);
  }
  // Value of a bucket.
  virtual double BucketCount(int index) = 0;

 protected:
  Histogram() {}

  // Note that these *Internal interfaces require the mutex to be held.
  virtual double AverageInternal() = 0;
  virtual double PercentileInternal(const double perc) = 0;
  virtual double StandardDeviationInternal() = 0;
  virtual double CountInternal() = 0;
  virtual double MaximumInternal() = 0;
  virtual double MinimumInternal() = 0;

  virtual AbstractMutex* lock() = 0;

  // Helper function of Render(), write entries of histogram raw data table.
  // Each entry includes bucket range, bucket count, percentage,
  // cumulative percentage, bar. It looks like:
  // [0,1] 1 5%  5%  ||||
  // [2,3] 2 10% 15% ||||||||
  // Precondition: mutex held.
  void WriteRawHistogramData(Writer* writer, MessageHandler* handler);

 private:
  DISALLOW_COPY_AND_ASSIGN(Histogram);
};

// Trivial implementation. But Count() returns a meaningful value.
class CountHistogram : public Histogram {
 public:
  // Takes ownership of mutex.
  explicit CountHistogram(AbstractMutex* mutex);
  virtual ~CountHistogram();
  virtual void Add(double value) {
    ScopedMutex hold(lock());
    ++count_;
  }
  virtual void Clear() {
    ScopedMutex hold(lock());
    count_ = 0;
  }
  virtual int NumBuckets() { return 0; }
  virtual void EnableNegativeBuckets() { }
  virtual void SetMinValue(double value) { }
  virtual void SetMaxValue(double value) { }
  virtual void SetSuggestedNumBuckets(int i) { }
  virtual GoogleString GetName() const { return ""; }

 protected:
  virtual AbstractMutex* lock() LOCK_RETURNED(mutex_) { return mutex_.get(); }
  virtual double AverageInternal() { return 0.0; }
  virtual double PercentileInternal(const double perc) { return 0.0; }
  virtual double StandardDeviationInternal() { return 0.0; }
  virtual double CountInternal() EXCLUSIVE_LOCKS_REQUIRED(lock()) {
    return count_;
  }
  virtual double MaximumInternal() { return 0.0; }
  virtual double MinimumInternal() { return 0.0; }
  virtual double BucketStart(int index) { return 0.0; }
  virtual double BucketCount(int index) { return 0.0; }

 private:
  scoped_ptr<AbstractMutex> mutex_;
  int count_ GUARDED_BY(mutex_);

  DISALLOW_COPY_AND_ASSIGN(CountHistogram);
};

// TimedVariable is a statistic class returns the amount added in the
// last interval, which could be last 10 seconds, last minute
// last one hour and total.
class TimedVariable {
 public:
  // The intervals for which we keep stats.
  enum Levels { TENSEC, MINUTE, HOUR, START };
  virtual ~TimedVariable();
  // Update the stat value. delta is in milliseconds.
  virtual void IncBy(int64 delta) = 0;
  // Get the amount added over the last time interval
  // specified by "level".
  virtual int64 Get(int level) = 0;
  // Throw away all data.
  virtual void Clear() = 0;
};

// TimedVariable implementation that only updates a basic UpDownCounter.
class FakeTimedVariable : public TimedVariable {
 public:
  FakeTimedVariable(StringPiece name, Statistics* stats);
  virtual ~FakeTimedVariable();
  // Update the stat value. delta is in milliseconds.
  virtual void IncBy(int64 delta) {
    var_->Add(delta);
  }
  // Get the amount added over the last time interval
  // specified by "level".
  virtual int64 Get(int level) {
    // This is a default implementation. Variable can only return the
    // total value. This should be override in subclass if we want the
    // values for different levels.
    if (level == START) {
      return var_->Get();
    }
    return 0;
  }
  // Throw away all data.
  virtual void Clear() {
    return var_->Clear();
  }

 protected:
  Variable* var_;
};

// Base class for implementations of monitoring statistics.
class Statistics {
 public:
  // Default group for use with AddTimedVariable.
  static const char kDefaultGroup[];

  Statistics() {}
  virtual ~Statistics();

  // Add a new variable, or returns an existing one of that name.
  // The UpDownCounter* is owned by the Statistics class -- it should
  // not be deleted by the caller.
  virtual UpDownCounter* AddUpDownCounter(const StringPiece& name) = 0;

  // Like AddVariable, but asks the implementation to scope the variable to the
  // entire process, even if statistics are generally partitioned by domains or
  // the like. Default implementation simply forwards to AddVariable.
  virtual UpDownCounter* AddGlobalUpDownCounter(const StringPiece& name);

  // Find a variable from a name, returning NULL if not found.
  virtual UpDownCounter* FindUpDownCounter(const StringPiece& name) const = 0;

  // Find a variable from a name, aborting if not found.
  UpDownCounter* GetUpDownCounter(const StringPiece& name) const {
    UpDownCounter* var = FindUpDownCounter(name);
    CHECK(var != NULL) << "UpDownCounter not found: " << name;
    return var;
  }

  // Add a new variable, or returns an existing one of that name.
  // The Variable* is owned by the Statistics class -- it should
  // not be deleted by the caller.
  virtual Variable* AddVariable(const StringPiece& name) = 0;

  // Find a variable from a name, returning NULL if not found.
  virtual Variable* FindVariable(const StringPiece& name) const = 0;

  // Find a variable from a name, aborting if not found.
  Variable* GetVariable(const StringPiece& name) const {
    Variable* var = FindVariable(name);
    CHECK(var != NULL) << "Variable not found: " << name;
    return var;
  }


  // Add a new histogram, or returns an existing one of that name.
  // The Histogram* is owned by the Statistics class -- it should not
  // be deleted by the caller.
  virtual Histogram* AddHistogram(const StringPiece& name) = 0;
  // Find a histogram from a name, returning NULL if not found.
  virtual Histogram* FindHistogram(const StringPiece& name) const = 0;
  // Find a histogram from a name, aborting if not found.
  Histogram* GetHistogram(const StringPiece& name) const {
    Histogram* hist = FindHistogram(name);
    CHECK(hist != NULL) << "Histogram not found: " << name;
    return hist;
  }

  // Add a new TimedVariable, or returns an existing one of that name.
  // The TimedVariable* is owned by the Statistics class -- it should
  // not be deleted by the caller. Each stat belongs to a group, such as
  // "Statistics", "Disk Statistics", etc.
  virtual TimedVariable* AddTimedVariable(
      const StringPiece& name, const StringPiece& group) = 0;
  // Find a TimedVariable from a name, returning NULL if not found.
  virtual TimedVariable* FindTimedVariable(
      const StringPiece& name) const = 0;
  // Find a TimedVariable from a name, aborting if not found.
  TimedVariable* GetTimedVariable(
      const StringPiece& name) const {
    TimedVariable* stat = FindTimedVariable(name);
    CHECK(stat != NULL) << "TimedVariable not found: " << name;
    return stat;
  }
  // Return the names of all the histograms for render.
  virtual const StringVector& HistogramNames() = 0;
  // Return the map of groupnames and names of all timedvariables for render.
  virtual const std::map<GoogleString, StringVector>& TimedVariableMap() = 0;
  // Dump the variable-values to a writer.
  virtual void Dump(Writer* writer, MessageHandler* handler) = 0;
  // Dump the variable-values in JSON format to a writer.
  virtual void DumpJson(Writer* writer, MessageHandler* message_handler) = 0;
  virtual void RenderTimedVariables(Writer* writer,
                                    MessageHandler* handler);
  // Write all the histograms in this Statistic object to a writer.
  virtual void RenderHistograms(Writer* writer, MessageHandler* handler);
  // Set all variables to 0.
  // Throw away all data in histograms and stats.
  virtual void Clear() = 0;

  // This is implemented as NULL here because most Statistics don't
  // need it. In the context in which it is needed we only have access to a
  // Statistics*, rather than the specific subclass, hence its being here.
  // Return the StatisticsLogger associated with this Statistics.
  virtual StatisticsLogger* console_logger() { return NULL; }

  // Testing helper method to look up a statistics numeric value by name.
  // Please do not use this in production code.  This finds the current
  // value whether it is stored in a Variable, UpDownCounter, or TimedVariable.
  //
  // If the statistics is not found, the program check-fails.
  int64 LookupValue(StringPiece stat_name);

 private:
  DISALLOW_COPY_AND_ASSIGN(Statistics);
};

}  // namespace net_instaweb

#endif  // PAGESPEED_KERNEL_BASE_STATISTICS_H_
