/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#define USING_LOG_PREFIX OBLOG

#include "ob_log_trans_stat_mgr.h"
#include "ob_log_utils.h"

#define _STAT(level, fmt, args...) _OBLOG_LOG(level, "[TPS_STAT] " fmt, ##args)
#define STAT(level, fmt, args...) OBLOG_LOG(level, "[TPS_STAT] " fmt, ##args)
#define _ISTAT(fmt, args...) _STAT(INFO, fmt, ##args)
#define ISTAT(fmt, args...) STAT(INFO, fmt, ##args)

namespace oceanbase
{

using namespace common;

namespace liboblog
{

double TransTpsStatInfo::calc_tps(const int64_t delta_time)
{
  double create_tps = 0.0;

  int64_t local_created_trans_count = ATOMIC_LOAD(&created_trans_count_);
  int64_t local_last_created_trans_count = ATOMIC_LOAD(&last_created_trans_count_);
  int64_t delta_create_count = local_created_trans_count - local_last_created_trans_count;

  if (delta_time > 0) {
    create_tps = (double)(delta_create_count) * 1000000.0 / (double)delta_time;
  }

  // Update the last statistics
  last_created_trans_count_ = local_created_trans_count;

  return create_tps;
}

double TransRpsStatInfo::calc_rps(const int64_t delta_time)
{
  double create_rps = 0.0;

  int64_t local_created_records_count = ATOMIC_LOAD(&created_records_count_);
  int64_t local_last_created_records_count = ATOMIC_LOAD(&last_created_records_count_);
  int64_t delta_create_count = local_created_records_count - local_last_created_records_count;

  if (delta_time > 0) {
    create_rps = (double)(delta_create_count) * 1000000.0 / (double)delta_time;
  }

  // Update the last statistics
  last_created_records_count_ = local_created_records_count;

  return create_rps;
}

///////////////////////////// TransTpsRpsStatInfo ///////////////////////////
void TransTpsRpsStatInfo::reset()
{
  tps_stat_info_.reset();
  rps_stat_info_.reset();
}

double TransTpsRpsStatInfo::calc_tps(const int64_t delta_time)
{
  return tps_stat_info_.calc_tps(delta_time);
}

double TransTpsRpsStatInfo::calc_rps(const int64_t delta_time)
{
  return rps_stat_info_.calc_rps(delta_time);
}

///////////////////////////// TransTpsRpsStatInfo ///////////////////////////
ObLogTransStatMgr::ObLogTransStatMgr() :
    inited_(false),
    tps_stat_info_(),
    rps_stat_info_before_filter_(),
    rps_stat_info_after_filter_(),
    tenant_stat_info_map_(),
    tenant_stat_info_pool_(),
    next_record_stat_(),
    release_record_stat_(),
    last_stat_time_(0)
{
}

ObLogTransStatMgr::~ObLogTransStatMgr()
{
  destroy();
}

int ObLogTransStatMgr::init()
{
  int ret = OB_SUCCESS;

  if (OB_UNLIKELY(inited_)) {
    ret = OB_INIT_TWICE;
    LOG_ERROR("ObLogTransStatMgr has inited");
  } else if (OB_FAIL(tenant_stat_info_map_.init(ObModIds::OB_LOG_TENANT_STAT_MAP))) {
    LOG_ERROR("init tenant_stat_info_map_ fail", KR(ret));
  } else if (OB_FAIL(tenant_stat_info_pool_.init(CACHED_TENANT_STAT_INFO_COUNT,
      ObModIds::OB_LOG_TENANT_STAT_INFO))) {
    LOG_ERROR("init tenant_stat_info_pool_ fail", KR(ret), LITERAL_K(CACHED_TENANT_STAT_INFO_COUNT));
  } else {
    tps_stat_info_.reset();
    rps_stat_info_before_filter_.reset();
    rps_stat_info_after_filter_.reset();
    next_record_stat_.reset();
    release_record_stat_.reset();
    last_stat_time_ = 0;
    inited_ = true;
  }

  return ret;
}

void ObLogTransStatMgr::destroy()
{
  if (inited_) {
    clear_tenant_stat_info_();

    tps_stat_info_.reset();
    rps_stat_info_before_filter_.reset();
    rps_stat_info_after_filter_.reset();
    (void)tenant_stat_info_map_.destroy();
    tenant_stat_info_pool_.destroy();
    next_record_stat_.reset();
    release_record_stat_.reset();
    last_stat_time_ = 0;
    inited_ = false;
  }
}

void ObLogTransStatMgr::do_tps_stat()
{
  tps_stat_info_.do_tps_stat();
}

void ObLogTransStatMgr::do_rps_stat_before_filter(const int64_t record_count)
{
  rps_stat_info_before_filter_.do_rps_stat(record_count);
}

void ObLogTransStatMgr::do_rps_stat_after_filter(const int64_t record_count)
{
  rps_stat_info_after_filter_.do_rps_stat(record_count);
}

void ObLogTransStatMgr::do_drc_consume_tps_stat()
{
  next_record_stat_.do_tps_stat();
}

void ObLogTransStatMgr::do_drc_consume_rps_stat()
{
  next_record_stat_.do_rps_stat(1);
}

void ObLogTransStatMgr::do_drc_release_tps_stat()
{
  release_record_stat_.do_tps_stat();
}

void ObLogTransStatMgr::do_drc_release_rps_stat()
{
  release_record_stat_.do_rps_stat(1);
}

void ObLogTransStatMgr::print_stat_info()
{
  int ret = OB_SUCCESS;

  int64_t current_timestamp = get_timestamp();
  int64_t local_last_stat_time = last_stat_time_;
  int64_t delta_time = current_timestamp - local_last_stat_time;

  double create_tps = tps_stat_info_.calc_tps(delta_time);
  double create_rps_before_filter = rps_stat_info_before_filter_.calc_rps(delta_time);
  double create_rps_after_filter = rps_stat_info_after_filter_.calc_rps(delta_time);

  // Update the last statistics
  last_stat_time_ = current_timestamp;

  _ISTAT("TOTAL TPS=%.3lf RPS=%.3lf RPS_ALL=%.3lf ",
      create_tps, create_rps_after_filter, create_rps_before_filter);

  // Print Tenant Statistics
  TenantStatInfoPrinter printer(delta_time);
  if (OB_FAIL(tenant_stat_info_map_.for_each(printer))) {
    LOG_ERROR("TenantStatInfoMap for each fail", KR(ret));
  }

  // Print drc consumption statistics
  double next_record_tps = next_record_stat_.calc_tps(delta_time);
  double next_record_rps = next_record_stat_.calc_rps(delta_time);
  double release_record_tps = release_record_stat_.calc_tps(delta_time);
  double release_record_rps = release_record_stat_.calc_rps(delta_time);

  _ISTAT("[DRC] NEXT_RECORD_TPS=%.3lf RELEASE_RECORD_TPS=%.3lf "
      "NEXT_RECORD_RPS=%.3lf RELEASE_RECORD_RPS=%.3lf",
      next_record_tps, release_record_tps, next_record_rps, release_record_rps);
}

int ObLogTransStatMgr::add_served_tenant(const char *tenant_name, const uint64_t tenant_id)
{
  int ret = OB_SUCCESS;

  if (OB_UNLIKELY(! inited_)) {
    LOG_ERROR("ObLogTransStatMgr has not inited");
    ret = OB_NOT_INIT;
  } else if (OB_ISNULL(tenant_name) ||
      OB_UNLIKELY(OB_INVALID_ID == tenant_id)) {
    LOG_ERROR("invalid argument", K(tenant_name), K(tenant_id));
    ret = OB_INVALID_ARGUMENT;
  } else {
    int64_t pos = 0;
    TenantStatInfo *ts_info = NULL;
    TenantID tid(tenant_id);

    if (OB_FAIL(tenant_stat_info_pool_.alloc(ts_info))) {
      LOG_ERROR("alloc tenant stat info fail", KR(ret), K(tenant_id), K(tenant_name), KPC(ts_info));
    } else if (OB_FAIL(databuff_printf(ts_info->name_, sizeof(ts_info->name_), pos,
        "%s", tenant_name))) {
      LOG_ERROR("print tenant name fail", KR(ret), K(pos), K(tenant_id), K(tenant_name), KPC(ts_info));
    } else {
      if (OB_FAIL(tenant_stat_info_map_.insert(tid, ts_info))) {
        if (OB_ENTRY_EXIST != ret) {
          LOG_ERROR("insert into served_tenant_db_map_ fail", KR(ret),
              K(tid), KPC(ts_info));
        } else {
          ts_info->reset();
          tenant_stat_info_pool_.free(ts_info);
          ts_info = NULL;
          ret = OB_SUCCESS;
        }
      } else {
        _ISTAT("[ADD_TENANT] TENANT=%s(%lu) TOTAL_COUNT=%ld",
            tenant_name, tenant_id, tenant_stat_info_map_.count());
      }
    }

    // Recycle useless objects
    if (OB_SUCCESS != ret && NULL != ts_info) {
      ts_info->reset();
      tenant_stat_info_pool_.free(ts_info);
      ts_info = NULL;
    }
  }

  return ret;
}

int ObLogTransStatMgr::drop_served_tenant(const uint64_t tenant_id)
{
  int ret = OB_SUCCESS;

  TenantStatInfoErase erase_fn(tenant_id, tenant_stat_info_pool_);

  if (OB_FAIL(tenant_stat_info_map_.erase_if(tenant_id, erase_fn))) {
    if (OB_ENTRY_NOT_EXIST == ret) {
      ret = OB_SUCCESS;
      LOG_WARN("tenant has been drop in trans stat mgr", K(tenant_id));
    } else {
      LOG_ERROR("tenant stat info map erase_if tenant fail", KR(ret), K(tenant_id));
    }
  } else {
    // do nothing
  }

  return ret;
}

int ObLogTransStatMgr::do_tenant_tps_rps_stat(const uint64_t tenant_id, int64_t record_count)
{
  int ret = OB_SUCCESS;

  if (OB_UNLIKELY(! inited_)) {
    LOG_ERROR("ObLogTransStatMgr has not inited");
    ret = OB_NOT_INIT;
  } else {
    TenantRpsBeforeFilterUpdater updater(tenant_id, record_count);
    TenantID tid(tenant_id);
    if (OB_FAIL(tenant_stat_info_map_.operate(tid, updater))) {
      if (OB_ENTRY_NOT_EXIST != ret) {
        LOG_ERROR("dp tenant rps stat before filter fail", KR(ret), K(tid), K(record_count));
      } else {
        ret = OB_SUCCESS;
      }
    } else {
      // do nothing
    }
  }

  return ret;
}

int ObLogTransStatMgr::do_tenant_rps_stat_after_filter(const uint64_t tenant_id, int64_t record_count)
{
  int ret = OB_SUCCESS;

  if (OB_UNLIKELY(! inited_)) {
    LOG_ERROR("ObLogTransStatMgr has not inited");
    ret = OB_NOT_INIT;
  } else {
    TenantRpsAfterFilterUpdater updater(tenant_id, record_count);
    TenantID tid(tenant_id);
    if (OB_FAIL(tenant_stat_info_map_.operate(tid, updater))) {
      if (OB_ENTRY_NOT_EXIST != ret) {
        LOG_ERROR("dp tenant rps stat after filter fail", KR(ret), K(tid), K(record_count));
      } else {
        ret = OB_SUCCESS;
      }
    } else {
      // do nothing
    }
  }

  return ret;
}

void ObLogTransStatMgr::clear_tenant_stat_info_()
{
  int ret = OB_SUCCESS;

  if (OB_UNLIKELY(! inited_)) {
    LOG_ERROR("ObLogTransStatMgr has not inited");
    ret = OB_NOT_INIT;
  } else {
    TenantStatInfoClear clear(tenant_stat_info_pool_);

    if (OB_FAIL(tenant_stat_info_map_.remove_if(clear))) {
      LOG_ERROR("clear tenant_stat_info_map fail", KR(ret));
    }
  }
}

bool ObLogTransStatMgr::TenantRpsBeforeFilterUpdater::operator()(const TenantID &tid, TenantStatInfo *ts_info)
{
  if (tid.tenant_id_ == tenant_id_) {
    if (OB_ISNULL(ts_info)) {
      LOG_ERROR("tenant stat info is null", K(tid), KPC(ts_info));
    } else {
      ts_info->tps_stat_info_.do_tps_stat();
      ts_info->rps_stat_info_before_filter_.do_rps_stat(record_count_);
    }
  }

  return tid.tenant_id_ == tenant_id_;
}

bool ObLogTransStatMgr::TenantRpsAfterFilterUpdater::operator()(const TenantID &tid, TenantStatInfo *ts_info)
{
  if (tid.tenant_id_ == tenant_id_) {
    if (OB_ISNULL(ts_info)) {
      LOG_ERROR("tenant stat info is null", K(tid), KPC(ts_info));
    } else {
      ts_info->rps_stat_info_after_filter_.do_rps_stat(record_count_);
    }
  }

  return tid.tenant_id_ == tenant_id_;
}

bool ObLogTransStatMgr::TenantStatInfoPrinter::operator()(const TenantID &tid,
    TenantStatInfo *ts_info)
{
  if (NULL != ts_info) {
    double tps = ts_info->tps_stat_info_.calc_tps(delta_time_);
    double rps_before_filter = ts_info->rps_stat_info_before_filter_.calc_rps(delta_time_);
    double rps_after_filter = ts_info->rps_stat_info_after_filter_.calc_rps(delta_time_);

    _ISTAT("TENANT=%s(%lu) TPS=%.3lf RPS=%.3lf RPS_ALL=%.3lf",
        ts_info->name_, tid.tenant_id_, tps, rps_after_filter, rps_before_filter);
  }

  return true;
}

bool ObLogTransStatMgr::TenantStatInfoErase::operator()(const TenantID &tid,
    TenantStatInfo *ts_info)
{
  if (tid.tenant_id_ == tenant_id_ && NULL != ts_info) {
    ts_info->reset();
    (void)pool_.free(ts_info);
    ts_info = NULL;

    _ISTAT("[DROP_TENANT] TENANT=%lu", tid.tenant_id_);
  }

  return tid.tenant_id_ == tenant_id_;
}

bool ObLogTransStatMgr::TenantStatInfoClear::operator()(const TenantID &tid,
    TenantStatInfo *ts_info)
{
  UNUSED(tid);
  if (NULL != ts_info) {
    ts_info->reset();
    (void)pool_.free(ts_info);
    ts_info = NULL;

    _ISTAT("[CLEAR_TENANT] TENANT=%s(%lu)", ts_info->name_, tid.tenant_id_);
  }

  return true;
}

}
}
