/*                                             -*- mode:C++; fill-column:100 -*-
  SFBTicker.h - A basic 'where do the usec go?' profiling system
  Copyright (C) 2009 The Regents of the University of New Mexico.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA
*/

/**
  \file SFBTicker.h A basic 'where do the cycles go?' profiling system.
  \author David H. Ackley.  
  \date (C) 2009 All rights reserved.
  \lgpl
  \since 0.9.13
 */
#ifndef SFBTICKER_H_
#define SFBTICKER_H_

#include "SFBTypes.h"
#include "SFBHWPins.h"
#include "SFBHWMisc.h"          // For suspendInterrupts

/**
   The categories used by #Ticker and #Profile to record time usage.

   \since 0.9.13
 */
enum SFBTickerCategories {
  TICKER_BACKGROUND,      ///< All time not otherwise categorized, including time used by the
                          ///  #setup() and #loop() functions
  TICKER_SERIAL_ISR,      ///< Time on interrupt-level serial I/O, moving raw bytes between the
                          ///  packet buffers and the serial hardware buffers
  TICKER_OTHER_ISR,       ///< Non-serial interrupt time, including overhead on all interrupts, the
                          ///  core timer ISR, and any sketch ISR's such as used with #SFBHWTimer
  TICKER_PACKET_HANDLER,  ///< Time in packet handlers, core and sketch

  TICKER_ALARM_HANDLER,   ///< Time in alarm handlers, core and sketch

  TICKER_SKETCH0,         ///< Time flagged by the sketch as 'SKETCH0' using SFBTicker::pushTime()
                          ///  and SFBTicker::popTime()
  TICKER_SKETCH1,         ///< Time flagged by the sketch as 'SKETCH1' using SFBTicker::pushTime()
                          ///  and SFBTicker::popTime()
  TICKER_SKETCH2,         ///< Time flagged by the sketch as 'SKETCH2' using SFBTicker::pushTime()
                          ///  and SFBTicker::popTime()
  MAX_TICKERS
};

/**
   The short-term, high-resolution profiling information maintained by the core.  (See #SFBProfile
   for longer-term, lower-resolution data).  The core gathers microsecond-resolution data about
   where time is being spent, and assigns each microsecond to one of the #SFBTickerCategories as
   appropriate.  There is no 'double billing'; for example, microseconds spent in the #loop()
   function are assigned to #TICKER_BACKGROUND, but if #loop() calls #delay() which in turn calls,
   say, a packet handler, then the microseconds taken by that packet handler are assigned to
   #TICKER_PACKET_HANDLER and not to #TICKER_BACKGROUND.  Similarly, microseconds spent at
   interrupt level is assigned only to #TICKER_SERIAL_ISR or #TICKER_OTHER_ISR, not also to
   whatever the system was doing when the interrupt hit.
    
   The #SFBTicker system is designed to be lightweight while supporting microsecond accuracy, so
   all accounting is performed in \c u32, which means that internal profiling counters may begin to
   overflow after only a bit over an hour of runtime.  In many cases it may be more convenient to
   use the #SFBProfile mechanism in concert with #SFBTicker, which provides millisecond resolution
   valid over a month and half of runtime.
   
   \since 0.9.13
 */
class SFBTicker {
 public:

  /**
     Begin assigning time to \a code -- which should be one of the #SFBTickerCategories -- while
     using an internal stack to remember where time was previously being assigned.  From sketch
     level, #pushTime() should generally only be used with \a code values of #TICKER_SKETCH0,
     #TICKER_SKETCH1, or #TICKER_SKETCH2, to begin assigning time to one of those categories which
     are reserved for sketch purposes.

     Note that the internal stack used by #pushTime and #popTime can contain up to ten entries
     before it will overflow, and up to three entries may already be in use when sketch code is
     entered.  For proper accuracy, sketch code should avoid nesting #pushTime() calls deeper than
     five or six levels.  Overflowing the #SFBTicker stack will cause the profiling results to be
     incorrect but will not overwise affect or harm execution.

     \param code The #SFBTickerCategories value to use.  Note that for efficiency, #pushTime does
                 \e not die blinking if an invalid \a code is passed; instead, the passed value is
                 converted to some valid #SFBTickerCategories by an unspecified mechanism.
   */
  inline void pushTime(u32 code) {
    u32 var;
    suspendInterrupts(var);
    pushTimeISR(code);
    restoreInterrupts(var);
  }

  /**
     Undo the effect of the most recent #pushTime().  Stop assigning time to the category
     most-recently passed to #pushTime(), and resume assigning time to whatever category had
     been active before that.

     For correct results, #pushTime() and #popTime() calls must be properly matched.  An #popTime()
     call without a corresponding #pushTime(), or a #popTime() when the internal stack is empty,
     will cause the profiling results to be incorrect but will not overwise affect or harm
     execution.
   */
  inline void popTime() {
    u32 var;
    suspendInterrupts(var);
    popTimeISR();
    restoreInterrupts(var);
  }

  /**
     Can be used instead of #pushTime(u32) when the caller is \e certain that interrupts are
     already disabled.  If this method is used when interrupts are enabled, Ticker results (sooner
     or later) will be incorrect.
   */
  inline void pushTimeISR(u32 code) {
    u32 was = top();
    push(code);
    switchCode(was,top());
  }

  /**
     Can be used instead of #popTime() when the caller is \e certain that interrupts are already
     disabled.  If this method is used when interrupts are enabled, Ticker results (sooner or
     later) will be incorrect.
   */
  inline void popTimeISR() {
    u32 was = top();
    pop();
    switchCode(was,top());
  }

