/** @file

  A brief file description

  @section license License

  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.
 */

/****************************************************************************

  StatSystem.cc --
  Created On          : Fri Apr 3 19:41:39 1998
 ****************************************************************************/
#include "Main.h"
#include "StatSystem.h"
#include "P_EventSystem.h"
#include "Error.h"
#include "ProcessManager.h"
#include "ProxyConfig.h"
#include "StatPages.h"
#include "HTTP.h"
#include "I_Layout.h"

// defines

#define SNAP_USAGE_PERIOD         HRTIME_SECONDS(2)


// variables

#ifdef DEBUG
ink_mutex http_time_lock;
time_t last_http_local_time;
#endif
ink_stat_lock_t global_http_trans_stat_lock;
ink_unprot_global_stat_t global_http_trans_stats[MAX_HTTP_TRANS_STATS];
#ifndef USE_LOCKS_FOR_DYN_STATS
inkcoreapi ink_unprot_global_stat_t global_dyn_stats[MAX_DYN_STATS - DYN_STAT_START];
#else
inkcoreapi ink_prot_global_stat_t global_dyn_stats[MAX_DYN_STATS - DYN_STAT_START];
#endif

Ptr<ProxyMutex> rusage_snap_mutex;
struct rusage rusage_snap;
struct rusage rusage_snap_old;
ink_hrtime rusage_snap_time;
ink_hrtime rusage_snap_time_old;
int snap_stats_every = 60;

ink_hrtime http_handler_times[MAX_HTTP_HANDLER_EVENTS];
int http_handler_counts[MAX_HTTP_HANDLER_EVENTS];


char snap_filename[PATH_NAME_MAX+1] = DEFAULT_SNAP_FILENAME;

#define DEFAULT_PERSISTENT

#ifndef DEFAULT_PERSISTENT
static int persistent_stats[] = {
  http_incoming_requests_stat
};
#else
static int non_persistent_stats[] = {
  ////////////////////////////
  // Start of Cluster stats
  ////////////////////////////
  cluster_connections_open_stat,
  cluster_connections_openned_stat,
  cluster_con_total_time_stat,
  cluster_ctrl_msgs_sent_stat,
  cluster_slow_ctrl_msgs_sent_stat,
  cluster_ctrl_msgs_recvd_stat,
  cluster_slow_ctrl_msgs_recvd_stat,
  cluster_ctrl_msgs_send_time_stat,
  cluster_ctrl_msgs_recv_time_stat,
  cluster_read_bytes_stat,
  cluster_write_bytes_stat,
  cluster_op_delayed_for_lock_stat,
  cluster_connections_locked_stat,
  cluster_connections_bumped_stat,
  cluster_nodes_stat,
  cluster_net_backup_stat,
  cluster_machines_allocated_stat,
  cluster_machines_freed_stat,
  cluster_configuration_changes_stat,
  cluster_delayed_reads_stat,
  cluster_byte_bank_used_stat,
  cluster_alloc_data_news_stat,
  cluster_write_bb_mallocs_stat,
  cluster_partial_reads_stat,
  cluster_partial_writes_stat,
  cluster_cache_outstanding_stat,
  cluster_remote_op_timeouts_stat,
  cluster_remote_op_reply_timeouts_stat,
  cluster_chan_inuse_stat,
  cluster_open_delays_stat,
  cluster_open_delay_time_stat,
  cluster_cache_callbacks_stat,
  cluster_cache_callback_time_stat,
  cluster_cache_rmt_callbacks_stat,
  cluster_cache_rmt_callback_time_stat,
  cluster_cache_lkrmt_callbacks_stat,
  cluster_cache_lkrmt_callback_time_stat,
  cluster_thread_steal_expires_stat,
  cluster_local_connections_closed_stat,
  cluster_local_connection_time_stat,
  cluster_remote_connections_closed_stat,
  cluster_remote_connection_time_stat,
  cluster_rdmsg_assemble_time_stat,
  cluster_ping_time_stat,
  cluster_setdata_no_clustervc_stat,
  cluster_setdata_no_tunnel_stat,
  cluster_setdata_no_cachevc_stat,
  cluster_setdata_no_cluster_stat,
  cluster_vc_write_stall_stat,
  cluster_no_remote_space_stat,
  cluster_level1_bank_stat,
  cluster_multilevel_bank_stat,
  cluster_vc_cache_insert_lock_misses_stat,
  cluster_vc_cache_inserts_stat,
  cluster_vc_cache_lookup_lock_misses_stat,
  cluster_vc_cache_lookup_hits_stat,
  cluster_vc_cache_lookup_misses_stat,
  cluster_vc_cache_scans_stat,
  cluster_vc_cache_scan_lock_misses_stat,
  cluster_vc_cache_purges_stat,
  cluster_write_lock_misses_stat,
  /////////////////////////////////////
  // Start of Scheduled Update stats
  /////////////////////////////////////
  // DNS
  //dns_success_time_stat
};
#endif

