* This file is part of the MindStudio project.
* Copyright (c) 2025 Huawei Technologies Co.,Ltd.
*
* MindStudio is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* 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 PSL v2 for more details.
* ------------------------------------------------------------------------- */
#include "ms_op_prof.h"
#include <thread>
#include <csignal>
#include <atomic>
#include <memory>
#include <iostream>
#include <dlfcn.h>
#include "common/defs.h"
#include "common/prof_args.h"
#include "common/hal_helper.h"
#include "argparser/arg_checker.h"
#include "argparser/arg_normalize.h"
#include "argparser/utils.h"
#include "argparser/parser.h"
#include "filesystem.h"
#include "log.h"
#include "json_parser.h"
#include "smart_pointer.h"
#include "profiling/op_prof.h"
#include "profiling/device/op_device_prof.h"
#include "profiling/simulator/op_sim_prof.h"
#include "profiling/op_prof_task.h"
#include "profiling/op_prof_data_parse.h"
#include "ascend_helper.h"
using namespace Common;
using namespace Parser;
using namespace Utility;
namespace Interface {
std::atomic<bool> g_isFirstReceiveSignal {true};
void SignalHandler(int signo)
{
(void)signo;
if (g_isFirstReceiveSignal || Interface::IsProcessRunning()) {
g_isFirstReceiveSignal = false;
Interface::SetExitMode();
return;
}
signal(SIGINT, SIG_DFL);
}
ArgParser BuildDeviceArgParser(ProfArgs &args)
{
ArgParser argParser("msopprof", "operator profiling tool");
argParser.Add(Switch('h', "help", args.printHelp));
argParser.Add(Switch('v', "version", args.printVersion));
argParser.Add(Option<std::string>('\0', "config", "ConfigPath", args.argConfig));
argParser.Add(Option<std::string>('\0', "application", "CMD", args.argApplication));
argParser.Add(Option<std::string>('\0', "output", "STRING", args.argOutput));
argParser.Add(Option<ProfMetricsAbilityConfig>('\0', "aic-metrics", "MetricsLists", args.argAicMetrics));
argParser.Add(Option<std::string>('\0', "kernel-name", "STRING", args.argKernelName));
argParser.Add(Option<std::string>('\0', "launch-count", "STRING", args.argLaunchCount));
argParser.Add(Option<std::string>('\0', "launch-skip-before-match", "STRING", args.argLaunchSkipBeforeMatch));
argParser.Add(Option<std::string>('\0', "replay-mode", "STRING", args.argReplayMode));
argParser.Add(Option<std::string>('\0', "kill", "STRING", args.argKill));
argParser.Add(Option<std::string>('\0', "mstx", "STRING", args.argMstx));
argParser.Add(Option<std::string>('\0', "mstx-include", "STRING", args.argMstxInclude));
argParser.Add(Option<std::string>('\0', "warm-up", "STRING", args.argWarmUp));
argParser.Add(Option<std::string>('\0', "dump", "STRING", args.argDump));
argParser.Add(Option<std::string>('\0', "core-id", "STRING", args.argCoreId));
argParser.Add(Option<std::string>('\0', "custom-input", "ConfigPath", args.argCustomInput));
argParser.Add(Option<std::string>('\0', "instr-timeline-pipe", "STRING", args.argInstrTimelinePipe));
return argParser;
}
ArgParser BuildSimulatorArgParser(ProfArgs &args)
{
ArgParser argParser("msopprof", "operator profiling tool");
argParser.Add(Switch('h', "help", args.printHelp));
argParser.Add(Switch('v', "version", args.printVersion));
argParser.Add(Option<std::string>('\0', "config", "ConfigPath", args.argConfig));
argParser.Add(Option<std::string>('\0', "application", "CMD", args.argApplication));
argParser.Add(Option<std::string>('\0', "export", "STRING", args.argExport));
argParser.Add(Option<std::string>('\0', "output", "STRING", args.argOutput));
argParser.Add(Option<std::string>('\0', "kernel-name", "STRING", args.argKernelName));
argParser.Add(Option<std::string>('\0', "launch-count", "STRING", args.argLaunchCount));
argParser.Add(Option<ProfMetricsAbilityConfig>('\0', "aic-metrics", "MetricsLists", args.argAicMetrics));
argParser.Add(Option<std::string>('\0', "mstx", "STRING", args.argMstx));
argParser.Add(Option<std::string>('\0', "mstx-include", "STRING", args.argMstxInclude));
argParser.Add(Option<std::string>('\0', "soc-version", "STRING", args.argSocVersion));
argParser.Add(Option<std::string>('\0', "core-id", "STRING", args.argCoreId));
argParser.Add(Option<std::string>('\0', "timeout", "STRING", args.argTimeout));
argParser.Add(Option<std::string>('\0', "dump", "STRING", args.argDump));
return argParser;
}
std::string GetFuncInjectionRevision()
{
std::string revision = "<unknown>";
char buf[PATH_MAX];
ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
if (len <= 0) {
return revision;
}
buf[len] = 0;
std::string soPath = std::string(buf);
if (!Utility::RollbackPath(soPath, 2)) {
return revision;
}
soPath = Utility::JoinPath({soPath, "lib64/libmsopprof_injection.so"});
if (!CheckInputFileValid(soPath, "so")) {
return revision;
}
void *handle = dlopen(soPath.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (handle == nullptr) {
return revision;
}
using FuncType = char const *(*)();
FuncType func = reinterpret_cast<FuncType>(dlsym(handle, "GetFuncInjectionRevision"));
if (func != nullptr) {
auto ret = func();
if (ret != nullptr) {
revision = ret;
}
}
dlclose(handle);
return revision;
}
void PrintVersion()
{
std::cout
<< "msopprof is part of MindStudio Operator-dev Tools." << std::endl
<< "msopprof vesion is " << __PACKAGE_VERSION__ << "-" << __MSOPPROF_COMMIT_REVISION__ << std::endl
<< "msopscommon version is " << GetFuncInjectionRevision() << std::endl;
}
void PrintDeviceHelp(ChipType chipType)
{
std::cout
<< "msopprof (MindStudio Profiler For Operator) is part of MindStudio Operator-dev Tools." << std::endl
<< "Used for Ascend C operator profiling by running on the board." << "\n" << std::endl
<< "Options:" << std::endl
<< " --version / -v <Optional> Version message." << std::endl
<< " --help / -h <Optional> Help message." << std::endl
<< " --config <Optional> Json file for op config path." << std::endl
<< " --application <Optional> Executable file path." << std::endl
<< " --output <Optional> Output path." << std::endl
<< " --aic-metrics=ability1,ability2,... <Optional> Enable collection capability of ArithmeticUtilization "
"/ MemoryUB / Memory / MemoryL0 / L2Cache / PipeUtilization" << std::endl
<< " / ResourceConflictRatio / Default / BasicInfo / Roofline ";
if (chipType == Common::ChipType::ASCEND910B) {
std::cout << "/ Occupancy / TimelineDetail / KernelScale / Source / MemoryDetail." << std::endl;
}
if (chipType == Common::ChipType::ASCEND950) {
std::cout << "/ Occupancy / KernelScale / Source / PCSampling / MemoryDetail" << std::endl
<< " / PipeTimeline / InstrTimeline." << std::endl
<< " --instr-timeline-pipe <Optional> Specify the pipe for instr timeline, "
"only effective when --aic-metrics=InstrTimeline." << std::endl;
}
std::cout
<< " --kernel-name <Optional> Specify the kernel name to profile."
<< " It's not effective in config mode." << std::endl
<< " --launch-count <Optional> Number of kernel that can be collected,"
<< " number in [1, 5000]." << std::endl
<< " --launch-skip-before-match <Optional> Set the number of kernel launch that you want to"
<< " skip before " << std::endl
<< " starting to analyze the kernel,"
<< " number in [0, 1000]. " << std::endl
<< " --replay-mode <Optional> Data collection replay Mode, select application / kernel";
if (chipType == Common::ChipType::ASCEND910B || chipType == Common::ChipType::ASCEND950) {
std::cout << " / range";
}
std::cout << ", the default value is kernel." << std::endl
<< " --kill <Optional> Kill op process when the number of kernel reaches"
" launch-count," << std::endl
<< " select on / off, the default value is off." << std::endl
<< " --mstx <Optional> Enable mstx api or not, select on / off,"
<< " the default value is off." << std::endl
<< " --mstx-include <Optional> Specify the mstx range for msprof op to be collected."
<< std::endl
<< " --warm-up <Optional> Set the number of warm up times."
<< " The default value is 5, range in [0, 500]." << std::endl;
if (chipType == Common::ChipType::ASCEND910B) {
std::cout << " --dump <Optional> Enable or disable dump flushed to disk mode, "
"only effective when --aic-metrics=TimelineDetail." << std::endl;
std::cout << " --core-id <Optional> Specify the id of cores to be parsed, "
"only effective when --aic-metrics=TimelineDetail and only effective in simulation products."
<< std::endl;
}
std::cout << " --custom-input <Optional> Json file for op custom input." << std::endl;
}
void PrintSimulatorHelp(void)
{
std::cout
<< "msopprof (MindStudio Profiler For Operator) is part of MindStudio Operator-dev Tools." << std::endl
<< "Used for Ascend C operator profiling by running on the simulator." << "\n" << std::endl
<< "Options:" << std::endl
<< " --version / -v <Optional> Version message." << std::endl
<< " --help / -h <Optional> Help message." << std::endl
<< " --config <Optional> Json file for op config path" << std::endl
<< " --application <Optional> Executable file path" << std::endl
<< " --export <Optional> Specify the dump data path for parsing" << std::endl
<< " --output <Optional> Output path" << std::endl
<< " --kernel-name <Optional> Specify the kernel name to profile."
<< " It's not effective in config mode." << std::endl
<< " --aic-metrics=metric1,metric2,... <Optional> Enable PipeUtilization / ResourceConflictRatio / "
"PMSampling / Overhead metrics. PipeUtilization is required" << std::endl
<< " --launch-count <Optional> Number of kernel that can be collected,"
<< " number in [1, 5000]." << std::endl
<< " --mstx <Optional> Enable mstx api or not, select on / off,"
<< " the default value is off." << std::endl
<< " --mstx-include <Optional> Specify the mstx range for msprof op to be collected."
<< std::endl
<< " --soc-version <Optional> Specify a simulator in ascend toolkit."
<< " It's not effective in config mode." << std::endl
<< " --core-id <Optional> Specify the id of cores to be parsed." << std::endl
<< " --timeout <Optional> Set the timeout minutes for application runs,"
" range in [1, 2880]."
<< std::endl
<< " --dump <Optional> Enable or disable dump flushed to disk mode, "
"This parameter is valid only for A2/A3." << std::endl;
}
void PrintErrorMsg(std::string const &msg)
{
if (!msg.empty()) {
LogError("%s", msg.c_str());
}
LogInfo("Use msprof op --help or msprof op simulator --help for more details");
}
bool ProfArgsNormalize(Common::ProfArgs &args, std::string &msg)
{
ArgNormalize normalizer;
if (!normalizer.Normalize(args, msg)) {
return false;
}
return true;
}
bool ProfArgsChecker(const Common::ProfArgs &args, std::string &msg)
{
ArgChecker checker(args.runMode);
if (!checker.Check(args, msg)) {
return false;
}
return true;
}
bool Parse(ArgParser &parser, int argc, char** argv, std::string &msg, Common::ProfArgs &args)
{
Either ret = parser.Parse(TokenS{argc, argv}, args);
if (ret.Valid()) {
return true;
}
Error error = ret.Left();
msg = error.msg;
return false;
}
bool ProfArgsFileParser(Common::ProfArgs &args)
{
std::string mode = args.runMode == "device" ? "onboard" : "ca";
std::string configType;
if (!GetFileSuffix(args.argConfig, configType)) {
return false;
}
std::vector<CaseConfig> caseConfigs = ParseRunConfigJson(args.argConfig, "msopprof", mode);
if (caseConfigs.empty()) {
return false;
}
if (caseConfigs.size() != 1) {
LogError("Only support one case for kernel.");
return false;
}
args.kernelConfig = caseConfigs[0].kernelConfig;
return true;
}
bool ProfArgsParse(int argc, char *argv[], ProfArgs &args, std::string &msg)
{
constexpr int skipArgNum = 2;
bool parseRet;
if (argc >= skipArgNum && std::string(argv[1]) == "simulator") {
ArgParser argParser = BuildSimulatorArgParser(args);
parseRet = Parse(argParser, argc - skipArgNum, argv + skipArgNum, msg, args);
args.runMode = "simulator";
} else {
ArgParser argParser = BuildDeviceArgParser(args);
parseRet = Parse(argParser, argc - 1, argv + 1, msg, args);
}
return parseRet;
}
bool ProfArgsInit(Common::ProfArgs &args, int argc, char *argv[], char *env[])
{
(void)env;
std::string msg;
if (!ProfArgsParse(argc, argv, args, msg)) {
PrintErrorMsg(msg);
return false;
}
if (args.printHelp || args.printVersion) {
return true;
}
if (!ProfArgsNormalize(args, msg) || !ProfArgsChecker(args, msg)) {
PrintErrorMsg(msg);
return false;
}
auto it = ReplayModeMap.find(args.argReplayMode);
if (it != ReplayModeMap.end()) {
args.argAicMetrics.replayMode = it->second;
}
args.argOutput = Utility::RandomizeDir(args.argOutput + Path::MSOPPROF_DIR_PREFIX);
if (!args.argConfig.empty() && !ProfArgsFileParser(args)) {
return false;
}
return true;
}
bool ProfilingRun(const Common::ProfArgs &args)
{
signal(SIGINT, SignalHandler);
if (args.argAicMetrics.isDeviceToSimulator) {
std::unique_ptr<Profiling::OpProf> deviceProfiling = MakeUnique<Profiling::OpDeviceProf>(args);
if (deviceProfiling && deviceProfiling->RunTask()) {
std::unique_ptr<Profiling::OpProf> simProfiling = MakeUnique<Profiling::OpSimProf>(args);
if (!simProfiling) {
LogError("Simulator profiling failed because of nullptr.");
return false;
}
simProfiling->dump_ = deviceProfiling->dump_;
bool simParseRes = simProfiling->RunDataParse(false);
bool deviceParseRes = deviceProfiling->RunDataParse();
if (!simParseRes) {
LogWarn("TimelineDetail data collection failed");
}
return (deviceParseRes && simParseRes);
}
return false;
}
std::unique_ptr<Profiling::OpProf> runProfiling;
if (args.runMode == "device") {
runProfiling = MakeUnique<Profiling::OpDeviceProf>(args);
} else {
runProfiling = MakeUnique<Profiling::OpSimProf>(args);
}
if (!runProfiling) {
LogError("Profiling failed because of nullptr");
return false;
}
return runProfiling->Run();
}
bool IsProcessRunning()
{
return (Profiling::Task::GetExecutionStatus() == Profiling::ExecStatus::RUNNING);
}
void SetExitMode()
{
Profiling::DataParse::inExitMode = true;
Profiling::Task::inExitMode = true;
}
}