  /**
     Return the number of microseconds assigned to \a category since the last #reset().  This value
     may be inaccurate if more than about an hour has elapsed since the last call on #reset().

     \param category The #SFBTickerCategories category to access.

     \blinks #E_API_MAX_RANGE if \a category is not one of the #SFBTickerCategories values,
                              0..MAX_TICKERS-1

   */
  u32 getUsec(u32 category) ;

  /**
     Return the total number of microseconds since the last call on #reset.  This value will be
     inaccurate if more than about an hour has elapsed since the last #reset().
   */
  u32 getTotalUsec() { return micros()-startUsec; }

  /**
     Reset all profiling information, setting all profiling counts to zero.  The profiling stack is
     reinitialized as if an indefinite number of \c pushTime(TICKER_BACKGROUND) calls had been
     performed followed by a \c pushTime(intoState) call, so if it is desired to reset the #Ticker
     from inside an alarm or packet handler, then \c Ticker.reset(TICKER_ALARM_HANDLER) or \c
     Ticker.reset(TICKER_PACKET_HANDLER), respectively, will produce the most accurate counts.  

     \param intoState (Optional, default value #TICKER_BACKGROUND) The #SFBTickerCategories
                      category to place on the top of the Ticker stack.

     \blinks #E_API_MAX_RANGE if \a intoState is not one of the #SFBTickerCategories values,
                              0..MAX_TICKERS-1

   */
  void reset(u32 intoState = TICKER_BACKGROUND) ;

private:
  u32 totalUsec[MAX_TICKERS];
  u32 enterUsec[MAX_TICKERS];
  u32 startUsec;
  u32 stack;
  enum {
    BITS = 3,
    MASK = (1<<BITS)-1
  };
  inline u32 crop(u32 code) { return code&MASK; }
  inline void push(u32 code) { stack = (stack<<BITS)|crop(code); }
  inline void pop() { stack >>= BITS; }
  inline u32 top() { return crop(stack); }
  inline void switchCode(u32 from, u32 to) {
    u32 now = micros();
    totalUsec[from] += now - enterUsec[from]; 
    enterUsec[to] = now;
  }
};

/**
   The short-term, high-resolution profiling system object.  See #Profile for access to longer-term,
   lower-resolution profiling data.
 */
extern SFBTicker Ticker;

/**
   A profiling system providing millisecond resolution for over a month's worth of runtime.  Note
   that unlike #Ticker, which is an integral part of the core software, use of this #SFBProfile
   class (via its associated object #Profile) is \e optional, and it will not be included in builds
   unless your sketch code refers to it.  If you \e do make use of the #Profile, it will cost you 40
   bytes of RAM and one alarm from the #Alarms pool.

   To initialize the system, just call \c Profile.begin() from the #setup() function.  The #begin()
   method can be called again to clear and reinitialize the #SFBProfile if desired, to get 'fresh
   statistics' (or if your sketch runtime is exceeding about a month).

   While the #Profile is running, it will automatically gather data from the #Ticker as needed.  In
   addition, the #Profile \e automatically \e resets the #Ticker about every half hour, to keep it
   from overflowing.  Consequently, while the #Profile is being used, the sketch \e must \e not call
   SFBTicker::reset() itself, and calls to SFBTicker::getUsec() will return values relative to the
   time of the last #Profile-induced reset.  

   The sketch may call #end() to deactivate the #SFBProfile system prior to interacting directly with
   the #Ticker.

 */
class SFBProfile {
public:

  /**
     Return the number of milliseconds assigned to \a tickerCode.  This value may be inaccurate if
     more than forty-nine days have elapsed since the (last) call on #begin().

     \param tickerCode The #SFBTickerCategories category to access.

     \blinks #E_API_MAX_RANGE if \a category is not one of the #SFBTickerCategories values,
                              0..MAX_TICKERS-1

   */
  u32 getMsec(u32 tickerCode) ;

  /**
     Return the \e fraction of time assigned to \a tickerCode, in parts per million.  This value
     may be inaccurate if more than forty-nine days have elapsed since the (last) call on #begin().

     This value can be turned into a percentage by dividing by 10,000.  Calling #getPPM() on all
     categories and summing the results will generally produce a value slightly smaller than
     1,000,000, due to truncation.

     \param tickerCode The #SFBTickerCategories category to access.

     \returns a number from 0 to 1,000,000 representing how many millionths of the total time were
              assigned to this category.

     \blinks #E_API_MAX_RANGE if \a category is not one of the #SFBTickerCategories values,
                              0..MAX_TICKERS-1

   */
  u32 getPPM(u32 tickerCode) ;

  /**
     Return the total number of milliseconds since the (last) call on #begin.  This value will be
     inaccurate if more than about forty-nine days have elapsed since the last #begin().
   */
  u32 getTotalMsec() ;

  /**
     Initialize or reinitialize the system, clearing all counts.
   */
  void begin() ;

  /**
     Stop the #SFBProfile system.  When stopped, the #SFBProfile system will not reset the
     #Ticker object, so it can safely be manipulated by direct #SFBTicker calls.
   */
  void end() ;

private:
  friend void updateProfile(u32 when, void * arg);
  void update(u32 when);
  u32 totalMsec[MAX_TICKERS];
  u32 totalTime;
  u8 alarmNumber;
};

/**
   The longer-term, lower-resolution profiling system object.  See #Ticker for access to
   shorter-term, higher-resolution profiling data.
 */
extern SFBProfile Profile;
#endif /* SFBTICKER_H_ */
