* Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
* libkperf licensed under the 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 FITNESS FOR A PARTICULAR
* PURPOSE.
* See the Mulan PSL v2 for more details.
* Author: Wu
* Create: 2026-06-12
* Description: Java trace utility functions for symbol parsing, config loading, command building and UTraceData process
******************************************************************************/
#include "java_trace_util.h"
#ifdef JAVA_TRACE
#include "java_trace_config.h"
#endif
#include <algorithm>
#include <cerrno>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cctype>
#include <dlfcn.h>
#include <limits.h>
#include <regex>
#include <sys/wait.h>
#include <unistd.h>
#include <unordered_set>
#ifndef LIBKPERF_JAVA_REL_DIR
#define LIBKPERF_JAVA_REL_DIR "lib/java"
#endif
#ifndef LIBKPERF_JAVA_CONF_REL_DIR
#define LIBKPERF_JAVA_CONF_REL_DIR "conf"
#endif
#ifndef LIBKPERF_JAVA_CLI_JAR_NAME
#define LIBKPERF_JAVA_CLI_JAR_NAME "trace_cli.jar"
#endif
#ifndef LIBKPERF_JAVA_AGENT_JAR_NAME
#define LIBKPERF_JAVA_AGENT_JAR_NAME "trace_agent.jar"
#endif
#ifndef LIBKPERF_JAVA_NATIVE_LIB_NAME
#define LIBKPERF_JAVA_NATIVE_LIB_NAME "libtracex_threadinfo.so"
#endif
#ifndef LIBKPERF_JAVA_FILTER_CONFIG_NAME
#define LIBKPERF_JAVA_FILTER_CONFIG_NAME "trace_filter.conf"
#endif
#ifndef LIBKPERF_JAVA_DEFAULT_SLOT_COUNT
#define LIBKPERF_JAVA_DEFAULT_SLOT_COUNT 1048576U
#endif
#ifndef LIBKPERF_JAVA_MAX_SLOT_COUNT
#define LIBKPERF_JAVA_MAX_SLOT_COUNT 67108864U
#endif
namespace {
static constexpr const char *kDefaultJavaBin = "java";
static constexpr uint32_t kDefaultSlotCount = LIBKPERF_JAVA_DEFAULT_SLOT_COUNT;
static constexpr uint32_t kMaxSlotCount = LIBKPERF_JAVA_MAX_SLOT_COUNT;
struct JavaFunc {
bool valid = false;
std::string className;
std::string methodName;
};
static std::string Trim(const std::string &s)
{
size_t first = 0;
while (first < s.size() && std::isspace(static_cast<unsigned char>(s[first]))) {
++first;
}
size_t last = s.size();
while (last > first && std::isspace(static_cast<unsigned char>(s[last - 1]))) {
--last;
}
return s.substr(first, last - first);
}
static std::string StripComment(const std::string &line)
{
size_t end = line.size();
size_t hash = line.find('#');
if (hash != std::string::npos) {
end = std::min(end, hash);
}
size_t slashes = line.find("//");
if (slashes != std::string::npos) {
end = std::min(end, slashes);
}
return line.substr(0, end);
}
static bool FileExists(const std::string &path)
{
return !path.empty() && access(path.c_str(), R_OK) == 0;
}
static std::string JoinPath(const std::string &a, const std::string &b)
{
if (a.empty()) {
return b;
}
return a.back() == '/' ? a + b : a + "/" + b;
}
static std::string DirName(const std::string &path)
{
size_t pos = path.rfind('/');
if (pos == std::string::npos) {
return ".";
}
if (pos == 0) {
return "/";
}
return path.substr(0, pos);
}
static std::string SelfExeDir()
{
char buf[PATH_MAX];
ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
if (n <= 0) {
return "";
}
buf[n] = '\0';
return DirName(std::string(buf));
}
static std::string CurrentLibDir()
{
Dl_info info;
if (dladdr(reinterpret_cast<void *>(&CurrentLibDir), &info) == 0 || info.dli_fname == nullptr) {
return "";
}
return DirName(std::string(info.dli_fname));
}
static std::string FindJavaAsset(const char *fileName)
{
std::string exeDir = SelfExeDir();
if (!exeDir.empty()) {
std::string toolRoot = DirName(exeDir);
std::string p = JoinPath(JoinPath(toolRoot, LIBKPERF_JAVA_REL_DIR), fileName);
if (FileExists(p)) {
return p;
}
}
std::string libDir = CurrentLibDir();
if (!libDir.empty()) {
std::string libRoot = DirName(libDir);
std::string p = JoinPath(JoinPath(libRoot, LIBKPERF_JAVA_REL_DIR), fileName);
if (FileExists(p)) {
return p;
}
}
return "";
}
static std::string CliJarPath()
{
return FindJavaAsset(LIBKPERF_JAVA_CLI_JAR_NAME);
}
static std::string AgentJarPath()
{
return FindJavaAsset(LIBKPERF_JAVA_AGENT_JAR_NAME);
}
static std::string NativeLibPath()
{
return FindJavaAsset(LIBKPERF_JAVA_NATIVE_LIB_NAME);
}
static uint32_t ClampSlotCount(uint64_t value)
{
if (value < kDefaultSlotCount) {
return kDefaultSlotCount;
}
if (value > kMaxSlotCount) {
return kMaxSlotCount;
}
return static_cast<uint32_t>(value);
}
static uint32_t ParseSlotCountConfig(const std::string &value, uint32_t fallback)
{
char *end = nullptr;
unsigned long long parsed = std::strtoull(value.c_str(), &end, 10);
if (end != value.c_str() && *end == '\0' && parsed > 0) {
return ClampSlotCount(parsed);
}
return fallback;
}
static std::string EscapeShell(const std::string &s)
{
std::string out;
out.reserve(s.size() + 2);
out.push_back('\'');
for (char c : s) {
if (c == '\'') {
out += "'\\''";
} else {
out.push_back(c);
}
}
out.push_back('\'');
return out;
}
static JavaFunc ParseJavaFunction(const SymbolSource &src)
{
JavaFunc out;
const char *moduleName = src.moduleName;
const char *symbolName = src.symbolName;
if (symbolName != nullptr && symbolName[0] != '\0') {
static const std::regex javaFuncRe(R"(L([^;]+);::([^()\s]+))");
std::cmatch match;
if (std::regex_search(symbolName, match, javaFuncRe) && match.size() >= 3) {
out.valid = true;
out.className = match[1].str();
out.methodName = match[2].str();
return out;
}
}
if (moduleName != nullptr && moduleName[0] == 'L' && symbolName != nullptr && symbolName[0] != '\0') {
std::string cls = StripJavaClassName(moduleName);
if (!cls.empty()) {
out.valid = true;
out.className = cls;
out.methodName = symbolName;
return out;
}
}
return out;
}
static void AddSplitSymbol(std::vector<std::string> &modules, std::vector<std::string> &symbols,
std::vector<SymbolSource> &out, const std::string &module, const std::string &symbol)
{
modules.emplace_back(module);
symbols.emplace_back(symbol);
SymbolSource src = {0};
src.moduleName = const_cast<char *>(modules.back().c_str());
src.symbolName = const_cast<char *>(symbols.back().c_str());
out.emplace_back(src);
}
static std::string WriteIncludeFile(const JavaBackendImpl &impl)
{
if (impl.include_rules.empty()) {
return "";
}
std::string path = impl.shm_path + ".includes";
FILE *fp = std::fopen(path.c_str(), "wb");
if (fp == nullptr) {
std::fprintf(stderr, "[trace-java] open include file failed: %s, errno=%d(%s)\n", path.c_str(), errno, std::strerror(errno));
return "";
}
size_t expected = impl.include_rules.size();
size_t written = std::fwrite(impl.include_rules.data(), 1, expected, fp);
if (written != expected) {
int err = errno;
std::fprintf(stderr, "[trace-java] write include file incomplete: %s, written=%zu, expected=%zu, errno=%d(%s)\n",
path.c_str(), written, expected, err, std::strerror(err));
if (std::fclose(fp) != 0) {
std::fprintf(stderr, "[trace-java] close include file after write failure failed: %s, errno=%d(%s)\n",
path.c_str(), errno, std::strerror(errno));
}
std::remove(path.c_str());
return "";
}
if (std::fclose(fp) != 0) {
int err = errno;
std::fprintf(stderr, "[trace-java] close include file failed: %s, errno=%d(%s)\n", path.c_str(), err, std::strerror(err));
std::remove(path.c_str());
return "";
}
return path;
}
}
std::string StripJavaClassName(const std::string &s)
{
if (s.size() >= 2 && s.front() == 'L' && s.back() == ';') {
return s.substr(1, s.size() - 2);
}
return s;
}
SplitTraceAttr SplitSymbolsByRegex(const UTraceAttr *attr)
{
SplitTraceAttr out;
if (attr == nullptr || attr->symSrc == nullptr || attr->numSym == 0) {
return out;
}
out.javaModules.reserve(attr->numSym);
out.javaSymbols.reserve(attr->numSym);
out.javaSymSrc.reserve(attr->numSym);
out.nativeModules.reserve(attr->numSym);
out.nativeSymbols.reserve(attr->numSym);
out.nativeSymSrc.reserve(attr->numSym);
for (unsigned i = 0; i < attr->numSym; ++i) {
SymbolSource &src = attr->symSrc[i];
JavaFunc javaFunc = ParseJavaFunction(src);
if (javaFunc.valid) {
if (javaFunc.className.empty() || javaFunc.methodName.empty()) {
continue;
}
AddSplitSymbol(out.javaModules, out.javaSymbols, out.javaSymSrc, javaFunc.className, javaFunc.methodName);
continue;
}
std::string module = src.moduleName == nullptr ? "" : src.moduleName;
std::string symbol = src.symbolName == nullptr ? "" : src.symbolName;
if (module.empty() || symbol.empty()) {
continue;
}
AddSplitSymbol(out.nativeModules, out.nativeSymbols, out.nativeSymSrc, module, symbol);
}
return out;
}
UTraceAttr MakeSubAttr(const UTraceAttr *src, std::vector<SymbolSource> &symSrc)
{
UTraceAttr out = *src;
out.symSrc = symSrc.empty() ? nullptr : symSrc.data();
out.numSym = static_cast<unsigned>(symSrc.size());
return out;
}
std::string BuildJavaSymSrc(const UTraceAttr *attr)
{
if (attr == nullptr || attr->symSrc == nullptr || attr->numSym == 0) {
return "";
}
std::unordered_set<std::string> includes;
for (unsigned i = 0; i < attr->numSym; ++i) {
const char *module = attr->symSrc[i].moduleName;
const char *symbol = attr->symSrc[i].symbolName;
if (module == nullptr || module[0] == '\0') {
continue;
}
std::string mod = StripJavaClassName(module);
std::string sym = symbol == nullptr ? "" : symbol;
if (!sym.empty() && sym != "*") {
includes.emplace(mod + "/" + sym);
} else {
includes.emplace(mod);
}
}
std::string result;
for (const auto &include : includes) {
if (!result.empty()) {
result += ",";
}
result += include;
}
return result;
}
std::string FilterConfigPath()
{
std::string exeDir = SelfExeDir();
if (!exeDir.empty()) {
std::string toolRoot = DirName(exeDir);
std::string confPath = JoinPath(JoinPath(toolRoot, LIBKPERF_JAVA_CONF_REL_DIR), LIBKPERF_JAVA_FILTER_CONFIG_NAME);
if (FileExists(confPath)) {
return confPath;
}
}
std::string libDir = CurrentLibDir();
if (!libDir.empty()) {
std::string libRoot = DirName(libDir);
std::string confPath = JoinPath(JoinPath(libRoot, LIBKPERF_JAVA_CONF_REL_DIR), LIBKPERF_JAVA_FILTER_CONFIG_NAME);
if (FileExists(confPath)) {
return confPath;
}
}
return FindJavaAsset(LIBKPERF_JAVA_FILTER_CONFIG_NAME);
}
JavaTraceLocalConfig LoadLocalConfig(const std::string &path)
{
JavaTraceLocalConfig out{LIBKPERF_JAVA_DEFAULT_SLOT_COUNT};
if (path.empty()) {
out.slotCount = ClampSlotCount(out.slotCount);
return out;
}
FILE *fp = std::fopen(path.c_str(), "rb");
if (fp == nullptr) {
out.slotCount = ClampSlotCount(out.slotCount);
return out;
}
char buf[1024];
while (std::fgets(buf, sizeof(buf), fp) != nullptr) {
std::string s = Trim(StripComment(buf));
if (s.empty() || (s.front() == '[' && s.back() == ']')) {
continue;
}
size_t eq = s.find('=');
if (eq == std::string::npos || eq == 0) {
continue;
}
std::string key = Trim(s.substr(0, eq));
std::string value = Trim(s.substr(eq + 1));
if (key == "slot_count") {
out.slotCount = ParseSlotCountConfig(value, out.slotCount);
}
}
std::fclose(fp);
out.slotCount = ClampSlotCount(out.slotCount);
return out;
}
std::string TimestampSuffix()
{
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
char buf[32];
std::strftime(buf, sizeof(buf), "%Y%m%d%H%M%S", std::localtime(&t));
unsigned long long usec = static_cast<unsigned long long>(
std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count() % 1000000);
char suffix[48];
std::snprintf(suffix, sizeof(suffix), "%s%06llu", buf, usec);
return std::string(suffix);
}
std::string BuildEnableCommand(const JavaBackendImpl &impl)
{
std::string cliJar = CliJarPath();
std::string agentJar = AgentJarPath();
std::string nativeLib = NativeLibPath();
if (cliJar.empty()) {
std::fprintf(stderr, "[trace-java] error: trace java cli jar not found\n");
return "";
}
if (agentJar.empty()) {
std::fprintf(stderr, "[trace-java] error: trace java agent jar not found\n");
return "";
}
std::string cmd;
cmd.reserve(2048);
cmd += EscapeShell(kDefaultJavaBin);
cmd += " -jar ";
cmd += EscapeShell(cliJar);
cmd += " -p ";
cmd += std::to_string(impl.pid);
cmd += " --agent-jar ";
cmd += EscapeShell(agentJar);
cmd += " --action start";
cmd += " --shm-path ";
cmd += EscapeShell(impl.shm_path);
cmd += " --native-lib ";
cmd += EscapeShell(nativeLib);
if (!impl.filter_config_path.empty()) {
cmd += " --config-file ";
cmd += EscapeShell(impl.filter_config_path);
}
if (!impl.include_rules.empty()) {
std::string includeFile = WriteIncludeFile(impl);
if (includeFile.empty()) {
std::fprintf(stderr, "[trace-java] error: include file create failed\n");
return "";
}
cmd += " --include-file ";
cmd += EscapeShell(includeFile);
}
return cmd;
}
std::string BuildActionCommand(const JavaBackendImpl &impl, const char *action)
{
std::string cliJar = CliJarPath();
std::string agentJar = AgentJarPath();
if (cliJar.empty() || agentJar.empty() || impl.pid <= 0 || action == nullptr) {
return "";
}
std::string cmd;
cmd.reserve(1024);
cmd += EscapeShell(kDefaultJavaBin);
cmd += " -jar ";
cmd += EscapeShell(cliJar);
cmd += " -p ";
cmd += std::to_string(impl.pid);
cmd += " --agent-jar ";
cmd += EscapeShell(agentJar);
cmd += " --action ";
cmd += EscapeShell(action);
return cmd;
}
int RunCommand(const std::string &cmd)
{
int status = std::system(cmd.c_str());
if (status == -1) {
return -2;
}
if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
return status;
}
return 0;
}
char *TraceDupCString(const char *s)
{
if (s == nullptr) {
return nullptr;
}
size_t len = std::strlen(s);
char *p = static_cast<char *>(std::malloc(len + 1));
if (p == nullptr) {
return nullptr;
}
std::memcpy(p, s, len + 1);
return p;
}
char *TraceDupString(const std::string &s)
{
char *p = static_cast<char *>(std::malloc(s.size() + 1));
if (p == nullptr) {
return nullptr;
}
std::memcpy(p, s.c_str(), s.size() + 1);
return p;
}
UTraceData DeepCopyTraceData(const UTraceData &src)
{
UTraceData dst = src;
dst.comm = TraceDupCString(src.comm);
dst.module = TraceDupCString(src.module);
dst.func = TraceDupCString(src.func);
return dst;
}
void FreeTraceDataFields(UTraceData &data)
{
std::free(const_cast<char *>(data.comm));
std::free(const_cast<char *>(data.module));
std::free(const_cast<char *>(data.func));
data.comm = nullptr;
data.module = nullptr;
data.func = nullptr;
}