diff --git a/projects/rocprofiler-systems/source/bin/rocprof-sys-causal/impl.cpp b/projects/rocprofiler-systems/source/bin/rocprof-sys-causal/impl.cpp index 14bf10b6c3..6ca6ce5530 100644 --- a/projects/rocprofiler-systems/source/bin/rocprof-sys-causal/impl.cpp +++ b/projects/rocprofiler-systems/source/bin/rocprof-sys-causal/impl.cpp @@ -24,7 +24,6 @@ #include "common/defines.h" #include "common/environment.hpp" -#include "common/join.hpp" #include "common/path.hpp" #include "core/mproc.hpp" #include "core/utility.hpp" @@ -37,6 +36,7 @@ #include #include +#include #include #include #include @@ -58,6 +58,7 @@ namespace console = ::tim::utility::console; namespace argparse = ::tim::argparse; namespace path = rocprofsys::common::path; using namespace ::timemory::join; +using rocprofsys::common::update_mode; using ::rocprofsys::utility::parse_numeric_range; using ::tim::get_env; using ::tim::log::monochrome; @@ -301,61 +302,28 @@ void update_env(std::vector& _environ, std::string_view _env_var, Tp&& _env_val, bool _append, std::string_view _join_delim) { - updated_envs.emplace(_env_var); - - auto _key = join("", _env_var, "="); - for(auto& itr : _environ) - { - if(!itr) continue; - if(std::string_view{ itr }.find(_key) == 0) - { - if(_append) - { - if(std::string_view{ itr }.find(join("", _env_val)) == - std::string_view::npos) - { - auto _val = std::string{ itr }.substr(_key.length()); - free(itr); - if(_env_var == "LD_PRELOAD") - { - itr = - strdup(join('=', _env_var, join(_join_delim, _env_val, _val)) - .c_str()); - } - else - { - itr = - strdup(join('=', _env_var, join(_join_delim, _val, _env_val)) - .c_str()); - } - } - } - else - { - free(itr); - itr = strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str()); - } - return; - } - } - _environ.emplace_back( - strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str())); + auto _mode = _append ? update_mode::APPEND : update_mode::REPLACE; + rocprofsys::common::update_env(_environ, _env_var, std::forward(_env_val), _mode, + _join_delim, updated_envs, original_envs); } template void add_default_env(std::vector& _environ, std::string_view _env_var, Tp&& _env_val) { - auto _key = join("", _env_var, "="); - for(auto& itr : _environ) - { - if(!itr) continue; - if(std::string_view{ itr }.find(_key) == 0) return; - } + // Check if already exists + auto _key = join("", _env_var, "="); + const auto exists = + std::any_of(_environ.begin(), _environ.end(), [&_key](const char* itr) { + return itr && std::string_view{ itr }.find(_key) == 0; + }); - updated_envs.emplace(_env_var); - _environ.emplace_back( - strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str())); + if(exists) return; + + // If not exists, use common::update_env + rocprofsys::common::update_env(_environ, _env_var, std::forward(_env_val), + update_mode::REPLACE, ":", updated_envs, + original_envs); } std::vector diff --git a/projects/rocprofiler-systems/source/bin/rocprof-sys-causal/rocprof-sys-causal.hpp b/projects/rocprofiler-systems/source/bin/rocprof-sys-causal/rocprof-sys-causal.hpp index ef0de26a3e..41f7ce58b8 100644 --- a/projects/rocprofiler-systems/source/bin/rocprof-sys-causal/rocprof-sys-causal.hpp +++ b/projects/rocprofiler-systems/source/bin/rocprof-sys-causal/rocprof-sys-causal.hpp @@ -32,6 +32,11 @@ #include #include +template +void +update_env(std::vector& _environ, std::string_view _env_var, Tp&& _env_val, + bool _append = false, std::string_view _join_delim = ":"); + int get_verbose(); @@ -49,15 +54,6 @@ prepare_command_for_run(char*, std::vector&); void prepare_environment_for_run(std::vector&); -template -void -update_env(std::vector&, std::string_view, Tp&&, bool _append = false, - std::string_view _join_delim = ":"); - -template -void -add_default_env(std::vector&, std::string_view, Tp&&); - std::vector parse_args(int argc, char** argv, std::vector&, std::vector>&); diff --git a/projects/rocprofiler-systems/source/bin/rocprof-sys-run/impl.cpp b/projects/rocprofiler-systems/source/bin/rocprof-sys-run/impl.cpp index b9510236b1..f869ec8ca0 100644 --- a/projects/rocprofiler-systems/source/bin/rocprof-sys-run/impl.cpp +++ b/projects/rocprofiler-systems/source/bin/rocprof-sys-run/impl.cpp @@ -24,7 +24,6 @@ #include "common/defines.h" #include "common/environment.hpp" -#include "common/join.hpp" #include "common/path.hpp" #include "core/argparse.hpp" #include "core/timemory.hpp" @@ -76,71 +75,9 @@ to_string(bool _v) namespace { +using rocprofsys::common::update_mode; + auto original_envs = std::unordered_set{}; -enum update_mode : int -{ - UPD_REPLACE = 0, // no PREPEND/APPEND bits set - UPD_PREPEND = 1 << 0, // 0x01 - UPD_APPEND = 1 << 1, // 0x02 - UPD_WEAK = 1 << 2, // 0x04 -}; - -template -void -update_env(std::vector& _environ, std::string_view _env_var, Tp&& _env_val, - update_mode&& _mode, std::string_view _join_delim = ":") -{ - auto _prepend = (_mode & UPD_PREPEND) != 0; - auto _append = (_mode & UPD_APPEND) != 0; - auto _weak_upd = (_mode & UPD_WEAK) != 0; - - // if both flags are set, prefer append - if(_prepend && _append) - { - _prepend = false; - } - - auto _key = join("", _env_var, "="); - for(auto& itr : _environ) - { - if(!itr) continue; - if(std::string_view{ itr }.find(_key) == 0) - { - if(_weak_upd) - { - // if the value has changed, do not update but allow overridding the value - // inherited from the initial env - if(original_envs.find(std::string{ itr }) == original_envs.end()) return; - } - - if(_prepend || _append) - { - if(std::string_view{ itr }.find(join("", _env_val)) == - std::string_view::npos) - { - auto _val = std::string{ itr }.substr(_key.length()); - free(itr); - if(_prepend) - itr = - strdup(join('=', _env_var, join(_join_delim, _env_val, _val)) - .c_str()); - else - itr = - strdup(join('=', _env_var, join(_join_delim, _val, _env_val)) - .c_str()); - } - } - else - { - free(itr); - itr = strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str()); - } - return; - } - } - _environ.emplace_back( - strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str())); -} int get_verbose(parser_data_t& _data) @@ -172,16 +109,18 @@ get_initial_environment(parser_data_t& _data) auto _libexecpath = path::realpath(path::get_internal_script_path()); if(!_libexecpath.empty()) { - update_env(_data.current, "ROCPROFSYS_SCRIPT_PATH", _libexecpath, UPD_REPLACE); - _data.updated.emplace("ROCPROFSYS_SCRIPT_PATH"); + rocprofsys::common::update_env(_data.current, "ROCPROFSYS_SCRIPT_PATH", + _libexecpath, update_mode::REPLACE, ":", + _data.updated, original_envs); } const bool verbose = (get_verbose(_data) > 0); if(auto llvm_dir = rocprofsys::common::discover_llvm_libdir_for_ompt(verbose); !llvm_dir.empty()) { - update_env(_data.current, "LD_LIBRARY_PATH", llvm_dir, UPD_APPEND); - _data.updated.emplace("LD_LIBRARY_PATH"); + rocprofsys::common::update_env(_data.current, "LD_LIBRARY_PATH", llvm_dir, + update_mode::APPEND, ":", _data.updated, + original_envs); auto current_ld = getenv("LD_LIBRARY_PATH"); std::string new_ld = current_ld ? (llvm_dir + ":" + current_ld) : llvm_dir; setenv("LD_LIBRARY_PATH", new_ld.c_str(), 1); diff --git a/projects/rocprofiler-systems/source/bin/rocprof-sys-sample/impl.cpp b/projects/rocprofiler-systems/source/bin/rocprof-sys-sample/impl.cpp index 0b1d763e21..12894a1b79 100644 --- a/projects/rocprofiler-systems/source/bin/rocprof-sys-sample/impl.cpp +++ b/projects/rocprofiler-systems/source/bin/rocprof-sys-sample/impl.cpp @@ -23,7 +23,6 @@ #include "rocprof-sys-sample.hpp" #include "common/environment.hpp" -#include "common/join.hpp" #include "common/path.hpp" #include @@ -46,6 +45,7 @@ namespace color = tim::log::color; namespace path = rocprofsys::common::path; using namespace timemory::join; using rocprofsys::common::remove_env; +using rocprofsys::common::update_mode; using tim::get_env; using tim::log::monochrome; using tim::log::stream; @@ -118,21 +118,32 @@ get_initial_environment() auto _libexecpath = path::realpath(path::get_internal_script_path()); auto _rootpath = path::realpath(path::get_rocprofsys_root()); - update_env(_env, "ROCPROFSYS_ROOT", _rootpath, UPD_REPLACE); - update_env(_env, "LD_PRELOAD", _dl_libpath, UPD_APPEND); - update_env(_env, "LD_LIBRARY_PATH", tim::filepath::dirname(_dl_libpath), UPD_APPEND); - update_env(_env, "ROCPROFSYS_SCRIPT_PATH", _libexecpath, UPD_REPLACE); + rocprofsys::common::update_env(_env, "ROCPROFSYS_ROOT", _rootpath, + update_mode::REPLACE, ":", updated_envs, + original_envs); + rocprofsys::common::update_env(_env, "LD_PRELOAD", _dl_libpath, update_mode::APPEND, + ":", updated_envs, original_envs); + rocprofsys::common::update_env(_env, "LD_LIBRARY_PATH", + tim::filepath::dirname(_dl_libpath), + update_mode::APPEND, ":", updated_envs, original_envs); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SCRIPT_PATH", _libexecpath, + update_mode::REPLACE, ":", updated_envs, + original_envs); // Discover LLVM libdir containing libomptarget.so and append to LD_LIBRARY_PATH if(auto llvm_dir = rocprofsys::common::discover_llvm_libdir_for_ompt(verbose > 0); !llvm_dir.empty()) { - update_env(_env, "LD_LIBRARY_PATH", llvm_dir, UPD_APPEND); + rocprofsys::common::update_env(_env, "LD_LIBRARY_PATH", llvm_dir, + update_mode::APPEND, ":", updated_envs, + original_envs); } auto _mode = get_env("ROCPROFSYS_MODE", "sampling", false); - update_env(_env, "ROCPROFSYS_USE_SAMPLING", (_mode != "causal")); + rocprofsys::common::update_env(_env, "ROCPROFSYS_USE_SAMPLING", (_mode != "causal"), + update_mode::REPLACE, ":", updated_envs, + original_envs); return _env; } @@ -184,65 +195,6 @@ print_updated_environment(std::vector _env) std::cerr << std::endl; } -template -void -update_env(std::vector& _environ, std::string_view _env_var, Tp&& _env_val, - update_mode&& _mode, std::string_view _join_delim) -{ - updated_envs.emplace(_env_var); - - auto _prepend = (_mode & UPD_PREPEND) != 0; - auto _append = (_mode & UPD_APPEND) != 0; - auto _weak_upd = (_mode & UPD_WEAK) != 0; - - // if both flags are set, prefer append - if(_prepend && _append) - { - _prepend = false; - } - - auto _key = join("", _env_var, "="); - for(auto& itr : _environ) - { - if(!itr) continue; - if(std::string_view{ itr }.find(_key) == 0) - { - if(_weak_upd) - { - // if the value has changed, do not update but allow overridding the value - // inherited from the initial env - if(original_envs.find(std::string{ itr }) == original_envs.end()) return; - } - - if(_prepend || _append) - { - if(std::string_view{ itr }.find(join("", _env_val)) == - std::string_view::npos) - { - auto _val = std::string{ itr }.substr(_key.length()); - free(itr); - if(_prepend) - itr = - strdup(join('=', _env_var, join(_join_delim, _env_val, _val)) - .c_str()); - else - itr = - strdup(join('=', _env_var, join(_join_delim, _val, _env_val)) - .c_str()); - } - } - else - { - free(itr); - itr = strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str()); - } - return; - } - } - _environ.emplace_back( - strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str())); -} - std::vector parse_args(int argc, char** argv, std::vector& _env) { @@ -335,20 +287,28 @@ parse_args(int argc, char** argv, std::vector& _env) auto _monochrome = p.get("monochrome"); monochrome() = _monochrome; p.set_use_color(!_monochrome); - update_env(_env, "ROCPROFSYS_MONOCHROME", (_monochrome) ? "1" : "0"); - update_env(_env, "MONOCHROME", (_monochrome) ? "1" : "0"); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_MONOCHROME", (_monochrome) ? "1" : "0", + update_mode::REPLACE, ":", updated_envs, original_envs); + rocprofsys::common::update_env(_env, "MONOCHROME", (_monochrome) ? "1" : "0", + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser.add_argument({ "--debug" }, "Debug output") .max_count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_DEBUG", p.get("debug")); + rocprofsys::common::update_env(_env, "ROCPROFSYS_DEBUG", p.get("debug"), + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser.add_argument({ "-v", "--verbose" }, "Verbose output") .count(1) .action([&](parser_t& p) { auto _v = p.get("verbose"); verbose = _v; - update_env(_env, "ROCPROFSYS_VERBOSE", _v); + rocprofsys::common::update_env(_env, "ROCPROFSYS_VERBOSE", _v, + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser.start_group("GENERAL OPTIONS", @@ -357,9 +317,10 @@ parse_args(int argc, char** argv, std::vector& _env) .min_count(0) .dtype("filepath") .action([&](parser_t& p) { - update_env( + rocprofsys::common::update_env( _env, "ROCPROFSYS_CONFIG_FILE", - join(array_config{ ":" }, p.get>("config"))); + join(array_config{ ":" }, p.get>("config")), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument({ "-o", "--output" }, @@ -369,21 +330,30 @@ parse_args(int argc, char** argv, std::vector& _env) .max_count(2) .action([&](parser_t& p) { auto _v = p.get>("output"); - update_env(_env, "ROCPROFSYS_OUTPUT_PATH", _v.at(0)); - if(_v.size() > 1) update_env(_env, "ROCPROFSYS_OUTPUT_PREFIX", _v.at(1)); + rocprofsys::common::update_env(_env, "ROCPROFSYS_OUTPUT_PATH", _v.at(0), + update_mode::REPLACE, ":", updated_envs, + original_envs); + if(_v.size() > 1) + rocprofsys::common::update_env(_env, "ROCPROFSYS_OUTPUT_PREFIX", _v.at(1), + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser .add_argument({ "-T", "--trace" }, "Generate a detailed trace (perfetto output)") .max_count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_TRACE", p.get("trace")); + rocprofsys::common::update_env(_env, "ROCPROFSYS_TRACE", p.get("trace"), + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser .add_argument({ "--trace-cached" }, "Generate a detailed trace (perfetto output) from cached data ") .max_count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_TRACE_CACHED", p.get("trace-cached")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_TRACE_CACHED", p.get("trace-cached"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument( @@ -392,7 +362,9 @@ parse_args(int argc, char** argv, std::vector& _env) .max_count(1) .conflicts({ "flat-profile" }) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_PROFILE", p.get("profile")); + rocprofsys::common::update_env(_env, "ROCPROFSYS_PROFILE", + p.get("profile"), update_mode::REPLACE, + ":", updated_envs, original_envs); }); parser .add_argument({ "-F", "--flat-profile" }, @@ -400,8 +372,12 @@ parse_args(int argc, char** argv, std::vector& _env) .max_count(1) .conflicts({ "profile" }) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_PROFILE", p.get("flat-profile")); - update_env(_env, "ROCPROFSYS_FLAT_PROFILE", p.get("flat-profile")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_PROFILE", p.get("flat-profile"), + update_mode::REPLACE, ":", updated_envs, original_envs); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_FLAT_PROFILE", p.get("flat-profile"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument({ "-H", "--host" }, @@ -411,9 +387,16 @@ parse_args(int argc, char** argv, std::vector& _env) .action([&](parser_t& p) { auto _h = p.get("host"); auto _d = p.get("device"); - update_env(_env, "ROCPROFSYS_USE_PROCESS_SAMPLING", _h || _d); - update_env(_env, "ROCPROFSYS_CPU_FREQ_ENABLED", _h); - if(_h) update_env(_env, "ROCPROFSYS_USE_AMD_SMI", _d); + rocprofsys::common::update_env(_env, "ROCPROFSYS_USE_PROCESS_SAMPLING", + _h || _d, update_mode::REPLACE, ":", + updated_envs, original_envs); + rocprofsys::common::update_env(_env, "ROCPROFSYS_CPU_FREQ_ENABLED", _h, + update_mode::REPLACE, ":", updated_envs, + original_envs); + if(_h) + rocprofsys::common::update_env(_env, "ROCPROFSYS_USE_AMD_SMI", _d, + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser .add_argument({ "-D", "--device" }, @@ -423,9 +406,16 @@ parse_args(int argc, char** argv, std::vector& _env) .action([&](parser_t& p) { auto _h = p.get("host"); auto _d = p.get("device"); - update_env(_env, "ROCPROFSYS_USE_PROCESS_SAMPLING", _h || _d); - update_env(_env, "ROCPROFSYS_USE_AMD_SMI", _d); - if(_d) update_env(_env, "ROCPROFSYS_CPU_FREQ_ENABLED", _h); + rocprofsys::common::update_env(_env, "ROCPROFSYS_USE_PROCESS_SAMPLING", + _h || _d, update_mode::REPLACE, ":", + updated_envs, original_envs); + rocprofsys::common::update_env(_env, "ROCPROFSYS_USE_AMD_SMI", _d, + update_mode::REPLACE, ":", updated_envs, + original_envs); + if(_d) + rocprofsys::common::update_env(_env, "ROCPROFSYS_CPU_FREQ_ENABLED", _h, + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser .add_argument({ "-w", "--wait" }, @@ -433,8 +423,12 @@ parse_args(int argc, char** argv, std::vector& _env) "'--sampling-wait'. See the descriptions for those two options.") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_TRACE_DELAY", p.get("wait")); - update_env(_env, "ROCPROFSYS_SAMPLING_DELAY", p.get("wait")); + rocprofsys::common::update_env(_env, "ROCPROFSYS_TRACE_DELAY", + p.get("wait"), update_mode::REPLACE, + ":", updated_envs, original_envs); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_DELAY", + p.get("wait"), update_mode::REPLACE, + ":", updated_envs, original_envs); }); parser .add_argument( @@ -443,8 +437,12 @@ parse_args(int argc, char** argv, std::vector& _env) "'--sampling-duration'. See the descriptions for those two options.") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_TRACE_DURATION", p.get("duration")); - update_env(_env, "ROCPROFSYS_SAMPLING_DURATION", p.get("duration")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_TRACE_DURATION", p.get("duration"), + update_mode::REPLACE, ":", updated_envs, original_envs); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_SAMPLING_DURATION", p.get("duration"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser.start_group("TRACING OPTIONS", "Specific options controlling tracing (i.e. " @@ -456,8 +454,9 @@ parse_args(int argc, char** argv, std::vector& _env) .count(1) .dtype("filepath") .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_PERFETTO_FILE", - p.get("trace-file")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_PERFETTO_FILE", p.get("trace-file"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument({ "--trace-buffer-size" }, @@ -465,15 +464,19 @@ parse_args(int argc, char** argv, std::vector& _env) .count(1) .dtype("KB") .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_PERFETTO_BUFFER_SIZE_KB", - p.get("trace-buffer-size")); + rocprofsys::common::update_env(_env, "ROCPROFSYS_PERFETTO_BUFFER_SIZE_KB", + p.get("trace-buffer-size"), + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser.add_argument({ "--trace-fill-policy" }, _trace_policy_desc) .count(1) .choices({ "discard", "ring_buffer" }) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_PERFETTO_FILL_POLICY", - p.get("trace-fill-policy")); + rocprofsys::common::update_env(_env, "ROCPROFSYS_PERFETTO_FILL_POLICY", + p.get("trace-fill-policy"), + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser .add_argument({ "--trace-wait" }, @@ -483,7 +486,9 @@ parse_args(int argc, char** argv, std::vector& _env) "but that can changed via --trace-clock-id.") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_TRACE_DELAY", p.get("trace-wait")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_TRACE_DELAY", p.get("trace-wait"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument({ "--trace-duration" }, @@ -492,8 +497,9 @@ parse_args(int argc, char** argv, std::vector& _env) "that can changed via --trace-clock-id.") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_TRACE_DURATION", - p.get("trace-duration")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_TRACE_DURATION", p.get("trace-duration"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument( @@ -503,9 +509,11 @@ parse_args(int argc, char** argv, std::vector& _env) "and/or :::.") .min_count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_TRACE_PERIODS", - join(array_config{ ",", "", "" }, - p.get>("trace-periods"))); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_TRACE_PERIODS", + join(array_config{ ",", "", "" }, + p.get>("trace-periods")), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument( @@ -518,8 +526,9 @@ parse_args(int argc, char** argv, std::vector& _env) "rocprof-sys to auto-scale based on the number of threads.") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_TRACE_PERIOD_CLOCK_ID", - p.get("trace-clock-id")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_TRACE_PERIOD_CLOCK_ID", p.get("trace-clock-id"), + update_mode::REPLACE, ":", updated_envs, original_envs); }) .choices(clock_id_choices.first) .choice_aliases(clock_id_choices.second); @@ -534,12 +543,20 @@ parse_args(int argc, char** argv, std::vector& _env) .choices({ "text", "json", "console" }) .action([&](parser_t& p) { auto _v = p.get>("profile"); - update_env(_env, "ROCPROFSYS_PROFILE", true); + rocprofsys::common::update_env(_env, "ROCPROFSYS_PROFILE", true, + update_mode::REPLACE, ":", updated_envs, + original_envs); if(!_v.empty()) { - update_env(_env, "ROCPROFSYS_TEXT_OUTPUT", _v.count("text") != 0); - update_env(_env, "ROCPROFSYS_JSON_OUTPUT", _v.count("json") != 0); - update_env(_env, "ROCPROFSYS_COUT_OUTPUT", _v.count("console") != 0); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_TEXT_OUTPUT", _v.count("text") != 0, + update_mode::REPLACE, ":", updated_envs, original_envs); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_JSON_OUTPUT", _v.count("json") != 0, + update_mode::REPLACE, ":", updated_envs, original_envs); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_COUT_OUTPUT", _v.count("console") != 0, + update_mode::REPLACE, ":", updated_envs, original_envs); } }); @@ -552,9 +569,16 @@ parse_args(int argc, char** argv, std::vector& _env) .max_count(2) .action([&](parser_t& p) { auto _v = p.get>("profile-diff"); - update_env(_env, "ROCPROFSYS_DIFF_OUTPUT", true); - update_env(_env, "ROCPROFSYS_INPUT_PATH", _v.at(0)); - if(_v.size() > 1) update_env(_env, "ROCPROFSYS_INPUT_PREFIX", _v.at(1)); + rocprofsys::common::update_env(_env, "ROCPROFSYS_DIFF_OUTPUT", true, + update_mode::REPLACE, ":", updated_envs, + original_envs); + rocprofsys::common::update_env(_env, "ROCPROFSYS_INPUT_PATH", _v.at(0), + update_mode::REPLACE, ":", updated_envs, + original_envs); + if(_v.size() > 1) + rocprofsys::common::update_env(_env, "ROCPROFSYS_INPUT_PREFIX", _v.at(1), + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser.start_group( @@ -567,8 +591,9 @@ parse_args(int argc, char** argv, std::vector& _env) "(number of interrupts per second)") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_PROCESS_SAMPLING_FREQ", - p.get("process-freq")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_PROCESS_SAMPLING_FREQ", p.get("process-freq"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument({ "--process-wait" }, "Set the default wait time (i.e. delay) " @@ -576,8 +601,9 @@ parse_args(int argc, char** argv, std::vector& _env) "(in seconds of realtime)") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_PROCESS_SAMPLING_DELAY", - p.get("process-wait")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_PROCESS_SAMPLING_DELAY", p.get("process-wait"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument( @@ -585,8 +611,10 @@ parse_args(int argc, char** argv, std::vector& _env) "Set the duration of the host/device sampling (in seconds of realtime)") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_SAMPLING_PROCESS_DURATION", - p.get("process-duration")); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_PROCESS_DURATION", + p.get("process-duration"), + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser .add_argument({ "--cpus" }, @@ -594,9 +622,10 @@ parse_args(int argc, char** argv, std::vector& _env) .dtype("int or range") .required({ "host" }) .action([&](parser_t& p) { - update_env( + rocprofsys::common::update_env( _env, "ROCPROFSYS_SAMPLING_CPUS", - join(array_config{ "," }, p.get>("cpus"))); + join(array_config{ "," }, p.get>("cpus")), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument({ "--gpus" }, @@ -604,9 +633,10 @@ parse_args(int argc, char** argv, std::vector& _env) .dtype("int or range") .required({ "device" }) .action([&](parser_t& p) { - update_env( + rocprofsys::common::update_env( _env, "ROCPROFSYS_SAMPLING_GPUS", - join(array_config{ "," }, p.get>("gpus"))); + join(array_config{ "," }, p.get>("gpus")), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser.start_group("GENERAL SAMPLING OPTIONS", @@ -616,7 +646,9 @@ parse_args(int argc, char** argv, std::vector& _env) "(number of interrupts per second)") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_SAMPLING_FREQ", p.get("freq")); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_FREQ", + p.get("freq"), update_mode::REPLACE, + ":", updated_envs, original_envs); }); parser .add_argument( @@ -626,7 +658,9 @@ parse_args(int argc, char** argv, std::vector& _env) "delay of 1 second for CPU-clock sampler may not equal 1 second of realtime") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_SAMPLING_DELAY", p.get("sampling-wait")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_SAMPLING_DELAY", p.get("sampling-wait"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument( @@ -636,8 +670,9 @@ parse_args(int argc, char** argv, std::vector& _env) "real-time duration... resulting in zero samples being taken") .count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_SAMPLING_DURATION", - p.get("sampling-duration")); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_SAMPLING_DURATION", p.get("sampling-duration"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser .add_argument({ "-t", "--tids" }, @@ -646,8 +681,10 @@ parse_args(int argc, char** argv, std::vector& _env) "is assigned an atomically incrementing value.") .min_count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_SAMPLING_TIDS", - join(array_config{ ", " }, p.get>("tids"))); + rocprofsys::common::update_env( + _env, "ROCPROFSYS_SAMPLING_TIDS", + join(array_config{ ", " }, p.get>("tids")), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser.start_group( @@ -657,21 +694,29 @@ parse_args(int argc, char** argv, std::vector& _env) .min_count(0) .action([&](parser_t& p) { auto _v = p.get>("cputime"); - update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME", true); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME", true, + update_mode::REPLACE, ":", updated_envs, + original_envs); if(!_v.empty()) { - update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME_FREQ", _v.front()); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME_FREQ", + _v.front(), update_mode::REPLACE, ":", + updated_envs, original_envs); _v.pop_front(); } if(!_v.empty()) { - update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME_DELAY", _v.front()); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME_DELAY", + _v.front(), update_mode::REPLACE, ":", + updated_envs, original_envs); _v.pop_front(); } if(!_v.empty()) { - update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME_TIDS", - join(array_config{ "," }, _v)); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME_TIDS", + join(array_config{ "," }, _v), + update_mode::REPLACE, ":", updated_envs, + original_envs); } }); @@ -679,21 +724,29 @@ parse_args(int argc, char** argv, std::vector& _env) .min_count(0) .action([&](parser_t& p) { auto _v = p.get>("realtime"); - update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME", true); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME", true, + update_mode::REPLACE, ":", updated_envs, + original_envs); if(!_v.empty()) { - update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME_FREQ", _v.front()); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME_FREQ", + _v.front(), update_mode::REPLACE, ":", + updated_envs, original_envs); _v.pop_front(); } if(!_v.empty()) { - update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME_DELAY", _v.front()); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME_DELAY", + _v.front(), update_mode::REPLACE, ":", + updated_envs, original_envs); _v.pop_front(); } if(!_v.empty()) { - update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME_TIDS", - join(array_config{ "," }, _v)); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_REALTIME_TIDS", + join(array_config{ "," }, _v), + update_mode::REPLACE, ":", updated_envs, + original_envs); } }); @@ -726,7 +779,9 @@ parse_args(int argc, char** argv, std::vector& _env) .action([&](parser_t& p) { auto _v = p.get>("include"); auto _update = [&](const auto& _opt, bool _cond) { - if(_cond || _v.count("all") > 0) update_env(_env, _opt, true); + if(_cond || _v.count("all") > 0) + rocprofsys::common::update_env(_env, _opt, true, update_mode::REPLACE, + ":", updated_envs, original_envs); }; _update("ROCPROFSYS_USE_KOKKOSP", _v.count("kokkosp") > 0); _update("ROCPROFSYS_USE_MPIP", _v.count("mpip") > 0); @@ -739,7 +794,9 @@ parse_args(int argc, char** argv, std::vector& _env) _update("ROCPROFSYS_TRACE_THREAD_SPIN_LOCKS", _v.count("spin-locks") > 0); if(_v.count("all") > 0 || _v.count("kokkosp") > 0) - update_env(_env, "KOKKOS_TOOLS_LIBS", _omni_libpath, UPD_APPEND); + rocprofsys::common::update_env(_env, "KOKKOS_TOOLS_LIBS", _omni_libpath, + update_mode::APPEND, ":", updated_envs, + original_envs); }); parser.add_argument({ "-E", "--exclude" }, "Exclude data from these backends") @@ -747,7 +804,10 @@ parse_args(int argc, char** argv, std::vector& _env) .action([&](parser_t& p) { auto _v = p.get>("exclude"); auto _update = [&](const auto& _opt, bool _cond) { - if(_cond || _v.count("all") > 0) update_env(_env, _opt, false); + if(_cond || _v.count("all") > 0) + rocprofsys::common::update_env(_env, _opt, false, + update_mode::REPLACE, ":", + updated_envs, original_envs); }; _update("ROCPROFSYS_USE_KOKKOSP", _v.count("kokkosp") > 0); _update("ROCPROFSYS_USE_MPIP", _v.count("mpip") > 0); @@ -771,7 +831,9 @@ parse_args(int argc, char** argv, std::vector& _env) .action([&](parser_t& p) { auto _events = join(array_config{ "," }, p.get>("cpu-events")); - update_env(_env, "ROCPROFSYS_PAPI_EVENTS", _events); + rocprofsys::common::update_env(_env, "ROCPROFSYS_PAPI_EVENTS", _events, + update_mode::REPLACE, ":", updated_envs, + original_envs); }); parser.start_group("MISCELLANEOUS OPTIONS", ""); @@ -780,8 +842,9 @@ parse_args(int argc, char** argv, std::vector& _env) "Include inline info in output when available") .max_count(1) .action([&](parser_t& p) { - update_env(_env, "ROCPROFSYS_SAMPLING_INCLUDE_INLINES", - p.get("inlines")); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_INCLUDE_INLINES", + p.get("inlines"), update_mode::REPLACE, + ":", updated_envs, original_envs); }); parser.add_argument({ "--hsa-interrupt" }, _hsa_interrupt_desc) @@ -789,7 +852,9 @@ parse_args(int argc, char** argv, std::vector& _env) .dtype("int") .choices({ 0, 1 }) .action([&](parser_t& p) { - update_env(_env, "HSA_ENABLE_INTERRUPT", p.get("hsa-interrupt")); + rocprofsys::common::update_env( + _env, "HSA_ENABLE_INTERRUPT", p.get("hsa-interrupt"), + update_mode::REPLACE, ":", updated_envs, original_envs); }); parser.end_group(); @@ -820,7 +885,9 @@ parse_args(int argc, char** argv, std::vector& _env) throw std::runtime_error(_cerr.what()); if(parser.exists("realtime") && !parser.exists("cputime")) - update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME", false); + rocprofsys::common::update_env(_env, "ROCPROFSYS_SAMPLING_CPUTIME", false, + update_mode::REPLACE, ":", updated_envs, + original_envs); if(parser.exists("profile") && parser.exists("flat-profile")) throw std::runtime_error( "Error! '--profile' argument conflicts with '--flat-profile' argument"); diff --git a/projects/rocprofiler-systems/source/bin/rocprof-sys-sample/rocprof-sys-sample.hpp b/projects/rocprofiler-systems/source/bin/rocprof-sys-sample/rocprof-sys-sample.hpp index 9edf1afa0d..2134bc0680 100644 --- a/projects/rocprofiler-systems/source/bin/rocprof-sys-sample/rocprof-sys-sample.hpp +++ b/projects/rocprofiler-systems/source/bin/rocprof-sys-sample/rocprof-sys-sample.hpp @@ -22,17 +22,8 @@ #pragma once -#include #include -enum update_mode : int -{ - UPD_REPLACE = 0, // no PREPEND/APPEND bits set - UPD_PREPEND = 1 << 0, // 0x01 - UPD_APPEND = 1 << 1, // 0x02 - UPD_WEAK = 1 << 2, // 0x04 -}; - void print_command(const std::vector& _argv); @@ -42,10 +33,5 @@ print_updated_environment(std::vector _env); std::vector get_initial_environment(); -template -void -update_env(std::vector& _environ, std::string_view _env_var, Tp&& _env_val, - update_mode&& _mode = UPD_REPLACE, std::string_view _join_delim = ":"); - std::vector parse_args(int argc, char** argv, std::vector& envp); diff --git a/projects/rocprofiler-systems/source/lib/common/CMakeLists.txt b/projects/rocprofiler-systems/source/lib/common/CMakeLists.txt index 8ea90f2aed..19f843b246 100644 --- a/projects/rocprofiler-systems/source/lib/common/CMakeLists.txt +++ b/projects/rocprofiler-systems/source/lib/common/CMakeLists.txt @@ -48,3 +48,7 @@ target_compile_definitions( rocprofiler-systems-common-library INTERFACE $ ) + +if(ROCPROFSYS_BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/projects/rocprofiler-systems/source/lib/common/environment.hpp b/projects/rocprofiler-systems/source/lib/common/environment.hpp index f253dc2f01..ca7d37d09b 100644 --- a/projects/rocprofiler-systems/source/lib/common/environment.hpp +++ b/projects/rocprofiler-systems/source/lib/common/environment.hpp @@ -265,5 +265,82 @@ discover_llvm_libdir_for_ompt(bool verbose = false) return {}; } +enum class update_mode : uint8_t +{ + REPLACE = 0, + PREPEND, + APPEND, + WEAK, +}; + +template +inline std::string +to_env_string(Tp&& val) +{ + using T = std::decay_t; + static_assert(std::is_same_v || std::is_same_v || + std::is_same_v || std::is_arithmetic_v, + "to_env_string: unsupported type. Use string, bool, or numeric types."); + + if constexpr(std::is_same_v || std::is_same_v) + return std::string{ val }; + else if constexpr(std::is_same_v) + return val ? "true" : "false"; + else + return std::to_string(val); +} + +template +inline void +update_env(std::vector& _environ, std::string_view _env_var, Tp&& _env_val, + update_mode _mode, std::string_view _join_delim, + std::unordered_set& _updated_envs, + const std::unordered_set& _original_envs) +{ + _updated_envs.emplace(_env_var); + + const bool _prepend = (_mode == update_mode::PREPEND); + const bool _append = (_mode == update_mode::APPEND); + const bool _weak_upd = (_mode == update_mode::WEAK); + + auto _env_val_str = to_env_string(std::forward(_env_val)); + + auto _key = join("", _env_var, "="); + for(auto& itr : _environ) + { + if(!itr) continue; + if(std::string_view{ itr }.find(_key) != 0) continue; + + if(_weak_upd) + { + if(_original_envs.find(std::string{ itr }) == _original_envs.end()) return; + } + + if(_prepend || _append) + { + if(std::string_view{ itr }.find(_env_val_str) == std::string_view::npos) + { + auto _val = std::string{ itr }.substr(_key.length()); + free(itr); + if(_prepend) + itr = + strdup(join('=', _env_var, join(_join_delim, _env_val_str, _val)) + .c_str()); + else + itr = + strdup(join('=', _env_var, join(_join_delim, _val, _env_val_str)) + .c_str()); + } + } + else + { + free(itr); + itr = strdup(join('=', _env_var, _env_val_str).c_str()); + } + return; + } + _environ.emplace_back(strdup(join('=', _env_var, _env_val_str).c_str())); +} + } // namespace common } // namespace rocprofsys diff --git a/projects/rocprofiler-systems/source/lib/common/tests/CMakeLists.txt b/projects/rocprofiler-systems/source/lib/common/tests/CMakeLists.txt new file mode 100644 index 0000000000..1ea7c50f1e --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/common/tests/CMakeLists.txt @@ -0,0 +1,28 @@ +# MIT License +# +# Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +add_library(lib-common-tests OBJECT test_update_env.cpp) + +target_link_libraries( + lib-common-tests + PUBLIC rocprofiler-systems-common-library rocprofiler-systems-googletest-library +) diff --git a/projects/rocprofiler-systems/source/lib/common/tests/test_update_env.cpp b/projects/rocprofiler-systems/source/lib/common/tests/test_update_env.cpp new file mode 100644 index 0000000000..521b22a0a3 --- /dev/null +++ b/projects/rocprofiler-systems/source/lib/common/tests/test_update_env.cpp @@ -0,0 +1,420 @@ +// MIT License +// +// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "common/environment.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace rocprofsys::common; + +static std::string +find_env_var(const std::vector& env, std::string_view var_name) +{ + std::string prefix = std::string(var_name) + "="; + for(auto* itr : env) + { + if(!itr) continue; + if(std::string_view{ itr }.find(prefix) == 0) + { + return std::string{ itr }; + } + } + return ""; +} + +class UpdateEnvTest : public ::testing::Test +{ +protected: + void SetUp() override + { + env_vars.clear(); + updated_envs.clear(); + original_envs.clear(); + } + + void TearDown() override + { + for(auto* ptr : env_vars) + { + if(ptr) free(ptr); + } + } + + std::vector env_vars; + std::unordered_set updated_envs; + std::unordered_set original_envs; +}; + +TEST_F(UpdateEnvTest, ReplaceMode_NewVariable) +{ + update_env(env_vars, "TEST_VAR", "test_value", update_mode::REPLACE, ":", + updated_envs, original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "TEST_VAR=test_value"); + EXPECT_EQ(updated_envs.count("TEST_VAR"), 1); +} + +TEST_F(UpdateEnvTest, ReplaceMode_ExistingVariable) +{ + env_vars.push_back(strdup("TEST_VAR=old_value")); + original_envs.insert("TEST_VAR=old_value"); + + update_env(env_vars, "TEST_VAR", "new_value", update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "TEST_VAR=new_value"); + EXPECT_EQ(updated_envs.count("TEST_VAR"), 1); +} + +TEST_F(UpdateEnvTest, AppendMode_NewVariable) +{ + update_env(env_vars, "PATH", "/new/path", update_mode::APPEND, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "PATH=/new/path"); +} + +TEST_F(UpdateEnvTest, AppendMode_ExistingVariable) +{ + env_vars.push_back(strdup("PATH=/old/path")); + original_envs.insert("PATH=/old/path"); + + update_env(env_vars, "PATH", "/new/path", update_mode::APPEND, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "PATH=/old/path:/new/path"); + EXPECT_EQ(updated_envs.count("PATH"), 1); +} + +TEST_F(UpdateEnvTest, PrependMode_ExistingVariable) +{ + env_vars.push_back(strdup("LD_LIBRARY_PATH=/old/lib")); + original_envs.insert("LD_LIBRARY_PATH=/old/lib"); + + update_env(env_vars, "LD_LIBRARY_PATH", "/new/lib", update_mode::PREPEND, ":", + updated_envs, original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "LD_LIBRARY_PATH=/new/lib:/old/lib"); +} + +TEST_F(UpdateEnvTest, WeakMode_OriginalValue) +{ + env_vars.push_back(strdup("WEAK_VAR=original")); + original_envs.insert("WEAK_VAR=original"); + + update_env(env_vars, "WEAK_VAR", "new_value", update_mode::WEAK, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "WEAK_VAR=new_value"); +} + +TEST_F(UpdateEnvTest, WeakMode_ModifiedValue) +{ + env_vars.push_back(strdup("WEAK_VAR=original")); + original_envs.insert("WEAK_VAR=original"); + + free(env_vars[0]); + env_vars[0] = strdup("WEAK_VAR=modified"); + + update_env(env_vars, "WEAK_VAR", "new_value", update_mode::WEAK, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "WEAK_VAR=modified"); +} + +TEST_F(UpdateEnvTest, BooleanValue_True) +{ + update_env(env_vars, "BOOL_VAR", true, update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "BOOL_VAR=true"); +} + +TEST_F(UpdateEnvTest, BooleanValue_False) +{ + update_env(env_vars, "BOOL_VAR", false, update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "BOOL_VAR=false"); +} + +TEST_F(UpdateEnvTest, NumericValue) +{ + update_env(env_vars, "NUM_VAR", 42, update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "NUM_VAR=42"); +} + +TEST_F(UpdateEnvTest, AppendMode_AvoidsDuplicates) +{ + env_vars.push_back(strdup("PATH=/existing/path")); + original_envs.insert("PATH=/existing/path"); + + update_env(env_vars, "PATH", "/existing/path", update_mode::APPEND, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "PATH=/existing/path"); +} + +TEST_F(UpdateEnvTest, CustomDelimiter) +{ + env_vars.push_back(strdup("VAR=a")); + original_envs.insert("VAR=a"); + + update_env(env_vars, "VAR", "b", update_mode::APPEND, ",", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "VAR=a,b"); +} + +TEST_F(UpdateEnvTest, RealWorld_LD_LIBRARY_PATH_Append) +{ + env_vars.push_back(strdup("LD_LIBRARY_PATH=/usr/lib:/usr/local/lib")); + original_envs.insert("LD_LIBRARY_PATH=/usr/lib:/usr/local/lib"); + + update_env(env_vars, "LD_LIBRARY_PATH", "/opt/rocm/lib", update_mode::APPEND, ":", + updated_envs, original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "LD_LIBRARY_PATH=/usr/lib:/usr/local/lib:/opt/rocm/lib"); +} + +TEST_F(UpdateEnvTest, RealWorld_LD_PRELOAD_Prepend) +{ + env_vars.push_back(strdup("LD_PRELOAD=/lib/existing.so")); + original_envs.insert("LD_PRELOAD=/lib/existing.so"); + + update_env(env_vars, "LD_PRELOAD", "/opt/rocm/librocprof-sys-dl.so", + update_mode::PREPEND, ":", updated_envs, original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], + "LD_PRELOAD=/opt/rocm/librocprof-sys-dl.so:/lib/existing.so"); +} + +TEST_F(UpdateEnvTest, RealWorld_ROCPROFSYS_Environment_Variables) +{ + update_env(env_vars, "ROCPROFSYS_TRACE", true, update_mode::REPLACE, ":", + updated_envs, original_envs); + update_env(env_vars, "ROCPROFSYS_PROFILE", false, update_mode::REPLACE, ":", + updated_envs, original_envs); + update_env(env_vars, "ROCPROFSYS_USE_SAMPLING", true, update_mode::REPLACE, ":", + updated_envs, original_envs); + + ASSERT_EQ(env_vars.size(), 3); + EXPECT_STREQ(find_env_var(env_vars, "ROCPROFSYS_TRACE").c_str(), + "ROCPROFSYS_TRACE=true"); + EXPECT_STREQ(find_env_var(env_vars, "ROCPROFSYS_PROFILE").c_str(), + "ROCPROFSYS_PROFILE=false"); + EXPECT_STREQ(find_env_var(env_vars, "ROCPROFSYS_USE_SAMPLING").c_str(), + "ROCPROFSYS_USE_SAMPLING=true"); +} + +TEST_F(UpdateEnvTest, RealWorld_Timing_DoubleValues) +{ + update_env(env_vars, "ROCPROFSYS_TRACE_DELAY", 1.5, update_mode::REPLACE, ":", + updated_envs, original_envs); + update_env(env_vars, "ROCPROFSYS_SAMPLING_FREQ", 100.0, update_mode::REPLACE, ":", + updated_envs, original_envs); + + ASSERT_EQ(env_vars.size(), 2); + std::string delay_var = find_env_var(env_vars, "ROCPROFSYS_TRACE_DELAY"); + std::string freq_var = find_env_var(env_vars, "ROCPROFSYS_SAMPLING_FREQ"); + + EXPECT_TRUE(delay_var.find("ROCPROFSYS_TRACE_DELAY=") == 0); + EXPECT_TRUE(freq_var.find("ROCPROFSYS_SAMPLING_FREQ=") == 0); +} + +TEST_F(UpdateEnvTest, StringTypes_StdString) +{ + std::string value = "test_string_value"; + update_env(env_vars, "STRING_VAR", value, update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "STRING_VAR=test_string_value"); +} + +TEST_F(UpdateEnvTest, StringTypes_ConstCharPtr) +{ + const char* value = "const_char_value"; + update_env(env_vars, "CHAR_VAR", value, update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "CHAR_VAR=const_char_value"); +} + +TEST_F(UpdateEnvTest, EmptyStringValue) +{ + update_env(env_vars, "EMPTY_VAR", "", update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "EMPTY_VAR="); +} + +TEST_F(UpdateEnvTest, MultipleVariables_DifferentNames) +{ + update_env(env_vars, "VAR1", "value1", update_mode::REPLACE, ":", updated_envs, + original_envs); + update_env(env_vars, "VAR2", "value2", update_mode::REPLACE, ":", updated_envs, + original_envs); + update_env(env_vars, "VAR3", "value3", update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 3); + EXPECT_EQ(updated_envs.size(), 3); + EXPECT_STREQ(find_env_var(env_vars, "VAR1").c_str(), "VAR1=value1"); + EXPECT_STREQ(find_env_var(env_vars, "VAR2").c_str(), "VAR2=value2"); + EXPECT_STREQ(find_env_var(env_vars, "VAR3").c_str(), "VAR3=value3"); +} + +TEST_F(UpdateEnvTest, NullPointer_InEnvironmentVector) +{ + env_vars.push_back(strdup("VAR1=value1")); + env_vars.push_back(nullptr); + env_vars.push_back(strdup("VAR2=value2")); + original_envs.insert("VAR1=value1"); + original_envs.insert("VAR2=value2"); + + update_env(env_vars, "VAR2", "new_value2", update_mode::REPLACE, ":", updated_envs, + original_envs); + + EXPECT_STREQ(find_env_var(env_vars, "VAR2").c_str(), "VAR2=new_value2"); +} + +TEST_F(UpdateEnvTest, LongPath_Append) +{ + std::string long_path = "/very/long/path/to/some/directory/with/many/subdirectories/" + "that/might/be/used/in/real/world"; + env_vars.push_back(strdup("PATH=/usr/bin:/bin")); + original_envs.insert("PATH=/usr/bin:/bin"); + + update_env(env_vars, "PATH", long_path, update_mode::APPEND, ":", updated_envs, + original_envs); + + std::string expected = "PATH=/usr/bin:/bin:" + long_path; + EXPECT_STREQ(env_vars[0], expected.c_str()); +} + +TEST_F(UpdateEnvTest, SpecialCharacters_InValue) +{ + update_env(env_vars, "SPECIAL_VAR", "value-with_special.chars:123", + update_mode::REPLACE, ":", updated_envs, original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "SPECIAL_VAR=value-with_special.chars:123"); +} + +TEST_F(UpdateEnvTest, IntegerValues_Positive) +{ + update_env(env_vars, "INT_VAR", 12345, update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "INT_VAR=12345"); +} + +TEST_F(UpdateEnvTest, IntegerValues_Negative) +{ + update_env(env_vars, "NEGATIVE_VAR", -999, update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "NEGATIVE_VAR=-999"); +} + +TEST_F(UpdateEnvTest, IntegerValues_Zero) +{ + update_env(env_vars, "ZERO_VAR", 0, update_mode::REPLACE, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "ZERO_VAR=0"); +} + +TEST_F(UpdateEnvTest, UpdateTracking_MultipleUpdates) +{ + update_env(env_vars, "VAR1", "val1", update_mode::REPLACE, ":", updated_envs, + original_envs); + update_env(env_vars, "VAR2", "val2", update_mode::REPLACE, ":", updated_envs, + original_envs); + update_env(env_vars, "VAR1", "val1_updated", update_mode::REPLACE, ":", updated_envs, + original_envs); + + EXPECT_EQ(updated_envs.count("VAR1"), 1); + EXPECT_EQ(updated_envs.count("VAR2"), 1); + EXPECT_EQ(updated_envs.size(), 2); +} + +TEST_F(UpdateEnvTest, WeakMode_SequentialUpdates) +{ + env_vars.push_back(strdup("CONFIG_VAR=initial")); + original_envs.insert("CONFIG_VAR=initial"); + + update_env(env_vars, "CONFIG_VAR", "weak_update", update_mode::WEAK, ":", + updated_envs, original_envs); + + EXPECT_STREQ(env_vars[0], "CONFIG_VAR=weak_update"); + + free(env_vars[0]); + env_vars[0] = strdup("CONFIG_VAR=user_modified"); + + update_env(env_vars, "CONFIG_VAR", "another_weak_update", update_mode::WEAK, ":", + updated_envs, original_envs); + + EXPECT_STREQ(env_vars[0], "CONFIG_VAR=user_modified"); +} + +TEST_F(UpdateEnvTest, Append_MultiplePathsInSequence) +{ + update_env(env_vars, "BUILD_PATH", "/path1", update_mode::REPLACE, ":", updated_envs, + original_envs); + update_env(env_vars, "BUILD_PATH", "/path2", update_mode::APPEND, ":", updated_envs, + original_envs); + update_env(env_vars, "BUILD_PATH", "/path3", update_mode::APPEND, ":", updated_envs, + original_envs); + update_env(env_vars, "BUILD_PATH", "/path4", update_mode::APPEND, ":", updated_envs, + original_envs); + + ASSERT_EQ(env_vars.size(), 1); + EXPECT_STREQ(env_vars[0], "BUILD_PATH=/path1:/path2:/path3:/path4"); +} \ No newline at end of file diff --git a/projects/rocprofiler-systems/source/lib/core/argparse.cpp b/projects/rocprofiler-systems/source/lib/core/argparse.cpp index 5cd996f311..168bcf8a40 100644 --- a/projects/rocprofiler-systems/source/lib/core/argparse.cpp +++ b/projects/rocprofiler-systems/source/lib/core/argparse.cpp @@ -22,7 +22,6 @@ #include "argparse.hpp" #include "common/environment.hpp" -#include "common/join.hpp" #include "common/path.hpp" #include "config.hpp" #include "exception.hpp" @@ -81,71 +80,15 @@ get_clock_id_choices() return std::make_pair(_choices, _aliases); } -enum update_mode : int -{ - UPD_REPLACE = 0, // no PREPEND/APPEND bits set - UPD_PREPEND = 1 << 0, // 0x01 - UPD_APPEND = 1 << 1, // 0x02 - UPD_WEAK = 1 << 2, // 0x04 -}; +using rocprofsys::common::update_mode; template void update_env(parser_data& _data, std::string_view _env_var, Tp&& _env_val, - update_mode&& _mode = UPD_REPLACE, std::string_view _join_delim = ":") + update_mode _mode = update_mode::REPLACE, std::string_view _join_delim = ":") { - _data.updated.emplace(_env_var); - - auto _prepend = (_mode & UPD_PREPEND) != 0; - auto _append = (_mode & UPD_APPEND) != 0; - auto _weak_upd = (_mode & UPD_WEAK) != 0; - - // if both flags are set, prefer append - if(_prepend && _append) - { - _prepend = false; - } - - auto _key = join("", _env_var, "="); - for(auto& itr : _data.current) - { - if(!itr) continue; - if(std::string_view{ itr }.find(_key) == 0) - { - if(_weak_upd) - { - // if the value has changed, do not update but allow overridding the value - // inherited from the initial env - if(_data.initial.find(std::string{ itr }) == _data.initial.end()) return; - } - - if(_prepend || _append) - { - if(std::string_view{ itr }.find(join("", _env_val)) == - std::string_view::npos) - { - auto _val = std::string{ itr }.substr(_key.length()); - free(itr); - if(_prepend) - itr = - strdup(join('=', _env_var, join(_join_delim, _env_val, _val)) - .c_str()); - else - itr = - strdup(join('=', _env_var, join(_join_delim, _val, _env_val)) - .c_str()); - } - } - else - { - free(itr); - itr = strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str()); - } - return; - } - } - _data.current.emplace_back( - strdup(rocprofsys::common::join('=', _env_var, _env_val).c_str())); + rocprofsys::common::update_env(_data.current, _env_var, std::forward(_env_val), + _mode, _join_delim, _data.updated, _data.initial); } } // namespace @@ -199,10 +142,10 @@ init_parser(parser_data& _data) path::realpath(path::get_internal_libpath("librocprof-sys.so").c_str()); auto _libexecpath = path::realpath(path::get_internal_script_path()); - update_env(_data, "ROCPROFSYS_SCRIPT_PATH", _libexecpath, UPD_REPLACE); + update_env(_data, "ROCPROFSYS_SCRIPT_PATH", _libexecpath, update_mode::REPLACE); auto _rootpath = path::realpath(path::get_rocprofsys_root()); - update_env(_data, "ROCPROFSYS_ROOT", _rootpath, UPD_REPLACE); + update_env(_data, "ROCPROFSYS_ROOT", _rootpath, update_mode::REPLACE); return _data; } @@ -210,7 +153,7 @@ init_parser(parser_data& _data) parser_data& add_ld_preload(parser_data& _data) { - update_env(_data, "LD_PRELOAD", _data.dl_libpath, UPD_APPEND); + update_env(_data, "LD_PRELOAD", _data.dl_libpath, update_mode::APPEND); return _data; } @@ -219,7 +162,7 @@ add_ld_library_path(parser_data& _data) { auto _libdir = filepath::dirname(_data.dl_libpath); if(filepath::exists(_libdir)) - update_env(_data, "LD_LIBRARY_PATH", _libdir, UPD_APPEND); + update_env(_data, "LD_LIBRARY_PATH", _libdir, update_mode::APPEND); return _data; } @@ -413,9 +356,9 @@ add_core_arguments(parser_t& _parser, parser_data& _data) if(!_modes.empty()) { update_env(_data, "ROCPROFSYS_SAMPLING_CPUTIME", - _modes.count("cputime") > 0, UPD_WEAK); + _modes.count("cputime") > 0, update_mode::WEAK); update_env(_data, "ROCPROFSYS_SAMPLING_REALTIME", - _modes.count("realtime") > 0, UPD_WEAK); + _modes.count("realtime") > 0, update_mode::WEAK); } }); @@ -472,11 +415,11 @@ add_core_arguments(parser_t& _parser, parser_data& _data) .dtype("seconds") .action([&](parser_t& p) { update_env(_data, "ROCPROFSYS_TRACE_DELAY", p.get("wait"), - UPD_WEAK); + update_mode::WEAK); update_env(_data, "ROCPROFSYS_SAMPLING_DELAY", p.get("wait"), - UPD_WEAK); + update_mode::WEAK); update_env(_data, "ROCPROFSYS_CAUSAL_DELAY", p.get("wait"), - UPD_WEAK); + update_mode::WEAK); }); _data.processed_environs.emplace("wait"); @@ -493,11 +436,11 @@ add_core_arguments(parser_t& _parser, parser_data& _data) .dtype("seconds") .action([&](parser_t& p) { update_env(_data, "ROCPROFSYS_TRACE_DURATION", p.get("duration"), - UPD_WEAK); + update_mode::WEAK); update_env(_data, "ROCPROFSYS_SAMPLING_DURATION", - p.get("duration"), UPD_WEAK); + p.get("duration"), update_mode::WEAK); update_env(_data, "ROCPROFSYS_CAUSAL_DURATION", p.get("duration"), - UPD_WEAK); + update_mode::WEAK); }); _data.processed_environs.emplace("duration"); @@ -516,7 +459,7 @@ add_core_arguments(parser_t& _parser, parser_data& _data) update_env( _data, "ROCPROFSYS_TRACE_PERIODS", join(array_config_t{ " ", "", "" }, p.get("periods")), - UPD_WEAK); + update_mode::WEAK); }); _data.processed_environs.emplace("periods"); @@ -584,7 +527,7 @@ add_core_arguments(parser_t& _parser, parser_data& _data) if(_v.count("all") > 0 || _v.count("kokkosp") > 0) update_env(_data, "KOKKOS_TOOLS_LIBS", _data.omni_libpath, - UPD_PREPEND); + update_mode::PREPEND); }); _data.processed_environs.emplace("include"); diff --git a/projects/rocprofiler-systems/source/tests/CMakeLists.txt b/projects/rocprofiler-systems/source/tests/CMakeLists.txt index 1d42292b03..9b6e56d4fc 100644 --- a/projects/rocprofiler-systems/source/tests/CMakeLists.txt +++ b/projects/rocprofiler-systems/source/tests/CMakeLists.txt @@ -20,9 +20,17 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -add_executable(rocprof-sys-unit-tests dummy.cpp $) +add_executable( + rocprof-sys-unit-tests + dummy.cpp + $ + $ +) target_link_libraries( rocprof-sys-unit-tests - PRIVATE rocprofiler-systems-googletest-library rocprofiler-systems-core-library + PRIVATE + rocprofiler-systems-googletest-library + rocprofiler-systems-common-library + rocprofiler-systems-core-library )