// Copyright (c) Advanced Micro Devices, Inc. // SPDX-License-Identifier: MIT #include "generate_config.hpp" #include "common.hpp" #include "defines.hpp" #include "info_type.hpp" #include "common/delimit.hpp" #include "common/env_vars.hpp" #include "common/environment.hpp" #include "common/json_config.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace cereal = ::tim::cereal; namespace filepath = ::tim::filepath; using settings = ::tim::settings; using ::tim::tsettings; using ::tim::type_list; using ::tim::policy::output_archive; namespace { struct custom_setting_serializer { static std::array options; static const format_options* fmt; }; template bool ignore_setting(const Tp& _v, const format_options& fmt_opts) { if(_v->get_hidden()) return true; if(exclude_setting(_v->get_env_name())) return true; if(_v->get_config_updated() || _v->get_environ_updated()) return false; if(!is_selected(_v->get_env_name()) && !is_selected(_v->get_name())) return true; if(fmt_opts.available_only && !_v->get_enabled()) return true; if(!category_view.empty()) { bool _found = false; for(auto& category : _v->get_categories()) { if(category_view.count(category) > 0 || category_view.count(fmt::format("settings::{}", category)) > 0) { _found = true; break; } } if(!_found) return true; } if(category_view.count("deprecated") == 0 && category_view.count("settings::deprecated") == 0 && _v->get_categories().count("deprecated") > 0) return true; if(!fmt_opts.print_advanced && category_view.count("advanced") == 0 && category_view.count("settings::advanced") == 0 && _v->get_categories().count("advanced") > 0) return true; return false; } } // namespace std::array custom_setting_serializer::options = { false }; const format_options* custom_setting_serializer::fmt = nullptr; namespace tim { namespace operation { template struct setting_serialization { template void operator()(ArchiveT&, const char*, const Tp&) const {} template void operator()(ArchiveT&, const char*, Tp&&) const {} }; // template struct setting_serialization, custom_setting_serializer> { using value_type = tsettings; template void operator()(ArchiveT& _ar, value_type& _val) const { static_assert(concepts::is_output_archive::value, "Requires an output archive"); if(ignore_setting(&_val, *custom_setting_serializer::fmt)) return; auto _save = std::shared_ptr{}; if constexpr(concepts::is_string_type::value) { if(_val.get_name() != "time_format") _val.set(settings::format(_val.get(), settings::instance()->get_tag())); if(_val.get_name() == "config_file") { _save = std::make_shared(_val); _val.set(Tp{}); } } if(custom_setting_serializer::fmt->all_info) { _ar(cereal::make_nvp(_val.get_env_name().c_str(), _val)); } else { Tp _v = _val.get(); _ar.setNextName(_val.get_env_name().c_str()); _ar.startNode(); _ar(cereal::make_nvp("name", _val.get_name())); _ar(cereal::make_nvp("value", _v)); if(custom_setting_serializer::options[DESC]) _ar(cereal::make_nvp("description", _val.get_description())); if(custom_setting_serializer::options[CATEGORY]) _ar(cereal::make_nvp("description", _val.get_categories())); if(custom_setting_serializer::options[VAL]) _ar(cereal::make_nvp("choices", _val.get_choices())); _ar.finishNode(); } if(_save) _val.set(_save->get()); } }; } // namespace operation } // namespace tim template void push(type_list) { ROCPROFSYS_FOLD_EXPRESSION( settings::push_serialize_map_callback()); ROCPROFSYS_FOLD_EXPRESSION( settings::push_serialize_data_callback( type_list{})); } template void pop(type_list) { ROCPROFSYS_FOLD_EXPRESSION( settings::pop_serialize_map_callback()); ROCPROFSYS_FOLD_EXPRESSION( settings::pop_serialize_data_callback( type_list{})); } void update_choices(const std::shared_ptr&); void generate_config(std::string _config_file, const std::set& _config_fmts, const std::array& _options, const format_options& fmt_opts) { custom_setting_serializer::options = _options; custom_setting_serializer::fmt = &fmt_opts; auto _settings = tim::settings::shared_instance(); tim::settings::push(); _settings->find("suppress_config")->second->reset(); _settings->find("suppress_parsing")->second->reset(); _config_file = settings::format(_config_file, _settings->get_tag()); bool _absolute = _config_file.at(0) == '/'; auto _dirs = rocprofsys::delimit(_config_file, "/\\/"); _config_file = _dirs.back(); _dirs.pop_back(); std::string _output_dir = "."; if(!_dirs.empty() && !(_dirs.size() == 1 && _dirs.at(0) == ".")) { _output_dir = std::string{ (_absolute) ? "/" : "" } + _dirs.front(); _dirs.erase(_dirs.begin()); for(const auto& dir : _dirs) { _output_dir += '/' + dir; } } _output_dir += "/"; auto _fmts = std::set{}; std::string _txt_ext = ".cfg"; for(std::string itr : { ".cfg", ".txt", ".json", ".xml" }) { if(_config_file.length() <= itr.length()) continue; auto _pos = _config_file.rfind(itr); if(_pos == _config_file.length() - itr.length()) { if(itr == ".cfg" || itr == ".txt") _txt_ext = itr; _fmts.emplace(itr.substr(1)); _config_file = _config_file.substr(0, _pos); } } if(_fmts.empty() && _config_fmts.size() == 1) _fmts = _config_fmts; else if(!_fmts.empty()) { for(auto& itr : _config_fmts) _fmts.emplace(itr); } update_choices(_settings); using json_t = cereal::PrettyJSONOutputArchive; using xml_t = cereal::XMLOutputArchive; // stores the original serializer and replaces it with the custom one push(type_list{}); static std::time_t _time{ std::time(nullptr) }; auto _serialize = [_settings](auto&& _ar) { _ar->setNextName(TIMEMORY_PROJECT_NAME); _ar->startNode(); (*_ar)(cereal::make_nvp("version", std::string{ ROCPROFSYS_VERSION_STRING })); (*_ar)(cereal::make_nvp("date", tim::get_local_datetime("%F_%H.%M", &_time))); settings::serialize_settings(*_ar, *_settings); _ar->finishNode(); }; auto _nout = 0; auto _open = [&_nout, &fmt_opts](std::ofstream& _ofs, const std::string& _fname, const std::string& _type) -> std::ofstream& { ++_nout; if(file_exists(_fname)) { if(fmt_opts.force_config) { if(settings::verbose() >= 1) std::cout << "[rocprof-sys-avail] File '" << _fname << "' exists. Overwrite force...\n"; } else { std::cout << "[rocprof-sys-avail] File '" << _fname << "' exists. Overwrite? " << std::flush; std::string _response = {}; std::cin >> _response; if(!rocprofsys::to_bool(_response, false)) { std::exit(EXIT_FAILURE); } } } if(filepath::open(_ofs, _fname)) { if(settings::verbose() >= 0) printf("[rocprof-sys-avail] Outputting %s configuration file '%s'...\n", _type.c_str(), _fname.c_str()); } else { throw std::runtime_error( fmt::format("Error opening {} output file: {}", _type, _fname)); } return _ofs; }; if(_fmts.count("json") > 0) { // JSON schema output includes all ROCPROFSYS_* settings regardless of // --filter, --categories, or --advanced flags. This is intentional: the // schema has a fixed hierarchical structure and is designed for reuse // with --preset, so partial exports would produce incomplete configs. std::map env_map; for(const auto& itr : *_settings) { if(exclude_setting(itr.second->get_env_name())) continue; if(itr.second->get_hidden()) continue; const auto& env_name = itr.second->get_env_name(); if(env_name.find("ROCPROFSYS_") != 0) continue; // Include all vars, even empty ones — the schema function // handles empty values appropriately (e.g., empty string fields // are still valid and indicate the setting exists). env_map[env_name] = itr.second->as_string(); } // Convert to hierarchical preset JSON schema (compatible with --preset) auto preset_json = rocprofsys::json_config::env_vars_to_json_schema(env_map); // Add metadata auto preset_name = fmt_opts.preset_name; if(preset_name.empty()) preset_name = _config_file; preset_json["metadata"]["name"] = preset_name; if(!fmt_opts.preset_description.empty()) preset_json["metadata"]["description"] = fmt_opts.preset_description; auto _fname = settings::compose_output_filename(_config_file, ".json", false, -1, true, _output_dir); std::ofstream ofs{}; _open(ofs, _fname, "JSON") << preset_json.dump(4) << "\n"; } if(_fmts.count("xml") > 0) { std::stringstream _ss{}; output_archive::indent() = true; _serialize(output_archive::get(_ss)); auto _fname = settings::compose_output_filename(_config_file, ".xml", false, -1, true, _output_dir); std::ofstream ofs{}; _open(ofs, _fname, "XML") << _ss.str() << "\n"; } if(_fmts.count("txt") > 0 || _fmts.count("cfg") > 0 || _nout == 0) { std::stringstream _ss{}; size_t _w = fmt_opts.min_width; std::vector> _data{}; for(const auto& itr : *_settings) { if(exclude_setting(itr.second->get_env_name())) continue; for(const auto& citr : itr.second->get_categories()) if(citr == "deprecated") continue; if(ignore_setting(itr.second, fmt_opts)) continue; _data.emplace_back(itr.second); } if(fmt_opts.alphabetical) std::sort(_data.begin(), _data.end(), [](auto _lhs, auto _rhs) { return _lhs->get_name() < _rhs->get_name(); }); else { _settings->ordering(); std::sort(_data.begin(), _data.end(), [](auto _lhs, auto _rhs) { auto _lomni = _lhs->get_categories().count("rocprofsys") > 0; auto _romni = _rhs->get_categories().count("rocprofsys") > 0; if(_lomni && !_romni) return true; if(_romni && !_lomni) return false; namespace env_vars = rocprofsys::env_vars; for(const auto* itr : { env_vars::CONFIG, env_vars::MODE, env_vars::TRACE, env_vars::TRACE_LEGACY, env_vars::PROFILE, env_vars::USE_SAMPLING, env_vars::USE_PROCESS_SAMPLING, env_vars::USE_AMD_SMI, env_vars::USE_AINIC, env_vars::USE_KOKKOSP, env_vars::USE_OMPT, "ROCPROFSYS_USE", env_vars::OUTPUT }) { if(_lhs->get_env_name().find(itr) == 0 && _rhs->get_env_name().find(itr) != 0) return true; if(_rhs->get_env_name().find(itr) == 0 && _lhs->get_env_name().find(itr) != 0) return false; } for(const auto* itr : { env_vars::SUPPRESS_PARSING, env_vars::SUPPRESS_CONFIG }) { if(_lhs->get_env_name().find(itr) == 0 && _rhs->get_env_name().find(itr) != 0) return false; if(_rhs->get_env_name().find(itr) == 0 && _lhs->get_env_name().find(itr) != 0) return true; } return _lhs->get_name() < _rhs->get_name(); }); } for(const auto& itr : _data) { _w = std::max(_w, itr->get_env_name().length()); } for(const auto& itr : _data) { if(exclude_setting(itr->get_env_name())) continue; auto _has_info = (fmt_opts.all_info || _options[DESC] || _options[CATEGORY] || _options[VAL]); if(_has_info) _ss << "\n# name:\n# " << itr->get_name() << "\n#\n"; if(_options[DESC] || fmt_opts.all_info) { _ss << "# description:\n"; auto _desc = rocprofsys::delimit(itr->get_description(), " \n"); std::stringstream _line{}; _line << "# "; auto _write = [&_line, &_ss, _w](std::string_view _str) { if(_line.str().length() + _str.length() + 1 >= _w) { _ss << _line.str() << "\n"; _line = std::stringstream{}; _line << "# "; } _line << " " << _str; }; for(auto& iitr : _desc) _write(iitr); _ss << _line.str() << "\n#\n"; } if(_options[CATEGORY] || fmt_opts.all_info) { _ss << "# categories:\n"; for(const auto& iitr : itr->get_categories()) _ss << "# " << iitr << "\n"; _ss << "#\n"; } if((_options[VAL] || fmt_opts.all_info) && !itr->get_choices().empty()) { auto _choices = itr->get_choices(); filter_operations(itr->get_env_name(), _choices); if(!_choices.empty()) { _ss << "# choices:\n"; for(const auto& iitr : _choices) _ss << "# " << iitr << "\n"; _ss << "#\n"; } } if(_has_info) _ss << "\n"; _ss << std::left << std::setw(_w + 10) << itr->get_env_name() << " = "; auto _v = itr->as_string(); if(itr->get_name() == "config_file") _v = {}; if(!_v.empty() && fmt_opts.expand_keys && itr->get_name() != "time_format") _v = settings::format(_v, _settings->get_tag()); _ss << _v << "\n"; } auto _fname = settings::compose_output_filename(_config_file, _txt_ext, false, -1, true, _output_dir); std::ofstream ofs{}; _open(ofs, _fname, "text") << "# auto-generated by rocprof-sys-avail (version " << ROCPROFSYS_VERSION_STRING << ") on " << tim::get_local_datetime("%F @ %H:%M", &_time) << "\n\n" << _ss.str(); } // restores the original serializer pop(type_list{}); tim::settings::pop(); } void update_choices(const std::shared_ptr& _settings) { std::vector _info = get_component_info(); if(_settings->get_verbose() >= 2 || _settings->get_debug()) printf("[rocprof-sys-avail] # of component found: %zu\n", _info.size()); _info.erase(std::remove_if(_info.begin(), _info.end(), [](const auto& itr) { if(!itr.is_available()) return true; // NOLINTNEXTLINE for(const auto& nitr : { "cuda", "cupti", "nvtx", "roofline", "_bundle", "data_integer", "data_unsigned", "data_floating", "printer" }) { if(itr.name().find(nitr) != std::string::npos) return true; } return false; }), _info.end()); std::vector _component_choices = {}; _component_choices.reserve(_info.size()); for(const auto& itr : _info) _component_choices.emplace_back(itr.id_type()); if(_settings->get_verbose() >= 2 || _settings->get_debug()) printf("[rocprof-sys-avail] # of component choices: %zu\n", _component_choices.size()); _settings->find(std::string{ rocprofsys::env_vars::TIMEMORY_COMPONENTS }) ->second->set_choices(_component_choices); }