@@ -113,6 +113,11 @@ type Table struct {
113113 RedactColumn string
114114 // RedactedKeys is the set of RedactKeyColumn values for which RedactColumn is redacted.
115115 RedactedKeys []string
116+ // Query overrides the default SELECT for this table. When set, the query is used as-is
117+ // for both column discovery and data export. TimeColumn, RedactKeyColumn, RedactColumn,
118+ // and RedactedKeys are ignored when Query is set. The output filename is still derived
119+ // from Database and Name.
120+ Query string
116121}
117122
118123// sensitiveClusterSettings is the list of cluster setting names whose values are
@@ -140,6 +145,19 @@ var exportTables = []Table{
140145 {Database : "crdb_internal" , Name : "transaction_statistics" , TimeColumn : "aggregated_ts" , Scope : TenantScopeMain },
141146 {Database : "crdb_internal" , Name : "transaction_contention_events" , TimeColumn : "collection_ts" , Scope : TenantScopeMain },
142147 {Database : "crdb_internal" , Name : "gossip_nodes" , TimeColumn : "" , Optional : true , Scope : TenantScopeSystem },
148+ {
149+ Database : "crdb_internal" ,
150+ Name : "node_cpu_mem" ,
151+ Optional : true ,
152+ Scope : TenantScopeSystem ,
153+ Query : `SELECT node_id, address,` +
154+ ` ROUND(` +
155+ `((metrics->>'sys.cpu.user.percent')::FLOAT + (metrics->>'sys.cpu.sys.percent')::FLOAT)` +
156+ ` / NULLIF((metrics->>'sys.cpu.combined.percent-normalized')::FLOAT, 0)` +
157+ `)::INT AS num_vcpus,` +
158+ ` ROUND((metrics->>'sys.totalmem')::FLOAT / 1073741824, 1) AS total_mem_gib` +
159+ ` FROM crdb_internal.kv_node_status` ,
160+ },
143161 {Database : "" , Name : "crdb_internal.table_indexes" , TimeColumn : "" , Scope : TenantScopeMain }, // Use "" to query across all databases
144162 {Database : "system" , Name : "table_statistics" , TimeColumn : "" , Scope : TenantScopeMain },
145163 {
@@ -235,7 +253,7 @@ func (exporter *Exporter) Close() error {
235253// - Cluster metadata (version, ID, name, organization, settings)
236254// - Database schemas (CREATE statements for all user databases)
237255// - Zone configurations
238- // - Statistics tables (statement_statistics, transaction_statistics, transaction_contention_events, gossip_nodes, table_indexes across all databases, system.table_statistics)
256+ // - Statistics tables (statement_statistics, transaction_statistics, transaction_contention_events, gossip_nodes, node_cpu_mem, table_indexes across all databases, system.table_statistics)
239257// - Cluster settings (crdb_internal.cluster_settings, system.settings) with sensitive values redacted
240258//
241259// The statistics tables are filtered by the TimeRange specified in Config.
@@ -599,7 +617,13 @@ func (exporter *Exporter) doExportTable(ctx context.Context, dir string, table T
599617 }
600618
601619 // Get column names
602- rows , err := conn .Query (ctx , fmt .Sprintf ("SELECT * FROM %s LIMIT 0" , tableRef ))
620+ var colProbeSQL string
621+ if table .Query != "" {
622+ colProbeSQL = fmt .Sprintf ("SELECT * FROM (%s) AS q LIMIT 0" , table .Query )
623+ } else {
624+ colProbeSQL = fmt .Sprintf ("SELECT * FROM %s LIMIT 0" , tableRef )
625+ }
626+ rows , err := conn .Query (ctx , colProbeSQL )
603627 if err != nil {
604628 return err
605629 }
@@ -618,22 +642,28 @@ func (exporter *Exporter) doExportTable(ctx context.Context, dir string, table T
618642 return err
619643 }
620644
621- // Use a SQL query to export data in CSV format
622- var where string
623- if table .TimeColumn != "" {
624- where = fmt .Sprintf ("WHERE %s BETWEEN '%s' and '%s'" ,
625- pgx.Identifier {table .TimeColumn }.Sanitize (),
626- startTime (exporter .Config .TimeRange .Start ).Format ("2006-01-02 15:04:05" ), // offset for aggregation interval -- TODO
627- endTime (exporter .Config .TimeRange .End ).Format ("2006-01-02 15:04:05" ),
628- )
629- }
645+ // Build and run the COPY query.
646+ var copyQuery string
647+ if table .Query != "" {
648+ copyQuery = fmt .Sprintf ("COPY (%s) TO STDOUT WITH CSV" , table .Query )
649+ } else {
650+ // Use a SQL query to export data in CSV format
651+ var where string
652+ if table .TimeColumn != "" {
653+ where = fmt .Sprintf ("WHERE %s BETWEEN '%s' and '%s'" ,
654+ pgx.Identifier {table .TimeColumn }.Sanitize (),
655+ startTime (exporter .Config .TimeRange .Start ).Format ("2006-01-02 15:04:05" ), // offset for aggregation interval -- TODO
656+ endTime (exporter .Config .TimeRange .End ).Format ("2006-01-02 15:04:05" ),
657+ )
658+ }
630659
631- // Build SELECT expression, applying column-level redaction when configured.
632- selectExpr := buildSelectExpr (headers , table )
660+ // Build SELECT expression, applying column-level redaction when configured.
661+ selectExpr := buildSelectExpr (headers , table )
633662
634- copyQuery := fmt .Sprintf (
635- "COPY (SELECT %s FROM %s %s) TO STDOUT WITH CSV" ,
636- selectExpr , tableRef , where )
663+ copyQuery = fmt .Sprintf (
664+ "COPY (SELECT %s FROM %s %s) TO STDOUT WITH CSV" ,
665+ selectExpr , tableRef , where )
666+ }
637667 logrus .Info (copyQuery )
638668 _ , err = conn .PgConn ().CopyTo (ctx , file , copyQuery )
639669 if err != nil {
0 commit comments