// MIT License // // Copyright (c) 2023 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 "generateRocpd.hpp" #include "lib/common/uuid_v7.hpp" #include "metadata.hpp" #include "output_stream.hpp" #include "stream_info.hpp" #include "timestamps.hpp" #include "lib/common/container/small_vector.hpp" #include "lib/common/container/stable_vector.hpp" #include "lib/common/defines.hpp" #include "lib/common/demangle.hpp" #include "lib/common/filesystem.hpp" #include "lib/common/hasher.hpp" #include "lib/common/logging.hpp" #include "lib/common/md5sum.hpp" #include "lib/common/mpl.hpp" #include "lib/common/scope_destructor.hpp" #include "lib/common/simple_timer.hpp" #include "lib/common/utility.hpp" #include "lib/output/sql/common.hpp" #include "lib/output/sql/deferred_transaction.hpp" #include "lib/rocprofiler-sdk/agent.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ROCPROFILER_SDK_CEREAL_NAMESPACE_BEGIN template void save(ArchiveT& ar, const ::rocprofiler::tool::argument_info& data) { ar(cereal::make_nvp("pos", data.arg_number)); ar(cereal::make_nvp("type", data.arg_type)); ar(cereal::make_nvp("name", data.arg_name)); ar(cereal::make_nvp("value", data.arg_value)); } ROCPROFILER_SDK_CEREAL_NAMESPACE_END namespace std { template <> struct hash<::rocprofiler::tool::track_data> { size_t operator()(const ::rocprofiler::tool::track_data& val) const { return val.hash(); } }; } // namespace std namespace rocprofiler { namespace tool { namespace { namespace fs = ::rocprofiler::common::filesystem; using function_args_t = std::vector; auto main_tid = common::get_tid(); template auto replace_all(std::string val, Tp from, std::string_view to) { size_t pos = 0; while((pos = val.find(from, pos)) != std::string::npos) { if constexpr(std::is_same, char>::value) { val.replace(pos, 1, to); pos += to.length(); } else { val.replace(pos, std::string_view{from}.length(), to); pos += to.length(); } } return val; } std::string sanitize_sql_string(std::string input) { // Special characters that need to be escaped/sanitized in SQL strings constexpr auto ESCAPE_CHARS = std::string_view{";\n\r\t\b\f\v"}; using strpair_t = std::pair; for(char c : ESCAPE_CHARS) input = replace_all(input, c, ""); for(auto&& itr : {strpair_t{"'", "''"}}) input = replace_all(input, itr.first, itr.second); return input; } std::string sanitize_sql_string(const char* input) { return (input) ? sanitize_sql_string(std::string{input}) : std::string{}; } template std::string get_json_string(FuncT&& _func, Args&&... _args) { using json_archive = cereal::MinimalJSONOutputArchive; constexpr auto json_prec = 16; constexpr auto json_indent = json_archive::Options::IndentChar::space; auto json_ss = std::stringstream{}; { auto json_opts = json_archive::Options{json_prec, json_indent, 0}; auto ar = json_archive{json_ss, json_opts}; std::forward(_func)(ar, std::forward(_args)...); } return sanitize_sql_string(json_ss.str()); } template size_t get_hash_id(Tp&& _val) { using value_type = common::mpl::unqualified_type_t; if constexpr(!std::is_pointer::value) return std::hash{}(std::forward(_val)); else if constexpr(std::is_same::value) return get_hash_id(std::string_view{_val}); else return get_hash_id(*_val); } auto& get_guid() { static auto _v = std::string{}; return _v; } auto& get_uuid() { static thread_local auto _v = std::string{}; return _v; } auto replace_uuid(std::string_view inp) { const auto& _repl = get_uuid(); const auto replacement = (_repl.empty()) ? std::string{} : fmt::format("_{}", _repl); return replace_all(std::string{inp}, std::string_view{"{{uuid}}"}, replacement); } auto replace_placeholders(std::string_view inp) { return replace_uuid(inp); } template void add_string_entry(metadata& _metadata, Args&&... _args) { (_metadata.add_string_entry(get_hash_id(std::string_view{std::forward(_args)}), std::string_view{std::forward(_args)}), ...); } void read_file(rocpd_sql_engine_t engine, rocpd_sql_schema_kind_t kind, rocpd_sql_options_t options, const rocpd_sql_schema_jinja_variables_t* variables, const char* schema_path, const char* schema_content, void* user_data) { common::consume_args(engine, kind, options, variables, schema_path, schema_content, user_data); *static_cast(user_data) = replace_placeholders(schema_content); } std::string read_schema_file(rocpd_sql_schema_kind_t schema_kind) { auto _variables = common::init_public_api_struct(rocpd_sql_schema_jinja_variables_t{}); auto _options = ROCPD_SQL_OPTIONS_NONE; auto _schema_result = std::string{}; _variables.uuid = get_uuid().c_str(); _variables.guid = get_guid().c_str(); ROCPD_CHECK(rocpd_sql_load_schema(ROCPD_SQL_ENGINE_SQLITE3, schema_kind, _options, &_variables, read_file, nullptr, 0, &_schema_result)); return _schema_result; } size_t get_event_id() { static size_t event_id_index = 0; return ++event_id_index; } auto& get_tracks() { static auto _tracks = std::unordered_map{}; return _tracks; } int iterate_args_callback(rocprofiler_buffer_tracing_kind_t /*kind*/, rocprofiler_tracing_operation_t /*operation*/, uint32_t arg_number, const void* const /*arg_value_addr*/, int32_t /*arg_indirection_count*/, const char* arg_type, const char* arg_name, const char* arg_value_str, void* data) { ROCP_FATAL_IF(data == nullptr) << "nullptr to data for iterate_args_callback"; auto* _data = static_cast(data); if(arg_type && arg_name && arg_value_str) _data->emplace_back( argument_info{arg_number, common::cxx_demangle(arg_type), arg_name, arg_value_str}); return 0; } struct sql_insert_value { std::string_view name = {}; std::string value = {}; }; struct allow_empty_string {}; template struct is_optional : std::false_type { using value_type = Tp; }; template struct is_optional> : std::true_type { using value_type = Tp; }; template sql_insert_value insert_value(std::string_view _name, const Tp& _value, TraitT = {}) { using value_type = common::mpl::unqualified_type_t; static_assert(!is_optional::value, "overload resolution failed"); if constexpr(common::mpl::is_string_type::value) { if constexpr(!std::is_same::value) { if(std::string_view{_value}.empty()) { ROCP_CI_LOG(WARNING) << fmt::format("sql text value for {} is empty. Using NULL instead", _name); return sql_insert_value{_name, std::string{"NULL"}}; } } // Sanitize string values before embedding into SQL to escape quotes and remove // problematic control/separator characters. auto _sanitized = sanitize_sql_string(std::string{_value}); return sql_insert_value{_name, fmt::format("'{}'", _sanitized)}; } else { return sql_insert_value{_name, fmt::format("{}", _value)}; } } // // this overload works around around a (false) maybe-initialized warning in GCC 12.x // template ROCPROFILER_NOINLINE sql_insert_value insert_value(std::string_view _name, const std::optional& _value, TraitT = {}) { if(!_value.has_value()) return sql_insert_value{}; return insert_value(_name, _value.value(), TraitT{}); } template