/*
    +--------------------------------------------------------------------+
    | libmemcached-awesome - C/C++ Client Library for memcached          |
    +--------------------------------------------------------------------+
    | Redistribution and use in source and binary forms, with or without |
    | modification, are permitted under the terms of the BSD license.    |
    | You should have received a copy of the license in a bundled file   |
    | named LICENSE; in case you did not receive a copy you can review   |
    | the terms online at: https://opensource.org/licenses/BSD-3-Clause  |
    +--------------------------------------------------------------------+
    | Copyright (c) 2006-2014 Brian Aker   https://datadifferential.com/ |
    | Copyright (c) 2020 Michael Wallner   <mike@php.net>                |
    +--------------------------------------------------------------------+
*/

#include "mem_config.h"

#include <inttypes.h>
#include "ms_stats.h"

#define array_size(x) (sizeof(x) / sizeof((x)[0]))

static int ms_local_log2(uint64_t value);
static uint64_t ms_get_events(ms_stat_t *stat);

/**
 * get the index of local log2 array
 *
 * @param value
 *
 * @return return the index of local log2 array
 */
static int ms_local_log2(uint64_t value) {
  int result = 0;

  while (result <= 63 && ((uint64_t) 1 << result) < value) {
    result++;
  }

  return result;
} /* ms_local_log2 */

/**
 * initialize statistic structure
 *
 * @param stat, pointer of the statistic structure
 * @param name, name of the statistic
 */
void ms_init_stats(ms_stat_t *stat, const char *name) {
  memset(stat, 0, sizeof(*stat));

  stat->name = (char *) name;
  stat->min_time = (uint64_t) -1;
  stat->max_time = 0;
  stat->period_min_time = (uint64_t) -1;
  stat->period_max_time = 0;
  stat->log_product = 0;
  stat->total_time = 0;
  stat->pre_total_time = 0;
  stat->squares = 0;
  stat->pre_squares = 0;
  stat->pre_events = 0;
  stat->pre_log_product = 0;
  stat->get_miss = 0;
  stat->pre_get_miss = 0;
} /* ms_init_stats */

/**
 * record one event
 *
 * @param stat, pointer of the statistic structure
 * @param total_time, response time of the command
 * @param get_miss, whether it gets miss
 */
void ms_record_event(ms_stat_t *stat, uint64_t total_time, int get_miss) {
  stat->total_time += total_time;

  if (total_time < stat->min_time) {
    stat->min_time = total_time;
  }

  if (total_time > stat->max_time) {
    stat->max_time = total_time;
  }

  if (total_time < stat->period_min_time) {
    stat->period_min_time = total_time;
  }

  if (total_time > stat->period_max_time) {
    stat->period_max_time = total_time;
  }

  if (get_miss) {
    stat->get_miss++;
  }

  stat->dist[ms_local_log2(total_time)]++;
  stat->squares += (double) (total_time * total_time);

  if (total_time) {
    stat->log_product += log((double) total_time);
  }
} /* ms_record_event */

/**
 * get the events count
 *
 * @param stat, pointer of the statistic structure
 *
 * @return total events recorded
 */
#if HAVE_TSAN
__attribute__ (( no_sanitize_thread, no_sanitize("thread")))
#endif
static uint64_t ms_get_events(ms_stat_t *stat) {
  uint64_t events = 0;

  for (uint32_t i = 0; i < array_size(stat->dist); i++) {
    events += stat->dist[i];
  }

  return events;
} /* ms_get_events */

/**
 * dump the statistics
 *
 * @param stat, pointer of the statistic structure
 */
