Thanks for the review! On Tue, Feb 28, 2023 at 4:49 AM Drouvot, Bertrand <bertranddrouvot...@gmail.com> wrote: > On 2/26/23 5:03 PM, Melanie Plageman wrote: > > As suggested in [1], the attached patch adds IO times to pg_stat_io; > > Thanks for the patch! > > I started to have a look at it and figured out that a tiny rebase was needed > (due to > 728560db7d and b9f0e54bc9), so please find the rebase (aka V2) attached.
Thanks for doing that! > > The timings will only be non-zero when track_io_timing is on > > That could lead to incorrect interpretation if one wants to divide the timing > per operations, say: > > - track_io_timing is set to on while there is already operations > - or set to off while it was on (and the number of operations keeps growing) > > Might be worth to warn/highlight in the "track_io_timing" doc? This is a good point. I've added a note to the docs for pg_stat_io. > + if (track_io_timing) > + { > + INSTR_TIME_SET_CURRENT(io_time); > + INSTR_TIME_SUBTRACT(io_time, io_start); > + pgstat_count_io_time(io_object, io_context, > IOOP_EXTEND, io_time); > + } > + > + > pgstat_count_io_op(io_object, io_context, IOOP_EXTEND); > > vs > > @@ -1042,6 +1059,7 @@ ReadBuffer_common(SMgrRelation smgr, char > relpersistence, ForkNumber forkNum, > INSTR_TIME_SUBTRACT(io_time, io_start); > > pgstat_count_buffer_read_time(INSTR_TIME_GET_MICROSEC(io_time)); > INSTR_TIME_ADD(pgBufferUsage.blk_read_time, > io_time); > + pgstat_count_io_time(io_object, io_context, > IOOP_READ, io_time); > } > > That leads to pgstat_count_io_time() to be called before pgstat_count_io_op() > (for the IOOP_EXTEND case) and > after pgstat_count_io_op() (for the IOOP_READ case). > > What about calling them in the same order and so that pgstat_count_io_time() > is called before pgstat_count_io_op()? > > If so, the ordering would also need to be changed in: > > - FlushRelationBuffers() > - register_dirty_segment() Yes, good point. I've updated the code to use this suggested ordering in attached v3. > > There is one minor question (in the code as a TODO) which is whether or > > not it is worth cross-checking that IO counts and times are either both > > zero or neither zero in the validation function > > pgstat_bktype_io_stats_valid(). > > > > As pgstat_bktype_io_stats_valid() is called only in Assert(), I think that > would be a good idea > to also check that if counts are not Zero then times are not Zero. Yes, I think adding some validation around the relationship between counts and timing should help prevent developers from forgetting to call pg_stat_count_io_op() when calling pgstat_count_io_time() (as relevant). However, I think that we cannot check that if IO counts are non-zero that IO times are non-zero, because the user may not have track_io_timing enabled. We can check that if IO times are not zero, IO counts are not zero. I've done this in the attached v3. - Melanie
From 52d997001108a52c833b339f9b8dcb3d34ed3270 Mon Sep 17 00:00:00 2001 From: Melanie Plageman <melanieplage...@gmail.com> Date: Mon, 6 Mar 2023 10:41:51 -0500 Subject: [PATCH v3] Track IO times in pg_stat_io Add IO timing for reads, writes, extends, and fsyncs to pg_stat_io. Reviewed-by: Bertrand Drouvot <bertranddrouvot(dot)pg(at)gmail(dot)com> Discussion: https://www.postgresql.org/message-id/flat/CAAKRu_ay5iKmnbXZ3DsauViF3eMxu4m1oNnJXqV_HyqYeg55Ww%40mail.gmail.com --- doc/src/sgml/monitoring.sgml | 59 ++++++++++++++++ src/backend/catalog/system_views.sql | 4 ++ src/backend/storage/buffer/bufmgr.c | 40 +++++++++-- src/backend/storage/buffer/localbuf.c | 14 ++++ src/backend/storage/smgr/md.c | 47 ++++++++++--- src/backend/utils/activity/pgstat_io.c | 96 +++++++++++++++++++++----- src/backend/utils/adt/pgstatfuncs.c | 40 +++++++++-- src/include/catalog/pg_proc.dat | 6 +- src/include/pgstat.h | 5 +- src/test/regress/expected/rules.out | 6 +- 10 files changed, 275 insertions(+), 42 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 6249bb50d0..ad3667f258 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -3814,6 +3814,18 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i </entry> </row> + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>read_time</structfield> <type>double precision</type> + </para> + <para> + Time spent in read operations in milliseconds (if <xref + linkend="guc-track-io-timing"/> is enabled, otherwise zero) + </para> + </entry> + </row> + <row> <entry role="catalog_table_entry"> <para role="column_definition"> @@ -3826,6 +3838,18 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i </entry> </row> + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>write_time</structfield> <type>double precision</type> + </para> + <para> + Time spent in write operations in milliseconds (if <xref + linkend="guc-track-io-timing"/> is enabled, otherwise zero) + </para> + </entry> + </row> + <row> <entry role="catalog_table_entry"> <para role="column_definition"> @@ -3838,6 +3862,18 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i </entry> </row> + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>extend_time</structfield> <type>double precision</type> + </para> + <para> + Time spent in extend operations in milliseconds (if <xref + linkend="guc-track-io-timing"/> is enabled, otherwise zero) + </para> + </entry> + </row> + <row> <entry role="catalog_table_entry"> <para role="column_definition"> @@ -3902,6 +3938,18 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i </entry> </row> + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>fsync_time</structfield> <type>double precision</type> + </para> + <para> + Time spent in fsync operations in milliseconds (if <xref + linkend="guc-track-io-timing"/> is enabled, otherwise zero) + </para> + </entry> + </row> + <row> <entry role="catalog_table_entry"> <para role="column_definition"> @@ -3967,6 +4015,17 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i </itemizedlist> </para> + <note> + <para> + Columns tracking I/O time will only be non-zero when <xref + linkend="guc-track-io-timing"/> is enabled. The user should be careful when + using these columns in combination with their corresponding operations to + ensure that <varname>track_io_timing</varname> was enabled for the entire + time since the last reset. + </para> + </note> + + </sect2> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 34ca0e739f..39391bc2fc 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1123,12 +1123,16 @@ SELECT b.io_object, b.io_context, b.reads, + b.read_time, b.writes, + b.write_time, b.extends, + b.extend_time, b.op_bytes, b.evictions, b.reuses, b.fsyncs, + b.fsync_time, b.stats_reset FROM pg_stat_get_io() b; diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 0a05577b68..91aa52848a 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -1000,11 +1000,27 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, if (isExtend) { + instr_time io_start, + io_time; + /* new buffers are zero-filled */ MemSet((char *) bufBlock, 0, BLCKSZ); + + if (track_io_timing) + INSTR_TIME_SET_CURRENT(io_start); + else + INSTR_TIME_SET_ZERO(io_start); + /* don't set checksum for all-zero page */ smgrextend(smgr, forkNum, blockNum, bufBlock, false); + if (track_io_timing) + { + INSTR_TIME_SET_CURRENT(io_time); + INSTR_TIME_SUBTRACT(io_time, io_start); + pgstat_count_io_time(io_object, io_context, IOOP_EXTEND, io_time); + } + pgstat_count_io_op(io_object, io_context, IOOP_EXTEND); /* @@ -1034,16 +1050,17 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, smgrread(smgr, forkNum, blockNum, bufBlock); - pgstat_count_io_op(io_object, io_context, IOOP_READ); - if (track_io_timing) { INSTR_TIME_SET_CURRENT(io_time); INSTR_TIME_SUBTRACT(io_time, io_start); pgstat_count_buffer_read_time(INSTR_TIME_GET_MICROSEC(io_time)); INSTR_TIME_ADD(pgBufferUsage.blk_read_time, io_time); + pgstat_count_io_time(io_object, io_context, IOOP_READ, io_time); } + pgstat_count_io_op(io_object, io_context, IOOP_READ); + /* check for garbage data */ if (!PageIsVerifiedExtended((Page) bufBlock, blockNum, PIV_LOG_WARNING | PIV_REPORT_STAT)) @@ -2981,16 +2998,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln, IOObject io_object, * When a strategy is not in use, the write can only be a "regular" write * of a dirty shared buffer (IOCONTEXT_NORMAL IOOP_WRITE). */ - pgstat_count_io_op(IOOBJECT_RELATION, io_context, IOOP_WRITE); - if (track_io_timing) { INSTR_TIME_SET_CURRENT(io_time); INSTR_TIME_SUBTRACT(io_time, io_start); pgstat_count_buffer_write_time(INSTR_TIME_GET_MICROSEC(io_time)); INSTR_TIME_ADD(pgBufferUsage.blk_write_time, io_time); + pgstat_count_io_time(IOOBJECT_RELATION, io_context, IOOP_WRITE, io_time); } + pgstat_count_io_op(IOOBJECT_RELATION, io_context, IOOP_WRITE); pgBufferUsage.shared_blks_written++; /* @@ -3594,6 +3611,9 @@ FlushRelationBuffers(Relation rel) if (RelationUsesLocalBuffers(rel)) { + instr_time io_start, + io_time; + for (i = 0; i < NLocBuffer; i++) { uint32 buf_state; @@ -3616,6 +3636,11 @@ FlushRelationBuffers(Relation rel) PageSetChecksumInplace(localpage, bufHdr->tag.blockNum); + if (track_io_timing) + INSTR_TIME_SET_CURRENT(io_start); + else + INSTR_TIME_SET_ZERO(io_start); + smgrwrite(RelationGetSmgr(rel), BufTagGetForkNum(&bufHdr->tag), bufHdr->tag.blockNum, @@ -3625,6 +3650,13 @@ FlushRelationBuffers(Relation rel) buf_state &= ~(BM_DIRTY | BM_JUST_DIRTIED); pg_atomic_unlocked_write_u32(&bufHdr->state, buf_state); + if (track_io_timing) + { + INSTR_TIME_SET_CURRENT(io_time); + INSTR_TIME_SUBTRACT(io_time, io_start); + pgstat_count_io_time(IOOBJECT_TEMP_RELATION, IOCONTEXT_NORMAL, IOOP_WRITE, io_time); + } + pgstat_count_io_op(IOOBJECT_TEMP_RELATION, IOCONTEXT_NORMAL, IOOP_WRITE); /* Pop the error context stack */ diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c index 5325ddb663..b1272840bd 100644 --- a/src/backend/storage/buffer/localbuf.c +++ b/src/backend/storage/buffer/localbuf.c @@ -220,6 +220,8 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum, */ if (buf_state & BM_DIRTY) { + instr_time io_start, + io_time; SMgrRelation oreln; Page localpage = (char *) LocalBufHdrGetBlock(bufHdr); @@ -228,6 +230,11 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum, PageSetChecksumInplace(localpage, bufHdr->tag.blockNum); + if (track_io_timing) + INSTR_TIME_SET_CURRENT(io_start); + else + INSTR_TIME_SET_ZERO(io_start); + /* And write... */ smgrwrite(oreln, BufTagGetForkNum(&bufHdr->tag), @@ -239,6 +246,13 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum, buf_state &= ~BM_DIRTY; pg_atomic_unlocked_write_u32(&bufHdr->state, buf_state); + if (track_io_timing) + { + INSTR_TIME_SET_CURRENT(io_time); + INSTR_TIME_SUBTRACT(io_time, io_start); + pgstat_count_io_time(IOOBJECT_TEMP_RELATION, IOCONTEXT_NORMAL, IOOP_WRITE, io_time); + } + pgstat_count_io_op(IOOBJECT_TEMP_RELATION, IOCONTEXT_NORMAL, IOOP_WRITE); pgBufferUsage.local_blks_written++; } diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 352958e1fe..052875d86a 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -1030,6 +1030,30 @@ register_dirty_segment(SMgrRelation reln, ForkNumber forknum, MdfdVec *seg) if (!RegisterSyncRequest(&tag, SYNC_REQUEST, false /* retryOnError */ )) { + instr_time io_start, + io_time; + + if (track_io_timing) + INSTR_TIME_SET_CURRENT(io_start); + else + INSTR_TIME_SET_ZERO(io_start); + + ereport(DEBUG1, + (errmsg_internal("could not forward fsync request because request queue is full"))); + + if (FileSync(seg->mdfd_vfd, WAIT_EVENT_DATA_FILE_SYNC) < 0) + ereport(data_sync_elevel(ERROR), + (errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", + FilePathName(seg->mdfd_vfd)))); + + if (track_io_timing) + { + INSTR_TIME_SET_CURRENT(io_time); + INSTR_TIME_SUBTRACT(io_time, io_start); + pgstat_count_io_time(IOOBJECT_RELATION, IOCONTEXT_NORMAL, IOOP_FSYNC, io_time); + } + /* * We have no way of knowing if the current IOContext is * IOCONTEXT_NORMAL or IOCONTEXT_[BULKREAD, BULKWRITE, VACUUM] at this @@ -1042,15 +1066,6 @@ register_dirty_segment(SMgrRelation reln, ForkNumber forknum, MdfdVec *seg) * backend fsyncs. */ pgstat_count_io_op(IOOBJECT_RELATION, IOCONTEXT_NORMAL, IOOP_FSYNC); - - ereport(DEBUG1, - (errmsg_internal("could not forward fsync request because request queue is full"))); - - if (FileSync(seg->mdfd_vfd, WAIT_EVENT_DATA_FILE_SYNC) < 0) - ereport(data_sync_elevel(ERROR), - (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", - FilePathName(seg->mdfd_vfd)))); } } @@ -1399,6 +1414,8 @@ int mdsyncfiletag(const FileTag *ftag, char *path) { SMgrRelation reln = smgropen(ftag->rlocator, InvalidBackendId); + instr_time io_start, + io_time; File file; bool need_to_close; int result, @@ -1425,10 +1442,22 @@ mdsyncfiletag(const FileTag *ftag, char *path) need_to_close = true; } + if (track_io_timing) + INSTR_TIME_SET_CURRENT(io_start); + else + INSTR_TIME_SET_ZERO(io_start); + /* Sync the file. */ result = FileSync(file, WAIT_EVENT_DATA_FILE_SYNC); save_errno = errno; + if (track_io_timing) + { + INSTR_TIME_SET_CURRENT(io_time); + INSTR_TIME_SUBTRACT(io_time, io_start); + pgstat_count_io_time(IOOBJECT_RELATION, IOCONTEXT_NORMAL, IOOP_FSYNC, io_time); + } + if (need_to_close) FileClose(file); diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c index af5d554610..4a151afed6 100644 --- a/src/backend/utils/activity/pgstat_io.c +++ b/src/backend/utils/activity/pgstat_io.c @@ -25,36 +25,48 @@ bool have_iostats = false; /* * Check that stats have not been counted for any combination of IOObject, - * IOContext, and IOOp which are not tracked for the passed-in BackendType. The - * passed-in PgStat_BktypeIO must contain stats from the BackendType specified - * by the second parameter. Caller is responsible for locking the passed-in - * PgStat_BktypeIO, if needed. + * IOContext, and IOOp which are not tracked for the passed-in BackendType. If + * the IOOp is not counted for this combination but IO time is otherwise + * tracked for this IOOp, check that IO time has not been counted for this + * combination. If stats are tracked for this combination and IO times are + * non-zero, counts should be non-zero. + * + * The passed-in PgStat_BktypeIO must contain stats from the BackendType + * specified by the second parameter. Caller is responsible for locking the + * passed-in PgStat_BktypeIO, if needed. */ bool pgstat_bktype_io_stats_valid(PgStat_BktypeIO *backend_io, BackendType bktype) { - bool bktype_tracked = pgstat_tracks_io_bktype(bktype); - for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++) { for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++) { - /* - * Don't bother trying to skip to the next loop iteration if - * pgstat_tracks_io_object() would return false here. We still - * need to validate that each counter is zero anyway. - */ for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++) { - /* No stats, so nothing to validate */ - if (backend_io->data[io_object][io_context][io_op] == 0) + /* we do track it */ + if (pgstat_tracks_io_op(bktype, io_object, io_context, io_op)) + { + /* ensure that if IO times are non-zero, counts are > 0 */ + if (!INSTR_TIME_IS_ZERO(backend_io->times[io_object][io_context][io_op]) && + backend_io->counts[io_object][io_context][io_op] <= 0) + return false; + continue; + } - /* There are stats and there shouldn't be */ - if (!bktype_tracked || - !pgstat_tracks_io_op(bktype, io_object, io_context, io_op)) + /* we don't track it, and it is not 0 */ + if (backend_io->counts[io_object][io_context][io_op] != 0) return false; + + /* we don't track this IOOp, so make sure its IO time is zero */ + if (pgstat_tracks_io_time(io_op) > -1) + { + if (!INSTR_TIME_IS_ZERO(backend_io->times[io_object][io_context][io_op])) + return false; + } + } } } @@ -70,7 +82,21 @@ pgstat_count_io_op(IOObject io_object, IOContext io_context, IOOp io_op) Assert((unsigned int) io_op < IOOP_NUM_TYPES); Assert(pgstat_tracks_io_op(MyBackendType, io_object, io_context, io_op)); - PendingIOStats.data[io_object][io_context][io_op]++; + PendingIOStats.counts[io_object][io_context][io_op]++; + + have_iostats = true; +} + +void +pgstat_count_io_time(IOObject io_object, IOContext io_context, IOOp io_op, instr_time time) +{ + Assert(io_object < IOOBJECT_NUM_TYPES); + Assert(io_context < IOCONTEXT_NUM_TYPES); + Assert(io_op < IOOP_NUM_TYPES); + Assert(pgstat_tracks_io_op(MyBackendType, io_object, io_context, io_op)); + Assert(pgstat_tracks_io_time(io_op) != -1); + + INSTR_TIME_ADD(PendingIOStats.times[io_object][io_context][io_op], time); have_iostats = true; } @@ -114,8 +140,13 @@ pgstat_flush_io(bool nowait) for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++) { for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++) - bktype_shstats->data[io_object][io_context][io_op] += - PendingIOStats.data[io_object][io_context][io_op]; + { + bktype_shstats->counts[io_object][io_context][io_op] += + PendingIOStats.counts[io_object][io_context][io_op]; + + INSTR_TIME_ADD(bktype_shstats->times[io_object][io_context][io_op], + PendingIOStats.times[io_object][io_context][io_op]); + } } } @@ -384,3 +415,30 @@ pgstat_tracks_io_op(BackendType bktype, IOObject io_object, return true; } + +/* + * PgStat_BktypeIO->times contains IO times for IOOps. For simplicity this + * array has a spot for every IOOp. pgstat_tracks_io_time() is the source of + * truth for which IOOps have corresponding IO times. + */ +IOOp +pgstat_tracks_io_time(IOOp io_op) +{ + switch (io_op) + { + case IOOP_READ: + return IOOP_READ; + case IOOP_WRITE: + return IOOP_WRITE; + case IOOP_EXTEND: + return IOOP_EXTEND; + case IOOP_FSYNC: + return IOOP_FSYNC; + case IOOP_EVICT: + case IOOP_REUSE: + return -1; + } + + elog(ERROR, "unrecognized IOOp value: %d", io_op); + pg_unreachable(); +} diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index b61a12382b..c75e661290 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -1255,12 +1255,16 @@ typedef enum io_stat_col IO_COL_IO_OBJECT, IO_COL_IO_CONTEXT, IO_COL_READS, + IO_COL_READ_TIME, IO_COL_WRITES, + IO_COL_WRITE_TIME, IO_COL_EXTENDS, + IO_COL_EXTEND_TIME, IO_COL_CONVERSION, IO_COL_EVICTIONS, IO_COL_REUSES, IO_COL_FSYNCS, + IO_COL_FSYNC_TIME, IO_COL_RESET_TIME, IO_NUM_COLUMNS, } io_stat_col; @@ -1292,6 +1296,21 @@ pgstat_get_io_op_index(IOOp io_op) pg_unreachable(); } +/* + * Get the number of the column containing IO times for the specified IOOp. If + * the specified IOOp is one for which IO time is not tracked, return -1. Note + * that this function assumes that IO time for an IOOp is displayed in the view + * in the column directly after the IOOp counts. + */ +static io_stat_col +pgstat_get_io_time_index(IOOp io_op) +{ + if (pgstat_tracks_io_time(io_op) == -1) + return -1; + + return pgstat_get_io_op_index(io_op) + 1; +} + Datum pg_stat_get_io(PG_FUNCTION_ARGS) { @@ -1359,20 +1378,31 @@ pg_stat_get_io(PG_FUNCTION_ARGS) for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++) { - int col_idx = pgstat_get_io_op_index(io_op); + int i = pgstat_get_io_op_index(io_op); /* * Some combinations of BackendType and IOOp, of IOContext * and IOOp, and of IOObject and IOOp are not tracked. Set * these cells in the view NULL. */ - nulls[col_idx] = !pgstat_tracks_io_op(bktype, io_obj, io_context, io_op); + if (pgstat_tracks_io_op(bktype, io_obj, io_context, io_op)) + values[i] = Int64GetDatum(bktype_stats->counts[io_obj][io_context][io_op]); + else + nulls[i] = true; + } + + for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++) + { + int i = pgstat_get_io_time_index(io_op); - if (nulls[col_idx]) + if (i == -1) continue; - values[col_idx] = - Int64GetDatum(bktype_stats->data[io_obj][io_context][io_op]); + if (!nulls[pgstat_get_io_op_index(io_op)]) + values[i] = + Float8GetDatum(INSTR_TIME_GET_MILLISEC(bktype_stats->times[io_obj][io_context][io_op])); + else + nulls[i] = true; } tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 505595620e..80c0627209 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5721,9 +5721,9 @@ proname => 'pg_stat_get_io', provolatile => 'v', prorows => '30', proretset => 't', proparallel => 'r', prorettype => 'record', proargtypes => '', - proallargtypes => '{text,text,text,int8,int8,int8,int8,int8,int8,int8,timestamptz}', - proargmodes => '{o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{backend_type,io_object,io_context,reads,writes,extends,op_bytes,evictions,reuses,fsyncs,stats_reset}', + proallargtypes => '{text,text,text,int8,float8,int8,float8,int8,float8,int8,int8,int8,int8,float8,timestamptz}', + proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{backend_type,io_object,io_context,reads,read_time,writes,write_time,extends,extend_time,op_bytes,evictions,reuses,fsyncs,fsync_time,stats_reset}', prosrc => 'pg_stat_get_io' }, { oid => '1136', descr => 'statistics: information about WAL activity', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index f43fac09ed..687aff4859 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -313,7 +313,8 @@ typedef enum IOOp typedef struct PgStat_BktypeIO { - PgStat_Counter data[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES]; + PgStat_Counter counts[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES]; + instr_time times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES]; } PgStat_BktypeIO; typedef struct PgStat_IO @@ -507,6 +508,7 @@ extern PgStat_CheckpointerStats *pgstat_fetch_stat_checkpointer(void); extern bool pgstat_bktype_io_stats_valid(PgStat_BktypeIO *context_ops, BackendType bktype); extern void pgstat_count_io_op(IOObject io_object, IOContext io_context, IOOp io_op); +extern void pgstat_count_io_time(IOObject io_object, IOContext io_context, IOOp io_op, instr_time time); extern PgStat_IO *pgstat_fetch_stat_io(void); extern const char *pgstat_get_io_context_name(IOContext io_context); extern const char *pgstat_get_io_object_name(IOObject io_object); @@ -516,6 +518,7 @@ extern bool pgstat_tracks_io_object(BackendType bktype, IOObject io_object, IOContext io_context); extern bool pgstat_tracks_io_op(BackendType bktype, IOObject io_object, IOContext io_context, IOOp io_op); +extern IOOp pgstat_tracks_io_time(IOOp io_op); /* diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index e953d1f515..5434851314 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1880,14 +1880,18 @@ pg_stat_io| SELECT backend_type, io_object, io_context, reads, + read_time, writes, + write_time, extends, + extend_time, op_bytes, evictions, reuses, fsyncs, + fsync_time, stats_reset - FROM pg_stat_get_io() b(backend_type, io_object, io_context, reads, writes, extends, op_bytes, evictions, reuses, fsyncs, stats_reset); + FROM pg_stat_get_io() b(backend_type, io_object, io_context, reads, read_time, writes, write_time, extends, extend_time, op_bytes, evictions, reuses, fsyncs, fsync_time, stats_reset); pg_stat_progress_analyze| SELECT s.pid, s.datid, d.datname, -- 2.37.2