#define _HEADER \
DynamicStatsString_t DynamicStatsStrings[] = {

#define _FOOTER };
#define _D(_x) { _x, #_x },

#include "DynamicStats.h"
#undef _HEADER
#undef _FOOTER
#undef _D



// functions

static int
persistent_stat(int i)
{
#ifndef DEFAULT_PERSISTENT
  for (unsigned j = 0; j < countof(persistent_stats); j++)
    if (persistent_stats[j] == i)
      return 1;
  return 0;
#else
  for (unsigned j = 0; j < countof(non_persistent_stats); j++)
    if (non_persistent_stats[j] == i)
      return 0;
  return 1;
#endif
}

static int
open_stats_snap()
{
  int fd = socketManager.open(snap_filename,
                              O_CREAT | O_RDWR | _O_ATTRIB_NORMAL);
  if (fd < 0) {
    Warning("unable to open %s: %s", snap_filename, strerror(-fd));
    return -1;
  }
  return fd;
}

static void
clear_stats()
{
  int i = 0;

  int stats_size = MAX_HTTP_TRANS_STATS - NO_HTTP_TRANS_STATS - 1;
  for (i = 0; i < stats_size; i++) {
    if (persistent_stat(i + NO_HTTP_TRANS_STATS)) {
      global_http_trans_stats[i].sum = 0;
      global_http_trans_stats[i].count = 0;
    }
  }
  stats_size = MAX_DYN_STATS - NO_DYN_STATS - 1;
  for (i = 0; i < stats_size; i++) {
    if (persistent_stat(i + NO_DYN_STATS)) {
      global_dyn_stats[i].sum = 0;
      global_dyn_stats[i].count = 0;
    }
  }

  socketManager.unlink(snap_filename);
  Debug("stats", "clear_stats: clearing statistics");
}

static void
read_stats_snap()
{
  unsigned int version;
  unsigned int version_read;
  int count;
  int fd = -1;
  int i = 0;
  int stats_size = -1;

  version = STATS_MAJOR_VERSION;

  if ((fd = open_stats_snap()) < 0)
    goto Lmissmatch;

  // read and verify snap
  if (socketManager.read(fd, (char *) &version_read, sizeof(version_read))
      != sizeof(version_read))
    goto Lmissmatch;
  if (version != version_read)
    goto Lmissmatch;
  stats_size = MAX_HTTP_TRANS_STATS - NO_HTTP_TRANS_STATS + MAX_DYN_STATS - NO_DYN_STATS;
  if (socketManager.read(fd, (char *) &count, sizeof(count)) != sizeof(count))
    goto Lmissmatch;
  if (count != stats_size)
    goto Lmissmatch;

  stats_size = MAX_HTTP_TRANS_STATS - NO_HTTP_TRANS_STATS;
  for (i = 0; i < stats_size; i++) {
    if (socketManager.read(fd, (char *) &global_http_trans_stats[i].sum, sizeof(global_http_trans_stats[i].sum))
        != sizeof(global_http_trans_stats[i].sum))
      goto Lmissmatch;
    if (socketManager.read(fd, (char *) &global_http_trans_stats[i].count, sizeof(global_http_trans_stats[i].count))
        != sizeof(global_http_trans_stats[i].count))
      goto Lmissmatch;
  }
  stats_size = MAX_DYN_STATS - NO_DYN_STATS;
  for (i = 0; i < stats_size; i++) {
    if (socketManager.read(fd, (char *) &global_dyn_stats[i].sum, sizeof(global_dyn_stats[i].sum))
        != sizeof(global_dyn_stats[i].sum))
      goto Lmissmatch;
    if (socketManager.read(fd, (char *) &global_dyn_stats[i].count, sizeof(global_dyn_stats[i].count))
        != sizeof(global_dyn_stats[i].count))
      goto Lmissmatch;
  }
  Debug("stats", "read_stats_snap: read statistics");

  // close(fd);
  socketManager.close(fd);
  return;

Lmissmatch:
  Note("clearing statistics");
  clear_stats();
  //close(fd);
  socketManager.close(fd);
}

static void
write_stats_snap()
{
  int fd = 0;
  int version = STATS_MAJOR_VERSION;
  char *buf = NULL;

  if ((fd = open_stats_snap()) < 0) {
    Warning("unable to snap statistics");
    return;
  }

  {
    int stats_size = MAX_HTTP_TRANS_STATS - NO_HTTP_TRANS_STATS + MAX_DYN_STATS - NO_DYN_STATS;
    int buf_size = sizeof(unsigned int) * 3 + stats_size * (sizeof(global_dyn_stats[0].sum) + sizeof(global_dyn_stats[0].count));
    buf = (char *)ats_malloc(buf_size);
    char *p = buf;
    int i = 0;

    memcpy(p, (char *) &version, sizeof(version));
    p += sizeof(version);
    memcpy(p, (char *) &stats_size, sizeof(stats_size));
    p += sizeof(stats_size);

    stats_size = MAX_HTTP_TRANS_STATS - NO_HTTP_TRANS_STATS;
    STAT_LOCK_ACQUIRE(&(global_http_trans_stat_lock));
    for (i = 0; i < stats_size; i++) {
      memcpy(p, (char *) &global_http_trans_stats[i].sum, sizeof(global_http_trans_stats[i].sum));
      p += sizeof(global_http_trans_stats[i].sum);
      memcpy(p, (char *) &global_http_trans_stats[i].count, sizeof(global_http_trans_stats[i].count));
      p += sizeof(global_http_trans_stats[i].count);
    }
    STAT_LOCK_RELEASE(&(global_http_trans_stat_lock));
    stats_size = MAX_DYN_STATS - NO_DYN_STATS;
    for (i = 0; i < stats_size; i++) {
      // INKqa09981 (Clearing Host Database and DNS Statistics)
      ink_statval_t count, sum;
      READ_GLOBAL_DYN_STAT(i, count, sum);
      memcpy(p, (char *) &sum, sizeof(sum));
      p += sizeof(sum);
      memcpy(p, (char *) &count, sizeof(count));
      p += sizeof(count);
    }
    memcpy(p, (char *) &version, sizeof(version));

    if (socketManager.write(fd, buf, buf_size) != buf_size) {
      Warning("unable to snap statistics");
      ats_free(buf);
      socketManager.close(fd);
      return;
    }
  }
  ats_free(buf);
  socketManager.close(fd);
  Debug("stats", "snapped stats");
}

struct SnapStatsContinuation: public Continuation
{
  int mainEvent(int /* event ATS_UNUSED */, Event *e ATS_UNUSED)
  {
    write_stats_snap();
    e->schedule_every(HRTIME_SECONDS(snap_stats_every));
    return EVENT_CONT;
  }
  SnapStatsContinuation():Continuation(new_ProxyMutex())
  {
    SET_HANDLER(&SnapStatsContinuation::mainEvent);
  }
};

static void
take_rusage_snap()
{
  rusage_snap_old = rusage_snap;
  rusage_snap_time_old = rusage_snap_time;
  int retries = 3;
  while (retries--) {
    if (getrusage(RUSAGE_SELF, &rusage_snap) < 0) {
      if (errno == EINTR)
        continue;
      Note("getrusage [%d %s]", errno, strerror(errno));
    } else
      rusage_snap_time = ink_get_hrtime();
    break;
  }
  Debug("rusage", "took rusage snap %" PRId64"", rusage_snap_time);
}

struct SnapCont;
typedef int (SnapCont::*SnapContHandler) (int, void *);

struct SnapCont: public Continuation
{
  int mainEvent(int /* event ATS_UNUSED */, Event * e)
  {
    take_rusage_snap();
    e->schedule_every(SNAP_USAGE_PERIOD);
    return EVENT_CONT;
  }
  SnapCont(ProxyMutex * m):Continuation(m)
  {
    SET_HANDLER((SnapContHandler) & SnapCont::mainEvent);
  }
};

void
start_stats_snap()
{
  eventProcessor.schedule_every(new SnapCont(rusage_snap_mutex), SNAP_USAGE_PERIOD, ET_CALL);
  if (snap_stats_every)
    eventProcessor.schedule_every(new SnapStatsContinuation(), HRTIME_SECONDS(snap_stats_every), ET_CALL);
  else
    Warning("disabling statistics snap");
}

static Action *
stat_callback(Continuation * cont, HTTPHdr * header)
{
  URL *url;
  int length;
  const char *path;
  char *result = NULL;
  int result_size;
  bool empty;

  url = header->url_get();
  path = url->path_get(&length);

  char *buffer = NULL;
  int buffer_len = 0;
  int num_prefix_buffer;

  char *var_prefix = (char *)alloca((length + 1) * sizeof(char));

  memset(var_prefix, 0, ((length + 1) * sizeof(char)));
  if (path && length > 0)
    ink_strlcpy(var_prefix, path, length + 1);

  num_prefix_buffer = RecGetRecordPrefix_Xmalloc(var_prefix, &buffer, &buffer_len);
  empty = (num_prefix_buffer == 0);

  if (!empty) {
    result_size = (buffer_len + 16) * sizeof(char);
    result = (char *)ats_malloc(result_size);
    memset(result, 0, result_size);

    snprintf(result, result_size - 7, "<pre>\n%s", buffer);
  }


  if (!empty) {
    StatPageData data;

    ink_strlcat(result, "</pre>\n", result_size);

    data.data = result;
    data.length = strlen(result);
    cont->handleEvent(STAT_PAGE_SUCCESS, &data);
  } else {
    ats_free(result);
    cont->handleEvent(STAT_PAGE_FAILURE, NULL);
  }
  ats_free(buffer);

  return ACTION_RESULT_DONE;
}

static Action *
testpage_callback(Continuation * cont, HTTPHdr *)
{
  const int buf_size = 64000;
  char *buffer = (char *)ats_malloc(buf_size);

  for (int i = 0; i < buf_size; i++) {
    buffer[i] = (char) ('a' + (i % 26));
  }
  buffer[buf_size - 1] = '\0';

  StatPageData data;

  data.data = buffer;
  data.length = strlen(buffer);
  cont->handleEvent(STAT_PAGE_SUCCESS, &data);

  return ACTION_RESULT_DONE;
}

static void
testpage_callback_init()
{
  statPagesManager.register_http("test", testpage_callback);
}

void
initialize_all_global_stats()
{
  int istat, i;
  char snap_file[PATH_NAME_MAX + 1];
  ats_scoped_str rundir(RecConfigReadRuntimeDir());

  if (access(rundir, R_OK | W_OK) == -1) {
    Warning("Unable to access() local state directory '%s': %d, %s", (const char *)rundir, errno, strerror(errno));
    Warning(" Please set 'proxy.config.local_state_dir' to allow statistics collection");
  }
  REC_ReadConfigString(snap_file, "proxy.config.stats.snap_file", PATH_NAME_MAX);
  Layout::relative_to(snap_filename, sizeof(snap_filename), (const char *)rundir, snap_file);
  Debug("stats", "stat snap filename %s", snap_filename);

  statPagesManager.register_http("stat", stat_callback);

  testpage_callback_init();

  read_stats_snap();
  rusage_snap_mutex = new_ProxyMutex();
  take_rusage_snap();
  take_rusage_snap();           // fill in _old as well

  STAT_LOCK_INIT(&(global_http_trans_stat_lock), "Global Http Stats Lock");

  for (istat = NO_HTTP_TRANS_STATS; istat < MAX_HTTP_TRANS_STATS; istat++) {
    if (!persistent_stat(istat)) {
      INITIALIZE_GLOBAL_TRANS_STATS(global_http_trans_stats[istat]);
    }
  }

  for (istat = NO_DYN_STATS; istat < MAX_DYN_STATS; istat++) {
    if (!persistent_stat(istat)) {
      i = istat - DYN_STAT_START;
      INITIALIZE_GLOBAL_DYN_STATS(global_dyn_stats[i], "Dyn Stat Lock");
    }
  }

  // TODO: HMMMM, wtf does this do? The following is that this
  // function does:
  // ink_atomic_swap(&this->f_update_lock, (void *) func)
  //
  // pmgmt->record_data->registerUpdateLockFunc(tmp_stats_lock_function);

#ifdef DEBUG
  ink_mutex_init(&http_time_lock, "Http Time Function Lock");
  last_http_local_time = 0;
#endif

  clear_http_handler_times();
}

//void *
//tmp_stats_lock_function(UpdateLockAction action)
//{
//  stats_lock_function((void *) (&global_http_trans_stat_lock), action);
//
//  return NULL;
//}

//void *
//stats_lock_function(void *data, UpdateLockAction action)
//{
//  if (action == UPDATE_LOCK_ACQUIRE) {
//    STAT_LOCK_ACQUIRE((ink_stat_lock_t *) data);
//  } else {
//    STAT_LOCK_RELEASE((ink_stat_lock_t *) data);
//  }
//
//  return NULL;
//}

void *
dyn_stats_int_msecs_to_float_seconds_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_DYN_STAT((long) data, count, sum);

  float r;
  if (count == 0) {
    r = 0.0;
  } else {
    r = ((float) sum) / 1000.0;
  }
  *(float *) res = r;
  return res;
}

