/**
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
 * CANN Open Software License Agreement Version 2.0 (the "License").
 * Please refer to the License for details. You may not use this file except in compliance with the License.
 * 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 FITNESS FOR A PARTICULAR PURPOSE.
 * See LICENSE in the root of the software repository for the full text of the License.
 */

#include "qs_proc_mem_statistic.h"

#include <string>
#include <fstream>
#include <securec.h>
#include "bqs_util.h"

namespace bqs {
namespace {
constexpr const char* VM_RSS_NAME = "VmRSS:";
constexpr const char* VM_HWM_NAME = "VmHWM:";
constexpr const char* XSMEME_NAME = "summary:";
const std::string SVM_STAT_NAME = "peak_page_cnt=";
constexpr const uint64_t DEFAULT_PAGE_SIZE = 4 * 1024UL;
constexpr const uint64_t H_PAGE_SIZE = 2 * 1024 * 1024UL;
constexpr const uint64_t BYTE_TO_KBYTE = 1024UL;
constexpr const int32_t SVM_VALUE_CNT = 2;
constexpr const int32_t XSMEM_VALUE_CNT = 2;
} // namespace
QsProcMemStatistic::QsProcMemStatistic() : runDevice_(false), curPid_(0) {}

QsProcMemStatistic::~QsProcMemStatistic() {}

bool QsProcMemStatistic::GetSvmInfoFromFile(uint64_t& svmValue)
{
    std::ifstream svmFile(svmMemCfgFile_);
    if (!svmFile) {
        BQS_LOG_WARN(
            "open svmMem file not success, pid=%d, errno=%d, strerror=%s", static_cast<int32_t>(curPid_), errno,
            strerror(errno));
        return false;
    }
    const ScopeGuard fileGuard([&svmFile]() { svmFile.close(); });
    std::string svmStrInfo;
    std::string curLine;
    while (getline(svmFile, curLine)) {
        const size_t pos = curLine.find(SVM_STAT_NAME);
        if (pos != std::string::npos) {
            svmStrInfo = curLine.substr(pos);
            break;
        }
    }

    if (svmStrInfo.empty()) {
        BQS_LOG_WARN("svmStrInfo is empty");
        return false;
    }

    uint64_t normalPkgCnt = 0;
    uint64_t hugePkgCnt = 0;
    const int32_t ret =
        sscanf_s(svmStrInfo.c_str(), "peak_page_cnt=%llu; peak_hpage_cnt=%llu", &normalPkgCnt, &hugePkgCnt);
    if (ret < SVM_VALUE_CNT) {
        BQS_LOG_WARN(
            "get svmStrInfo:%s failed, ret:%d, peakPgCnt:%lu, peakHPgCnt:%lu", svmStrInfo.c_str(), ret, normalPkgCnt,
            hugePkgCnt);
        return false;
    }

    BQS_LOG_INFO("peakPgCnt:%lu, peakHPgCnt:%lu summary:%s.", normalPkgCnt, hugePkgCnt, svmStrInfo.c_str());
    if ((normalPkgCnt == 0UL) && (hugePkgCnt == 0UL)) {
        return false;
    }
    svmValue = normalPkgCnt * DEFAULT_PAGE_SIZE + hugePkgCnt * H_PAGE_SIZE;
    return true;
}

void QsProcMemStatistic::StatisticProcSvmMemInfo()
{
    if (!runDevice_) {
        return;
    }
    uint64_t svmValue = 0UL;
    if (!GetSvmInfoFromFile(svmValue)) {
        return;
    }
    if (svmValue > svmMem_.memHwm) {
        svmMem_.memHwm = svmValue;
    }
    svmMem_.memTotal += svmValue;
    svmMem_.statCnt++;
}

bool QsProcMemStatistic::GetXsMemInfoFromFile(uint64_t& xsMemValue)
{
    std::ifstream inFile(xsMemCfgFile_);
    if (!inFile) {
        BQS_LOG_WARN(
            "open xsMem file not success, pid=%d, errno=%d, strerror=%s", static_cast<int32_t>(curPid_), errno,
            strerror(errno));
        return false;
    }
    const ScopeGuard fileGuard([&inFile]() { inFile.close(); });
    std::string summaryStr;
    std::string inputLine;
    while (getline(inFile, inputLine)) {
        if (inputLine.compare(0, strlen(XSMEME_NAME), XSMEME_NAME) == 0) {
            summaryStr = std::move(inputLine);
            break;
        }
    }

    if (summaryStr.empty()) {
        BQS_LOG_WARN("summaryStr is empty");
        return false;
    }

    uint64_t allocSize = 0;
    uint64_t realSize = 0;
    const int32_t ret = sscanf_s(summaryStr.c_str(), "summary: %llu %llu", &allocSize, &realSize);
    if (ret < XSMEM_VALUE_CNT) {
        BQS_LOG_WARN(
            "read summaryStr:%s failed, ret:%d, allocsize:%lu, real size:%lu", summaryStr.c_str(), ret, allocSize,
            realSize);
        return false;
    }

    BQS_LOG_INFO("allocsize:%lu B, real size:%lu B, %s.", allocSize, realSize, summaryStr.c_str());
    if (realSize == 0) {
        return false;
    }
    xsMemValue = realSize;
    return true;
}

void QsProcMemStatistic::StatisticProcXsMemInfo()
{
    uint64_t xsMemValue = 0UL;
    if (!GetXsMemInfoFromFile(xsMemValue)) {
        return;
    }
    if (xsMemValue > xsMem_.memHwm) {
        xsMem_.memHwm = xsMemValue;
    }
    xsMem_.memTotal += xsMemValue;
    xsMem_.statCnt++;
}

bool QsProcMemStatistic::GetOsMemInfoFromFile(uint64_t& rssValue, uint64_t& hwmValue)
{
    std::ifstream ifFile(rssMemCfgFile_);
    if (!ifFile) {
        BQS_LOG_WARN(
            "open rss file not success, pid=%d, errno=%d, strerror=%s", static_cast<int32_t>(curPid_), errno,
            strerror(errno));
        return false;
    }
    const ScopeGuard fileGuard([&ifFile]() { ifFile.close(); });
    std::string vmRssStr;
    std::string vmHwmStr;
    std::string inputLine;
    while (getline(ifFile, inputLine)) {
        if (inputLine.compare(0, strlen(VM_RSS_NAME), VM_RSS_NAME) == 0) {
            vmRssStr = std::move(inputLine);
        } else if (inputLine.compare(0, strlen(VM_HWM_NAME), VM_HWM_NAME) == 0) {
            vmHwmStr = std::move(inputLine);
        } else {
            continue;
        }
        if ((!vmRssStr.empty()) && (!vmHwmStr.empty())) {
            break;
        }
    }

    if ((vmRssStr.empty()) || (vmHwmStr.empty())) {
        BQS_LOG_RUN_INFO("cannot get vmrss or vmhwm");
        return false;
    }

    if (!bqs::TransStrToull(vmRssStr.substr(strlen(VM_RSS_NAME)), rssValue)) {
        BQS_LOG_WARN("get vmRssError:%s nok", vmRssStr.c_str());
        return false;
    }

    if (!bqs::TransStrToull(vmHwmStr.substr(strlen(VM_HWM_NAME)), hwmValue)) {
        BQS_LOG_WARN("get vmHwmError:%s nok", vmRssStr.c_str());
        return false;
    }
    BQS_LOG_INFO("vmRss:%lu KB, vmHwm:%lu KB.", rssValue, hwmValue);
    return true;
}

void QsProcMemStatistic::StatisticProcOsMemInfo()
{
    uint64_t rssValue = 0UL;
    uint64_t hwmValue = 0UL;
    if (!GetOsMemInfoFromFile(rssValue, hwmValue)) {
        BQS_LOG_WARN("get os meminfo nok");
        return;
    }
    rssMem_.memHwm = hwmValue;
    rssMem_.memTotal += rssValue;
    rssMem_.statCnt++;
}

void QsProcMemStatistic::StatisticProcMemInfo()
{
    StatisticProcOsMemInfo();
    StatisticProcXsMemInfo();
    StatisticProcSvmMemInfo();
}

bool QsProcMemStatistic::InitProcMemStatistic()
{
    runDevice_ = (bqs::GetRunContext() == bqs::RunContext::DEVICE);
    auto ret = memset_s(&rssMem_, sizeof(rssMem_), 0x00, sizeof(rssMem_));
    if (ret != EOK) {
        BQS_LOG_ERROR("memset rssMem failed ret:%d", ret);
        return false;
    }

    ret = memset_s(&svmMem_, sizeof(svmMem_), 0x00, sizeof(svmMem_));
    if (ret != EOK) {
        BQS_LOG_ERROR("memset svmMem_ failed ret:%d", ret);
        return false;
    }

    ret = memset_s(&xsMem_, sizeof(xsMem_), 0x00, sizeof(xsMem_));
    if (ret != EOK) {
        BQS_LOG_ERROR("memset xsMem_ failed ret:%d", ret);
        return false;
    }
    curPid_ = getpid();
    if (curPid_ < 0) {
        BQS_LOG_ERROR("get pid error:%d", curPid_);
        return false;
    }
    rssMemCfgFile_ = "/proc/" + std::to_string(curPid_) + "/status";
    xsMemCfgFile_ = "/proc/xsmem/task/" + std::to_string(curPid_) + "/summary";
    svmMemCfgFile_ = "/proc/svm/task/" + std::to_string(curPid_) + "/summary";
    return true;
}

void QsProcMemStatistic::PrintOutProcMemInfo(const uint32_t hostPid)
{
    uint64_t rssavg = 0UL;
    if (rssMem_.statCnt > 0) {
        rssavg = rssMem_.memTotal / rssMem_.statCnt;
    }
    BQS_LOG_INFO("rssavg:%lu KB, rsstotal:%lu KB, rsscnt:%lu", rssavg, rssMem_.memTotal, rssMem_.statCnt);
    uint64_t xsmAvg = 0UL;
    if (xsMem_.statCnt > 0) {
        xsmAvg = xsMem_.memTotal / xsMem_.statCnt;
    }
    BQS_LOG_INFO("xsmAvg:%lu B, xsmtotal:%lu B, xsmcnt:%lu", xsmAvg, xsMem_.memTotal, xsMem_.statCnt);
    if (runDevice_) {
        uint64_t svmAvg = 0UL;
        if (svmMem_.statCnt > 0) {
            svmAvg = svmMem_.memTotal / svmMem_.statCnt;
        }
        BQS_LOG_INFO("svmAvg:%lu B, svmtotal:%lu B, svmcnt:%lu", svmAvg, svmMem_.memTotal, svmMem_.statCnt);
        BQS_LOG_RUN_INFO(
            "proc_metrics:pid=%u, rssavg=%lu B, rsshwm=%lu B, xsmemavg=%lu B, xsmemhwm=%lu B, "
            "svmmemavg=%lu B, svmmemhwm=%lu B",
            hostPid, rssavg * BYTE_TO_KBYTE, rssMem_.memHwm * BYTE_TO_KBYTE, xsmAvg, xsMem_.memHwm, svmAvg,
            svmMem_.memHwm);
    } else {
        BQS_LOG_RUN_INFO(
            "proc_metrics:pid=%u, rssavg=%lu B, rsshwm=%lu B, xsmemavg=%lu B, xsmemhwm=%lu B", hostPid,
            rssavg * BYTE_TO_KBYTE, rssMem_.memHwm * BYTE_TO_KBYTE, xsmAvg, xsMem_.memHwm);
    }
}
} // namespace bqs