diff --git a/pkg/roachpb/app_stats.proto b/pkg/roachpb/app_stats.proto index c5d3a8822143..658ef94698b5 100644 --- a/pkg/roachpb/app_stats.proto +++ b/pkg/roachpb/app_stats.proto @@ -224,6 +224,7 @@ message AggregatedStatementMetadata { optional int64 full_scan_count = 10 [(gogoproto.nullable) = false]; optional int64 vec_count = 11 [(gogoproto.nullable) = false]; optional int64 total_count = 12 [(gogoproto.nullable) = false]; + optional string fingerprint_id = 13 [(gogoproto.nullable) = false, (gogoproto.customname) = "FingerprintID"]; } // CollectedStatementStatistics wraps collected timings and metadata for some diff --git a/pkg/server/combined_statement_stats.go b/pkg/server/combined_statement_stats.go index fe26562bc2e7..6d115f49a059 100644 --- a/pkg/server/combined_statement_stats.go +++ b/pkg/server/combined_statement_stats.go @@ -480,7 +480,7 @@ func getStatementDetailsQueryClausesAndArgs( return whereClause, args, nil } -// getTotalStatementDetails return all the statistics for the selectec statement combined. +// getTotalStatementDetails return all the statistics for the selected statement combined. func getTotalStatementDetails( ctx context.Context, ie *sql.InternalExecutor, whereClause string, args []interface{}, ) (serverpb.StatementDetailsResponse_CollectedStatementSummary, error) { @@ -490,13 +490,15 @@ func getTotalStatementDetails( aggregation_interval, array_agg(app_name) as app_names, crdb_internal.merge_statement_stats(array_agg(statistics)) AS statistics, - max(sampled_plan) as sampled_plan + max(sampled_plan) as sampled_plan, + encode(fingerprint_id, 'hex') as fingerprint_id FROM crdb_internal.statement_statistics %s GROUP BY - aggregation_interval + aggregation_interval, + fingerprint_id LIMIT 1`, whereClause) - const expectedNumDatums = 5 + const expectedNumDatums = 6 var statement serverpb.StatementDetailsResponse_CollectedStatementSummary row, err := ie.QueryRowEx(ctx, "combined-stmts-details-total", nil, @@ -552,6 +554,8 @@ func getTotalStatementDetails( cfg.LineWidth = tree.ConsoleLineWidth aggregatedMetadata.FormattedQuery = cfg.Pretty(queryTree.AST) + aggregatedMetadata.FingerprintID = string(tree.MustBeDString(row[5])) + statement = serverpb.StatementDetailsResponse_CollectedStatementSummary{ Metadata: aggregatedMetadata, AggregationInterval: time.Duration(aggInterval.Nanos()), diff --git a/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_encoding.go b/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_encoding.go index 7d32b50963ba..09e527e03d43 100644 --- a/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_encoding.go +++ b/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_encoding.go @@ -267,7 +267,9 @@ func BuildTxnStatisticsJSON(statistics *roachpb.CollectedTransactionStatistics) // "properties": { // "stmtType": { "type": "string" }, // "query": { "type": "string" }, +// "fingerprintID": { "type": "string" }, // "querySummary": { "type": "string" }, +// "formattedQuery": { "type": "string" }, // "implicitTxn": { "type": "boolean" }, // "distSQLCount": { "type": "number" }, // "failedCount": { "type": "number" }, @@ -280,6 +282,12 @@ func BuildTxnStatisticsJSON(statistics *roachpb.CollectedTransactionStatistics) // "type": "string" // } // }, +// "appNames": { +// "type": "array", +// "items": { +// "type": "string" +// } +// }, // } // } func BuildStmtDetailsMetadataJSON( diff --git a/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_encoding_test.go b/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_encoding_test.go index acb5d2a895e5..d9e83f9c9248 100644 --- a/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_encoding_test.go +++ b/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_encoding_test.go @@ -395,7 +395,8 @@ func TestSQLStatsJsonEncoding(t *testing.T) { "fullScanCount": {{.Int64}}, "totalCount": {{.Int64}}, "db": [{{joinStrings .StringArray}}], - "appNames": [{{joinStrings .StringArray}}] + "appNames": [{{joinStrings .StringArray}}], + "fingerprintID": "{{.String}}" } ` expectedAggregatedMetadataStr := fillTemplate(t, expectedAggregatedMetadataStrTemplate, data) diff --git a/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_impl.go b/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_impl.go index f68a806b1998..dc8d163bde22 100644 --- a/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_impl.go +++ b/pkg/sql/sqlstats/persistedsqlstats/sqlstatsutil/json_impl.go @@ -120,6 +120,7 @@ func (s *aggregatedMetadata) jsonFields() jsonFields { {"stmtType", (*jsonString)(&s.StmtType)}, {"vecCount", (*jsonInt)(&s.VecCount)}, {"totalCount", (*jsonInt)(&s.TotalCount)}, + {"fingerprintID", (*jsonString)(&s.FingerprintID)}, } } diff --git a/pkg/ui/workspaces/cluster-ui/src/api/insightsApi.ts b/pkg/ui/workspaces/cluster-ui/src/api/insightsApi.ts index 9971dc3a447a..003ce1c3797f 100644 --- a/pkg/ui/workspaces/cluster-ui/src/api/insightsApi.ts +++ b/pkg/ui/workspaces/cluster-ui/src/api/insightsApi.ts @@ -30,7 +30,7 @@ import { } from "src/insights"; import moment from "moment"; import { INTERNAL_APP_NAME_PREFIX } from "src/activeExecutions/activeStatementUtils"; -import { CheckHexValue } from "../util"; +import { FixFingerprintHexValue } from "../util"; // Transaction contention insight events. @@ -87,7 +87,9 @@ function formatTxnContentionResults( return response.execution.txn_results[0].rows.map(row => ({ transactionID: row.waiting_txn_id, - transactionFingerprintID: CheckHexValue(row.waiting_txn_fingerprint_id), + transactionFingerprintID: FixFingerprintHexValue( + row.waiting_txn_fingerprint_id, + ), startTime: moment(row.collection_ts).utc(), contentionDuration: moment.duration(row.contention_duration), contentionThreshold: moment.duration(row.threshold).asMilliseconds(), @@ -134,7 +136,9 @@ function formatTxnFingerprintsResults( } return response.execution.txn_results[0].rows.map(row => ({ - transactionFingerprintID: CheckHexValue(row.transaction_fingerprint_id), + transactionFingerprintID: FixFingerprintHexValue( + row.transaction_fingerprint_id, + ), queryIDs: row.query_ids, application: row.app_name, })); @@ -169,7 +173,10 @@ function createStmtFingerprintToQueryMap( return idToQuery; } response.execution.txn_results[0].rows.forEach(row => { - idToQuery.set(CheckHexValue(row.statement_fingerprint_id), row.query); + idToQuery.set( + FixFingerprintHexValue(row.statement_fingerprint_id), + row.query, + ); }); return idToQuery; @@ -364,7 +371,7 @@ function formatTxnContentionDetailsResponse( totalContentionTime += contentionTimeInMs; blockingContentionDetails[idx] = { blockingExecutionID: value.blocking_txn_id, - blockingTxnFingerprintID: CheckHexValue( + blockingTxnFingerprintID: FixFingerprintHexValue( value.blocking_txn_fingerprint_id, ), blockingQueries: null, @@ -385,7 +392,9 @@ function formatTxnContentionDetailsResponse( const contentionThreshold = moment.duration(row.threshold).asMilliseconds(); return { transactionExecutionID: row.waiting_txn_id, - transactionFingerprintID: CheckHexValue(row.waiting_txn_fingerprint_id), + transactionFingerprintID: FixFingerprintHexValue( + row.waiting_txn_fingerprint_id, + ), startTime: moment(row.collection_ts).utc(), totalContentionTimeMs: totalContentionTime, blockingContentionDetails: blockingContentionDetails, @@ -551,7 +560,9 @@ function organizeExecutionInsightsResponseIntoTxns( if (!txnInsight) { txnInsight = { transactionExecutionID: row.txn_id, - transactionFingerprintID: CheckHexValue(row.txn_fingerprint_id), + transactionFingerprintID: FixFingerprintHexValue( + row.txn_fingerprint_id, + ), implicitTxn: row.implicit_txn, databaseName: row.database_name, application: row.app_name, @@ -575,7 +586,7 @@ function organizeExecutionInsightsResponseIntoTxns( endTime: end, elapsedTimeMillis: end.diff(start, "milliseconds"), statementExecutionID: row.stmt_id, - statementFingerprintID: CheckHexValue(row.stmt_fingerprint_id), + statementFingerprintID: FixFingerprintHexValue(row.stmt_fingerprint_id), isFullScan: row.full_scan, rowsRead: row.rows_read, rowsWritten: row.rows_written, diff --git a/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx index cdfbb98ffe0c..081a61ada4d2 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/statementDetails/statementDetails.tsx @@ -30,6 +30,7 @@ import { AlignedData, Options } from "uplot"; import { appAttr, appNamesAttr, + FixFingerprintHexValue, DATE_FORMAT_24_UTC, intersperse, queryByName, @@ -72,7 +73,6 @@ import { import { Delayed } from "../delayed"; import moment from "moment"; import { - CancelStmtDiagnosticRequest, InsertStmtDiagnosticRequest, StatementDiagnosticsReport, } from "../api"; @@ -514,6 +514,7 @@ export class StatementDetails extends React.Component< const { app_names, databases, + fingerprint_id, failed_count, full_scan_count, vec_count, @@ -651,6 +652,10 @@ export class StatementDetails extends React.Component< ", ", )} /> + diff --git a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.selectors.ts b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.selectors.ts index 06fa07c2a0a7..c42d81fa0663 100644 --- a/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.selectors.ts +++ b/pkg/ui/workspaces/cluster-ui/src/statementsPage/statementsPage.selectors.ts @@ -12,7 +12,7 @@ import { createSelector } from "reselect"; import { aggregateStatementStats, appAttr, - CheckHexValue, + FixFingerprintHexValue, combineStatementStats, ExecutionStatistics, flattenStatementStats, @@ -178,7 +178,7 @@ export const selectStatements = createSelector( if (!(key in statsByStatementKey)) { statsByStatementKey[key] = { statementFingerprintID: stmt.statement_fingerprint_id?.toString(), - statementFingerprintHexID: CheckHexValue( + statementFingerprintHexID: FixFingerprintHexValue( stmt.statement_fingerprint_id?.toString(16), ), statement: stmt.statement, @@ -199,7 +199,7 @@ export const selectStatements = createSelector( const stmt = statsByStatementKey[key]; return { aggregatedFingerprintID: stmt.statementFingerprintID, - aggregatedFingerprintHexID: CheckHexValue( + aggregatedFingerprintHexID: FixFingerprintHexValue( stmt.statementFingerprintHexID, ), label: stmt.statement, diff --git a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx index 6178e78b0ae6..f98aace149bb 100644 --- a/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx +++ b/pkg/ui/workspaces/cluster-ui/src/transactionDetails/transactionDetails.tsx @@ -39,6 +39,7 @@ import { SummaryCard, SummaryCardItem } from "../summaryCard"; import { Bytes, calculateTotalWorkload, + FixFingerprintHexValue, Duration, formatNumberForDisplay, unset, @@ -410,6 +411,14 @@ export class TransactionDetails extends React.Component< : unset } /> +