void *
dyn_stats_count_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_DYN_STAT((long) data, count, sum);
  (void)sum;
  ink_atomic_swap((ink_statval_t *) res, count);
  return res;
}

void *
dyn_stats_sum_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_DYN_STAT((long) data, count, sum);
  (void)count;
  ink_atomic_swap((ink_statval_t *) res, sum);
  return res;
}

void *
dyn_stats_avg_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_DYN_STAT((long) data, count, sum);
  if (count == 0) {
    *(float *) res = 0.0;
  } else {
    *(float *) res = (float) sum / (float) count;
  }
  return res;
}

void *
dyn_stats_fsum_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_DYN_STAT((long) data, count, sum);
  (void)count;
  *(float *) res = (double) sum;
  return res;
}

void *
dyn_stats_favg_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_DYN_STAT((long) data, count, sum);
  if (count == 0) {
    *(float *) res = 0.0;
  } else {
    *(float *) res = (double) sum / (double) count;
  }
  return res;
}

void *
dyn_stats_time_seconds_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  float r;
  READ_DYN_STAT((long) data, count, sum);
  if (count == 0) {
    r = 0.0;
  } else {
    r = (float) sum / (float) count;
    r = r / (float) HRTIME_SECOND;
  }
  *(float *) res = r;
  return res;
}

