diff --git a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestYbQueryDiagnostics.java b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestYbQueryDiagnostics.java index 407099f645ef..399c19c08954 100644 --- a/java/yb-pgsql/src/test/java/org/yb/pgsql/TestYbQueryDiagnostics.java +++ b/java/yb-pgsql/src/test/java/org/yb/pgsql/TestYbQueryDiagnostics.java @@ -19,6 +19,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -28,6 +29,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.json.JSONObject; import org.junit.Before; @@ -57,6 +59,11 @@ public QueryDiagnosticsParams(int diagnosticsInterval, int explainSampleRate, } private static final int ASH_SAMPLING_INTERVAL_MS = 500; + private static final String noQueriesExecutedWarning = "No query executed;"; + private static final String pgssResetWarning = + "pg_stat_statements was reset, query string not available;"; + private static final String permissionDeniedWarning = + "Failed to create query diagnostics directory, Permission denied;"; @Before public void setUp() throws Exception { @@ -293,9 +300,7 @@ public void testYbQueryDiagnosticsStatus() throws Exception { assertQueryDiagnosticsStatus(resultSet, fileErrorBundlePath.getParent() /* expectedViewPath */, "Error" /* expectedStatus */, - "Failed to create query " + - "diagnostics directory, " + - "Permission denied;" /* expectedDescription */, + permissionDeniedWarning /* expectedDescription */, fileErrorRunParams); resultSet = statement.executeQuery( @@ -417,8 +422,15 @@ public void checkAshData() throws Exception { long sleep_time_s = diagnosticsInterval + 1; try (Statement statement = connection.createStatement()) { - String queryId = getQueryIdFromPgStatStatements(statement, "PREPARE%"); + statement.execute("SELECT pg_sleep(0.5)"); + + String queryId = getQueryIdFromPgStatStatements(statement, "%pg_sleep%"); Path bundleDataPath = runQueryDiagnostics(statement, queryId, params); + + /* Protects from "No query executed;" warning */ + statement.execute("SELECT pg_sleep(0.1)"); + + /* sleep for bundle expiry */ statement.execute("SELECT pg_sleep(" + sleep_time_s + ")"); Path ashPath = bundleDataPath.resolve("active_session_history.csv"); @@ -462,7 +474,7 @@ public void checkAshData() throws Exception { @Test public void checkPgssData() throws Exception { - int diagnosticsInterval = 10; + int diagnosticsInterval = (5 * ASH_SAMPLING_INTERVAL_MS) / 1000; /* convert to seconds */ QueryDiagnosticsParams queryDiagnosticsParams = new QueryDiagnosticsParams( diagnosticsInterval, 100 /* explainSampleRate */, @@ -471,6 +483,9 @@ public void checkPgssData() throws Exception { false /* explainDebug */, 0 /* bindVarQueryMinDuration */); + /* sleep time is diagnosticsInterval + 1 sec to ensure that the bundle has expired */ + long sleep_time_s = diagnosticsInterval + 1; + try (Statement statement = connection.createStatement()) { statement.execute("SELECT pg_sleep(0.5)"); @@ -481,10 +496,8 @@ public void checkPgssData() throws Exception { statement.execute("SELECT * from pg_class"); statement.execute("SELECT pg_sleep(0.2)"); - /* - * Thread sleeps for diagnosticsInterval + 1 sec to ensure that the bundle has expired - */ - Thread.sleep((diagnosticsInterval + 1) * 1000); + /* sleep for bundle expiry */ + statement.execute("SELECT pg_sleep(" + sleep_time_s + ")"); Path pgssPath = bundleDataPath.resolve("pg_stat_statements.csv"); @@ -518,4 +531,150 @@ public void checkPgssData() throws Exception { Math.abs(Float.parseFloat(tokens[6]) - expectedMeanTime), epsilon); } } + + private void runBundleWithQueries(Statement statement, String queryId, + QueryDiagnosticsParams queryDiagnosticsParams, + String[] queries, String warning) throws Exception { + /* sleep time is diagnosticsInterval + 1 sec to ensure that the bundle has expired */ + long sleep_time_s = queryDiagnosticsParams.diagnosticsInterval + 1; + + Path bundleDataPath = runQueryDiagnostics(statement, queryId, queryDiagnosticsParams); + + for (String query : queries) { + statement.execute(query); + } + + /* + * Sleep for the bundle expiry duration. + * This also prevents from "No data available in ASH for the given time range;" warning. + */ + statement.execute("SELECT pg_sleep(" + sleep_time_s + ")"); + + /* Select the last executed bundle */ + ResultSet resultSet = statement.executeQuery("SELECT * " + + "FROM yb_query_diagnostics_status " + + "ORDER BY start_time DESC"); + if (!resultSet.next()) + fail("yb_query_diagnostics_status view does not have expected data"); + + assertQueryDiagnosticsStatus(resultSet, + bundleDataPath /* expectedViewPath */, + "Success" /* expectedStatus */, + warning /* expectedDescription */, + queryDiagnosticsParams); + + if (!warning.equals(noQueriesExecutedWarning)) { + Path pgssPath = bundleDataPath.resolve("pg_stat_statements.csv"); + assertTrue("pg_stat_statements file does not exist", Files.exists(pgssPath)); + assertGreaterThan("pg_stat_statements.csv file is empty", + Files.size(pgssPath) , 0L); + + /* Read the pg_stat_statements.csv file */ + List pgssData = Files.readAllLines(pgssPath); + String[] tokens = pgssData.get(1).split(","); + + /* Ensure that the query string in pg_stat_statements is empty as expected */ + assertEquals("pg_stat_statements query is incorrect", "\"\"", tokens[1]); + } + } + + @Test + public void testPgssResetBetweenDiagnostics() throws Exception { + int diagnosticsInterval = (5 * ASH_SAMPLING_INTERVAL_MS) / 1000; /* convert to seconds */ + QueryDiagnosticsParams queryDiagnosticsParams = new QueryDiagnosticsParams( + diagnosticsInterval, + 100 /* explainSampleRate */, + true /* explainAnalyze */, + true /* explainDist */, + false /* explainDebug */, + 0 /* bindVarQueryMinDuration */); + + try (Statement statement = connection.createStatement()) { + /* + * If pg_stat_statements resets during the bundle creation process, + * the query string in the pg_stat_statements output file will not be available. + * A warning will be included in the description field of the catalog view + * to indicate the same. + */ + String queryId = getQueryIdFromPgStatStatements(statement, "PREPARE%"); + + /* Test different scenarios of pgss reset */ + + /* reset */ + runBundleWithQueries(statement, queryId, queryDiagnosticsParams, new String[] { + "SELECT pg_stat_statements_reset()", + }, noQueriesExecutedWarning); + + /* statement -> reset */ + runBundleWithQueries(statement, queryId, queryDiagnosticsParams, new String[] { + "EXECUTE stmt('var1', 1, 1.1)", + "SELECT pg_stat_statements_reset()", + }, pgssResetWarning); + + /* reset -> statement */ + runBundleWithQueries(statement, queryId, queryDiagnosticsParams, new String[] { + "SELECT pg_stat_statements_reset()", + "EXECUTE stmt('var2', 2, 2.2)" + }, pgssResetWarning); + + /* + * statement -> reset -> statement + * + * Note that this also emits pgssResetWarning, although a statement is executed + * after the reset. This is intentional as if we were to implement a check for + * last_time_query_bundled against last_time_reset, it would require a + * GetCurrentTimestamp() call per bundled query, which could be expensive. + */ + runBundleWithQueries(statement, queryId, queryDiagnosticsParams, new String[] { + "EXECUTE stmt('var1', 1, 1.1)", + "SELECT pg_stat_statements_reset()", + "EXECUTE stmt('var2', 2, 2.2)" + }, pgssResetWarning); + } + } + + @Test + public void emptyBundle() throws Exception { + int diagnosticsInterval = (5 * ASH_SAMPLING_INTERVAL_MS) / 1000; /* convert to seconds */ + QueryDiagnosticsParams params = new QueryDiagnosticsParams( + diagnosticsInterval, + 100 /* explainSampleRate */, + true /* explainAnalyze */, + true /* explainDist */, + false /* explainDebug */, + 0 /* bindVarQueryMinDuration */); + + /* sleep time is diagnosticsInterval + 1 sec to ensure that the bundle has expired */ + long sleep_time_s = diagnosticsInterval + 1; + + try (Statement statement = connection.createStatement()) { + /* Run query diagnostics on the prepared stmt */ + String queryId = getQueryIdFromPgStatStatements(statement, "PREPARE%"); + Path bundleDataPath = runQueryDiagnostics(statement, queryId, params); + + /* sleep for bundle expiry */ + statement.execute("SELECT pg_sleep(" + sleep_time_s + ")"); + + /* Check that the bundle is empty */ + try (Stream files = Files.list(bundleDataPath)) { + if (files.findAny().isPresent()) { + fail("The bundle directory is not empty, even though no queries were fired"); + } + } catch (IOException e) { + fail("Failed to list files in the bundle directory: " + e.getMessage()); + } + + /* Check that the bundle is empty in the view */ + ResultSet resultSet = statement.executeQuery( + "SELECT * FROM yb_query_diagnostics_status"); + if (!resultSet.next()) + fail("yb_query_diagnostics_status view does not have expected data"); + + assertQueryDiagnosticsStatus(resultSet, + bundleDataPath /* expectedViewPath */, + "Success" /* expectedStatus */, + noQueriesExecutedWarning /* expectedDescription */, + params); + } + } } diff --git a/src/postgres/contrib/pg_stat_statements/pg_stat_statements.c b/src/postgres/contrib/pg_stat_statements/pg_stat_statements.c index 96ac68f92feb..86891313f802 100644 --- a/src/postgres/contrib/pg_stat_statements/pg_stat_statements.c +++ b/src/postgres/contrib/pg_stat_statements/pg_stat_statements.c @@ -1757,6 +1757,9 @@ pgss_store(const char *query, uint64 queryId, Datum pg_stat_statements_reset(PG_FUNCTION_ARGS) { + if (YBIsQueryDiagnosticsEnabled()) + *yb_pgss_last_reset_time = GetCurrentTimestamp(); + if (!pgss || !pgss_hash) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), @@ -3820,7 +3823,6 @@ yb_track_nested_queries(void) return pgss_track == PGSS_TRACK_ALL; } - static void YbGetPgssNormalizedQueryText(Size query_offset, int query_len, char *normalized_query) { @@ -3830,7 +3832,7 @@ YbGetPgssNormalizedQueryText(Size query_offset, int query_len, char *normalized_ qbuffer = qtext_load_file(&qbuffer_size); memcpy(normalized_query, qtext_fetch(query_offset, query_len, qbuffer, qbuffer_size), query_len); - normalized_query[query_len - 1] = '\0'; /* Ensure null-termination */ + normalized_query[query_len] = '\0'; /* Ensure null-termination */ free(qbuffer); -} \ No newline at end of file +} diff --git a/src/postgres/src/backend/utils/misc/yb_ash.c b/src/postgres/src/backend/utils/misc/yb_ash.c index 782682b97118..820d6f323065 100644 --- a/src/postgres/src/backend/utils/misc/yb_ash.c +++ b/src/postgres/src/backend/utils/misc/yb_ash.c @@ -1244,11 +1244,9 @@ GetAshRangeIndexes(TimestampTz start_time, TimestampTz end_time, int64 query_id, /* Time range is not there in the buffer */ if (start_time > buffer_max_time || end_time < buffer_min_time) { - const char *message = (end_time < buffer_min_time) ? - "ASH circular buffer has wrapped around, " \ - "Unable to fetch ASH data" : - "No data available in ASH for the given time range"; - snprintf(description, YB_QD_DESCRIPTION_LEN, "%s; ", message); + AppendToDescription(description, (end_time < buffer_min_time) ? + "ASH circular buffer has wrapped around, unable to fetch ASH data;" : + "No data available in ASH for the given time range;"); return; } diff --git a/src/postgres/src/backend/utils/misc/yb_query_diagnostics.c b/src/postgres/src/backend/utils/misc/yb_query_diagnostics.c index 588aadf2ae66..cd5b1ae8404d 100644 --- a/src/postgres/src/backend/utils/misc/yb_query_diagnostics.c +++ b/src/postgres/src/backend/utils/misc/yb_query_diagnostics.c @@ -81,6 +81,7 @@ static volatile sig_atomic_t got_sigterm = false; static volatile sig_atomic_t got_sighup = false; YbGetNormalizedQueryFuncPtr yb_get_normalized_query = NULL; +TimestampTz *yb_pgss_last_reset_time; static HTAB *bundles_in_progress = NULL; static LWLock *bundles_in_progress_lock; /* protects bundles_in_progress hash table */ @@ -95,8 +96,7 @@ static void InsertNewBundleInfo(YbQueryDiagnosticsMetadata *metadata); static void FetchParams(YbQueryDiagnosticsParams *params, FunctionCallInfo fcinfo); static void ConstructDiagnosticsPath(YbQueryDiagnosticsMetadata *metadata); static void FormatParams(StringInfo buf, const ParamListInfo params); -static bool DumpToFile(const char *path, const char *file_name, const char *data, - int *status, char *description); +static int DumpToFile(const char *path, const char *file_name, const char *data, char *description); static void RemoveExpiredEntries(); static void AccumulateBindVariables(YbQueryDiagnosticsEntry *entry, const double totaltime_ms, const ParamListInfo params); @@ -109,7 +109,7 @@ static int YbQueryDiagnosticsBundlesShmemSize(void); static Datum CreateJsonb(const YbQueryDiagnosticsParams *params); static void CreateJsonbInt(JsonbParseState *state, char *key, int64 value); static void CreateJsonbBool(JsonbParseState *state, char *key, bool value); -static void InsertCompletedBundleInfo(YbQueryDiagnosticsMetadata *metadata, int status, +static void InsertCompletedBundleInfo(const YbQueryDiagnosticsMetadata *metadata, int status, const char *description); static void OutputBundle(const YbQueryDiagnosticsMetadata metadata, const char *description, const char *status, Tuplestorestate *tupstore, TupleDesc tupdesc); @@ -119,7 +119,7 @@ static inline int CircularBufferMaxEntries(void); static void PgssToString(int64 query_id, char *pgss_str, YbQueryDiagnosticsPgss pgss, const char *queryString); static void AccumulatePgss(QueryDesc *queryDesc, YbQueryDiagnosticsEntry *result); -static void AppendToErrorDescription(char *description, const char *format, ...); +static void RemoveBundleInfo(int64 query_id, int status, const char *description); void YbQueryDiagnosticsInstallHook(void) @@ -159,6 +159,7 @@ YbQueryDiagnosticsShmemSize(void) size = add_size(size, hash_estimate_size(QUERY_DIAGNOSTICS_HASH_MAX_SIZE, sizeof(YbQueryDiagnosticsEntry))); size = add_size(size, YbQueryDiagnosticsBundlesShmemSize()); + size = add_size(size, sizeof(TimestampTz)); return size; } @@ -182,7 +183,6 @@ YbQueryDiagnosticsShmemInit(void) ctl.entrysize = sizeof(YbQueryDiagnosticsEntry); /* Create the hash table in shared memory */ - LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); bundles_in_progress_lock = (LWLock *)ShmemInitStruct("YbQueryDiagnostics Lock", sizeof(LWLock), &found); @@ -201,8 +201,6 @@ YbQueryDiagnosticsShmemInit(void) &ctl, HASH_ELEM | HASH_BLOBS); - LWLockRelease(AddinShmemInitLock); - bundles_completed = (YbQueryDiagnosticsBundles *) ShmemInitStruct("YbQueryDiagnostics Status", YbQueryDiagnosticsBundlesShmemSize(), @@ -221,6 +219,9 @@ YbQueryDiagnosticsShmemInit(void) LWLockInitialize(&bundles_completed->lock, LWTRANCHE_YB_QUERY_DIAGNOSTICS_CIRCULAR_BUFFER); } + + yb_pgss_last_reset_time = (TimestampTz *) ShmemAlloc(sizeof(TimestampTz)); + (*yb_pgss_last_reset_time) = 0; } static inline int @@ -234,7 +235,7 @@ CircularBufferMaxEntries(void) * Add a query diagnostics entry to the circular buffer. */ static void -InsertCompletedBundleInfo(YbQueryDiagnosticsMetadata *metadata, int status, +InsertCompletedBundleInfo(const YbQueryDiagnosticsMetadata *metadata, int status, const char *description) { BundleInfo *sample; @@ -827,20 +828,19 @@ BundleEndTime(const YbQueryDiagnosticsEntry *entry) (entry->metadata.params.diagnostics_interval_sec * USECS_PER_SEC); } -static void -AppendToErrorDescription(char *description, const char *format, ...) +void +AppendToDescription(char *description, const char *format, ...) { int current_len = strlen(description); - int remaining_len = YB_QD_DESCRIPTION_LEN - current_len - 1; /* -1 for '\0' */ - char msg[YB_QD_DESCRIPTION_LEN]; va_list args; + int remaining_len = YB_QD_DESCRIPTION_LEN - current_len - 1; /* -1 for '\0' */ + + if (remaining_len <= 0) + return; va_start(args, format); - vsnprintf(msg, YB_QD_DESCRIPTION_LEN, format, args); + vsnprintf(description + current_len, remaining_len, format, args); va_end(args); - - if (remaining_len > 0) - strncat(description, msg, remaining_len); } static void @@ -891,49 +891,71 @@ RemoveExpiredEntries() { snprintf(description, YB_QD_DESCRIPTION_LEN, "Failed to create query diagnostics directory, %s;", strerror(errno)); + status = DIAGNOSTICS_ERROR; + goto remove_entry; } else { - bool has_data_to_dump = false; + /* No queries were executed that needed to be bundled */ + if (pgss_copy.query_len == 0) + { + AppendToDescription(description, "No query executed;"); + goto remove_entry; + } /* Dump bind variables */ - has_data_to_dump |= DumpToFile(metadata_copy.path, BIND_VAR_FILE, - bind_var_copy, &status, description); + status = DumpToFile(metadata_copy.path, BIND_VAR_FILE, + bind_var_copy, description); if (status == DIAGNOSTICS_ERROR) - goto removeEntry; + goto remove_entry; /* Get pgss normalized query string */ - char query_str[pgss_copy.query_len]; + char query_str[pgss_copy.query_len + 1]; query_str[0] = '\0'; - if (yb_get_normalized_query) + /* + * Ensure that pg_stat_statements was not reset in the bundle duration + * + * Note that there are separate checks for pg_stat_statements reset and + * pgss_copy.query_len == 0. This is because resetting pgss does not automatically + * set pgss_copy.query_len to 0. The query_len is only updated when a new query + * is executed. Therefore, even after a pgss reset, pgss_copy.query_len + * may still hold a non-zero garbage value until a new query is run + * and query_len is updated. + */ + if (*yb_pgss_last_reset_time >= metadata_copy.start_time) + AppendToDescription(description, "pg_stat_statements was reset, " \ + "query string not available;"); + else { - /* Extract query string from pgss_query_texts.stat file */ - yb_get_normalized_query(pgss_copy.query_offset, pgss_copy.query_len, query_str); - - if (query_str[0] == '\0') - ereport(LOG, - (errmsg("Error fetching queryString for %ld", entry->metadata.params.query_id))); + if (yb_get_normalized_query) + { + /* Extract query string from pgss_query_texts.stat file */ + yb_get_normalized_query(pgss_copy.query_offset, pgss_copy.query_len, query_str); + + if (query_str[0] == '\0') + AppendToDescription(description, "Error fetching pg_stat_statements normalized query string;"); + } } char pgss_str[YB_QD_MAX_PGSS_LEN]; PgssToString(entry->metadata.params.query_id, pgss_str, pgss_copy, query_str); /* Dump pg_stat_statements */ - has_data_to_dump |= DumpToFile(entry->metadata.path, PGSS_FILE, - pgss_str, &status, description); + status = DumpToFile(metadata_copy.path, PGSS_FILE, + pgss_str, description); if (status == DIAGNOSTICS_ERROR) - goto removeEntry; + goto remove_entry; /* Dump explain plan */ - has_data_to_dump |= DumpToFile(metadata_copy.path, EXPLAIN_PLAN_FILE, - explain_plan_copy, &status, description); + status = DumpToFile(metadata_copy.path, EXPLAIN_PLAN_FILE, + explain_plan_copy, description); if (status == DIAGNOSTICS_ERROR) - goto removeEntry; + goto remove_entry; /* Dump ASH */ if (yb_ash_enable_infra) @@ -947,40 +969,43 @@ RemoveExpiredEntries() metadata_copy.params.query_id, &ash_buffer, description); - has_data_to_dump |= DumpToFile(metadata_copy.path, ASH_FILE, - ash_buffer.data, &status, description); + status = DumpToFile(metadata_copy.path, ASH_FILE, + ash_buffer.data, description); pfree(ash_buffer.data); } - - if (!has_data_to_dump) - AppendToErrorDescription(description, "No data to dump;"); } -removeEntry: +remove_entry: InsertCompletedBundleInfo(&metadata_copy, status, description); - - LWLockAcquire(bundles_in_progress_lock, LW_EXCLUSIVE); - - hash_search(bundles_in_progress, &metadata_copy.params.query_id, - HASH_REMOVE, NULL); - - LWLockRelease(bundles_in_progress_lock); + RemoveBundleInfo(metadata_copy.params.query_id, status, description); LWLockAcquire(bundles_in_progress_lock, LW_SHARED); } } LWLockRelease(bundles_in_progress_lock); } +static void +RemoveBundleInfo(int64 query_id, int status, const char *description) +{ + LWLockAcquire(bundles_in_progress_lock, LW_EXCLUSIVE); + + hash_search(bundles_in_progress, &query_id, + HASH_REMOVE, NULL); + + LWLockRelease(bundles_in_progress_lock); +} + /* * DumpToFile * Creates the file (/path/file_name) and writes the data to it. + * Returns: + * DIAGNOSTICS_SUCCESS if the file was successfully written, DIAGNOSTICS_ERROR otherwise. */ -static bool -DumpToFile(const char *path, const char *file_name, const char *data, - int *status, char *description) +static int +DumpToFile(const char *path, const char *file_name, const char *data, char *description) { - bool has_data_to_dump = false; + bool ok = false; File file = 0; const int file_path_len = MAXPGPATH + strlen(file_name) + 1; char file_path[file_path_len]; @@ -1011,7 +1036,7 @@ DumpToFile(const char *path, const char *file_name, const char *data, snprintf(description, YB_QD_DESCRIPTION_LEN, "Error writing to file; %m"); else - has_data_to_dump = true; + ok = true; } PG_CATCH(); { @@ -1030,7 +1055,7 @@ DumpToFile(const char *path, const char *file_name, const char *data, if (file > 0) FileClose(file); - return has_data_to_dump; + return ok ? DIAGNOSTICS_SUCCESS : DIAGNOSTICS_ERROR; } /* @@ -1101,7 +1126,7 @@ YbQueryDiagnosticsMain(Datum main_arg) static void ConstructDiagnosticsPath(YbQueryDiagnosticsMetadata *metadata) { - int rand_num = DatumGetUInt32(hash_any((unsigned char*)&metadata->start_time, + int rand_num = DatumGetUInt32(hash_any((unsigned char*)&metadata->start_time, sizeof(metadata->start_time))); #ifdef WIN32 const char *format = "%s\\%s\\%ld\\%d\\"; diff --git a/src/postgres/src/include/yb_query_diagnostics.h b/src/postgres/src/include/yb_query_diagnostics.h index a32feb537a20..dad8693f297d 100644 --- a/src/postgres/src/include/yb_query_diagnostics.h +++ b/src/postgres/src/include/yb_query_diagnostics.h @@ -137,6 +137,8 @@ typedef struct YbQueryDiagnosticsEntry char explain_plan[YB_QD_MAX_EXPLAIN_PLAN_LEN]; } YbQueryDiagnosticsEntry; +extern TimestampTz *yb_pgss_last_reset_time; + typedef void (*YbGetNormalizedQueryFuncPtr)(Size query_offset, int query_len, char *normalized_query); extern YbGetNormalizedQueryFuncPtr yb_get_normalized_query; @@ -146,5 +148,6 @@ extern void YbQueryDiagnosticsShmemInit(void); extern void YbQueryDiagnosticsBgWorkerRegister(void); extern void YbQueryDiagnosticsMain(Datum main_arg); extern void YbSetPgssNormalizedQueryText(int64 query_id, const Size query_offset, int query_len); +extern void AppendToDescription(char *description, const char *format, ...); #endif /* YB_QUERY_DIAGNOSTICS_H */