- CheckHexValue(item.stats_data?.transaction_fingerprint_id.toString(16)), + FixFingerprintHexValue( + item.stats_data?.transaction_fingerprint_id.toString(16), + ), sort: (item: TransactionInfo) => item.stats_data?.transaction_fingerprint_id.toString(16), showByDefault: false, diff --git a/pkg/ui/workspaces/cluster-ui/src/util/format.spec.ts b/pkg/ui/workspaces/cluster-ui/src/util/format.spec.ts index 818ac7374db7..515f1992dd81 100644 --- a/pkg/ui/workspaces/cluster-ui/src/util/format.spec.ts +++ b/pkg/ui/workspaces/cluster-ui/src/util/format.spec.ts @@ -15,7 +15,7 @@ import { BytesFitScale, byteUnits, HexStringToInt64String, - CheckHexValue, + FixFingerprintHexValue, } from "./format"; describe("Format utils", () => { @@ -61,13 +61,17 @@ describe("Format utils", () => { }); }); - describe("CheckHexValue", () => { + describe("FixFingerprintHexValue", () => { it("add leading 0 to hex values", () => { - expect(CheckHexValue(undefined)).toBe(""); - expect(CheckHexValue(null)).toBe(""); - expect(CheckHexValue("fb9111f22f2213b7")).toBe("fb9111f22f2213b7"); - expect(CheckHexValue("b9111f22f2213b7")).toBe("0b9111f22f2213b7"); - expect(CheckHexValue("9111f22f2213b7")).toBe("009111f22f2213b7"); + expect(FixFingerprintHexValue(undefined)).toBe(""); + expect(FixFingerprintHexValue(null)).toBe(""); + expect(FixFingerprintHexValue("fb9111f22f2213b7")).toBe( + "fb9111f22f2213b7", + ); + expect(FixFingerprintHexValue("b9111f22f2213b7")).toBe( + "0b9111f22f2213b7", + ); + expect(FixFingerprintHexValue("9111f22f2213b7")).toBe("009111f22f2213b7"); }); }); }); diff --git a/pkg/ui/workspaces/cluster-ui/src/util/format.ts b/pkg/ui/workspaces/cluster-ui/src/util/format.ts index 60c1ddfbd913..11a4ed2a884e 100644 --- a/pkg/ui/workspaces/cluster-ui/src/util/format.ts +++ b/pkg/ui/workspaces/cluster-ui/src/util/format.ts @@ -256,15 +256,15 @@ export function HexStringToInt64String(s: string): string { return dec; } -// CheckHexValue adds the leading 0 on strings with hex value that -// have a length < 16. -export function CheckHexValue(s: string): string { +// FixFingerprintHexValue adds the leading 0 on strings with hex value that +// have a length < 16. This can occur because it was returned like this from the DB +// or because the hex value was generated using `.toString(16)` (which removes the +// leading zeros). +// The zeros need to be added back to match the value on our sql stats tables. +export function FixFingerprintHexValue(s: string): string { if (s === undefined || s === null || s.length === 0) { return ""; } - if (s?.length === 16) { - return s; - } while (s.length < 16) { s = `0${s}`; } diff --git a/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx b/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx index b513716c150b..c3a2cf3f7b3d 100644 --- a/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx +++ b/pkg/ui/workspaces/db-console/src/views/statements/statementsPage.tsx @@ -142,7 +142,7 @@ export const selectStatements = createSelector( if (!(key in statsByStatementKey)) { statsByStatementKey[key] = { statementFingerprintID: stmt.statement_fingerprint_id?.toString(), - statementFingerprintHexID: util.CheckHexValue( + statementFingerprintHexID: util.FixFingerprintHexValue( stmt.statement_fingerprint_id?.toString(16), ), statement: stmt.statement, @@ -163,7 +163,7 @@ export const selectStatements = createSelector( const stmt = statsByStatementKey[key]; return { aggregatedFingerprintID: stmt.statementFingerprintID, - aggregatedFingerprintHexID: util.CheckHexValue( + aggregatedFingerprintHexID: util.FixFingerprintHexValue( stmt.statementFingerprintHexID, ), label: stmt.statement,