void *
dyn_stats_time_mseconds_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  float r;
  READ_DYN_STAT((long) data, count, sum);
  if (count == 0) {
    r = 0.0;
  } else {
    r = (float) sum / (float) count;
    r = r / (float) HRTIME_MSECOND;
  }
  *(float *) res = r;
  return res;
}

void *
dyn_stats_time_useconds_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  float r;
  READ_DYN_STAT((long) data, count, sum);
  if (count == 0) {
    r = 0.0;
  } else {
    r = (float) sum / (float) count;
    r = r / (float) HRTIME_USECOND;
  }
  *(float *) res = r;
  return res;
}

// http trans stat functions
// there is the implicit assumption that the lock has
// been acquired.
void *
http_trans_stats_int_msecs_to_float_seconds_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_HTTP_TRANS_STAT((long) data, count, sum);

  float r;
  if (count == 0) {
    r = 0.0;
  } else {
    r = ((float) sum) / 1000.0;
  }
  *(float *) res = r;
  return res;
}

void *
http_trans_stats_count_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_HTTP_TRANS_STAT((long) data, count, sum);
  (void)sum;
  ink_atomic_swap((ink_statval_t *) res, count);
  return res;
}

void *
http_trans_stats_sum_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_HTTP_TRANS_STAT((long) data, count, sum);
  (void)count;
  ink_atomic_swap((ink_statval_t *) res, sum);
  return res;
}

