Files
email-tracker/external/duckdb/test/api/capi/test_capi_profiling.cpp
2025-10-24 19:21:19 -05:00

417 lines
14 KiB
C++

#include "capi_tester.hpp"
using namespace duckdb;
using namespace std;
string BuildSettingsString(const duckdb::vector<string> &settings) {
string result = "'{";
for (idx_t i = 0; i < settings.size(); i++) {
result += "\"" + settings[i] + "\": \"true\"";
if (i < settings.size() - 1) {
result += ", ";
}
}
result += "}'";
return result;
}
void RetrieveMetrics(duckdb_profiling_info info, duckdb::map<string, double> &cumulative_counter,
duckdb::map<string, double> &cumulative_result, const idx_t depth) {
auto map = duckdb_profiling_info_get_metrics(info);
REQUIRE(map);
auto count = duckdb_get_map_size(map);
REQUIRE(count != 0);
// Test index out of bounds for MAP value.
if (depth == 0) {
auto invalid_key = duckdb_get_map_key(map, 10000000);
REQUIRE(!invalid_key);
auto invalid_value = duckdb_get_map_value(map, 10000000);
REQUIRE(!invalid_value);
}
for (idx_t i = 0; i < count; i++) {
auto key = duckdb_get_map_key(map, i);
REQUIRE(key);
auto value = duckdb_get_map_value(map, i);
REQUIRE(value);
auto key_c_str = duckdb_get_varchar(key);
auto value_c_str = duckdb_get_varchar(value);
auto key_str = duckdb::string(key_c_str);
auto value_str = duckdb::string(value_c_str);
if (depth == 0) {
REQUIRE(key_str != EnumUtil::ToString(MetricsType::OPERATOR_CARDINALITY));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::OPERATOR_ROWS_SCANNED));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::OPERATOR_TIMING));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::OPERATOR_NAME));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::OPERATOR_TYPE));
} else {
REQUIRE(key_str != EnumUtil::ToString(MetricsType::QUERY_NAME));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::BLOCKED_THREAD_TIME));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::LATENCY));
REQUIRE(key_str != EnumUtil::ToString(MetricsType::ROWS_RETURNED));
}
if (key_str == EnumUtil::ToString(MetricsType::QUERY_NAME) ||
key_str == EnumUtil::ToString(MetricsType::OPERATOR_NAME) ||
key_str == EnumUtil::ToString(MetricsType::OPERATOR_TYPE) ||
key_str == EnumUtil::ToString(MetricsType::EXTRA_INFO)) {
REQUIRE(!value_str.empty());
} else {
double result = 0;
try {
result = std::stod(value_str);
} catch (std::invalid_argument &e) {
REQUIRE(false);
}
if (cumulative_counter.find(key_str) != cumulative_counter.end()) {
cumulative_counter[key_str] += result;
}
if (cumulative_result.find(key_str) != cumulative_result.end() && cumulative_result[key_str] == 0) {
cumulative_result[key_str] = result;
}
}
duckdb_destroy_value(&key);
duckdb_destroy_value(&value);
duckdb_free(key_c_str);
duckdb_free(value_c_str);
}
duckdb_destroy_value(&map);
}
void TraverseTree(duckdb_profiling_info profiling_info, duckdb::map<string, double> &cumulative_counter,
duckdb::map<string, double> &cumulative_result, const idx_t depth) {
RetrieveMetrics(profiling_info, cumulative_counter, cumulative_result, depth);
// Recurse into the child node.
auto child_count = duckdb_profiling_info_get_child_count(profiling_info);
if (depth == 0) {
REQUIRE(child_count != 0);
}
for (idx_t i = 0; i < child_count; i++) {
auto child = duckdb_profiling_info_get_child(profiling_info, i);
TraverseTree(child, cumulative_counter, cumulative_result, depth + 1);
}
}
idx_t ConvertToInt(double value) {
return idx_t(value * 1000);
}
TEST_CASE("Test profiling with a single metric and get_value", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
// test only CPU_TIME profiling
duckdb::vector<string> settings = {"CPU_TIME"};
REQUIRE_NO_FAIL(tester.Query("PRAGMA custom_profiling_settings=" + BuildSettingsString(settings)));
REQUIRE_NO_FAIL(tester.Query("SELECT 42"));
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info != nullptr);
// Retrieve a metric that is not enabled.
REQUIRE(duckdb_profiling_info_get_value(info, "EXTRA_INFO") == nullptr);
duckdb::map<string, double> cumulative_counter;
duckdb::map<string, double> cumulative_result;
TraverseTree(info, cumulative_counter, cumulative_result, 0);
tester.Cleanup();
}
TEST_CASE("Test profiling with cumulative metrics", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
// test all profiling metrics
duckdb::vector<string> settings = {"BLOCKED_THREAD_TIME", "CPU_TIME", "CUMULATIVE_CARDINALITY", "EXTRA_INFO",
"OPERATOR_CARDINALITY", "OPERATOR_TIMING"};
REQUIRE_NO_FAIL(tester.Query("PRAGMA custom_profiling_settings=" + BuildSettingsString(settings)));
REQUIRE_NO_FAIL(tester.Query("SELECT 42"));
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info != nullptr);
duckdb::map<string, double> cumulative_counter = {{"OPERATOR_TIMING", 0}, {"OPERATOR_CARDINALITY", 0}};
duckdb::map<string, double> cumulative_result {
{"CPU_TIME", 0},
{"CUMULATIVE_CARDINALITY", 0},
};
TraverseTree(info, cumulative_counter, cumulative_result, 0);
REQUIRE(ConvertToInt(cumulative_result["CPU_TIME"]) == ConvertToInt(cumulative_counter["OPERATOR_TIMING"]));
REQUIRE(ConvertToInt(cumulative_result["CUMULATIVE_CARDINALITY"]) ==
ConvertToInt(cumulative_counter["OPERATOR_CARDINALITY"]));
tester.Cleanup();
}
TEST_CASE("Test profiling without profiling enabled", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
// Retrieve info without profiling enabled.
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info == nullptr);
tester.Cleanup();
}
TEST_CASE("Test profiling with detailed profiling mode enabled", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
REQUIRE_NO_FAIL(tester.Query("PRAGMA profiling_mode = 'detailed'"));
REQUIRE_NO_FAIL(tester.Query("SELECT 42"));
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info != nullptr);
duckdb::map<string, double> cumulative_counter;
duckdb::map<string, double> cumulative_result;
TraverseTree(info, cumulative_counter, cumulative_result, 0);
tester.Cleanup();
}
TEST_CASE("Test invalid use of profiling API", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
REQUIRE_NO_FAIL(tester.Query("PRAGMA profiling_mode = 'detailed'"));
REQUIRE_NO_FAIL(tester.Query("SELECT 42"));
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info != nullptr);
// Incorrect usage tests.
auto map = duckdb_profiling_info_get_metrics(nullptr);
REQUIRE(map == nullptr);
map = duckdb_profiling_info_get_metrics(info);
auto dummy_value = duckdb_create_bool(true);
auto count = duckdb_get_map_size(nullptr);
REQUIRE(count == 0);
count = duckdb_get_map_size(dummy_value);
REQUIRE(count == 0);
count = duckdb_get_map_size(map);
for (idx_t i = 0; i < count; i++) {
auto key = duckdb_get_map_key(nullptr, i);
REQUIRE(key == nullptr);
key = duckdb_get_map_key(map, DConstants::INVALID_INDEX);
REQUIRE(key == nullptr);
key = duckdb_get_map_key(dummy_value, i);
REQUIRE(key == nullptr);
auto value = duckdb_get_map_value(nullptr, i);
REQUIRE(value == nullptr);
value = duckdb_get_map_value(map, DConstants::INVALID_INDEX);
REQUIRE(value == nullptr);
value = duckdb_get_map_value(dummy_value, i);
REQUIRE(value == nullptr);
break;
}
duckdb_destroy_value(&dummy_value);
duckdb_destroy_value(&map);
tester.Cleanup();
}
TEST_CASE("Test profiling after throwing an error", "[capi]") {
CAPITester tester;
auto main_db = TestCreatePath("profiling_error.db");
REQUIRE(tester.OpenDatabase(main_db.c_str()));
auto path = TestCreatePath("profiling_error.db");
REQUIRE_NO_FAIL(tester.Query("ATTACH IF NOT EXISTS '" + path + "' (TYPE DUCKDB)"));
REQUIRE_NO_FAIL(tester.Query("CREATE TABLE profiling_error.tbl AS SELECT range AS id FROM range(10)"));
REQUIRE_NO_FAIL(tester.Query("SET enable_profiling = 'no_output'"));
REQUIRE_NO_FAIL(tester.Query("SET profiling_mode = 'standard'"));
CAPIPrepared prepared_q1;
CAPIPending pending_q1;
REQUIRE(prepared_q1.Prepare(tester, "SELECT * FROM profiling_error.tbl"));
REQUIRE(pending_q1.Pending(prepared_q1));
auto result = pending_q1.Execute();
REQUIRE(result);
REQUIRE(!result->HasError());
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info != nullptr);
CAPIPrepared prepared_q2;
REQUIRE(!prepared_q2.Prepare(tester, "SELECT * FROM profiling_error.does_not_exist"));
info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info == nullptr);
tester.Cleanup();
}
TEST_CASE("Test profiling with Extra Info enabled", "[capi]") {
CAPITester tester;
REQUIRE(tester.OpenDatabase(nullptr));
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
duckdb::vector<string> settings = {"EXTRA_INFO"};
REQUIRE_NO_FAIL(tester.Query("PRAGMA custom_profiling_settings=" + BuildSettingsString(settings)));
REQUIRE_NO_FAIL(tester.Query("SELECT 1"));
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info);
// Retrieve the child node.
auto child_info = duckdb_profiling_info_get_child(info, 0);
REQUIRE(duckdb_profiling_info_get_child_count(child_info) != 0);
auto map = duckdb_profiling_info_get_metrics(child_info);
REQUIRE(map);
auto count = duckdb_get_map_size(map);
REQUIRE(count != 0);
bool found_extra_info = false;
for (idx_t i = 0; i < count; i++) {
auto key = duckdb_get_map_key(map, i);
REQUIRE(key);
auto key_c_str = duckdb_get_varchar(key);
auto key_str = duckdb::string(key_c_str);
auto value = duckdb_get_map_value(map, i);
REQUIRE(value);
auto value_c_str = duckdb_get_varchar(value);
auto value_str = duckdb::string(value_c_str);
if (key_str == EnumUtil::ToString(MetricsType::EXTRA_INFO)) {
REQUIRE(value_str.find("__order_by__"));
REQUIRE(value_str.find("ASC"));
found_extra_info = true;
}
if (key) {
duckdb_destroy_value(&key);
duckdb_free(key_c_str);
}
if (value) {
duckdb_destroy_value(&value);
duckdb_free(value_c_str);
}
}
REQUIRE(found_extra_info);
duckdb_destroy_value(&map);
tester.Cleanup();
}
TEST_CASE("Test profiling with the appender", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
REQUIRE(tester.OpenDatabase(nullptr));
tester.Query("CREATE TABLE tbl (i INT PRIMARY KEY, value VARCHAR)");
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
REQUIRE_NO_FAIL(tester.Query("SET profiling_coverage='ALL'"));
duckdb_appender appender;
string query = "INSERT INTO tbl FROM my_appended_data";
duckdb_logical_type types[2];
types[0] = duckdb_create_logical_type(DUCKDB_TYPE_INTEGER);
types[1] = duckdb_create_logical_type(DUCKDB_TYPE_VARCHAR);
auto status = duckdb_appender_create_query(tester.connection, query.c_str(), 2, types, "my_appended_data", nullptr,
&appender);
duckdb_destroy_logical_type(&types[0]);
duckdb_destroy_logical_type(&types[1]);
REQUIRE(status == DuckDBSuccess);
REQUIRE(duckdb_appender_error(appender) == nullptr);
REQUIRE(duckdb_appender_begin_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_append_int32(appender, 1) == DuckDBSuccess);
REQUIRE(duckdb_append_varchar(appender, "hello world") == DuckDBSuccess);
REQUIRE(duckdb_appender_end_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_flush(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_close(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_destroy(&appender) == DuckDBSuccess);
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info);
// Check that the query name matches the appender query.
auto query_name = duckdb_profiling_info_get_value(info, "QUERY_NAME");
REQUIRE(query_name);
auto query_name_c_str = duckdb_get_varchar(query_name);
auto query_name_str = duckdb::string(query_name_c_str);
REQUIRE(query_name_str == query);
duckdb_destroy_value(&query_name);
duckdb_free(query_name_c_str);
duckdb::map<string, double> cumulative_counter;
duckdb::map<string, double> cumulative_result;
TraverseTree(info, cumulative_counter, cumulative_result, 0);
tester.Cleanup();
}
TEST_CASE("Test profiling with the non-query appender", "[capi]") {
CAPITester tester;
duckdb::unique_ptr<CAPIResult> result;
duckdb_state status;
REQUIRE(tester.OpenDatabase(nullptr));
tester.Query("CREATE TABLE test (i INTEGER)");
REQUIRE_NO_FAIL(tester.Query("PRAGMA enable_profiling = 'no_output'"));
REQUIRE_NO_FAIL(tester.Query("SET profiling_coverage='ALL'"));
duckdb_appender appender;
REQUIRE(duckdb_appender_create(tester.connection, nullptr, "test", &appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_error(appender) == nullptr);
// Appending a row.
REQUIRE(duckdb_appender_begin_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_append_int32(appender, 42) == DuckDBSuccess);
// Finish and flush.
REQUIRE(duckdb_appender_end_row(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_flush(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_close(appender) == DuckDBSuccess);
REQUIRE(duckdb_appender_destroy(&appender) == DuckDBSuccess);
auto info = duckdb_get_profiling_info(tester.connection);
REQUIRE(info);
// Check that the query name matches the appender query.
auto query_name = duckdb_profiling_info_get_value(info, "QUERY_NAME");
REQUIRE(query_name);
auto query_name_c_str = duckdb_get_varchar(query_name);
auto query_name_str = duckdb::string(query_name_c_str);
auto query = "INSERT INTO main.test FROM __duckdb_internal_appended_data";
REQUIRE(query_name_str == query);
duckdb_destroy_value(&query_name);
duckdb_free(query_name_c_str);
duckdb::map<string, double> cumulative_counter;
duckdb::map<string, double> cumulative_result;
TraverseTree(info, cumulative_counter, cumulative_result, 0);
tester.Cleanup();
}