diff --git a/Makefile b/Makefile index 3ab06ce..780ddfc 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,13 @@ SHAREDIR = ${PREFIX}/share MANDIR = ${SHAREDIR}/man/man1 SOURCE = ${PROGRAM_NAME}.go -COMMIT=$(shell git rev-parse HEAD) +COMMIT=$(shell git rev-parse --short HEAD) BRANCH=$(shell git rev-parse --abbrev-ref HEAD) +TAG=$(shell git describe --tags |cut -d- -f1) -LDFLAGS = -ldflags "-X main.COMMIT=${COMMIT} -X main.BRANCH=${BRANCH}" +LDFLAGS = -ldflags "-X github.com/lesovsky/pgcenter/cmd.GitTag=${TAG} \ +-X github.com/lesovsky/pgcenter/cmd.GitCommit=${COMMIT} \ +-X github.com/lesovsky/pgcenter/cmd.GitBranch=${BRANCH}" DESTDIR ?= diff --git a/README.md b/README.md index 1e674a2..c421a95 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ pgCenter is a command-line admin tool for observing and troubleshooting Postgres Postgres provides various activity statistics that include detailed information about its behaviour: connections, statements, database operations, replication, resources usage and more. General purpose of the statistics is to help DBAs to monitor and troubleshoot Postgres. However, these statistics provided in textual form retrieved from SQL functions and views, and Postgres doesn't provide any tools for working with them. pgCenter's main goal is to help Postgres DBA manage statistics that theу have in their databases and see all the necessary data in convenient format based on builtin stats views and functions. +![](doc/images/pgcenter-demo.gif) + #### Key features - Top-like interface that allows you to monitor stats changes as you go. See details [here](doc/pgcenter-top-readme.md). - Configuration management function allows viewing and editing of current configuration files and reloading the service, if needed. @@ -45,7 +47,9 @@ pgCenter supports majority of statistics views available in Postgres, and at the - [pg_stat_user_functions](https://www.postgresql.org/docs/current/static/monitoring-stats.html#PG-STAT-USER-FUNCTIONS-VIEW) - statistics on execution of functions. - [pg_stat_statements](https://www.postgresql.org/docs/current/static/pgstatstatements.html) - statistics on SQL statements executed including time and resources usage. - statistics on tables sizes based on `pg_relation_size()` and `pg_total_relation_size()` functions; -- [pg_stat_progress_vacuum](https://www.postgresql.org/docs/current/static/progress-reporting.html#VACUUM-PROGRESS-REPORTING) - information about (auto)vacuums status. +- [pg_stat_progress_vacuum](https://www.postgresql.org/docs/current/progress-reporting.html#VACUUM-PROGRESS-REPORTING) - information about progress of (auto)vacuums status. +- [pg_stat_progress_cluster](https://www.postgresql.org/docs/current/progress-reporting.html#CLUSTER-PROGRESS-REPORTING) - information about progress of CLUSTER and VACUUM FULL operations. +- [pg_stat_progress_create_index](https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING) - information about progress of CREATE INDEX and REINDEX operations. ##### System statistics `pgcenter top` also provides system usage information based on statistics from `procfs` filesystem: diff --git a/cmd/help.go b/cmd/help.go index 4cc5c54..6b9642c 100644 --- a/cmd/help.go +++ b/cmd/help.go @@ -137,7 +137,8 @@ Report options: -R, --replication show pg_stat_replication statistics -T, --tables show pg_stat_user_tables statistics -I, --indexes show pg_stat_user_indexes and pg_statio_user_indexes statistics - -V, --vacuum show pg_stat_progress_vacuum statistics + -P, --progress [X] show pg_stat_progress_* statistics, use additional selector to choose stats. + 'v' - vacuum; 'c' - cluster; 'i' - create index. -X, --statements [X] show pg_stat_statements statistics, use additional selector to choose stats. 'm' - timings; 'g' - general; 'i' - io; 't' - temp files io; 'l' - local files io. diff --git a/cmd/report/report.go b/cmd/report/report.go index 84b30d5..2841468 100644 --- a/cmd/report/report.go +++ b/cmd/report/report.go @@ -43,7 +43,7 @@ var ( showReplication bool // Show stats from pg_stat_replication showTables bool // Show stats from pg_stat_user_tables, pg_statio_user_tables showIndexes bool // Show stats from pg_stat_user_indexes, pg_statio_user_indexes - showVacuum bool // Show stats from pg_stat_progress_vacuum + showProgress string // Show stats from pg_stat_progress_* stats showStatements string // Show stats from pg_stat_statements showSizes bool // Show tables sizes describe bool // Show description of requested stats view @@ -60,7 +60,7 @@ var ( "replication": {view: stat.ReplicationView, ctx: stat.PgStatReplicationUnit}, "tables": {view: stat.TablesView, ctx: stat.PgStatTablesUnit}, "indexes": {view: stat.IndexesView, ctx: stat.PgStatIndexesUnit}, - "vacuum": {view: stat.VacuumView, ctx: stat.PgStatVacuumUnit}, + "progress": {view: "_PROGRESS_"}, "statements": {view: "_STATEMENTS_"}, } // statementsReports is the statements reports available for user's choice @@ -74,6 +74,14 @@ var ( "t": {view: stat.StatementsTempView, ctx: stat.PgSSTempUnit}, "l": {view: stat.StatementsLocalView, ctx: stat.PgSSLocalUnit}, } + progressReports = map[string]struct { + view string + ctx stat.ContextUnit + }{ + "v": {view: stat.ProgressVacuumView, ctx: stat.PgStatProgressVacuumUnit}, + "c": {view: stat.ProgressClusterView, ctx: stat.PgStatProgressClusterUnit}, + "i": {view: stat.ProgressCreateIndexView, ctx: stat.PgStatProgressCreateIndexUnit}, + } ) func init() { @@ -85,7 +93,7 @@ func init() { CommandDefinition.Flags().BoolVarP(&showReplication, "replication", "R", false, "show pg_stat_replication stats") CommandDefinition.Flags().BoolVarP(&showTables, "tables", "T", false, "show pg_stat_user_tables and pg_statio_user_tables stats") CommandDefinition.Flags().BoolVarP(&showIndexes, "indexes", "I", false, "show pg_stat_user_indexes and pg_statio_user_indexes stats") - CommandDefinition.Flags().BoolVarP(&showVacuum, "vacuum", "V", false, "show pg_stat_progress_vacuum stats") + CommandDefinition.Flags().StringVarP(&showProgress, "progress", "P", "", "show pg_stat_progress_* stats") CommandDefinition.Flags().StringVarP(&showStatements, "statements", "X", "", "show pg_stat_statements stats") CommandDefinition.Flags().StringVarP(&tsStart, "start", "s", "", "starting time of the report") CommandDefinition.Flags().StringVarP(&tsEnd, "end", "e", "", "ending time of the report") @@ -126,12 +134,19 @@ func preFlightSetup(c *cobra.Command, _ []string) { // selectReport selects appropriate type of the report depending on user's choice func selectReport(f *pflag.Flag) { if b, ok := basicReports[f.Name]; ok { - if b.view == "_STATEMENTS_" { + switch b.view { + case "_STATEMENTS_": if s, ok := statementsReports[f.Value.String()]; ok { opts.ReportType = s.view opts.Context = s.ctx return } + case "_PROGRESS_": + if s, ok := progressReports[f.Value.String()]; ok { + opts.ReportType = s.view + opts.Context = s.ctx + return + } } opts.ReportType = b.view opts.Context = b.ctx @@ -216,19 +231,21 @@ func parseFilterString() { // doDescribe shows detailed description of the requested stats func doDescribe() { var m = map[string]string{ - stat.DatabaseView: stat.PgStatDatabaseDescription, - stat.ActivityView: stat.PgStatActivityDescription, - stat.ReplicationView: stat.PgStatReplicationDescription, - stat.TablesView: stat.PgStatTablesDescription, - stat.IndexesView: stat.PgStatIndexesDescription, - stat.FunctionsView: stat.PgStatFunctionsDescription, - stat.SizesView: stat.PgStatSizesDescription, - stat.VacuumView: stat.PgStatVacuumDescription, - stat.StatementsTimingView: stat.PgStatStatementsTimingDescription, - stat.StatementsGeneralView: stat.PgStatStatementsGeneralDescription, - stat.StatementsIOView: stat.PgStatStatementsIODescription, - stat.StatementsTempView: stat.PgStatStatementsTempDescription, - stat.StatementsLocalView: stat.PgStatStatementsLocalDescription, + stat.DatabaseView: stat.PgStatDatabaseDescription, + stat.ActivityView: stat.PgStatActivityDescription, + stat.ReplicationView: stat.PgStatReplicationDescription, + stat.TablesView: stat.PgStatTablesDescription, + stat.IndexesView: stat.PgStatIndexesDescription, + stat.FunctionsView: stat.PgStatFunctionsDescription, + stat.SizesView: stat.PgStatSizesDescription, + stat.ProgressVacuumView: stat.PgStatProgressVacuumDescription, + stat.ProgressClusterView: stat.PgStatProgressClusterDescription, + stat.ProgressCreateIndexView: stat.PgStatProgressCreateIndexDescription, + stat.StatementsTimingView: stat.PgStatStatementsTimingDescription, + stat.StatementsGeneralView: stat.PgStatStatementsGeneralDescription, + stat.StatementsIOView: stat.PgStatStatementsIODescription, + stat.StatementsTempView: stat.PgStatStatementsTempDescription, + stat.StatementsLocalView: stat.PgStatStatementsLocalDescription, } if description, ok := m[opts.ReportType]; ok { diff --git a/cmd/version.go b/cmd/version.go index 275c591..595c5f2 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -9,15 +9,15 @@ import ( const ( // ProgramName is the name of this program ProgramName = "pgcenter" - // ProgramVersion is the version of this program - ProgramVersion = "0.6" - // ProgramRelease is release number of this program - ProgramRelease = "2" // ProgramIssuesUrl is the public URL for posting issues, bug reports and asking questions ProgramIssuesUrl = "https://github.com/lesovsky/pgcenter/issues" ) +var ( + GitTag, GitCommit, GitBranch string +) + // PrintVersion prints the name and version of this program func PrintVersion() string { - return fmt.Sprintf("%s %s.%s\n", ProgramName, ProgramVersion, ProgramRelease) + return fmt.Sprintf("%s %s %s-%s\n", ProgramName, GitTag, GitCommit, GitBranch) } diff --git a/doc/Changelog b/doc/Changelog index 5afcf3c..b0ce2eb 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,20 +1,30 @@ +pgcenter (0.6.2) unstable; urgency=low + * report: print header at the beginning of the output + * top/report: refactoring aligning + * adjust header's styles of iostat/nicstat tabs in top utility + * added support for pg_stat_database.checksum_failures (pg-12) + * added support of pg_stat_progress_cluster and pg_stat_progress_create_index views + * inject program version info from git at build time + * fix #66, pgcenter crashes when no running postgres + * avoid rude disconnection from postgres at top-program exit + pgcenter (0.6.1) unstable; urgency=low * improved aligning and query truncation - record: records complete length of pg_stat_statements.query (be careful, it might produce high-volume stats file) - - record: add startup paramter for configuring truncation - - report: can use '--truncate 0', for disabling trucation only for pg_stat_statements.query (might produce an "eyes-bleeding" output) + - record: add startup parameter for configuring truncation + - report: can use '--truncate 0', for disabling truncation only for pg_stat_statements.query (might produce an "eyes-bleeding" output) * reworked pg_stat_statements queries: - remove unnecessary GROUP BY, ORDER BY and aggregations - simplify usage of regexp_replace() - use pg_get_userbyid() instead of joining pg_roles view - use configurable limit for truncating length of queries - * top: read remote stats only if pgcenter schma is available + * top: read remote stats only if pgcenter schema is available * top: do polling pg_stat_statements only if it's available (issue #59) * top: fixed handling of libpq errors during connection establishment (issue #58) - * top: fix srong assebling of path to logfile (issue #55) + * top: fix strong assembling of path to logfile (issue #55) pgcenter (0.6.0) unstable; urgency=low - * added goreleser support + * added goreleaser support * top: fix wrong handling of group cancel/terminate mask * implemented GoReport recommendations * profile: emit error if '-P' is not specified diff --git a/doc/images/pgcenter-demo.gif b/doc/images/pgcenter-demo.gif new file mode 100644 index 0000000..c28211b Binary files /dev/null and b/doc/images/pgcenter-demo.gif differ diff --git a/doc/pgcenter-config-readme.md b/doc/pgcenter-config-readme.md index 2e10e09..0d12b75 100644 --- a/doc/pgcenter-config-readme.md +++ b/doc/pgcenter-config-readme.md @@ -19,26 +19,66 @@ Installing and removing functions is possible with `pgcenter config`, however, w - `plperlu` (which means *untrusted plperl*) procedural language must be installed manually in the database you want to connect pgCenter to (see details [here](https://www.postgresql.org/docs/current/static/plperl.html)). - perl module `Linux::Ethtool::Settings` should be installed in the system, it's used to get speed and duplex of network interfaces and properly calculate some metrics. -Of course, `pgcenter top` can also work with remote Postgres which don't have these SQL functions installed. In this case zeroes will be shown in the system stats interface (load average, cpu, memory, swap, io, network) and multiple errors will  appear in Postgres log. For easier distribution, SQL functions and views used by pgCenter are hard-coded into the source code, but their usage is not limited, so feel free to use it. - -Another limitation is related to `procfs` filesystem, which is Linux-specific file system, hence there might be problematic to run pgCenter on operation systems other than Linux. But you can still run pgCenter in Docker. - #### Main functions - installing and removing SQL functions and views in desired database. #### Usage -In general, a prefered way is installing dependencies using distro’s default package manager, but it might happen that `Linux::Ethtool::Settings` will not be available in the official package repo. In this case, you can install perl module using CPAN, but extra dependencies would have to be resolved, such as `make` and `gcc`. Below is an example for Ubuntu Linux. +Run `config` command and install stats schema into a database: ``` -apt install gcc make perl -cpan Linux::Ethtool::Settings +pgcenter config --install -h 1.2.3.4 -U postgres db_production ``` -Perhaps it’s possible to use the same approach in other distros, because of perl module name is the same. +If `Linux::Ethtool::Settings` module is not installed in the system, `pgcenter config -i` will fail with the following error: -Run `config` command and install stats schema into a database: ``` -pgcenter config --install -h 1.2.3.4 -U postgres db_production +# pgcenter config -i +ERROR: Can't locate Linux/Ethtool/Settings.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at line 2. +BEGIN failed--compilation aborted at line 2. +DETAIL: +HINT: +STATEMENT: CREATE FUNCTION pgcenter.get_netdev_link_settings(INOUT iface CHARACTER VARYING, OUT speed BIGINT, OUT duplex INTEGER) RETURNS RECORD + LANGUAGE plperlu + AS $$ + use Linux::Ethtool::Settings; + if (my $settings = Linux::Ethtool::Settings->new($_[0])) { + my $if_speed = $settings->speed(); + my $if_duplex = $settings->duplex() ? 1 : 0; + return {iface => $_[0], speed => $if_speed, duplex => $if_duplex}; + } else { + return {iface => $_[0], speed => 0, duplex => -1}; + } + $$; ``` -See other usage examples [here](examples.md). \ No newline at end of file +As you can see the problem is related to `pgcenter.get_netdev_link_settings()` function which depends on `Linux::Ethtool::Settings` module. To fix the issue you need to install the module into your system. + +In general, a preferred way is installing dependencies using distro's default package manager, but it might happen that `Linux::Ethtool::Settings` will not be available in the official package repo. In this case, you can install perl module using CPAN, but extra dependencies would have to be resolved, such as `make`,`gcc` and others. + +Here is a complete example reproduced in Docker environment using using [official docker image for postgresql](https://hub.docker.com/r/centos/postgresql). + +``` +# yum install -y postgresql-plperl +# yum install -y gcc make perl cpan +# cpan Module::Build +# cpan Linux::Ethtool::Settings +# psql -U postgres -c 'CREATE LANGUAGE plperlu' +# pgcenter config -i -U postgres +# psql -U postgres -c "select * from pgcenter.get_netdev_link_settings('eth0')" + iface | speed | duplex +-------+-------+-------- + eth0 | 10000 | 1 +(1 row) +``` +As you can see, finally function `pgcenter.get_netdev_link_settings()` works well. + +Perhaps it’s possible to use the same approach in other distros, because of perl module name is the same, but names of other packages may vary (eg. `postgresql-10-plperl` instead of `postgresql-plperl`). + +#### Other notes + +Of course, `pgcenter top` can also work with remote Postgres which don't have these SQL functions installed. In this case zeroes will be shown in the system stats interface (load average, cpu, memory, swap, io, network) and multiple errors will  appear in Postgres log. For easier distribution, SQL functions and views used by pgCenter are hard-coded into the source code, but their usage is not limited, so feel free to use it. + +Another limitation is related to `procfs` filesystem, which is Linux-specific file system, hence there might be problematic to run pgCenter on operation systems other than Linux. But you can still run pgCenter in Docker. + + +See other usage examples [here](examples.md). diff --git a/lib/stat/context.go b/lib/stat/context.go index f9993d8..5a9659e 100644 --- a/lib/stat/context.go +++ b/lib/stat/context.go @@ -35,8 +35,8 @@ var ( PgStatDatabaseUnit = ContextUnit{ Name: DatabaseView, Query: PgStatDatabaseQueryDefault, - DiffIntvl: [2]int{1, 15}, - Ncols: 17, + DiffIntvl: [2]int{1, 16}, + Ncols: 18, OrderKey: 0, OrderDesc: true, ColsWidth: map[int]int{}, @@ -104,16 +104,40 @@ var ( Filters: map[int]*regexp.Regexp{}, } // PgStatVacuumUnit describes how to handle pg_stat_progress_vacuum view - PgStatVacuumUnit = ContextUnit{ - Name: VacuumView, - Query: PgStatVacuumQueryDefault, + PgStatProgressVacuumUnit = ContextUnit{ + Name: ProgressVacuumView, + Query: PgStatProgressVacuumQueryDefault, //DiffIntvl: NoDiff, - DiffIntvl: [2]int{9, 10}, + DiffIntvl: [2]int{10, 11}, + Ncols: 13, + OrderKey: 0, + OrderDesc: true, + ColsWidth: map[int]int{}, + Msg: "Show vacuum progress statistics", + Filters: map[int]*regexp.Regexp{}, + } + // PgStatProgressClusterUnit describes how to handle pg_stat_progress_cluster view + PgStatProgressClusterUnit = ContextUnit{ + Name: ProgressClusterView, + Query: PgStatProgressClusterQueryDefault, + //DiffIntvl: NoDiff, + DiffIntvl: [2]int{10, 11}, + Ncols: 13, + OrderKey: 0, + OrderDesc: true, + ColsWidth: map[int]int{}, + Msg: "Show cluster/vacuum full progress statistics", + Filters: map[int]*regexp.Regexp{}, + } + PgStatProgressCreateIndexUnit = ContextUnit{ + Name: ProgressCreateIndexView, + Query: PgStatProgressCreateIndexQueryDefault, + DiffIntvl: NoDiff, Ncols: 14, OrderKey: 0, OrderDesc: true, ColsWidth: map[int]int{}, - Msg: "Show vacuum statistics", + Msg: "Show create index/reindex progress statistics", Filters: map[int]*regexp.Regexp{}, } // PgStatActivityUnit describes how to handle pg_stat_activity view @@ -232,6 +256,14 @@ func (cl ContextList) AdjustQueries(pi PgInfo) { // use defaults assigned in context unit } } + case DatabaseView: + switch { + // versions prior 12 don't have checksum_failures column + case pi.PgVersionNum < 120000: + cl[c].Query = PgStatDatabaseQuery11 + cl[c].Ncols = 17 + cl[c].DiffIntvl = [2]int{1, 15} + } } } } diff --git a/lib/stat/help.go b/lib/stat/help.go index 10d5534..50e84df 100644 --- a/lib/stat/help.go +++ b/lib/stat/help.go @@ -164,8 +164,8 @@ Details: https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS- Details: https://www.postgresql.org/docs/current/monitoring-stats.html#PG-STAT-ACTIVITY-VIEW` - // PgStatVacuumDescription is the detailed description of pg_stat_progress_vacuum view - PgStatVacuumDescription = `Statistics about progress of vacuums based on pg_stat_progress_vacuum view: + // PgStatProgressVacuumDescription is the detailed description of pg_stat_progress_vacuum view + PgStatProgressVacuumDescription = `Statistics about progress of vacuums based on pg_stat_progress_vacuum view: column origin description - pid pid Process ID of this worker @@ -173,20 +173,64 @@ Details: https://www.postgresql.org/docs/current/monitoring-stats.html#PG-STAT-A - datname datname Name of the database this worker is connected to - relation relid Name of the relation which is vacuumed by this worker - state state Current overall state of this worker +- waiting* wait_event_type,wait_event Wait event name and type for which the worker is waiting, if any - phase phase Current processing phase of vacuum -- total* heap_blks_total Total size of the table, in kB -- t_scanned* heap_blks_scanned Total amount of data scanned, in kB -- t_vacuumed* heap_blks_vacuumed Total amount of data vacuumed, in kB -- scanned heap_blks_scanned Amount of data scanned per second, in kB -- vacuumed heap_blks_vacuumed Amount of data vacuumed per second, in kB -- wait_etype wait_event_type The type of event for which the worker is waiting, if any -- wait_event wait_event Wait event name if worker is currently waiting +- t_size* heap_blks_total Total size of the table, in kB +- t_scanned_%* heap_blks_scanned The percent of data scanned, in kB +- t_vacuumed_%* heap_blks_vacuumed The percent of data vacuumed, in kB +- scanned heap_blks_scanned Amount of data scanned per interval, in kB +- vacuumed heap_blks_vacuumed Amount of data vacuumed per interval, in kB - query query Text of this workers's "query" * - extended value, based on origin and calculated using additional functions. Details: https://www.postgresql.org/docs/current/progress-reporting.html#VACUUM-PROGRESS-REPORTING` + // PgStatProgressClusterDescription is the detailed description of pg_stat_progress_cluster view + PgStatProgressClusterDescription = `Statistics about progress of cluster and vacuum full operations based on pg_stat_progress_cluster view: + + column origin description +- pid pid Process ID of this worker +- xact_age* xact_start Current transaction's duration if active +- datname datname Name of the database this worker is connected to +- relation relid Name of the relation which is processed by this worker +- index cluster_index_relid Name of the relation which is processed by this worker +- state state Current overall state of this worker +- waiting* wait_event_type,wait_event Wait event name and type for which the worker is waiting, if any +- phase phase Current processing phase of operation +- t_size* heap_blks_total Total size of the table, in kB +- t_scanned_%* heap_blks_scanned The percent of data scanned, in kB +- tup_scanned heap_tuples_scanned Number of heap tuples scanned +- tup_written heap_tuples_written Number of heap tuples written +- query query Text of this workers's "query" + +* - extended value, based on origin and calculated using additional functions. + +Details: https://www.postgresql.org/docs/current/progress-reporting.html#CLUSTER-PROGRESS-REPORTING` + + // PgStatProgressCreateIndexDescription is the detailed description of pg_stat_progress_cluster view + PgStatProgressCreateIndexDescription = `Statistics about progress of create index/reindex operations based on pg_stat_progress_create_index view: + + column origin description +- pid pid Process ID of this worker +- xact_age* xact_start Current transaction's duration if active +- datname datname Name of the database this worker is connected to +- relation relid Name of the relation which is processed by this worker +- index cluster_index_relid Name of the relation which is processed by this worker +- state state Current overall state of this worker +- waiting* wait_event_type,wait_event Wait event name and type for which the worker is waiting, if any +- phase phase Current processing phase of operation +- locker_pid current_locker_pid Process ID of the locker currently being waited for +- lockers* lockers_total,lockers_done Total number of lockers to wait for, and number of lockers already waited for. +- size_total/done_%* blocks_total,blocks_done Total size to be processed and percent of already processed in the current phase, in kB +- tup_total/done_%* tuples_total,tuples_done Total number of tuples to be processed and percent of already processed in the current phase +- parts_total/done_%* partitions_total,partitions_done Total number of partitions on which the index is to be created, and the number of partitions on which the index has been completed +- query query Text of this workers's "query" + +* - extended value, based on origin and calculated using additional functions. + +Details: https://www.postgresql.org/docs/current/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING` + // PgStatStatementsTimingDescription is the detailed description of pg_stat_statements section about timing stats PgStatStatementsTimingDescription = `Statements timing statistics based on pg_stat_statements: diff --git a/lib/stat/pgstat.go b/lib/stat/pgstat.go index 86b3a50..bc9c799 100644 --- a/lib/stat/pgstat.go +++ b/lib/stat/pgstat.go @@ -26,8 +26,14 @@ const ( SizesView = "pg_stat_sizes" // FunctionsView is the name of view with functions stats FunctionsView = "pg_stat_user_functions" - // VacuumView is the name of view with (auto)vacuum stats - VacuumView = "pg_stat_progress_vacuum" + // ProgressView is the name of pseudo-view with progress stats + ProgressView = "pg_stat_progress" + // ProgressVacuumView is the name of pg_stat_progress_vacuum view + ProgressVacuumView = "pg_stat_progress_vacuum" + // ProgressClusterView is the name of pg_stat_progress_cluster view + ProgressClusterView = "pg_stat_progress_cluster" + // ProgressCreateIndexView is the name of pg_stat_progress_create_index view + ProgressCreateIndexView = "pg_stat_progress_create_index" // ActivityView is the name of view with activity stats ActivityView = "pg_stat_activity" // StatementsView is the name of view with statements stats @@ -426,19 +432,22 @@ func (r *PGresult) Sort(key int, desc bool) { } // SetAlign method aligns length of values depending of the columns width -func (r *PGresult) SetAlign(widthes map[int]int, truncLimit int, dynamic bool) { +func (r *PGresult) SetAlign(widthes map[int]int, truncLimit int, dynamic bool) (err error) { var lastColTruncLimit, lastColMaxWidth int lastColTruncLimit = truncLimit truncLimit = utils.Max(truncLimit, colsTruncMinLimit) - /* calculate max length of columns based on the longest value of the column */ - var valuelen, colnamelen int - for colidx, colname := range r.Cols { // walk per-column - if len(r.Result) == 0 { - // no rows in result, set width using length of a column name + // no rows in result, set width using length of a column name and return with error (because not aligned using result's values) + if len(r.Result) == 0 { + for colidx, colname := range r.Cols { // walk per-column widthes[colidx] = len(colname) } + return fmt.Errorf("RESULT_NO_ROWS") + } + /* calculate max length of columns based on the longest value of the column */ + var valuelen, colnamelen int + for colidx, colname := range r.Cols { // walk per-column for rownum := 0; rownum < len(r.Result); rownum++ { // walk through rows valuelen = len(r.Result[rownum][colidx].String) colnamelen = utils.Max(len(colname), colsWidthMin) @@ -457,7 +466,11 @@ func (r *PGresult) SetAlign(widthes map[int]int, truncLimit int, dynamic bool) { if dynamic { widthes[colidx] = valuelen } else { - widthes[colidx] = colnamelen + if valuelen > colnamelen*2 { + widthes[colidx] = utils.Min(valuelen, 32) + } else { + widthes[colidx] = colnamelen + } } // for last column set width using truncation limit case colidx == r.Ncols-1: @@ -482,6 +495,7 @@ func (r *PGresult) SetAlign(widthes map[int]int, truncLimit int, dynamic bool) { } } } + return nil } // aligningIsValueEmpty is the aligning helper: return true if value is empty, e.g. NULL - set width based on colname length @@ -494,7 +508,7 @@ func aligningIsLessThanColname(vlen, cnlen, width int) bool { return vlen > 0 && vlen <= cnlen && vlen >= width } -// aligningIsMoreThanColname isthe aligning helper: returns true if passed non-empty values, but for if its length longer than length of colnames +// aligningIsMoreThanColname is the aligning helper: returns true if passed non-empty values, but for if its length longer than length of colnames func aligningIsMoreThanColname(vlen, cnlen, width, trunclim, colidx, cols int) bool { return vlen > 0 && vlen > cnlen && vlen < trunclim && vlen >= width && colidx < cols-1 } diff --git a/lib/stat/queries.go b/lib/stat/queries.go index cf38dd5..05e6f98 100644 --- a/lib/stat/queries.go +++ b/lib/stat/queries.go @@ -126,7 +126,7 @@ FROM pg_prepared_xacts)` PgStatementsQuery = `SELECT (sum(total_time) / sum(calls))::numeric(20,2) AS avg_query, sum(calls) AS total_calls FROM pg_stat_statements` // PgStatDatabaseQueryDefault is the default query for getting databases' stats from pg_stat_database view - // { Name: "pg_stat_database", Query: common.PgStatDatabaseQueryDefault, DiffIntvl: [2]int{1,15}, Ncols: 17, OrderKey: 0, OrderDesc: true } + // { Name: "pg_stat_database", Query: common.PgStatDatabaseQueryDefault, DiffIntvl: [2]int{1,16}, Ncols: 18, OrderKey: 0, OrderDesc: true } PgStatDatabaseQueryDefault = `SELECT datname, coalesce(xact_commit, 0) AS commits, @@ -140,6 +140,30 @@ coalesce(tup_updated, 0) AS updates, coalesce(tup_deleted, 0) AS deletes, coalesce(conflicts, 0) AS conflicts, coalesce(deadlocks, 0) AS deadlocks, +coalesce(checksum_failures, 0) AS csum_fails, +coalesce(temp_files, 0) AS temp_files, +coalesce(temp_bytes, 0) AS temp_bytes, +coalesce(blk_read_time, 0)::numeric(20,2) AS read_t, +coalesce(blk_write_time, 0)::numeric(20,2) AS write_t, +date_trunc('seconds', now() - stats_reset)::text AS stats_age +FROM pg_stat_database +ORDER BY datname DESC` + + // PgStatDatabaseQuery11 is the query for getting databases' stats from pg_stat_database view for versions prior 12 + // { Name: "pg_stat_database", Query: common.PgStatDatabaseQuery11, DiffIntvl: [2]int{1,15}, Ncols: 17, OrderKey: 0, OrderDesc: true } + PgStatDatabaseQuery11 = `SELECT +datname, +coalesce(xact_commit, 0) AS commits, +coalesce(xact_rollback, 0) AS rollbacks, +coalesce(blks_read * (SELECT current_setting('block_size')::int / 1024), 0) AS reads, +coalesce(blks_hit, 0) AS hits, +coalesce(tup_returned, 0) AS returned, +coalesce(tup_fetched, 0) AS fetched, +coalesce(tup_inserted, 0) AS inserts, +coalesce(tup_updated, 0) AS updates, +coalesce(tup_deleted, 0) AS deletes, +coalesce(conflicts, 0) AS conflicts, +coalesce(deadlocks, 0) AS deadlocks, coalesce(temp_files, 0) AS temp_files, coalesce(temp_bytes, 0) AS temp_bytes, coalesce(blk_read_time, 0)::numeric(20,2) AS read_t, @@ -299,26 +323,68 @@ round((self_time / greatest(calls, 1))::numeric(20,2), 4) AS avg_self_t FROM pg_stat_user_functions ORDER BY funcid DESC` - // PgStatVacuumQueryDefault is the default query for getting stats from pg_stat_progress_vacuum view - // { Name: "pg_stat_vacuum", Query: common.PgStatVacuumQueryDefault, DiffIntvl: [2]int{9,10}, Ncols: 14, OrderKey: 0, OrderDesc: true } - PgStatVacuumQueryDefault = `SELECT + // PgStatProgressVacuumQueryDefault is the default query for getting stats from pg_stat_progress_vacuum view + // { Name: "pg_stat_vacuum", Query: common.PgStatVacuumQueryDefault, DiffIntvl: [2]int{10,11}, Ncols: 13, OrderKey: 0, OrderDesc: true } + PgStatProgressVacuumQueryDefault = `SELECT a.pid, date_trunc('seconds', clock_timestamp() - xact_start)::text AS xact_age, v.datname, v.relid::regclass AS relation, a.state, +coalesce((a.wait_event_type ||'.'|| a.wait_event), 'f') AS waiting, v.phase, -v.heap_blks_total * (SELECT current_setting('block_size')::int / 1024) AS total, -v.heap_blks_scanned * (SELECT current_setting('block_size')::int / 1024) AS t_scanned, -v.heap_blks_vacuumed * (SELECT current_setting('block_size')::int / 1024) AS t_vacuumed, +v.heap_blks_total * (SELECT current_setting('block_size')::int / 1024) AS t_size, +round(100 * v.heap_blks_scanned / v.heap_blks_total, 2) AS "t_scanned_%", +round(100 * v.heap_blks_vacuumed / v.heap_blks_total, 2) AS "t_vacuumed_%", coalesce(v.heap_blks_scanned * (SELECT current_setting('block_size')::int / 1024), 0) AS scanned, coalesce(v.heap_blks_vacuumed * (SELECT current_setting('block_size')::int / 1024), 0) AS vacuumed, -a.wait_event_type AS wait_etype, -a.wait_event, a.query FROM pg_stat_progress_vacuum v RIGHT JOIN pg_stat_activity a ON v.pid = a.pid WHERE (a.query ~* '^autovacuum:' OR a.query ~* '^vacuum') AND a.pid <> pg_backend_pid() +ORDER BY a.pid DESC` + + // PgStatProgressClusterQueryDefault is the default query for getting stats from pg_stat_progress_cluster view + // { Name: "pg_stat_progress_cluster", Query: common.PgStatProgressClusterQueryDefault, DiffIntvl: [2]int{10,11}, Ncols: 13, OrderKey: 0, OrderDesc: true } + PgStatProgressClusterQueryDefault = `SELECT +a.pid, +date_trunc('seconds', clock_timestamp() - xact_start)::text AS xact_age, +p.datname, +p.relid::regclass AS relation, +p.cluster_index_relid::regclass AS index, +a.state, +coalesce((a.wait_event_type ||'.'|| a.wait_event), 'f') AS waiting, +p.phase, +p.heap_blks_total * (SELECT current_setting('block_size')::int / 1024) AS t_size, +round(100 * p.heap_blks_scanned / greatest(p.heap_blks_total,1), 2) AS "scanned_%", +coalesce(p.heap_tuples_scanned, 0) AS tup_scanned, +coalesce(p.heap_tuples_written, 0) AS tup_written, +a.query +FROM pg_stat_progress_cluster p +INNER JOIN pg_stat_activity a ON p.pid = a.pid +WHERE a.pid <> pg_backend_pid() +ORDER BY a.pid DESC` + + // PgStatProgressCreateIndexQueryDefault is the default query for getting stats from pg_stat_progress_cluster view + // { Name: "pg_stat_progress_create_index", Query: common.PgStatProgressCreateIndexQueryDefault, DiffIntvl: [2]int{99,99}, Ncols: 14, OrderKey: 0, OrderDesc: true } + PgStatProgressCreateIndexQueryDefault = `SELECT +a.pid, +date_trunc('seconds', clock_timestamp() - xact_start)::text AS xact_age, +p.datname, +p.relid::regclass AS relation, +p.index_relid::regclass AS index, +a.state, +coalesce((a.wait_event_type ||'.'|| a.wait_event), 'f') AS waiting, +p.phase, +current_locker_pid AS locker_pid, +lockers_total ||'/'|| lockers_done AS lockers, +p.blocks_total * (SELECT current_setting('block_size')::int / 1024) ||'/'|| round(100 * p.blocks_done / greatest(p.blocks_total, 1), 2) AS "size_total/done_%", +p.tuples_total ||'/'|| round(100 * p.tuples_done / greatest(p.tuples_total, 1), 2) AS "tup_total/done_%", +p.partitions_total ||'/'|| round(100 * p.partitions_done / greatest(p.partitions_total, 1), 2) AS "parts_total/done_%", +a.query +FROM pg_stat_progress_create_index p +INNER JOIN pg_stat_activity a ON p.pid = a.pid +WHERE a.pid <> pg_backend_pid() ORDER BY a.pid DESC` // PgStatActivityQueryDefault is the default query for getting stats from pg_stat_activity view diff --git a/lib/utils/postgres.go b/lib/utils/postgres.go index 0b95b51..475a477 100644 --- a/lib/utils/postgres.go +++ b/lib/utils/postgres.go @@ -80,22 +80,32 @@ func PQconnectdb(c *Conninfo, connstr string) (conn *sql.DB, err error) { for { conn, err = sql.Open(dbDriver, connstr) if err = PQstatus(conn); err != nil { - switch { - case err == pq.ErrSSLNotSupported: - // By default pq-driver tries to connect with SSL. So if SSL is not enabled on the other side - fix our connection string and try to reconnect - connstr = connstr + " sslmode=disable" - case err.(*pq.Error).Code == errCodeInvalidPassword: - fmt.Printf("Password for user %s: ", c.User) - bytePassword, err := terminal.ReadPassword(0) - if err != nil { - return nil, err + // Use type switch to understand kind of the error. Firstly handle libpq-related errors, and next all other. + switch v := err.(type) { + case *pq.Error: + if v.Code == errCodeInvalidPassword { + fmt.Printf("Password for user %s: ", c.User) + bytePassword, err := terminal.ReadPassword(0) + if err != nil { + return nil, err + } + connstr = fmt.Sprintf("%s password=%s ", connstr, string(bytePassword)) + fmt.Println() + } else { + return nil, fmt.Errorf("failed connection establishing: %s", err) } - connstr = fmt.Sprintf("%s password=%s ", connstr, string(bytePassword)) - fmt.Println() default: - return nil, fmt.Errorf("failed connection establishing: %s", err) + switch { + case err == pq.ErrSSLNotSupported: + // By default pq-driver tries to connect with SSL. So if SSL is not enabled on the other side - fix our connection string and try to reconnect + fmt.Println("SSL is not enabled on the server, connecting with sslmode=disable") + connstr = connstr + " sslmode=disable" + default: + return nil, fmt.Errorf("failed connection establishing: %s", err) + } } } else { + // connection successful return conn, nil } } diff --git a/record/context.go b/record/context.go index 8d4ed9b..cb6bdcd 100644 --- a/record/context.go +++ b/record/context.go @@ -9,19 +9,21 @@ import ( // Setup method performs context's setup - select queries and adjusts then depending on Postgres version func (o *RecordOptions) Setup(pginfo stat.PgInfo) { o.contextList = stat.ContextList{ - stat.DatabaseView: &stat.PgStatDatabaseUnit, - stat.ReplicationView: &stat.PgStatReplicationUnit, - stat.TablesView: &stat.PgStatTablesUnit, - stat.IndexesView: &stat.PgStatIndexesUnit, - stat.SizesView: &stat.PgTablesSizesUnit, - stat.FunctionsView: &stat.PgStatFunctionsUnit, - stat.VacuumView: &stat.PgStatVacuumUnit, - stat.ActivityView: &stat.PgStatActivityUnit, - stat.StatementsTimingView: &stat.PgSSTimingUnit, - stat.StatementsGeneralView: &stat.PgSSGeneralUnit, - stat.StatementsIOView: &stat.PgSSIoUnit, - stat.StatementsTempView: &stat.PgSSTempUnit, - stat.StatementsLocalView: &stat.PgSSLocalUnit, + stat.DatabaseView: &stat.PgStatDatabaseUnit, + stat.ReplicationView: &stat.PgStatReplicationUnit, + stat.TablesView: &stat.PgStatTablesUnit, + stat.IndexesView: &stat.PgStatIndexesUnit, + stat.SizesView: &stat.PgTablesSizesUnit, + stat.FunctionsView: &stat.PgStatFunctionsUnit, + stat.ProgressVacuumView: &stat.PgStatProgressVacuumUnit, + stat.ProgressClusterView: &stat.PgStatProgressClusterUnit, + stat.ProgressCreateIndexView: &stat.PgStatProgressCreateIndexUnit, + stat.ActivityView: &stat.PgStatActivityUnit, + stat.StatementsTimingView: &stat.PgSSTimingUnit, + stat.StatementsGeneralView: &stat.PgSSGeneralUnit, + stat.StatementsIOView: &stat.PgSSIoUnit, + stat.StatementsTempView: &stat.PgSSTempUnit, + stat.StatementsLocalView: &stat.PgSSLocalUnit, } // Adjust queries depending on Postgres version diff --git a/report/report.go b/report/report.go index cc18e1e..4c9f61f 100644 --- a/report/report.go +++ b/report/report.go @@ -61,7 +61,7 @@ func RunMain(args []string, opts ReportOptions) { func doReport(r *tar.Reader, opts ReportOptions) error { var prevStat, diffStat stat.PGresult var prevTs time.Time - var linesPrinted int8 + var linesPrinted int8 = repeatHeaderAfter // initial value means print header at the beginning of all output // read files headers continuously, read stats files requested by user and skip others. for { @@ -150,14 +150,17 @@ func formatReport(d *stat.PGresult, opts *ReportOptions) { // align values for printing, use dynamic aligning if !opts.Context.Aligned { - d.SetAlign(opts.Context.ColsWidth, opts.TruncLimit, true) - opts.Context.Aligned = true + err := d.SetAlign(opts.Context.ColsWidth, opts.TruncLimit, true) + if err == nil { + opts.Context.Aligned = true + } } } // printStatHeader periodically prints names of stats columns func printStatHeader(printedNum int8, cols []string, opts ReportOptions) int8 { - if printedNum >= repeatHeaderAfter { + + if printedNum >= repeatHeaderAfter && opts.Context.Aligned { fmt.Printf(" ") for i, name := range cols { fmt.Printf("\033[%d;%dm%-*s\033[0m", 37, 1, opts.Context.ColsWidth[i]+2, name) diff --git a/top/context.go b/top/context.go index 602a336..8d27d5c 100644 --- a/top/context.go +++ b/top/context.go @@ -40,19 +40,21 @@ var ( // List of available units in 'pgcenter top' program ctxList = stat.ContextList{ - stat.DatabaseView: &stat.PgStatDatabaseUnit, - stat.ReplicationView: &stat.PgStatReplicationUnit, - stat.TablesView: &stat.PgStatTablesUnit, - stat.IndexesView: &stat.PgStatIndexesUnit, - stat.SizesView: &stat.PgTablesSizesUnit, - stat.FunctionsView: &stat.PgStatFunctionsUnit, - stat.VacuumView: &stat.PgStatVacuumUnit, - stat.ActivityView: &stat.PgStatActivityUnit, - stat.StatementsTimingView: &stat.PgSSTimingUnit, - stat.StatementsGeneralView: &stat.PgSSGeneralUnit, - stat.StatementsIOView: &stat.PgSSIoUnit, - stat.StatementsTempView: &stat.PgSSTempUnit, - stat.StatementsLocalView: &stat.PgSSLocalUnit, + stat.DatabaseView: &stat.PgStatDatabaseUnit, + stat.ReplicationView: &stat.PgStatReplicationUnit, + stat.TablesView: &stat.PgStatTablesUnit, + stat.IndexesView: &stat.PgStatIndexesUnit, + stat.SizesView: &stat.PgTablesSizesUnit, + stat.FunctionsView: &stat.PgStatFunctionsUnit, + stat.ProgressVacuumView: &stat.PgStatProgressVacuumUnit, + stat.ProgressClusterView: &stat.PgStatProgressClusterUnit, + stat.ProgressCreateIndexView: &stat.PgStatProgressCreateIndexUnit, + stat.ActivityView: &stat.PgStatActivityUnit, + stat.StatementsTimingView: &stat.PgSSTimingUnit, + stat.StatementsGeneralView: &stat.PgSSGeneralUnit, + stat.StatementsIOView: &stat.PgSSIoUnit, + stat.StatementsTempView: &stat.PgSSTempUnit, + stat.StatementsLocalView: &stat.PgSSLocalUnit, } ) @@ -163,9 +165,9 @@ func switchContextTo(c string) func(g *gocui.Gui, v *gocui.View) error { ctx.contextList[ctx.current.Name] = ctx.current // Load new context unit (with settings) from the list - if c != stat.StatementsView { - ctx.current = ctx.contextList[c] - } else { + switch c { + case stat.StatementsView: + // fall through another switch and select appropriate pg_stat_statements stats switch ctx.current.Name { case stat.StatementsTimingView: ctx.current = ctx.contextList[stat.StatementsGeneralView] @@ -180,6 +182,20 @@ func switchContextTo(c string) func(g *gocui.Gui, v *gocui.View) error { default: ctx.current = ctx.contextList[stat.StatementsTimingView] } + case stat.ProgressView: + // fall through another switch and select appropriate pg_stat_progress_* stats + switch ctx.current.Name { + case stat.ProgressVacuumView: + ctx.current = ctx.contextList[stat.ProgressClusterView] + case stat.ProgressClusterView: + ctx.current = ctx.contextList[stat.ProgressCreateIndexView] + case stat.ProgressCreateIndexView: + ctx.current = ctx.contextList[stat.ProgressVacuumView] + default: + ctx.current = ctx.contextList[stat.ProgressVacuumView] + } + default: + ctx.current = ctx.contextList[c] } printCmdline(g, ctx.current.Msg) @@ -189,6 +205,8 @@ func switchContextTo(c string) func(g *gocui.Gui, v *gocui.View) error { } } +// TODO: looks like these two functions below are redundant, their code is the same - possibly they should be replaced with switchContextTo() function + // Switch pg_stat_statements context units func switchContextToPgss(g *gocui.Gui, c string) { // Save current context unit with its settings into context list @@ -201,6 +219,18 @@ func switchContextToPgss(g *gocui.Gui, c string) { doUpdate <- 1 } +// Switch pg_stat_progress_* context units +func switchContextToProgress(g *gocui.Gui, c string) { + // Save current context unit with its settings into context list + ctx.contextList[ctx.current.Name] = ctx.current + + // Load new context unit (with settings) from the list + ctx.current = ctx.contextList[c] + + printCmdline(g, ctx.current.Msg) + doUpdate <- 1 +} + // A toggle to show system tables stats func toggleSysTables(g *gocui.Gui, _ *gocui.View) error { switch ctx.sharedOptions.ViewType { diff --git a/top/help.go b/top/help.go index 9ab1a2b..c355197 100644 --- a/top/help.go +++ b/top/help.go @@ -14,10 +14,11 @@ general actions: a,d,f,r mode: 'a' activity, 'd' databases, 'f' functions, 'r' replication, s,t,i,v 's' tables sizes, 't' tables, 'i' indexes, 'v' vacuum progress, x,X 'x' pg_stat_statements switch, 'X' pg_stat_statements menu. + p,P 'p' pg_stat_progress_* switch, 'P' pg_stat_progress_* menu. Left,Right,<,/ 'Left,Right' change column sort, '<' desc/asc sort toggle, '/' set filter. Up,Down 'Up' increase column width, 'Down' decrease column width. C,E,R config: 'C' show config, 'E' edit configs, 'R' reload config. - p start psql session. + ~ start psql session. l open log file with pager. aux stats actions: diff --git a/top/keybindings.go b/top/keybindings.go index 3086d48..5befd27 100644 --- a/top/keybindings.go +++ b/top/keybindings.go @@ -38,15 +38,16 @@ func keybindings(g *gocui.Gui) error { {"sysstat", 'i', switchContextTo(stat.IndexesView)}, {"sysstat", 's', switchContextTo(stat.SizesView)}, {"sysstat", 'f', switchContextTo(stat.FunctionsView)}, - {"sysstat", 'v', switchContextTo(stat.VacuumView)}, + {"sysstat", 'p', switchContextTo(stat.ProgressView)}, {"sysstat", 'a', switchContextTo(stat.ActivityView)}, {"sysstat", 'x', switchContextTo(stat.StatementsView)}, {"sysstat", 'Q', resetStat}, {"sysstat", 'E', menuOpen(menuConfStyle)}, {"sysstat", 'X', menuOpen(menuPgssStyle)}, + {"sysstat", 'P', menuOpen(menuProgressStyle)}, {"sysstat", 'l', showPgLog}, {"sysstat", 'C', showPgConfig}, - {"sysstat", 'p', runPsql}, + {"sysstat", '~', runPsql}, {"sysstat", 'B', showAux(auxDiskstat)}, {"sysstat", 'N', showAux(auxNicstat)}, {"sysstat", 'L', showAux(auxLogtail)}, @@ -114,6 +115,12 @@ func quit(g *gocui.Gui, _ *gocui.View) error { close(doUpdate) close(doExit) g.Close() + + err := conn.Close() + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "ERROR: failed closing pgsql connection, ignoring") + } + os.Exit(0) return gocui.ErrQuit } diff --git a/top/menu.go b/top/menu.go index 1e0f1b1..a7a6fbe 100644 --- a/top/menu.go +++ b/top/menu.go @@ -18,6 +18,7 @@ type direction int const ( menuNone menuType = iota menuPgss + menuProgress menuConf ) @@ -48,6 +49,17 @@ var ( }, } + // pg_stat_progress_* menu + menuProgressStyle = menuStyle{ + menuType: menuProgress, + menuTitle: " Choose pg_stat_progress_* view (Enter to choose, Esc to exit): ", + menuItems: []string{ + " pg_stat_progress_vacuum", + " pg_stat_progress_cluster", + " pg_stat_progress_create_index", + }, + } + // edit configuration files menuConfStyle = menuStyle{ menuType: menuConf, @@ -114,6 +126,15 @@ func menuSelect(g *gocui.Gui, v *gocui.View) error { default: switchContextToPgss(g, stat.StatementsTimingView) } + case menuProgress: + switch cy { + case 0: + switchContextToProgress(g, stat.ProgressVacuumView) + case 1: + switchContextToProgress(g, stat.ProgressClusterView) + case 2: + switchContextToProgress(g, stat.ProgressCreateIndexView) + } case menuConf: switch cy { case 0: diff --git a/top/stat.go b/top/stat.go index 8e74218..ec31c85 100644 --- a/top/stat.go +++ b/top/stat.go @@ -165,8 +165,10 @@ func printDbstat(v *gocui.View, s stat.Stat) { // configure aligning, use fixed aligning instead of dynamic if !ctx.current.Aligned { - s.DiffPGresult.SetAlign(ctx.current.ColsWidth, 1000, false) // we don't want truncate lines here, so just use high limit - ctx.current.Aligned = true + err := s.DiffPGresult.SetAlign(ctx.current.ColsWidth, 1000, false) // we don't want truncate lines here, so just use high limit + if err == nil { + ctx.current.Aligned = true + } } // is filter required? @@ -258,7 +260,7 @@ func printStatData(v *gocui.View, s *stat.Stat, filter bool) { // Print iostat - block devices stats. func printIostat(v *gocui.View, s stat.Diskstats) { // print header - fmt.Fprintf(v, " \033[30;47mDevice: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await %%util\033[0m\n") + fmt.Fprintf(v, "\033[30;47m Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await %%util\033[0m\n") for i := 0; i < len(s); i++ { // skip devices which never do IOs @@ -267,7 +269,7 @@ func printIostat(v *gocui.View, s stat.Diskstats) { } // print stats - fmt.Fprintf(v, "%14s\t%10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f\n", + fmt.Fprintf(v, "%20s\t%10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f %10.2f\n", s[i].Device, s[i].Rmerged, s[i].Wmerged, s[i].Rcompleted, s[i].Wcompleted, @@ -280,7 +282,7 @@ func printIostat(v *gocui.View, s stat.Diskstats) { // Print nicstat - network interfaces stat. func printNicstat(v *gocui.View, s stat.Netdevs) { // print header - fmt.Fprintf(v, " \033[30;47mInterface: rMbps wMbps rPk/s wPk/s rAvs wAvs IErr OErr Coll Sat %%rUtil %%wUtil %%Util\033[0m\n") + fmt.Fprintf(v, "\033[30;47m Interface: rMbps wMbps rPk/s wPk/s rAvs wAvs IErr OErr Coll Sat %%rUtil %%wUtil %%Util\033[0m\n") for i := 0; i < len(s); i++ { // skip interfaces which never seen packets @@ -289,7 +291,7 @@ func printNicstat(v *gocui.View, s stat.Netdevs) { } // print stats - fmt.Fprintf(v, "%14s%8.2f%8.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f\n", + fmt.Fprintf(v, "%20s%8.2f%8.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f%9.2f\n", s[i].Ifname, s[i].Rbytes/1024/128, s[i].Tbytes/1024/128, // conversion to Mbps s[i].Rpackets, s[i].Tpackets, s[i].Raverage, s[i].Taverage,