void *
http_trans_stats_avg_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_HTTP_TRANS_STAT((long) data, count, sum);
  if (count == 0) {
    *(float *) res = 0.0;
  } else {
    *(float *) res = (float) sum / (float) count;
  }
  return res;
}

void *
http_trans_stats_fsum_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_HTTP_TRANS_STAT((long) data, count, sum);
  (void)count;
  *(float *) res = (double) sum;
  return res;
}

void *
http_trans_stats_favg_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  READ_HTTP_TRANS_STAT((long) data, count, sum);
  if (count == 0) {
    *(float *) res = 0.0;
  } else {
    *(float *) res = (double) sum / (double) count;
  }
  return res;
}

void *
http_trans_stats_time_seconds_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  float r;
  READ_HTTP_TRANS_STAT((long) data, count, sum);
  if (count == 0) {
    r = 0.0;
  } else {
    r = (float) sum / (float) count;
    r = r / (float) HRTIME_SECOND;
  }
  *(float *) res = r;
  return res;
}

void *
http_trans_stats_time_mseconds_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  float r;
  READ_HTTP_TRANS_STAT((long) data, count, sum);
  if (count == 0) {
    r = 0.0;
  } else {
    r = (float) sum / (float) count;
    r = r / (float) HRTIME_MSECOND;
  }
  *(float *) res = r;
  return res;
}

void *
http_trans_stats_time_useconds_cb(void *data, void *res)
{
  ink_statval_t count, sum;
  float r;
  READ_HTTP_TRANS_STAT((long) data, count, sum);
  if (count == 0) {
    r = 0.0;
  } else {
    r = (float) sum / (float) count;
    r = r / (float) HRTIME_USECOND;
  }
  *(float *) res = r;
  return res;
}