void ms_dump_stats(ms_stat_t *stat) {
  uint64_t events = 0;
  int max_non_zero = 0;
  int min_non_zero = 0;
  double average = 0;

  for (uint32_t i = 0; i < array_size(stat->dist); i++) {
    events += stat->dist[i];
    if (stat->dist[i]) {
      max_non_zero = (int) i;
    }
  }

  if (events == 0) {
    return;
  }
  average = (double) (stat->total_time / events);

  printf("%s Statistics (%lld events)\n", stat->name, (long long) events);
  printf("   Min:  %8lld\n", (long long) stat->min_time);
  printf("   Max:  %8lld\n", (long long) stat->max_time);
  printf("   Avg:  %8lld\n", (long long) (stat->total_time / events));
  printf("   Geo:  %8.2lf\n", exp(stat->log_product / (double) events));

  if (events > 1) {
    printf("   Std:  %8.2lf\n",
           sqrt((stat->squares - (double) events * average * average) / ((double) events - 1)));
  }
  printf("   Log2 Dist:");

  for (int i = 0; i <= max_non_zero - 4; i += 4) {
    if ((stat->dist[i + 0]) || (stat->dist[i + 1]) || (stat->dist[i + 2])
        || (stat->dist[i + 3]))
    {
      min_non_zero = i;
      break;
    }
  }

  for (int i = min_non_zero; i <= max_non_zero; i++) {
    if ((i % 4) == 0) {
      printf("\n      %2d:", (int) i);
    }
    printf("   %6" PRIu64, stat->dist[i]);
  }

  printf("\n\n");
} /* ms_dump_stats */

/**
 * dump the format statistics
 *
 * @param stat, pointer of the statistic structure
 * @param run_time, the total run time
 * @param freq, statistic frequency
 * @param obj_size, average object size
 */
#if HAVE_TSAN
__attribute__ (( no_sanitize_thread, no_sanitize("thread")))
#endif
void ms_dump_format_stats(ms_stat_t *stat, int run_time, int freq, int obj_size) {
  uint64_t events = 0;
  double global_average = 0;
  uint64_t global_tps = 0;
  double global_rate = 0;
  double global_std = 0;
  double global_log = 0;

  double period_average = 0;
  uint64_t period_tps = 0;
  double period_rate = 0;
  double period_std = 0;
  double period_log = 0;

  if ((events = ms_get_events(stat)) == 0) {
    return;
  }

  global_average = (double) (stat->total_time / events);
  global_tps = events / (uint64_t) run_time;
  global_rate = (double) events * obj_size / 1024 / 1024 / run_time;
  global_std = sqrt((stat->squares - (double) events * global_average * global_average)
                    / (double) (events - 1));
  global_log = exp(stat->log_product / (double) events);

  uint64_t diff_time = stat->total_time - stat->pre_total_time;
  uint64_t diff_events = events - stat->pre_events;
  if (diff_events >= 1) {
    period_average = (double) (diff_time / diff_events);
    period_tps = diff_events / (uint64_t) freq;
    period_rate = (double) diff_events * obj_size / 1024 / 1024 / freq;
    double diff_squares = (double) stat->squares - (double) stat->pre_squares;
    period_std = sqrt((diff_squares - (double) diff_events * period_average * period_average)
                      / (double) (diff_events - 1));
    double diff_log_product = stat->log_product - stat->pre_log_product;
    period_log = exp(diff_log_product / (double) diff_events);
  }

  printf("%s Statistics\n", stat->name);
  printf("%-8s %-8s %-12s %-12s %-10s %-10s %-8s %-10s %-10s %-10s %-10s\n", "Type", "Time(s)",
         "Ops", "TPS(ops/s)", "Net(M/s)", "Get_miss", "Min(us)", "Max(us)", "Avg(us)", "Std_dev",
         "Geo_dist");

  printf("%-8s %-8d %-12llu %-12lld %-10.1f %-10lld %-8lld %-10lld %-10lld %-10.2f %.2f\n",
         "Period", freq, (long long) diff_events, (long long) period_tps, period_rate,
         (long long) (stat->get_miss - stat->pre_get_miss), (long long) stat->period_min_time,
         (long long) stat->period_max_time, (long long) period_average, period_std, period_log);

  printf("%-8s %-8d %-12llu %-12lld %-10.1f %-10lld %-8lld %-10lld %-10lld %-10.2f %.2f\n\n",
         "Global", run_time, (long long) events, (long long) global_tps, global_rate,
         (long long) stat->get_miss, (long long) stat->min_time, (long long) stat->max_time,
         (long long) global_average, global_std, global_log);

  stat->pre_events = events;
  stat->pre_squares = (uint64_t) stat->squares;
  stat->pre_total_time = stat->total_time;
  stat->pre_log_product = stat->log_product;
  stat->period_min_time = (uint64_t) -1;
  stat->period_max_time = 0;
  stat->pre_get_miss = stat->get_miss;
} /* ms_dump_format_stats */
