anchor.diff:  %% 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.
  +%%
  +%% @doc Store and aggregate statistics collected from memcached.
  +%%
   
   -module(stats_archiver).
   
  
positive.code: 
  -define(NUM_SAMPLES, 61).
  
  -record(state, {bucket, samples}).
  
  -export([start_link/1,
           latest/2, latest/3, latest/4,
  
negative.code:     {reply, ringbuffer:to_list(N, Samples), State}.
  
  
  handle_cast({stats, Sample}, State = #state{bucket=Bucket, table=Tab}) ->
      ok = mnesia:activity(transaction,
                           fun () -> mnesia:write(Tab, Sample, write) end),
      gen_event:notify(ns_stats_event, {sample_archived, Bucket, Sample}),
      {noreply, State}.
  
  
  handle_info(unhandled, unhandled) ->
      unhandled.
  
  
  terminate(_Reason, _State) ->
      ok.
  
  
  %%
  %% Internal functions
  %%
  
  %% @doc Compute the average of a list of entries.
  %% Timestamp is taken from the first entry.
  avg(Samples) ->
      [First|Rest] = Samples,
      {TS, FirstList} = stat_to_list(First),
      Sums = lists:foldl(fun (E, Acc) ->
                                 {_, L} = stat_to_list(E),
                                 lists:zipwith(fun (A, B) -> A + B end, L, Acc)
                         end, FirstList, Rest),
      Count = length(Samples),
      Avgs = [X / Count || X <- Sums],
      list_to_stat(TS, Avgs).
  
  
  create_table(Bucket) ->
      TableName = table_name(Bucket),
      case mnesia:create_table(TableName,
                               [{disc_copies, [node()]},
                                {record_name, stat_entry},
                                {type, ordered_set},
                                {attributes, record_info(fields, stat_entry)}]) of
          {atomic, ok} ->
              ok;
          {aborted, {already_exists, _}} ->
              ok
      end,
      TableName.
  
  
  %% @doc Convert a list of values from stat_to_list back to a stat entry.
  list_to_stat(TS, List) ->
      list_to_tuple([stat_entry, TS | List]).
  
  
  %% @doc Pass received stats to the archiver.
  received(Pid, Stats) ->
      gen_server:cast(Pid, {stats, Stats}).
  
  
  %% @doc Convert a stat entry to a list of values.
  stat_to_list(Entry) ->
      [stat_entry, TS | L] = tuple_to_list(Entry),
      {TS, L}.
  
  
  %% @doc Truncate a timestamp to a multiple of the given number of seconds.
  ts_trunc(Resolution, TS) ->
      {MegaSecs, Secs, _} = TS,
      MegaSecs * 1000000 + trunc(Secs / Resolution) * Resolution.
  
  
  %% @doc Generate a suitable name for the per-bucket gen_server.
  server(Bucket) ->
      list_to_atom(?MODULE_STRING ++ "-" ++ Bucket).
  
  %% @doc Generate a suitable name for the Mnesia table.
  table_name(Bucket) ->
      list_to_atom(lists:flatten(io_lib:format("~s-~s-~s", [?MODULE_STRING,
                                                            node(), Bucket]))).
  