/*
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed 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.
 */

#define HILOG_TAG "Record"

#include "subcommand_record.h"

#include <csignal>
#include <cstdlib>
#include <ctime>
#include <memory>
#include <poll.h>
#if defined(CONFIG_HAS_SYSPARA)
#include <parameters.h>
#endif
#include <sys/stat.h>
#include <sys/utsname.h>
#include <unistd.h>

#include "command.h"
#include "debug_logger.h"
#include "hiperf_client.h"
#include "option.h"
#include "perf_event_record.h"
#include "perf_file_reader.h"
#include "utilities.h"

using namespace std::chrono;
namespace OHOS {
namespace Developtools {
namespace HiPerf {
const std::string CONTROL_CMD_PREPARE = "prepare";
const std::string CONTROL_CMD_START = "start";
const std::string CONTROL_CMD_PAUSE = "pause";
const std::string CONTROL_CMD_RESUME = "resume";
const std::string CONTROL_CMD_STOP = "stop";
const std::string CONTROL_FIFO_FILE_C2S = "/data/local/tmp/.hiperf_record_control_c2s";
const std::string CONTROL_FIFO_FILE_S2C = "/data/local/tmp/.hiperf_record_control_s2c";

const std::string PERF_CPU_TIME_MAX_PERCENT = "/proc/sys/kernel/perf_cpu_time_max_percent";
const std::string PERF_EVENT_MAX_SAMPLE_RATE = "/proc/sys/kernel/perf_event_max_sample_rate";
const std::string PERF_EVENT_MLOCK_KB = "/proc/sys/kernel/perf_event_mlock_kb";
const std::string SCHED_SWITCH = "/sys/kernel/tracing/events/sched/sched_switch/enable";
const std::string SCHED_SWITCH_DEBUG = "/sys/kernel/debug/tracing/events/sched/sched_switch/enable";
const std::string SCHED_SWITCH_HM = "/sys/kernel/tracing/hongmeng/events/sched/sched_switch/enable";
const std::string PROC_VERSION = "/proc/version";
const std::string SAVED_CMDLINES_SIZE = "/sys/kernel/tracing/saved_cmdlines_size";

// when there are many events, start record will take more time.
const std::chrono::milliseconds CONTROL_WAITREPY_TOMEOUT = 2000ms;
const std::chrono::milliseconds CONTROL_WAITREPY_TOMEOUT_CHECK = 1000ms;

constexpr uint64_t MASK_ALIGNED_8 = 7;
constexpr size_t MAX_DWARF_CALL_CHAIN = 2;
constexpr uint64_t TYPE_PERF_SAMPLE_BRANCH = PERF_SAMPLE_BRANCH_ANY | PERF_SAMPLE_BRANCH_ANY_CALL |
                                             PERF_SAMPLE_BRANCH_ANY_RETURN |
                                             PERF_SAMPLE_BRANCH_IND_CALL;

int GetClockId(const std::string &name)
{
    static std::map<std::string, int> mapClockid = {
        {"realtime", CLOCK_REALTIME},   {"boottime", CLOCK_BOOTTIME},
        {"monotonic", CLOCK_MONOTONIC}, {"monotonic_raw", CLOCK_MONOTONIC_RAW},
        {"clock_tai", CLOCK_TAI},
    };

    auto it = mapClockid.find(name);
    if (it == mapClockid.end()) {
        return -1;
    } else {
        return it->second;
    }
}

uint64_t GetBranchSampleType(const std::string &name)
{
    static std::map<std::string, uint64_t> mapBranchSampleType = {
        {"u", PERF_SAMPLE_BRANCH_USER},
        {"k", PERF_SAMPLE_BRANCH_KERNEL},
        {"any", PERF_SAMPLE_BRANCH_ANY},
        {"any_call", PERF_SAMPLE_BRANCH_ANY_CALL},
        {"any_ret", PERF_SAMPLE_BRANCH_ANY_RETURN},
        {"ind_call", PERF_SAMPLE_BRANCH_IND_CALL},
    };

    auto it = mapBranchSampleType.find(name);
    if (it == mapBranchSampleType.end()) {
        return 0;
    } else {
        return it->second;
    }
}

SubCommandRecord::~SubCommandRecord()
{
    CloseClientThread();
}

void SubCommandRecord::DumpOptions() const
{
    printf("DumpOptions:\n");
    printf(" targetSystemWide:\t%s\n", targetSystemWide_ ? "true" : "false");
    printf(" selectCpus:\t%s\n", VectorToString(selectCpus_).c_str());
    printf(" timeStopSec:\t%f sec\n", timeStopSec_);
    printf(" frequency:\t%d\n", frequency_);
    printf(" selectEvents:\t%s\n", VectorToString(selectEvents_).c_str());
    int i = 0;
    for (auto &group : selectGroups_) {
        i++;
        printf(" selectGroups:\t%2d:%s\n", i, VectorToString(group).c_str());
    }
    printf(" no_inherit:\t%s\n", noInherit_ ? "true" : "false");
    printf(" selectPids:\t%s\n", VectorToString(selectPids_).c_str());
    printf(" selectTids:\t%s\n", VectorToString(selectTids_).c_str());
    printf(" excludeTids:\t%s\n", VectorToString(excludeTids_).c_str());
    printf(" excludeThreads:\t%s\n", VectorToString(excludeThreadNames_).c_str());
    printf(" kernelCallChain:\t%s\n", kernelCallChain_ ? "true" : "false");
    printf(" callChainUserOnly_:\t%s\n", callChainUserOnly_ ? "true" : "false");
    printf(" restart:\t%s\n", restart_ ? "true" : "false");
    printf(" verbose:\t%s\n", verboseReport_ ? "true" : "false");
    printf(" excludePerf:\t%d\n", excludeHiperf_);
    printf(" cpuPercent:\t%d\n", cpuPercent_);
    printf(" offCPU_:\t%d\n", offCPU_);
    printf(" delayUnwind_:\t%d\n", delayUnwind_);
    printf(" disableUnwind_:\t%d\n", disableUnwind_);
    printf(" disableCallstackExpend_:\t%d\n", disableCallstackExpend_);
    printf(" enableDebugInfoSymbolic:\t%d\n", enableDebugInfoSymbolic_);
    printf(" symbolDir_:\t%s\n", VectorToString(symbolDir_).c_str());
    printf(" outputFilename_:\t%s\n", outputFilename_.c_str());
    printf(" appPackage_:\t%s\n", appPackage_.c_str());
    printf(" checkAppMs_:\t%d\n", checkAppMs_);
    printf(" clockId_:\t%s\n", clockId_.c_str());
    printf(" mmapPages_:\t%d\n", mmapPages_);
    printf(" dataLimit:\t%s\n", strLimit_.c_str());
    printf(" callStack:\t%s\n", VectorToString(callStackType_).c_str());
    printf(" branchSampleTypes:\t%s\n", VectorToString(vecBranchFilters_).c_str());
    printf(" trackedCommand:\t%s\n", VectorToString(trackedCommand_).c_str());
    printf(" pipe_input:\t%d\n", clientPipeInput_);
    printf(" pipe_output:\t%d\n", clientPipeOutput_);
    printf(" cmdlinesSize_:\t%d\n", cmdlinesSize_);
}

bool SubCommandRecord::GetOptions(std::vector<std::string> &args)
{
    if (!Option::GetOptionValue(args, "-a", targetSystemWide_)) {
        return false;
    }
    if (targetSystemWide_ && !IsSupportNonDebuggableApp()) {
        HLOGD("-a option needs root privilege for system wide profiling.");
        printf("-a option needs root privilege for system wide profiling.\n");
        return false;
    }
    if (!Option::GetOptionValue(args, "--exclude-hiperf", excludeHiperf_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-z", compressData_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--no-inherit", noInherit_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--offcpu", offCPU_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--delay-unwind", delayUnwind_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--disable-unwind", disableUnwind_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--disable-callstack-expand", disableCallstackExpend_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--enable-debuginfo-symbolic", enableDebugInfoSymbolic_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--verbose", verboseReport_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-d", timeStopSec_)) {
        return false;
    }
    if (!GetOptionFrequencyAndPeriod(args)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--cpu-limit", cpuPercent_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-m", mmapPages_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--symbol-dir", symbolDir_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-o", outputFilename_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--app", appPackage_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--chkms", checkAppMs_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--clockid", clockId_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-c", selectCpus_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-p", selectPids_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-t", selectTids_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-e", selectEvents_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-g", selectGroups_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-s", callStackType_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--kernel-callchain", kernelCallChain_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--callchain-useronly", callChainUserOnly_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--exclude-tid", excludeTids_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--exclude-thread", excludeThreadNames_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--restart", restart_)) {
        return false;
    }
    std::vector<std::string> callStackType = {};
    if (!Option::GetOptionValue(args, "--call-stack", callStackType)) {
        return false;
    }
    if (!callStackType_.empty()) {
        if (!callStackType.empty()) {
            printf("'-s %s --call-stack %s' option usage error, please check usage.\n",
                VectorToString(callStackType_).c_str(), VectorToString(callStackType).c_str());
            return false;
        }
    } else {
        callStackType_ = callStackType;
    }
    if (!Option::GetOptionValue(args, "--data-limit", strLimit_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "-j", vecBranchFilters_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--pipe_input", clientPipeInput_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--pipe_output", clientPipeOutput_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--control", controlCmd_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--dedup_stack", dedupStack_)) {
        return false;
    }
    if (!Option::GetOptionValue(args, "--cmdline-size", cmdlinesSize_)) {
        return false;
    }
    if (targetSystemWide_ && dedupStack_) {
        printf("-a option is conflict with --dedup_stack.\n");
        return false;
    }
    if (!Option::GetOptionTrackedCommand(args, trackedCommand_)) {
        return false;
    }
    if (!args.empty()) {
        printf("'%s' option usage error, please check usage.\n", VectorToString(args).c_str());
        return false;
    }
    return true;
}

bool SubCommandRecord::GetOptionFrequencyAndPeriod(std::vector<std::string> &args)
{
    if (Option::FindOption(args, "-f") != args.end()) {
        if (!Option::GetOptionValue(args, "-f", frequency_)) {
            return false;
        }
        if (frequency_ < MIN_SAMPLE_FREQUENCY || frequency_ > MAX_SAMPLE_FREQUENCY) {
            printf("Invalid -f value '%d', frequency should be in %d~%d \n", frequency_,
                   MIN_SAMPLE_FREQUENCY, MAX_SAMPLE_FREQUENCY);
            return false;
        }
    }
    if (Option::FindOption(args, "--period") != args.end()) {
        if (frequency_ != 0) {
            printf("option -f and --period is conflict, please check usage\n");
            return false;
        }
        if (!Option::GetOptionValue(args, "--period", period_)) {
            return false;
        }
        if (period_ <= 0) {
            printf("Invalid --period value '%d', period should be greater than 0\n", period_);
            return false;
        }
    }
    return true;
}

bool SubCommandRecord::CheckDataLimitOption()
{
    if (!strLimit_.empty()) {
        if (!ParseDataLimitOption(strLimit_)) {
            printf("Invalid --data-limit value %s\n", strLimit_.c_str());
            return false;
        }
    }
    return true;
}

bool SubCommandRecord::CheckSelectCpuPidOption()
{
    if (!selectCpus_.empty()) {
        int maxCpuid = sysconf(_SC_NPROCESSORS_CONF) - 1;
        for (auto cpu : selectCpus_) {
            if (cpu < 0 || cpu > maxCpuid) {
                printf("Invalid -c value '%d', the CPU ID should be in 0~%d \n", cpu, maxCpuid);
                return false;
            }
        }
    }

    if (!selectPids_.empty()) {
        for (auto pid : selectPids_) {
            if (pid <= 0) {
                printf("Invalid -p value '%d', the pid should be larger than 0\n", pid);
                return false;
            }
        }
    }
    if (!selectTids_.empty()) {
        for (auto tid : selectTids_) {
            if (tid <= 0) {
                printf("Invalid -t value '%d', the tid should be larger than 0\n", tid);
                return false;
            }
        }
    }
    return true;
}

bool SubCommandRecord::CheckArgsRange()
{
    if (timeStopSec_ < MIN_STOP_SECONDS || timeStopSec_ > MAX_STOP_SECONDS) {
        printf("Invalid -d value '%.3f', the seconds should be in %.3f~%.3f  \n", timeStopSec_,
               MIN_STOP_SECONDS, MAX_STOP_SECONDS);
        return false;
    }
    if (cpuPercent_ < MIN_CPU_PERCENT || cpuPercent_ > MAX_CPU_PERCENT) {
        printf("Invalid --cpu-limit value '%d', CPU percent should be in %d~%d \n", cpuPercent_,
               MIN_CPU_PERCENT, MAX_CPU_PERCENT);
        return false;
    }
    if (checkAppMs_ < MIN_CHECK_APP_MS || checkAppMs_ > MAX_CHECK_APP_MS) {
        printf("Invalid --chkms value '%d', the milliseconds should be in %d~%d \n", checkAppMs_,
               MIN_CHECK_APP_MS, MAX_CHECK_APP_MS);
        return false;
    }
    if (mmapPages_ < MIN_PERF_MMAP_PAGE || mmapPages_ > MAX_PERF_MMAP_PAGE ||
        !PowerOfTwo(mmapPages_)) {
        printf("Invalid -m value '%d', value should be in %d~%d and must be a power of two \n",
               mmapPages_, MIN_PERF_MMAP_PAGE, MAX_PERF_MMAP_PAGE);
        return false;
    }
    if (cmdlinesSize_ < MIN_SAVED_CMDLINES_SIZE || cmdlinesSize_ > MAX_SAVED_CMDLINES_SIZE ||
        !PowerOfTwo(cmdlinesSize_)) {
        printf("Invalid --cmdline-size value '%d', value should be in %d~%d and must be a power of two \n",
               cmdlinesSize_, MIN_SAVED_CMDLINES_SIZE, MAX_SAVED_CMDLINES_SIZE);
        return false;
    }
    if (!clockId_.empty() && GetClockId(clockId_) == -1) {
        printf("Invalid --clockid value %s\n", clockId_.c_str());
        return false;
    }
    if (!targetSystemWide_ && excludeHiperf_) {
        printf("--exclude-hiperf must be used with -a\n");
        return false;
    }
    return true;
}

bool SubCommandRecord::CheckOptions()
{
    if (!CheckArgsRange()) {
        return false;
    }
    if (!CheckDataLimitOption()) {
        return false;
    }
    if (!ParseCallStackOption(callStackType_)) {
        return false;
    }
    if (!ParseBranchSampleType(vecBranchFilters_)) {
        return false;
    }
    if (!CheckSelectCpuPidOption()) {
        return false;
    }
    if (!ParseControlCmd(controlCmd_)) {
        return false;
    }
    if (!CheckTargetProcessOptions()) {
        return false;
    }
    return true;
}

bool SubCommandRecord::ParseOption(std::vector<std::string> &args)
{
    if (!GetOptions(args)) {
        return false;
    }
    if (!args.empty()) {
        printf("unknown option %s\n", args.begin()->c_str());
        return false;
    }
    if (controlCmd_.empty()) {
        if (!CheckRestartOption(appPackage_, targetSystemWide_, restart_, selectPids_)) {
            return false;
        }
    }
    return CheckOptions();
}

bool SubCommandRecord::CheckTargetProcessOptions()
{
    bool hasTarget = false;
    if (targetSystemWide_) {
        hasTarget = true;
    }
    if (!selectPids_.empty() || !selectTids_.empty()) {
        if (hasTarget) {
            printf("-p/-t %s options conflict, please check usage\n",
                   VectorToString(selectPids_).c_str());
            return false;
        }
        hasTarget = true;
    }
    if (!trackedCommand_.empty()) {
        if (hasTarget) {
            printf("%s options conflict, please check usage\n",
                   VectorToString(trackedCommand_).c_str());
            return false;
        }
        hasTarget = true;
    }
    if (appPackage_ != "") {
        if (hasTarget) {
            printf("--app %s options conflict, please check usage\n", appPackage_.c_str());
            return false;
        }
        hasTarget = true;
    }
    if (!hasTarget and (controlCmd_.empty() or controlCmd_ == CONTROL_CMD_PREPARE)) {
        printf("please select a target process\n");
        return false;
    }
    if (controlCmd_ == CONTROL_CMD_PREPARE) {
        if (!CheckRestartOption(appPackage_, targetSystemWide_, restart_, selectPids_)) {
            return false;
        }
    }
    return CheckTargetPids();
}

bool SubCommandRecord::CheckTargetPids()
{
    for (auto pid : selectPids_) {
        if (!IsDir("/proc/" + std::to_string(pid))) {
            printf("not exist pid %d\n", pid);
            return false;
        }
    }
    for (auto tid : selectTids_) {
        if (!IsDir("/proc/" + std::to_string(tid))) {
            printf("not exist tid %d\n", tid);
            return false;
        }
    }
    if (!CheckAppIsRunning(selectPids_, appPackage_, checkAppMs_)) {
        return false;
    }
    if (!selectPids_.empty()) {
        for (auto pid : selectPids_) {
            auto tids = GetSubthreadIDs(pid);
            if (!tids.empty()) {
                selectTids_.insert(selectTids_.end(), tids.begin(), tids.end());
                mapPids_[pid] = tids;
            }
        }
    }
    if (!SubCommand::HandleSubCommandExclude(excludeTids_, excludeThreadNames_, selectTids_)) {
        return false;
    }
    selectPids_.insert(selectPids_.end(), selectTids_.begin(), selectTids_.end());

    return true;
}

bool SubCommandRecord::ParseDataLimitOption(const std::string &str)
{
    uint unit = 1;
    char c = str.at(str.size() - 1);
    if (c == 'K' or c == 'k') {
        unit = KILO;
    } else if (c == 'm' or c == 'M') {
        unit = KILO * KILO;
    } else if (c == 'g' or c == 'G') {
        unit = KILO * KILO * KILO;
    } else {
        return false;
    }

    std::string num = str.substr(0, str.size() - 1);
    int64_t size = 0;
    try {
        size = std::stoul(num);
    } catch (...) {
        return false;
    }
    if (size <= 0) {
        return false;
    }

    dataSizeLimit_ = size * unit;

    return true;
}

bool SubCommandRecord::ParseCallStackOption(const std::vector<std::string> &callStackType)
{
    if (callStackType.empty()) {
        return true;
    } else if (callStackType[0] == "fp") {
        if (callStackType.size() != 1) {
            printf("Invalid -s value %s.\n", VectorToString(callStackType).c_str());
            return false;
        }
        isCallStackFp_ = true;
    } else if (callStackType[0] == "dwarf") {
        if (callStackType.size() > MAX_DWARF_CALL_CHAIN) {
            printf("Invalid -s value %s.\n", VectorToString(callStackType).c_str());
            return false;
        } else if (callStackType.size() == MAX_DWARF_CALL_CHAIN) {
            try {
                callStackDwarfSize_ = std::stoul(callStackType.at(1));
            } catch (...) {
                printf("Invalid -s value, dwarf stack size, '%s' is illegal.\n",
                       callStackType.at(1).c_str());
                return false;
            }
            if (callStackDwarfSize_ < MIN_SAMPLE_STACK_SIZE) {
                printf("Invalid -s value, dwarf stack size, '%s' is too small.\n",
                       callStackType.at(1).c_str());
                return false;
            }
            if (callStackDwarfSize_ > MAX_SAMPLE_STACK_SIZE) {
                printf("Invalid -s value, dwarf stack size, '%s' is bigger than max value %u.\n",
                       callStackType.at(1).c_str(), MAX_SAMPLE_STACK_SIZE);
                return false;
            }
            if ((callStackDwarfSize_ & MASK_ALIGNED_8) != 0) {
                printf("Invalid -s value, dwarf stack size, '%s' is not 8 byte aligned.\n",
                       callStackType.at(1).c_str());
                return false;
            }
        }
        isCallStackDwarf_ = true;
    } else {
        printf("Invalid -s value '%s'.\n", callStackType.at(0).c_str());
        return false;
    }
    return true;
}

bool SubCommandRecord::ParseBranchSampleType(const std::vector<std::string> &vecBranchSampleTypes)
{
    if (!vecBranchSampleTypes.empty()) {
        for (auto &item : vecBranchSampleTypes) {
            uint64_t type = GetBranchSampleType(item);
            if (type != 0) {
                branchSampleType_ |= type;
            } else {
                printf("Invalid -j value '%s'\n", item.c_str());
                return false;
            }
        }
        if ((branchSampleType_ & TYPE_PERF_SAMPLE_BRANCH) == 0) {
            printf(
                "Invalid -j value, requires at least one of any, any_call, any_ret, ind_call.\n");
            return false;
        }
    }
    return true;
}

bool SubCommandRecord::ParseControlCmd(const std::string cmd)
{
    if (cmd.empty() or cmd == CONTROL_CMD_PREPARE or cmd == CONTROL_CMD_START or
        cmd == CONTROL_CMD_PAUSE or cmd == CONTROL_CMD_RESUME or cmd == CONTROL_CMD_STOP) {
        return true;
    }

    printf("Invalid --control %s option, command should be: prepare, start, pause, resume, stop.\n",
           cmd.c_str());
    return false;
}

bool SubCommandRecord::SetPerfLimit(const std::string& file, int value, std::function<bool (int, int)> const& cmp,
    const std::string& param)
{
    int oldValue = 0;
    if (!ReadIntFromProcFile(file, oldValue)) {
        printf("read %s fail.\n", file.c_str());
        return false;
    }

    if (cmp(oldValue, value)) {
        HLOGI("cmp return true.");
        return true;
    }

    if (IsRoot()) {
        bool ret = WriteIntToProcFile(file, value);
        if (!ret) {
            printf("please set %s to %d manually if perf limit is wanted.\n", file.c_str(), value);
        }
    }

    if (!OHOS::system::SetParameter(param, std::to_string(value))) {
        printf("set parameter %s fail.\n", param.c_str());
        return false;
    }
    isNeedSetPerfHarden_ = true;
    return true;
}

bool SubCommandRecord::SetPerfCpuMaxPercent()
{
    auto cmp = [](int oldValue, int newValue) { return oldValue == newValue; };
    return SetPerfLimit(PERF_CPU_TIME_MAX_PERCENT, cpuPercent_, cmp, "hiviewdfx.hiperf.perf_cpu_time_max_percent");
}

bool SubCommandRecord::SetPerfMaxSampleRate()
{
    auto cmp = [](int oldValue, int newValue) { return oldValue == newValue; };
    int frequency = frequency_ != 0 ? frequency_ : PerfEvents::DEFAULT_SAMPLE_FREQUNCY;
    int maxRate = 0;
    if (!ReadIntFromProcFile(PERF_EVENT_MAX_SAMPLE_RATE, maxRate)) {
        printf("read %s fail.\n", PERF_EVENT_MAX_SAMPLE_RATE.c_str());
        return false;
    }
    if (maxRate > frequency) {
        return true;
    }
    int newRate = frequency > static_cast<int>(PerfEvents::DEFAULT_EVENT_MAX_SAMPLE_RATE) ? frequency :
                  static_cast<int>(PerfEvents::DEFAULT_EVENT_MAX_SAMPLE_RATE);
    return SetPerfLimit(PERF_EVENT_MAX_SAMPLE_RATE, newRate, cmp,
                        "hiviewdfx.hiperf.perf_event_max_sample_rate");
}

bool SubCommandRecord::SetPerfEventMlock()
{
    auto cmp = [](int oldValue, int newValue) { return oldValue == newValue; };
    int mlock_kb = sysconf(_SC_NPROCESSORS_CONF) * (mmapPages_ + 1) * 4;
    return SetPerfLimit(PERF_EVENT_MLOCK_KB, mlock_kb, cmp, "hiviewdfx.hiperf.perf_event_mlock_kb");
}

bool SubCommandRecord::SetPerfHarden()
{
    if (!isNeedSetPerfHarden_) {
        return true;
    }

    std::string perfHarden = OHOS::system::GetParameter(PERF_DISABLE_PARAM, "1");
    if (perfHarden == "1") {
        if (!OHOS::system::SetParameter(PERF_DISABLE_PARAM, "0")) {
            printf("set parameter security.perf_harden to 0 fail.");
            return false;
        }
    }

    if (!OHOS::system::SetParameter(PERF_DISABLE_PARAM, "1")) {
        printf("set parameter security.perf_harden to 1 fail.");
        return false;
    }
    return true;
}

bool SubCommandRecord::TraceOffCpu()
{
    // whether system support sched_switch event
    int enable = -1;
    std::string node = SCHED_SWITCH;
    if (isHM_) {
        node = SCHED_SWITCH_HM;
    }
    const std::string nodeDebug = SCHED_SWITCH_DEBUG;
    if (!ReadIntFromProcFile(node.c_str(), enable) and
        !ReadIntFromProcFile(nodeDebug.c_str(), enable)) {
        printf("Cannot trace off CPU, event sched:sched_switch is not available (%s or %s)\n",
            node.c_str(), nodeDebug.c_str());
        return false;
    }

    return true;
}

void SubCommandRecord::SetSavedCmdlinesSize()
{
    if (!ReadIntFromProcFile(SAVED_CMDLINES_SIZE, oldCmdlinesSize_)) {
        printf("Failed to read from %s.\n", SAVED_CMDLINES_SIZE.c_str());
    }
    if (!WriteIntToProcFile(SAVED_CMDLINES_SIZE, cmdlinesSize_)) {
        printf("Failed to write size:%d to %s.\n", cmdlinesSize_, SAVED_CMDLINES_SIZE.c_str());
    }
}

void SubCommandRecord::RecoverSavedCmdlinesSize()
{
    if (oldCmdlinesSize_ == 0) {
        return;
    }
    if (!WriteIntToProcFile(SAVED_CMDLINES_SIZE, oldCmdlinesSize_)) {
        printf("Failed to recover value of %s.\n", SAVED_CMDLINES_SIZE.c_str());
    }
}

bool SubCommandRecord::PreparePerfEvent()
{
    // we need to notify perfEvents_ sampling mode by SetRecordCallBack first
    auto processRecord = std::bind(&SubCommandRecord::ProcessRecord, this, std::placeholders::_1);
    perfEvents_.SetRecordCallBack(processRecord);

    perfEvents_.SetCpu(selectCpus_);
    perfEvents_.SetPid(selectPids_); // Tids has insert Pids in CheckTargetProcessOptions()

    perfEvents_.SetSystemTarget(targetSystemWide_);
    perfEvents_.SetTimeOut(timeStopSec_);
    perfEvents_.SetVerboseReport(verboseReport_);
    perfEvents_.SetMmapPages(mmapPages_);
    if (isCallStackFp_) {
        perfEvents_.SetSampleStackType(PerfEvents::SampleStackType::FP);
    } else if (isCallStackDwarf_) {
        perfEvents_.SetSampleStackType(PerfEvents::SampleStackType::DWARF);
        perfEvents_.SetDwarfSampleStackSize(callStackDwarfSize_);
    }
    if (!perfEvents_.SetBranchSampleType(branchSampleType_)) {
        printf("branch sample %s is not supported\n", VectorToString(vecBranchFilters_).c_str());
        HLOGE("Fail to SetBranchSampleType %" PRIx64 "", branchSampleType_);
        return false;
    }
    if (!clockId_.empty()) {
        perfEvents_.SetClockId(GetClockId(clockId_));
    }

    if (frequency_ > 0) {
        perfEvents_.SetSampleFrequency(frequency_);
    } else if (period_ > 0) {
        perfEvents_.SetSamplePeriod(period_);
    }

    perfEvents_.SetInherit(!noInherit_);
    perfEvents_.SetTrackedCommand(trackedCommand_);

    // set default sample event
    if (selectEvents_.empty() && selectGroups_.empty()) {
        selectEvents_.push_back("hw-cpu-cycles");
    }

    if (!perfEvents_.AddEvents(selectEvents_)) {
        HLOGE("Fail to AddEvents events");
        return false;
    }
    for (auto &group : selectGroups_) {
        if (!perfEvents_.AddEvents(group, true)) {
            HLOGE("Fail to AddEvents groups");
            return false;
        }
    }
    // cpu off add after default event (we need both sched_switch and user selected events)
    if (offCPU_) {
        if (std::find(selectEvents_.begin(), selectEvents_.end(), "sched_switch") !=
            selectEvents_.end()) {
            printf("--offcpu is not supported event sched_switch\n");
            return false;
        }
        // insert a sched_switch event to trace offcpu event
        if (!perfEvents_.AddOffCpuEvent()) {
            HLOGE("Fail to AddEOffCpuvent");
            return false;
        }
    }

    return true;
}

bool SubCommandRecord::PrepareSysKernel()
{
    SetHM();
    SetSavedCmdlinesSize();
    if (!SetPerfMaxSampleRate()) {
        HLOGE("Fail to call SetPerfMaxSampleRate(%d)", frequency_);
        return false;
    }
    if (!SetPerfCpuMaxPercent()) {
        HLOGE("Fail to set perf event cpu limit to %d\n", cpuPercent_);
        return false;
    }

    if (!SetPerfEventMlock()) {
        HLOGE("Fail to set perf event mlock limit\n");
        return false;
    }

    if (!SetPerfHarden()) {
        HLOGE("Fail to set perf event harden\n");
        return false;
    }

    if (offCPU_ && !TraceOffCpu()) {
        HLOGE("Fail to TraceOffCpu");
        return false;
    }

    return true;
}

bool SubCommandRecord::PrepareVirtualRuntime()
{
    auto saveRecord = std::bind(&SubCommandRecord::SaveRecord, this, std::placeholders::_1, false);
    virtualRuntime_.SetRecordMode(saveRecord);

    // do some config for virtualRuntime_
    virtualRuntime_.SetCallStackExpend(disableCallstackExpend_ ? 0 : 1);
    // these is same for virtual runtime
    virtualRuntime_.SetDisableUnwind(disableUnwind_ or delayUnwind_);
    virtualRuntime_.EnableDebugInfoSymbolic(enableDebugInfoSymbolic_);
    if (!symbolDir_.empty()) {
        if (!virtualRuntime_.SetSymbolsPaths(symbolDir_)) {
            printf("Failed to set symbol path(%s)\n", VectorToString(symbolDir_).c_str());
            return false;
        }
    }

    // load vsdo first
    virtualRuntime_.LoadVdso();

    if (!callChainUserOnly_) {
        // prepare from kernel and ko
        virtualRuntime_.SetNeedKernelCallChain(!callChainUserOnly_);
        virtualRuntime_.UpdateKernelSpaceMaps();
        virtualRuntime_.UpdateKernelModulesSpaceMaps();
        if (isHM_) {
            virtualRuntime_.UpdateServiceSpaceMaps();
        }
    }

    if (isHM_) {
        virtualRuntime_.UpdateDevhostSpaceMaps();
    }
    if (dedupStack_) {
        virtualRuntime_.SetDedupStack();
        auto collectSymbol =
            std::bind(&SubCommandRecord::CollectSymbol, this, std::placeholders::_1);
        virtualRuntime_.SetCollectSymbolCallBack(collectSymbol);
    }
    return true;
}

void SubCommandRecord::WriteCommEventBeforeSampling()
{
    for (auto it = mapPids_.begin(); it != mapPids_.end(); ++it) {
        virtualRuntime_.GetThread(it->first, it->first);
        for (auto tid : it->second) {
            virtualRuntime_.GetThread(it->first, tid);
        }
    }
}

bool SubCommandRecord::ClientCommandResponse(bool OK)
{
    using namespace HiperfClient;
    if (OK) {
        size_t size = write(clientPipeOutput_, ReplyOK.c_str(), ReplyOK.size());
        if (size != ReplyOK.size()) {
            char errInfo[ERRINFOLEN] = { 0 };
            strerror_r(errno, errInfo, ERRINFOLEN);
            HLOGD("Server:%s -> %d : %zd %d:%s", ReplyOK.c_str(), clientPipeOutput_, size, errno,
                  errInfo);
            return false;
        }
        return true;
    } else {
        size_t size = write(clientPipeOutput_, ReplyFAIL.c_str(), ReplyFAIL.size());
        if (size != ReplyFAIL.size()) {
            char errInfo[ERRINFOLEN] = { 0 };
            strerror_r(errno, errInfo, ERRINFOLEN);
            HLOGD("Server:%s -> %d : %zd %d:%s", ReplyFAIL.c_str(), clientPipeOutput_, size, errno,
                  errInfo);
            return false;
        }
        return true;
    }
}

bool SubCommandRecord::IsSamplingRunning()
{
    constexpr int maxWaitTrackingCount = 1000 / 100; // wait 1 second
    int waitTrackingCount = maxWaitTrackingCount;
    while (!perfEvents_.IsTrackRunning()) {
        waitTrackingCount--;
        if (waitTrackingCount <= 0) {
            return false;
        }
        constexpr uint64_t waitTrackingSleepMs = 100;
        std::this_thread::sleep_for(milliseconds(waitTrackingSleepMs));
    }
    return true;
}

void SubCommandRecord::ClientCommandHandle()
{
    using namespace HiperfClient;
    if (!IsSamplingRunning()) {
        return;
    }
    // tell the caller if Exist
    ClientCommandResponse(true);

    bool hasRead = true;
    while (!clientExit_) {
        if (isFifoServer_ && hasRead) {
            if (clientPipeInput_ != -1) {
                // after read(), block is disabled, the poll will be waked neven if no data
                close(clientPipeInput_);
            }
            clientPipeInput_ = open(CONTROL_FIFO_FILE_C2S.c_str(), O_RDONLY | O_NONBLOCK);
        }
        struct pollfd pollFd {
            clientPipeInput_, POLLIN, 0
        };
        int polled = poll(&pollFd, 1, CONTROL_WAITREPY_TOMEOUT.count());
        if (polled <= 0) {
            hasRead = false;
            continue;
        }
        hasRead = true;
        std::string command;
        while (true) {
            char c;
            ssize_t result = TEMP_FAILURE_RETRY(read(clientPipeInput_, &c, 1));
            if (result <= 0) {
                HLOGD("server :read from pipe file failed");
                break;
            }
            command.push_back(c);
            if (c == '\n') {
                break;
            }
        }
        HLOGD("server:new command %s", command.c_str());
        if (command == ReplyStart) {
            ClientCommandResponse(perfEvents_.EnableTracking());
        } else if (command == ReplyCheck) {
            ClientCommandResponse(!clientExit_);
        } else if (command == ReplyStop) {
            ClientCommandResponse(perfEvents_.StopTracking());
        } else if (command == ReplyPause) {
            ClientCommandResponse(perfEvents_.PauseTracking());
        } else if (command == ReplyResume) {
            ClientCommandResponse(perfEvents_.ResumeTracking());
        }
    }
}

bool SubCommandRecord::ProcessControl()
{
    if (controlCmd_.empty()) {
        return true;
    }

    if (controlCmd_ == CONTROL_CMD_PREPARE) {
        if (!CreateFifoServer()) {
            return false;
        }
        return true;
    }

    isFifoClient_ = true;
    bool ret = false;
    if (controlCmd_ == CONTROL_CMD_START) {
        ret = SendFifoAndWaitReply(HiperfClient::ReplyStart, CONTROL_WAITREPY_TOMEOUT);
    } else if (controlCmd_ == CONTROL_CMD_RESUME) {
        ret = SendFifoAndWaitReply(HiperfClient::ReplyResume, CONTROL_WAITREPY_TOMEOUT);
    } else if (controlCmd_ == CONTROL_CMD_PAUSE) {
        ret = SendFifoAndWaitReply(HiperfClient::ReplyPause, CONTROL_WAITREPY_TOMEOUT);
    } else if (controlCmd_ == CONTROL_CMD_STOP) {
        ret = SendFifoAndWaitReply(HiperfClient::ReplyStop, CONTROL_WAITREPY_TOMEOUT);
        if (ret) {
            // wait sampling process exit really
            static constexpr uint64_t waitCheckSleepMs = 200;
            std::this_thread::sleep_for(milliseconds(waitCheckSleepMs));
            while (SendFifoAndWaitReply(HiperfClient::ReplyCheck, CONTROL_WAITREPY_TOMEOUT_CHECK)) {
                std::this_thread::sleep_for(milliseconds(waitCheckSleepMs));
            }
            HLOGI("wait reply check end.");
        }
        remove(CONTROL_FIFO_FILE_C2S.c_str());
        remove(CONTROL_FIFO_FILE_S2C.c_str());
    }

    if (ret) {
        printf("%s sampling success.\n", controlCmd_.c_str());
    } else {
        printf("%s sampling failed.\n", controlCmd_.c_str());
    }
    return ret;
}

bool SubCommandRecord::CreateFifoServer()
{
    char errInfo[ERRINFOLEN] = { 0 };
    const mode_t fifoMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
    if (mkfifo(CONTROL_FIFO_FILE_S2C.c_str(), fifoMode) != 0 or
        mkfifo(CONTROL_FIFO_FILE_C2S.c_str(), fifoMode) != 0) {
        if (errno == EEXIST) {
            printf("another sampling service is running.\n");
        } else {
            remove(CONTROL_FIFO_FILE_S2C.c_str());
            remove(CONTROL_FIFO_FILE_C2S.c_str());
        }
        strerror_r(errno, errInfo, ERRINFOLEN);
        HLOGE("create fifo file failed. %d:%s", errno, errInfo);
        return false;
    }

    pid_t pid = fork();
    if (pid == -1) {
        strerror_r(errno, errInfo, ERRINFOLEN);
        HLOGE("fork failed. %d:%s", errno, errInfo);
        return false;
    } else if (pid == 0) { // child process
        close(STDIN_FILENO);
        close(STDERR_FILENO);
        isFifoServer_ = true;
        clientPipeOutput_ = open(CONTROL_FIFO_FILE_S2C.c_str(), O_WRONLY);
        if (clientPipeOutput_ == -1) {
            strerror_r(errno, errInfo, ERRINFOLEN);
            HLOGE("open fifo file(%s) failed. %d:%s", CONTROL_FIFO_FILE_S2C.c_str(), errno, errInfo);
            return false;
        }
        nullFd_ = open("/dev/null", O_WRONLY);
        (void)dup2(nullFd_, STDOUT_FILENO); // redirect stdout to /dev/null
    } else {            // parent process
        isFifoClient_ = true;
        int fd = open(CONTROL_FIFO_FILE_S2C.c_str(), O_RDONLY | O_NONBLOCK);
        if (fd == -1 or !WaitFifoReply(fd, CONTROL_WAITREPY_TOMEOUT)) {
            close(fd);
            kill(pid, SIGKILL);
            remove(CONTROL_FIFO_FILE_C2S.c_str());
            remove(CONTROL_FIFO_FILE_S2C.c_str());
            strerror_r(errno, errInfo, ERRINFOLEN);
            printf("create control hiperf sampling failed. %d:%s\n", errno, errInfo);
            return false;
        }
        close(fd);
        printf("%s control hiperf sampling success.\n", restart_ ? "start" : "create");
    }
    return true;
}

bool SubCommandRecord::SendFifoAndWaitReply(const std::string &cmd, const std::chrono::milliseconds &timeOut)
{
    // need open for read first, because server maybe send reply before client wait to read
    int fdRead = open(CONTROL_FIFO_FILE_S2C.c_str(), O_RDONLY | O_NONBLOCK);
    if (fdRead == -1) {
        HLOGE("can not open fifo file(%s)", CONTROL_FIFO_FILE_S2C.c_str());
        return false;
    }
    int fdWrite = open(CONTROL_FIFO_FILE_C2S.c_str(), O_WRONLY | O_NONBLOCK);
    if (fdWrite == -1) {
        HLOGE("can not open fifo file(%s)", CONTROL_FIFO_FILE_C2S.c_str());
        close(fdRead);
        return false;
    }
    size_t size = write(fdWrite, cmd.c_str(), cmd.size());
    if (size != cmd.size()) {
        HLOGE("failed to write fifo file(%s) command(%s)", CONTROL_FIFO_FILE_C2S.c_str(),
              cmd.c_str());
        close(fdWrite);
        close(fdRead);
        return false;
    }
    close(fdWrite);

    bool ret = WaitFifoReply(fdRead, timeOut);
    close(fdRead);
    return ret;
}

bool SubCommandRecord::WaitFifoReply(int fd, const std::chrono::milliseconds &timeOut)
{
    struct pollfd pollFd {
        fd, POLLIN, 0
    };
    int polled = poll(&pollFd, 1, timeOut.count());
    std::string reply;
    if (polled > 0) {
        while (true) {
            char c;
            ssize_t result = TEMP_FAILURE_RETRY(read(fd, &c, 1));
            if (result <= 0) {
                HLOGD("read from fifo file(%s) failed", CONTROL_FIFO_FILE_S2C.c_str());
                break;
            }
            reply.push_back(c);
            if (c == '\n') {
                break;
            }
        }
    } else if (polled == 0) {
        HLOGD("wait fifo file(%s) timeout", CONTROL_FIFO_FILE_S2C.c_str());
    } else {
        HLOGD("wait fifo file(%s) failed", CONTROL_FIFO_FILE_S2C.c_str());
    }

    if (reply == HiperfClient::ReplyOK) {
        return true;
    }
    return false;
}

bool SubCommandRecord::OnSubCommand(std::vector<std::string> &args)
{
    if (!ProcessControl()) {
        return false;
    } else if (isFifoClient_) {
        return true;
    }

    // prepare PerfEvents
    if (!PrepareSysKernel() or !PreparePerfEvent()) {
        return false;
    }

    // prepar some attr before CreateInitRecordFile
    if (!perfEvents_.PrepareTracking()) {
        HLOGE("Fail to prepare tracking ");
        return false;
    }

    if (!CreateInitRecordFile(delayUnwind_ ? false : compressData_)) {
        HLOGE("Fail to create record file %s", outputFilename_.c_str());
        return false;
    }

    if (!PrepareVirtualRuntime()) {
        return false;
    }

    //write comm event
    WriteCommEventBeforeSampling();

    // make a thread wait the other command
    if (clientPipeOutput_ != -1) {
        clientCommandHanle_ = std::thread(&SubCommandRecord::ClientCommandHandle, this);
    }

    // start tracking
    if (isDataSizeLimitStop_) {
        // mmap record size has been larger than limit, dont start sampling.
    } else if (restart_ && controlCmd_ == CONTROL_CMD_PREPARE) {
        if (!perfEvents_.StartTracking(isFifoServer_)) {
            return false;
        }
    } else {
        if (!perfEvents_.StartTracking(!isFifoServer_)) {
            return false;
        }
    }

    startSaveFileTimes_ = steady_clock::now();
    if (!FinishWriteRecordFile()) {
        HLOGE("Fail to finish record file %s", outputFilename_.c_str());
        return false;
    } else if (!PostProcessRecordFile()) {
        HLOGE("Fail to post process record file");
        return false;
    }

    // finial report
    RecordCompleted();
    RecoverSavedCmdlinesSize();
    CloseClientThread();
    return true;
}

void SubCommandRecord::CloseClientThread()
{
    if (clientCommandHanle_.joinable()) {
        clientExit_ = true;
        HLOGI("CloseClientThread");
        if (nullFd_ != -1) {
            close(nullFd_);
        }
        clientCommandHanle_.join();
        close(clientPipeInput_);
        close(clientPipeOutput_);
        if (isFifoServer_) {
            remove(CONTROL_FIFO_FILE_C2S.c_str());
            remove(CONTROL_FIFO_FILE_S2C.c_str());
        }
    }
}

bool SubCommandRecord::ProcessRecord(std::unique_ptr<PerfEventRecord> record)
{
    if (record == nullptr) {
        HLOGE("record is null");
        return false;
    }
#if HIDEBUG_RECORD_NOT_PROCESS
    // some times we want to check performance
    // but we still want to see the record number
    if (record->GetType() == PERF_RECORD_SAMPLE) {
        recordSamples_++;
    } else {
        recordNoSamples_++;
    }
    if (record->GetType() == PERF_RECORD_SAMPLE) {
        // when the record is allowed from a cache memory, does not free memory after use
        record.release();
    }
    return true;
#else
#ifdef HIPERF_DEBUG_TIME
    const auto startTime = steady_clock::now();
#endif
    if (excludeHiperf_) {
        static pid_t pid = getpid();
        if (record->GetPid() == pid) {
            if (record->GetType() == PERF_RECORD_SAMPLE) {
                // when the record is allowed from a cache memory, does not free memory after use
                record.release();
            }
            // discard record
            return true;
        }
    }

    // May create some simulated events
    // it will call ProcessRecord before next line
#if !HIDEBUG_RECORD_NOT_PROCESS_VM
    virtualRuntime_.UpdateFromRecord(*record);
#endif
#ifdef HIPERF_DEBUG_TIME
    prcessRecordTimes_ += duration_cast<microseconds>(steady_clock::now() - startTime);
#endif
    return SaveRecord(std::move(record), true);
#endif
}

bool SubCommandRecord::SaveRecord(std::unique_ptr<PerfEventRecord> record, bool ptrReleaseFlag)
{
    ON_SCOPE_EXIT {
        if (ptrReleaseFlag && record->GetType() == PERF_RECORD_SAMPLE) {
            // when the record is allowed from a cache memory, does not free memory after use
            record.release();
        }
    };
#if HIDEBUG_RECORD_NOT_SAVE
    return true;
#endif
    if (dataSizeLimit_ > 0u) {
        if (dataSizeLimit_ <= fileWriter_->GetDataSize()) {
            if (isDataSizeLimitStop_) {
                return false;
            }
            printf("record size %" PRIu64 " is large than limit %" PRIu64 ". stop sampling.\n",
                fileWriter_->GetDataSize(), dataSizeLimit_);
            perfEvents_.StopTracking();
            isDataSizeLimitStop_ = true;
            return false;
        }
    }

    if (record) {
#ifdef HIPERF_DEBUG_TIME
        const auto saveTime = steady_clock::now();
#endif
        if (!fileWriter_->WriteRecord(*record)) {
            // write file failed, need stop record
            perfEvents_.StopTracking();
            HLOGV("fail to write record %s", record->GetName().c_str());
            return false;
        }
        if (record->GetType() == PERF_RECORD_SAMPLE) {
            recordSamples_++;
        } else {
            recordNoSamples_++;
        }
        HLOGV(" write done. size=%zu name=%s", record->GetSize(), record->GetName().c_str());
#ifdef HIPERF_DEBUG_TIME
        saveRecordTimes_ += duration_cast<microseconds>(steady_clock::now() - saveTime);
#endif
        return true;
    }
    return false;
}

uint32_t SubCommandRecord::GetCountFromFile(const std::string &fileName)
{
    uint32_t ret = 0;
    std::string str = ReadFileToString(fileName);
    std::vector<std::string> subStrs = StringSplit(str);
    for (auto subStr : subStrs) {
        ret++;
        std::vector<std::string> vSubstr = StringSplit(subStr, "-");
        static const size_t BEGIN_END = 2;
        if (vSubstr.size() == BEGIN_END) {
            ret += (std::stoi(vSubstr[1]) - std::stoi(vSubstr[0]));
        }
    }
    return ret;
}

std::string SubCommandRecord::GetCpuDescFromFile()
{
    std::string str = ReadFileToString("/proc/cpuinfo");
    std::vector<std::string> subStrs = StringSplit(str, "\n");
    for (auto subStr : subStrs) {
        if (subStr.find("model name") == std::string::npos) {
            continue;
        }

        std::vector<std::string> vSubstr = StringSplit(subStr, ": ");
        static const size_t NAME_VALUE = 2;
        if (vSubstr.size() == NAME_VALUE) {
            return vSubstr[1];
        } else {
            return "";
        }
    }
    return "";
}

bool SubCommandRecord::AddCpuFeature()
{
    utsname unameBuf;
    if ((uname(&unameBuf)) != 0) {
        perror("uname() failed");
        return false;
    }

    fileWriter_->AddStringFeature(FEATURE::OSRELEASE, unameBuf.release);
    fileWriter_->AddStringFeature(FEATURE::HOSTNAME, unameBuf.nodename);
    fileWriter_->AddStringFeature(FEATURE::ARCH, unameBuf.machine);

    try {
        uint32_t cpuPresent = GetCountFromFile("/sys/devices/system/cpu/present");
        uint32_t cpuOnline = GetCountFromFile("/sys/devices/system/cpu/online");
        fileWriter_->AddNrCpusFeature(FEATURE::NRCPUS, cpuPresent - cpuOnline, cpuOnline);
    } catch (...) {
        HLOGD("get NRCPUS failed");
        return false;
    }
    std::string cpuDesc = GetCpuDescFromFile();
    if (!fileWriter_->AddStringFeature(FEATURE::CPUDESC, cpuDesc)) {
        return false;
    }

    // CPUID(vendor,family,model,stepping in /proc/cpuinfo) isn't supported on Hi3516DV300
    // CPU_TOPOLOGY(sockets,dies,threads), isn't supported on Hi3516DV300
    // NUMA_TOPOLOGY
    // HEADER_PMU_MAPPINGS(/sys/bus/event_source/devices/cpu/type) isn't supported on Hi3516DV300

    return true;
}

void SubCommandRecord::AddMemTotalFeature()
{
    std::string str = ReadFileToString("/proc/meminfo");
    std::vector<std::string> subStrs = StringSplit(str, " ");
    for (auto it = subStrs.begin(); it != subStrs.end(); it++) {
        if (it->find("MemTotal:") == std::string::npos) {
            continue;
        }

        if ((it + 1) != subStrs.end()) {
            uint64_t memTotal = std::stoul(*(it + 1));
            fileWriter_->AddU64Feature(FEATURE::TOTAL_MEM, memTotal);
        }
        break;
    }
}

void SubCommandRecord::AddEventDescFeature()
{
    fileWriter_->AddEventDescFeature(FEATURE::EVENT_DESC, perfEvents_.GetAttrWithId());
}

void SubCommandRecord::AddRecordTimeFeature()
{
    // create time
    std::time_t time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    // clang-format off
    char buf[256] = { 0 };
    ctime_r(&time, buf);
    fileWriter_->AddStringFeature(FEATURE::HIPERF_RECORD_TIME,
                                  StringReplace(buf, "\n", ""));
    // clang-format on
    return;
}

void SubCommandRecord::AddWorkloadCmdFeature()
{
    if (trackedCommand_.size() > 0) {
        fileWriter_->AddStringFeature(FEATURE::HIPERF_WORKLOAD_CMD, trackedCommand_.at(0));
    } else {
        HLOGD("no trackedCommand");
    }
}

void SubCommandRecord::AddCommandLineFeature()
{
    // cmdline may end with some no ascii code
    // so we cp it with c_str again
    std::string fullCommandline =
        ReadFileToString("/proc/self/cmdline").c_str() + Command::fullArgument;
    fileWriter_->AddStringFeature(FEATURE::CMDLINE, fullCommandline);
}

void SubCommandRecord::AddCpuOffFeature()
{
    if (offCPU_) {
        fileWriter_->AddBoolFeature(FEATURE::HIPERF_CPU_OFF);
    }
}

void SubCommandRecord::AddDevhostFeature()
{
    if (isHM_) {
        fileWriter_->AddStringFeature(FEATURE::HIPERF_HM_DEVHOST,
            StringPrintf("%d", virtualRuntime_.devhostPid_));
    }
}

bool SubCommandRecord::AddFeatureRecordFile()
{
    // VERSION

    if (!AddCpuFeature()) {
        return false;
    }

    AddMemTotalFeature();

    AddCommandLineFeature();

    AddEventDescFeature();

    AddRecordTimeFeature();

    AddWorkloadCmdFeature();

    AddCpuOffFeature();

    AddDevhostFeature();

    return true;
}

bool SubCommandRecord::CreateInitRecordFile(bool compressData)
{
    if (fileWriter_ == nullptr) {
        fileWriter_ = std::make_unique<PerfFileWriter>();
    }

    if (!fileWriter_->Open(outputFilename_, compressData)) {
        return false;
    }

    if (!fileWriter_->WriteAttrAndId(perfEvents_.GetAttrWithId())) {
        return false;
    }

    if (!AddFeatureRecordFile()) {
        return false;
    }

    HLOGD("create new record file %s", outputFilename_.c_str());
    return true;
}

bool SubCommandRecord::PostProcessRecordFile()
{
    if (delayUnwind_) {
        // 1. prepare file to rewrite
        std::string tempFileName = outputFilename_ + ".tmp";
        if (rename(outputFilename_.c_str(), tempFileName.c_str()) != 0) {
            HLOGE("rename failed. unabel to do delay unwind");
            perror("Fail to rename data file");
            return false;
        } else {
            HLOGD("use temp file '%s' for delay unwind", tempFileName.c_str());
        }

        // renew record file
        // release the old one
        fileWriter_.reset();
        if (!CreateInitRecordFile(compressData_)) {
            // create again
            HLOGEP("Fail to open data file %s ", outputFilename_.c_str());
            return false;
        }

        // read temp file
        auto fileReader = PerfFileReader::Instance(tempFileName);
        if (fileReader == nullptr) {
            HLOGEP("Fail to open data file %s ", tempFileName.c_str());
            return false;
        }

        // 2. read out the file and unwind
        auto record_callback = [&](std::unique_ptr<PerfEventRecord> record) {
            if (record == nullptr) {
                // return false in callback can stop the read process
                return false;
            } else if (record->GetType() == PERF_RECORD_SAMPLE) {
                HLOGM("readback record for unwind");
                virtualRuntime_.UnwindFromRecord(static_cast<PerfRecordSample &>(*record));
            }
            SaveRecord(std::move(record));
            return true;
        };
        fileReader->ReadDataSection(record_callback);

        // 3. close again

        // lte FinishWriteRecordFile write matched only symbols
        delayUnwind_ = false;
        if (!FinishWriteRecordFile()) {
            HLOGE("Fail to finish record file %s", outputFilename_.c_str());
            return false;
        }

        remove(tempFileName.c_str());
    }
    return true;
}

#if USE_COLLECT_SYMBOLIC
void SubCommandRecord::SymbolicHits()
{
    if (isHM_) {
        for (auto &processPair : kernelThreadSymbolsHits_) {
            for (auto &vaddr : processPair.second) {
                virtualRuntime_.GetSymbol(vaddr, processPair.first, processPair.first,
                                          PERF_CONTEXT_MAX);
            }
        }
    }

    for (auto &vaddr : kernelSymbolsHits_) {
        virtualRuntime_.GetSymbol(vaddr, 0, 0, PERF_CONTEXT_KERNEL);
    }

    for (auto &processPair : userSymbolsHits_) {
        for (auto &vaddr : processPair.second) {
            virtualRuntime_.GetSymbol(vaddr, processPair.first, processPair.first,
                                      PERF_CONTEXT_USER);
        }
    }
}
#endif

bool SubCommandRecord::CollectionSymbol(std::unique_ptr<PerfEventRecord> record)
{
    if (record->GetType() == PERF_RECORD_SAMPLE) {
        PerfRecordSample *sample = static_cast<PerfRecordSample *>(record.get());
#if USE_COLLECT_SYMBOLIC
        CollectSymbol(sample);
#else
        virtualRuntime_.SymbolicRecord(*sample);
#endif
        // the record is allowed from a cache memory, does not free memory after use
        record.release();
    }
    return true;
}

void SubCommandRecord::CollectSymbol(PerfRecordSample *sample)
{
    perf_callchain_context context = sample->inKernel() ? PERF_CONTEXT_KERNEL
                                                        : PERF_CONTEXT_USER;
    pid_t server_pid;
    // if no nr use ip ? remove stack nr == 0?
    if (sample->data_.nr == 0) {
        server_pid = sample->GetServerPidof(0);
        if (virtualRuntime_.IsKernelThread(server_pid)) {
            kernelThreadSymbolsHits_[server_pid].insert(sample->data_.ip);
        } else if (context == PERF_CONTEXT_KERNEL) {
            kernelSymbolsHits_.insert(sample->data_.ip);
        } else {
            userSymbolsHits_[sample->data_.pid].insert(sample->data_.ip);
        }
    } else {
        for (u64 i = 0; i < sample->data_.nr; i++) {
            if (sample->data_.ips[i] >= PERF_CONTEXT_MAX) {
                if (sample->data_.ips[i] == PERF_CONTEXT_KERNEL) {
                    context = PERF_CONTEXT_KERNEL;
                } else {
                    context = PERF_CONTEXT_USER;
                }
            } else {
                server_pid = sample->GetServerPidof(i);
                if (virtualRuntime_.IsKernelThread(server_pid)) {
                    kernelThreadSymbolsHits_[server_pid].insert(sample->data_.ips[i]);
                } else if (context == PERF_CONTEXT_KERNEL) {
                    kernelSymbolsHits_.insert(sample->data_.ips[i]);
                } else {
                    userSymbolsHits_[sample->data_.pid].insert(sample->data_.ips[i]);
                }
            }
        }
    }
}

// finish writing data file, then close file
bool SubCommandRecord::FinishWriteRecordFile()
{
#ifdef HIPERF_DEBUG_TIME
    const auto startTime = steady_clock::now();
#endif
#if !HIDEBUG_SKIP_PROCESS_SYMBOLS
    if (!delayUnwind_) {
#if !HIDEBUG_SKIP_LOAD_KERNEL_SYMBOLS
        if (!callChainUserOnly_) {
            virtualRuntime_.UpdateKernelSymbols();
            virtualRuntime_.UpdateKernelModulesSymbols();
            if (isHM_) {
                virtualRuntime_.UpdateServiceSymbols();
            }
        }
        if (isHM_) {
            virtualRuntime_.UpdateDevhostSymbols();
        }
#endif
        HLOGD("Load user symbols");
        if (dedupStack_) {
            virtualRuntime_.CollectDedupSymbol(kernelSymbolsHits_, userSymbolsHits_);
        } else {
            fileWriter_->ReadDataSection(
                std::bind(&SubCommandRecord::CollectionSymbol, this, std::placeholders::_1));
        }
#if USE_COLLECT_SYMBOLIC
        SymbolicHits();
#endif
#if HIDEBUG_SKIP_MATCH_SYMBOLS
        disableUnwind_ = true;
#endif
#if !HIDEBUG_SKIP_SAVE_SYMBOLS
        if (!fileWriter_->AddSymbolsFeature(virtualRuntime_.GetSymbolsFiles())) {
            HLOGE("Fail to AddSymbolsFeature");
            return false;
        }
#endif
    }
#endif

    if (dedupStack_ &&
        !fileWriter_->AddUniStackTableFeature(virtualRuntime_.GetUniStackTable())) {
        return false;
    }
    if (!fileWriter_->Close()) {
        HLOGE("Fail to close record file %s", outputFilename_.c_str());
        return false;
    }
#ifdef HIPERF_DEBUG_TIME
    saveFeatureTimes_ += duration_cast<microseconds>(steady_clock::now() - startTime);
#endif
    return true;
}

#ifdef HIPERF_DEBUG_TIME
void SubCommandRecord::ReportTime()
{
    printf("updateSymbolsTimes: %0.3f ms\n",
           virtualRuntime_.updateSymbolsTimes_.count() / MS_DURATION);
    printf("saveFeatureTimes: %0.3f ms\n", saveFeatureTimes_.count() / MS_DURATION);

    printf("prcessRecordTimes: %0.3f ms\n", prcessRecordTimes_.count() / MS_DURATION);
    printf("-prcessSampleRecordTimes: %0.3f ms\n",
           virtualRuntime_.processSampleRecordTimes_.count() / MS_DURATION);
    printf("--unwindFromRecordTimes: %0.3f ms\n",
           virtualRuntime_.unwindFromRecordTimes_.count() / MS_DURATION);
    printf("-prcessMmapRecordTimes: %0.3f ms\n",
           virtualRuntime_.processMmapRecordTimes_.count() / MS_DURATION);
    printf("-prcessMmap2RecordTimes: %0.3f ms\n",
           virtualRuntime_.processMmap2RecordTimes_.count() / MS_DURATION);
    printf("-prcessCommRecordTimes: %0.3f ms\n",
           virtualRuntime_.processCommRecordTimes_.count() / MS_DURATION);
    printf("-prcessMmap2RecordTimes: %0.3f ms\n",
           virtualRuntime_.processMmap2RecordTimes_.count() / MS_DURATION);
    printf("--updateThreadTimes: %0.3f ms\n",
           virtualRuntime_.updateThreadTimes_.count() / MS_DURATION);
    printf("---threadParseMapsTimes: %0.3f ms\n",
           virtualRuntime_.threadParseMapsTimes_.count() / MS_DURATION);
    printf("---threadCreateMmapTimes: %0.3f ms\n",
           virtualRuntime_.threadCreateMmapTimes_.count() / MS_DURATION);
    printf("--unwindCallStackTimes: %0.3f ms\n",
           virtualRuntime_.unwindCallStackTimes_.count() / MS_DURATION);
    printf("-symbolicRecordTimes: %0.3f ms\n",
           virtualRuntime_.symbolicRecordTimes_.count() / MS_DURATION);
    printf("saveRecordTimes: %0.3f ms\n", saveRecordTimes_.count() / MS_DURATION);
    printf("-writeTimes: %0.3f ms\n", fileWriter_->writeTimes_.count() / MS_DURATION);

    printf("logTimes: %0.3f ms\n", DebugLogger::GetInstance()->logTimes_.count() / MS_DURATION);
    printf("-logSprintfTimes: %0.3f ms\n",
           DebugLogger::GetInstance()->logSprintfTimes_.count() / MS_DURATION);
    printf("-logWriteTimes: %0.3f ms\n",
           DebugLogger::GetInstance()->logWriteTimes_.count() / MS_DURATION);
    printf("logCount: %zu (%4.2f ms/log)\n", DebugLogger::GetInstance()->logCount_,
           DebugLogger::GetInstance()->logTimes_.count() /
               static_cast<double>(DebugLogger::GetInstance()->logCount_) / MS_DURATION);
}
#endif

bool SubCommandRecord::RecordCompleted()
{
    if (verboseReport_) {
        printf("Save Record used %0.3f ms.\n",
               duration_cast<microseconds>(steady_clock::now() - startSaveFileTimes_).count() /
                   MS_DURATION);
    }
    HLOGV("Save Record used %0.3f ms.\n",
          duration_cast<microseconds>(steady_clock::now() - startSaveFileTimes_).count() /
              MS_DURATION);

    // print brief file info
    double mb = static_cast<double>(fileWriter_->GetDataSize()) / (KILO * KILO);
    if (compressData_) {
        printf("[ hiperf record: Captured and compressed %.3f MB perf data. ]\n", mb);
    } else {
        printf("[ hiperf record: Captured %.3f MB perf data. ]\n", mb);
    }
    printf("[ Sample records: %zu, Non sample records: %zu ]\n", recordSamples_, recordNoSamples_);
    // Show brief sample lost.
    size_t lostSamples = 0;
    size_t lostNonSamples = 0;
    perfEvents_.GetLostSamples(lostSamples, lostNonSamples);
    printf("[ Sample lost: %zu, Non sample lost: %zu ]\n", lostSamples, lostNonSamples);

#ifdef HIPERF_DEBUG_TIME
    ReportTime();
#endif
    return true;
}

bool SubCommandRecord::RegisterSubCommandRecord(void)
{
    return SubCommand::RegisterSubCommand("record", std::make_unique<SubCommandRecord>());
}

void SubCommandRecord::SetHM()
{
    utsname unameBuf;
    if ((uname(&unameBuf)) == 0) {
        std::string osrelease = unameBuf.release;
        isHM_ = osrelease.find(HMKERNEL) != std::string::npos;
    }
    virtualRuntime_.SetHM(isHM_);
    perfEvents_.SetHM(isHM_);
    HLOGD("Set isHM_: %d", isHM_);
    if (isHM_) {
        // find devhost pid
        const std::string basePath {"/proc/"};
        std::vector<std::string> subDirs = GetSubDirs(basePath);
        for (const auto &subDir : subDirs) {
            if (!IsDigits(subDir)) {
                continue;
            }
            pid_t pid = std::stoll(subDir);
            std::string cmdline = GetProcessName(pid);
            if (cmdline == "/bin/" + DEVHOST_FILE_NAME) {
                virtualRuntime_.SetDevhostPid(pid);
                break;
            }
        }
    }
}
} // namespace HiPerf
} // namespace Developtools
} // namespace OHOS