// MIT License // // Copyright (c) 2023-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. #define _GNU_SOURCE 1 #include "lib/common/environment.hpp" #include "lib/common/filesystem.hpp" #include "lib/common/logging.hpp" #include "lib/common/mpl.hpp" #include "lib/common/string_entry.hpp" #include "lib/common/utility.hpp" #include #include #include #include #include #include #include #include #include namespace rocpd { namespace sql { namespace { namespace common = ::rocprofiler::common; namespace fs = ::rocprofiler::common::filesystem; std::string get_install_path() { auto* _rocpd_sql_load_schema_sym = dlsym(RTLD_DEFAULT, "rocpd_sql_load_schema"); ROCP_CI_LOG_IF(WARNING, !_rocpd_sql_load_schema_sym) << "[rocprofiler-sdk-rocpd] dlsym(RTLD_DEFAULT, 'rocpd_sql_load_schema') failed " "(unexpectedly) from within the rocprofiler-sdk-rocpd library"; if(!_rocpd_sql_load_schema_sym) _rocpd_sql_load_schema_sym = reinterpret_cast(&rocpd_sql_load_schema); if(Dl_info dl_info = {}; dladdr(_rocpd_sql_load_schema_sym, &dl_info) != 0 && dl_info.dli_fname != nullptr) { auto _share_path = fs::path{dl_info.dli_fname}.lexically_normal().parent_path().parent_path() / std::string{"share/rocprofiler-sdk-rocpd"}; ROCP_INFO << fmt::format("[rocprofiler-sdk-rocpd] resolved rocprofiler-sdk-rocpd SQL " "schema path as '{}' (dli_fname: {})", _share_path.string(), dl_info.dli_fname); return _share_path; } ROCP_CI_LOG(WARNING) << "Failed to locate the installation path of rocprofiler-sdk-rocpd via dladdr of the " "'rocpd_sql_load_schema' symbol (which should be in librocprofiler-sdk-rocpd.so)"; return std::string{}; } 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; } } // namespace } // namespace sql } // namespace rocpd extern "C" { rocpd_status_t rocpd_sql_load_schema(rocpd_sql_engine_t engine, rocpd_sql_schema_kind_t kind, rocpd_sql_options_t options, const rocpd_sql_schema_jinja_variables_t* variables, rocpd_sql_load_schema_cb_t callback, const char** schema_path_hints, uint64_t num_schema_path_hints, void* user_data) { namespace fs = ::rocpd::sql::fs; switch(engine) { case ROCPD_SQL_ENGINE_SQLITE3: { break; } case ROCPD_SQL_ENGINE_NONE: case ROCPD_SQL_ENGINE_LAST: { return ROCPD_STATUS_ERROR_SQL_INVALID_ENGINE; } } const auto kind_file_names = std::unordered_map{ {ROCPD_SQL_SCHEMA_ROCPD_TABLES, "rocpd_tables.sql"}, {ROCPD_SQL_SCHEMA_ROCPD_INDEXES, "rocpd_indexes.sql"}, {ROCPD_SQL_SCHEMA_ROCPD_VIEWS, "rocpd_views.sql"}, {ROCPD_SQL_SCHEMA_ROCPD_DATA_VIEWS, "data_views.sql"}, {ROCPD_SQL_SCHEMA_ROCPD_SUMMARY_VIEWS, "summary_views.sql"}, {ROCPD_SQL_SCHEMA_ROCPD_MARKER_VIEWS, "marker_views.sql"}, }; const auto _lib_schema_path = rocpd::sql::get_install_path(); const auto _env_schema_path = rocprofiler::common::get_env("ROCPD_SCHEMA_PATH", ""); const auto _usr_schema_path = (schema_path_hints) ? fmt::format( "{}", fmt::join(schema_path_hints, schema_path_hints + num_schema_path_hints, ":")) : std::string{}; const auto _schema_paths = fmt::format("{}:{}:{}", _usr_schema_path, _env_schema_path, _lib_schema_path); if(kind_file_names.count(kind) == 0) return ROCPD_STATUS_ERROR_SQL_INVALID_SCHEMA_KIND; auto _schema_file = std::optional{}; for(const auto& itr : rocprofiler::sdk::parse::tokenize(_schema_paths, ":")) { auto _fpath = fs::path{itr} / kind_file_names.at(kind); ROCP_TRACE << fmt::format("[rocprofiler-sdk-rocpd] Searching for schema file: '{}'", _fpath.string()); if(fs::exists(_fpath)) { ROCP_INFO << fmt::format("[rocprofiler-sdk-rocpd] Found schema file: '{}'", _fpath.string()); _schema_file = _fpath; break; } } if(!_schema_file) return ROCPD_STATUS_ERROR_SQL_SCHEMA_NOT_FOUND; auto read_file = [](const std::string& _file_path) -> std::string { auto _ifs = std::ifstream{_file_path, std::ios::in | std::ios::binary}; if(!_ifs.is_open()) return std::string{}; auto _buffer = std::stringstream{}; _buffer << _ifs.rdbuf(); return _buffer.str(); }; auto _contents = read_file(*_schema_file); if(_contents.empty()) return ROCPD_STATUS_ERROR_SQL_SCHEMA_PERMISSION_DENIED; if(engine == ROCPD_SQL_ENGINE_SQLITE3) { if((options & ROCPD_SQL_OPTIONS_SQLITE3_PRAGMA_FOREIGN_KEYS) == ROCPD_SQL_OPTIONS_SQLITE3_PRAGMA_FOREIGN_KEYS) _contents = fmt::format("PRAGMA foreign_keys = ON;\n\n{}", _contents); } auto _substitutions = std::vector>{}; using jinja_init_list_t = std::initializer_list>; if(variables != nullptr) { if(variables->size == 0) { return ROCPD_STATUS_ERROR_SQL_INVALID_SCHEMA_KIND; } // {{uuid}} is used in table names and require special handling if(const auto* value = variables->uuid; value != nullptr) { auto _value = std::string{value}; // non-empty strings are prefixed with underscore for readability if(!_value.empty() && _value.find('_') != 0) _value = fmt::format("_{}", _value); // replace hyphens with underscores since these are used in table/view names if(_value.find('-') != std::string::npos) _value = rocpd::sql::replace_all(_value, "-", "_"); // make substitutions _contents = rocpd::sql::replace_all(_contents, "{{uuid}}", _value); } // make substitutions for remaining variables which do not require special handling like // {{uuid}} for(auto [key, value] : jinja_init_list_t{{"{{guid}}", variables->guid}}) { if(value != nullptr) { _contents = rocpd::sql::replace_all(_contents, key, std::string_view{value}); } } } const auto* cb_schema_path = _schema_file->c_str(); const auto* cb_schema_contents = _contents.c_str(); if((options & ROCPD_SQL_OPTIONS_SQLITE3_PRAGMA_FOREIGN_KEYS) == ROCPD_SQL_OPTIONS_SQLITE3_PRAGMA_FOREIGN_KEYS) { cb_schema_path = rocprofiler::common::get_string_entry(cb_schema_path)->c_str(); cb_schema_contents = rocprofiler::common::get_string_entry(cb_schema_contents)->c_str(); } callback(engine, kind, options, variables, cb_schema_path, cb_schema_contents, user_data); return ROCPD_STATUS_SUCCESS; } }