@@ -60,6 +60,15 @@ const char* CmdTypeToString(PschCmdType cmd) {
6060 }
6161}
6262
63+ // Clamp a field length to its buffer maximum, warning on overflow.
64+ template <typename LenT>
65+ LenT ClampFieldLen (LenT len, LenT max, const char * field_name) {
66+ if (len <= max)
67+ return len;
68+ elog (WARNING, " pg_stat_ch: invalid %s %u, clamping" , field_name, static_cast <unsigned >(len));
69+ return max;
70+ }
71+
6372// Dequeue events from the shared memory queue
6473std::vector<PschEvent> DequeueEvents (int max_events) {
6574 std::vector<PschEvent> events;
@@ -78,25 +87,16 @@ void ExportEventStats(const std::vector<PschEvent>& events, StatsExporter* expor
7887
7988 exporter->BeginBatch ();
8089
81- elog (DEBUG2, " pg_stat_ch: creating column objects" );
82-
83- // Basic columns
84- elog (DEBUG3, " pg_stat_ch: creating col_ts_start" );
8590 auto col_ts_start = exporter->RecordDateTime (" ts_start" );
86- elog (DEBUG3, " pg_stat_ch: col_ts_start created" );
8791 auto col_duration_us = exporter->DbDurationColumn ();
88- // Use pre-resolved names from event (resolved at capture time in hooks)
8992 auto col_db = exporter->DbNameColumn ();
9093 auto col_username = exporter->DbUserColumn ();
91- elog (DEBUG3, " pg_stat_ch: basic columns created" );
9294 auto col_pid = exporter->RecordInt32 (" pid" );
9395 auto col_query_id = exporter->RecordInt64 (" query_id" );
9496 auto col_cmd_type = exporter->DbOperationColumn ();
9597 auto col_rows = exporter->MetricUInt64 (" rows" );
9698 auto col_query = exporter->DbQueryTextColumn ();
97- elog (DEBUG3, " pg_stat_ch: all basic columns created" );
9899
99- // Buffer usage columns (hit/read are histograms, rest are records)
100100 auto col_shared_blks_hit = exporter->MetricInt64 (" shared_blks_hit" );
101101 auto col_shared_blks_read = exporter->MetricInt64 (" shared_blks_read" );
102102 auto col_shared_blks_dirtied = exporter->RecordInt64 (" shared_blks_dirtied" );
@@ -108,77 +108,52 @@ void ExportEventStats(const std::vector<PschEvent>& events, StatsExporter* expor
108108 auto col_temp_blks_read = exporter->RecordInt64 (" temp_blks_read" );
109109 auto col_temp_blks_written = exporter->RecordInt64 (" temp_blks_written" );
110110
111- // I/O timing columns (records — rarely non-zero)
112111 auto col_shared_blk_read_time_us = exporter->RecordInt64 (" shared_blk_read_time_us" );
113112 auto col_shared_blk_write_time_us = exporter->RecordInt64 (" shared_blk_write_time_us" );
114113 auto col_local_blk_read_time_us = exporter->RecordInt64 (" local_blk_read_time_us" );
115114 auto col_local_blk_write_time_us = exporter->RecordInt64 (" local_blk_write_time_us" );
116115 auto col_temp_blk_read_time_us = exporter->RecordInt64 (" temp_blk_read_time_us" );
117116 auto col_temp_blk_write_time_us = exporter->RecordInt64 (" temp_blk_write_time_us" );
118117
119- // WAL usage columns (records — rarely non-zero for reads)
120118 auto col_wal_records = exporter->RecordInt64 (" wal_records" );
121119 auto col_wal_fpi = exporter->RecordInt64 (" wal_fpi" );
122120 auto col_wal_bytes = exporter->RecordUInt64 (" wal_bytes" );
123121
124- // CPU time columns (records)
125122 auto col_cpu_user_time_us = exporter->RecordInt64 (" cpu_user_time_us" );
126123 auto col_cpu_sys_time_us = exporter->RecordInt64 (" cpu_sys_time_us" );
127124
128- // JIT columns (records — rarely non-zero)
129125 auto col_jit_functions = exporter->RecordInt32 (" jit_functions" );
130126 auto col_jit_generation_time_us = exporter->RecordInt32 (" jit_generation_time_us" );
131127 auto col_jit_deform_time_us = exporter->RecordInt32 (" jit_deform_time_us" );
132128 auto col_jit_inlining_time_us = exporter->RecordInt32 (" jit_inlining_time_us" );
133129 auto col_jit_optimization_time_us = exporter->RecordInt32 (" jit_optimization_time_us" );
134130 auto col_jit_emission_time_us = exporter->RecordInt32 (" jit_emission_time_us" );
135131
136- // Parallel worker columns (records)
137132 auto col_parallel_workers_planned = exporter->RecordInt16 (" parallel_workers_planned" );
138133 auto col_parallel_workers_launched = exporter->RecordInt16 (" parallel_workers_launched" );
139134
140- elog (DEBUG3, " pg_stat_ch: creating error columns" );
141- // Error columns (records)
142135 auto col_err_sqlstate = exporter->MetricFixedString (5 , " err_sqlstate" );
143136 auto col_err_elevel = exporter->RecordUInt8 (" err_elevel" );
144137 auto col_err_message = exporter->RecordString (" err_message" );
145- elog (DEBUG3, " pg_stat_ch: error columns created" );
146138
147- // Client context columns; records rather than tags (no histogram in OTel)
148139 auto col_app = exporter->RecordString (" app" );
149140 auto col_client_addr = exporter->RecordString (" client_addr" );
150141
151- elog (DEBUG2, " pg_stat_ch: all columns created, starting event loop" );
152- size_t event_idx = 0 ;
153142 for (const auto & ev : events) {
154- elog (DEBUG2, " pg_stat_ch: processing event %zu: pid=%d, query_len=%u" , event_idx, ev.pid ,
155- ev.query_len );
156143 exporter->BeginRow ();
157144
158- int64_t unix_us = ev.ts_start + kPostgresEpochOffsetUs ;
159- col_ts_start->Append (unix_us);
145+ col_ts_start->Append (ev.ts_start + kPostgresEpochOffsetUs );
160146 col_duration_us->Append (ev.duration_us );
161-
162- // Use pre-resolved names from event (resolved at capture time in hooks)
163147 col_db->Append (std::string (ev.datname , ev.datname_len ));
164148 col_username->Append (std::string (ev.username , ev.username_len ));
165-
166149 col_pid->Append (ev.pid );
167150 col_query_id->Append (static_cast <int64_t >(ev.queryid ));
168151 col_cmd_type->Append (CmdTypeToString (ev.cmd_type ));
169152 col_rows->Append (ev.rows );
170153
171- // Validate query_len before using it
172- uint16 safe_query_len = ev.query_len ;
173- if (safe_query_len > PSCH_MAX_QUERY_LEN) {
174- elog (WARNING, " pg_stat_ch: event %zu has invalid query_len %u, clamping" , event_idx,
175- safe_query_len);
176- safe_query_len = PSCH_MAX_QUERY_LEN;
177- }
178- col_query->Append (std::string (ev.query , safe_query_len));
154+ auto qlen = ClampFieldLen (ev.query_len , static_cast <uint16>(PSCH_MAX_QUERY_LEN), " query_len" );
155+ col_query->Append (std::string (ev.query , qlen));
179156
180- elog (DEBUG3, " pg_stat_ch: event %zu - buffer usage" , event_idx);
181- // Buffer usage
182157 col_shared_blks_hit->Append (ev.shared_blks_hit );
183158 col_shared_blks_read->Append (ev.shared_blks_read );
184159 col_shared_blks_dirtied->Append (ev.shared_blks_dirtied );
@@ -190,78 +165,52 @@ void ExportEventStats(const std::vector<PschEvent>& events, StatsExporter* expor
190165 col_temp_blks_read->Append (ev.temp_blks_read );
191166 col_temp_blks_written->Append (ev.temp_blks_written );
192167
193- elog (DEBUG3, " pg_stat_ch: event %zu - I/O timing" , event_idx);
194- // I/O timing
195168 col_shared_blk_read_time_us->Append (ev.shared_blk_read_time_us );
196169 col_shared_blk_write_time_us->Append (ev.shared_blk_write_time_us );
197170 col_local_blk_read_time_us->Append (ev.local_blk_read_time_us );
198171 col_local_blk_write_time_us->Append (ev.local_blk_write_time_us );
199172 col_temp_blk_read_time_us->Append (ev.temp_blk_read_time_us );
200173 col_temp_blk_write_time_us->Append (ev.temp_blk_write_time_us );
201174
202- elog (DEBUG3, " pg_stat_ch: event %zu - WAL usage" , event_idx);
203- // WAL usage
204175 col_wal_records->Append (ev.wal_records );
205176 col_wal_fpi->Append (ev.wal_fpi );
206177 col_wal_bytes->Append (ev.wal_bytes );
207178
208- elog (DEBUG3, " pg_stat_ch: event %zu - CPU time" , event_idx);
209- // CPU time
210179 col_cpu_user_time_us->Append (ev.cpu_user_time_us );
211180 col_cpu_sys_time_us->Append (ev.cpu_sys_time_us );
212181
213- elog (DEBUG3, " pg_stat_ch: event %zu - JIT" , event_idx);
214- // JIT
215182 col_jit_functions->Append (ev.jit_functions );
216183 col_jit_generation_time_us->Append (ev.jit_generation_time_us );
217184 col_jit_deform_time_us->Append (ev.jit_deform_time_us );
218185 col_jit_inlining_time_us->Append (ev.jit_inlining_time_us );
219186 col_jit_optimization_time_us->Append (ev.jit_optimization_time_us );
220187 col_jit_emission_time_us->Append (ev.jit_emission_time_us );
221188
222- elog (DEBUG3, " pg_stat_ch: event %zu - parallel workers" , event_idx);
223- // Parallel workers
224189 col_parallel_workers_planned->Append (ev.parallel_workers_planned );
225190 col_parallel_workers_launched->Append (ev.parallel_workers_launched );
226191
227- elog (DEBUG3, " pg_stat_ch: event %zu - error info" , event_idx);
228- // Error info (5-char SQLSTATE, trimmed)
229192 col_err_sqlstate->Append (std::string_view (ev.err_sqlstate , 5 ));
230193 col_err_elevel->Append (ev.err_elevel );
231- // Error message (validate length)
232- uint16 safe_err_msg_len = ev.err_message_len ;
233- if (safe_err_msg_len > PSCH_MAX_ERR_MSG_LEN) {
234- elog (WARNING, " pg_stat_ch: event %zu has invalid err_message_len %u, clamping" , event_idx,
235- safe_err_msg_len);
236- safe_err_msg_len = PSCH_MAX_ERR_MSG_LEN;
237- }
238- col_err_message->Append (std::string (ev.err_message , safe_err_msg_len));
239-
240- elog (DEBUG3, " pg_stat_ch: event %zu - client context (app_len=%u, addr_len=%u)" , event_idx,
241- ev.application_name_len , ev.client_addr_len );
242- // Client context - validate lengths
243- uint8 safe_app_len = ev.application_name_len ;
244- if (safe_app_len > 63 ) {
245- elog (WARNING, " pg_stat_ch: event %zu has invalid app_name_len %u, clamping" , event_idx,
246- safe_app_len);
247- safe_app_len = 63 ;
248- }
249- uint8 safe_addr_len = ev.client_addr_len ;
250- if (safe_addr_len > 45 ) {
251- elog (WARNING, " pg_stat_ch: event %zu has invalid client_addr_len %u, clamping" , event_idx,
252- safe_addr_len);
253- safe_addr_len = 45 ;
254- }
255- col_app->Append (std::string (ev.application_name , safe_app_len));
256- col_client_addr->Append (std::string (ev.client_addr , safe_addr_len));
257-
258- event_idx++;
194+ auto elen = ClampFieldLen (ev.err_message_len , static_cast <uint16>(PSCH_MAX_ERR_MSG_LEN),
195+ " err_message_len" );
196+ col_err_message->Append (std::string (ev.err_message , elen));
197+
198+ auto alen = ClampFieldLen (ev.application_name_len , static_cast <uint8>(PSCH_MAX_APP_NAME_LEN),
199+ " app_name_len" );
200+ auto clen = ClampFieldLen (ev.client_addr_len , static_cast <uint8>(PSCH_MAX_CLIENT_ADDR_LEN),
201+ " client_addr_len" );
202+ col_app->Append (std::string (ev.application_name , alen));
203+ col_client_addr->Append (std::string (ev.client_addr , clen));
259204 }
260- elog (DEBUG1, " pg_stat_ch: finished processing %zu events" , event_idx );
205+ elog (DEBUG1, " pg_stat_ch: finished processing %zu events" , events. size () );
261206}
262207
263208} // namespace
264209
210+ void LogExporterWarning (const char * context, const char * message) {
211+ ereport (WARNING, errmsg (" pg_stat_ch: %s: %s" , context, message));
212+ }
213+
265214// Used to report negative values, which are not supported by OTel.
266215void LogNegativeValue (const std::string& column_name, int64_t value) {
267216 static std::chrono::steady_clock::time_point last_log = {};
0 commit comments