├── pg_linux_stats.control ├── Makefile ├── free.h ├── vmstat.h ├── netstat.h ├── mpstat.h ├── netstat.c ├── iostat.c ├── vmstat.c ├── free.c ├── mpstat.c ├── iostat.h ├── pg_linux_stats--1.0.sql ├── README.md └── pg_linux_stats.c /pg_linux_stats.control: -------------------------------------------------------------------------------- 1 | # pg_linux_stats extension 2 | comment = 'run XXstat command on Linux' 3 | default_version = '1.0' 4 | module_pathname = '$libdir/pg_linux_stats' 5 | relocatable = true 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # pg_linux_stats/Makefile 2 | 3 | MODULE_big = pg_linux_stats 4 | OBJS = pg_linux_stats.o vmstat.o free.o netstat.o iostat.o mpstat.o 5 | 6 | EXTENSION = pg_linux_stats 7 | DATA = pg_linux_stats--1.0.sql 8 | 9 | ifdef USE_PGXS 10 | PG_CONFIG = pg_config 11 | PGXS := $(shell $(PG_CONFIG) --pgxs) 12 | include $(PGXS) 13 | else 14 | subdir = contrib/pg_linux_stats 15 | top_builddir = ../.. 16 | include $(top_builddir)/src/Makefile.global 17 | include $(top_srcdir)/contrib/contrib-global.mk 18 | endif 19 | 20 | -------------------------------------------------------------------------------- /free.h: -------------------------------------------------------------------------------- 1 | 2 | /*------------------------------------------------------------------------- 3 | * 4 | * free.h 5 | * Show free info on Linux 6 | * 7 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 8 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 9 | * 10 | *------------------------------------------------------------------------- 11 | */ 12 | #include "postgres.h" 13 | 14 | #ifndef __FREE_H__ 15 | #define __FREE_H__ 16 | 17 | /* 18 | $ free 19 | total used free shared buff/cache available 20 | Mem: 980320 255048 67340 48176 657932 514884 21 | Swap: 0 0 0 22 | */ 23 | 24 | typedef struct Free 25 | { 26 | int64 total; 27 | int64 used; 28 | int64 free; 29 | int64 shared; 30 | int64 buff; 31 | int64 available; 32 | int64 swap_total; 33 | int64 swap_used; 34 | int64 swap_free; 35 | } Free; 36 | 37 | extern bool get_free(struct Free *free); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /vmstat.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * vmstat.h 4 | * Show vmstat info on Linux 5 | * 6 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 7 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | #include "postgres.h" 12 | 13 | #ifndef __VMSTAT_H__ 14 | #define __VMSTAT_H__ 15 | 16 | /* 17 | $ vmstat 18 | procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- 19 | r b swpd free buff cache si so bi bo in cs us sy id wa st 20 | 1 0 0 67340 31860 626072 0 0 14 14 5 5 0 0 100 0 0 21 | */ 22 | 23 | 24 | typedef struct VmStat 25 | { 26 | int64 r; 27 | int64 b; 28 | int64 swpd; 29 | int64 free; 30 | int64 buff; 31 | int64 cache; 32 | int64 si; 33 | int64 so; 34 | int64 bi; 35 | int64 bo; 36 | int64 in; 37 | int64 cs; 38 | int64 us; 39 | int64 sy; 40 | int64 id; 41 | int64 wa; 42 | int64 st; 43 | } VmStat; 44 | 45 | extern bool get_vmstat(struct VmStat *vmstat); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /netstat.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * netstat.h 4 | * Show netstat info on Linux 5 | * 6 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 7 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | #include "postgres.h" 12 | 13 | #ifndef __NETSTAT_H__ 14 | #define __NETSTAT_H__ 15 | 16 | /* 17 | $ netstat -i 18 | Kernel Interface table 19 | Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg 20 | enp0s3 1500 1180782 0 0 0 219844 0 0 0 BMRU 21 | enp0s8 1500 19953 0 0 0 883 0 0 0 BMRU 22 | enp0s9 1500 2745270 0 0 0 1862516 0 0 0 BMRU 23 | lo 65536 2791 0 0 0 2791 0 0 0 LRU 24 | */ 25 | 26 | typedef struct NetStat 27 | { 28 | char iface[16]; 29 | int64 mtu; 30 | int64 rx_ok; 31 | int64 rx_err; 32 | int64 rx_drp; 33 | int64 rx_ovr; 34 | int64 tx_ok; 35 | int64 tx_err; 36 | int64 tx_drp; 37 | int64 tx_ovr; 38 | char flg[16]; 39 | 40 | } NetStat; 41 | 42 | 43 | extern List *get_netstat(List *netstat); 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /mpstat.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * mpstat.h 4 | * Show mpstat info on Linux 5 | * 6 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 7 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | #include "postgres.h" 12 | 13 | #ifndef __MPSTAT_H__ 14 | #define __MPSTAT_H__ 15 | 16 | /* 17 | $ mpstat 18 | Linux 5.15.0-94-generic (ubuntu-jammy) 03/27/24 _x86_64_ (2 CPU) 19 | 20 | 23:00:16 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 21 | 23:00:16 all 0.15 0.00 0.10 0.01 0.00 0.02 0.00 0.00 0.00 99.72 22 | 23 | $ mpstat -P ALL 24 | Linux 5.15.0-94-generic (ubuntu-jammy) 03/28/24 _x86_64_ (2 CPU) 25 | 26 | 08:58:39 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 27 | 08:58:39 all 0.15 0.01 0.10 0.01 0.00 0.02 0.00 0.00 0.00 99.72 28 | 08:58:39 0 0.16 0.00 0.11 0.01 0.00 0.01 0.00 0.00 0.00 99.71 29 | 08:58:39 1 0.14 0.01 0.09 0.00 0.00 0.03 0.00 0.00 0.00 99.73 30 | 31 | */ 32 | 33 | typedef struct MpStat 34 | { 35 | char cpu[8]; 36 | float4 usr; 37 | float4 nice; 38 | float4 sys; 39 | float4 iowait; 40 | float4 irq; 41 | float4 soft; 42 | float4 steal; 43 | float4 guest; 44 | float4 gnice; 45 | float4 idle; 46 | 47 | } MpStat; 48 | 49 | 50 | extern List *get_mpstat(List *mpstat); 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /netstat.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * netstat.c 4 | * Show netstat info on Linux 5 | * 6 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 7 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | 12 | #include "postgres.h" 13 | #include "nodes/pg_list.h" 14 | 15 | #include "netstat.h" 16 | 17 | 18 | List * 19 | get_netstat(List *netstat) 20 | { 21 | FILE *fp; 22 | char buffer[256]; 23 | char *cmd = "/usr/bin/netstat -i"; 24 | 25 | if ((fp = popen(cmd, "r")) == NULL) 26 | ereport(ERROR, 27 | (errcode_for_file_access(), 28 | errmsg("could not execute \"%s\": ", cmd))); 29 | 30 | 31 | while (fgets(buffer, sizeof(buffer), fp) != NULL) 32 | { 33 | NetStat *ns; 34 | 35 | if (strncmp(buffer, "Kernel", 6) == 0 || strncmp(buffer, "Iface", 5) == 0) 36 | continue; 37 | 38 | ns = (NetStat *) palloc0(sizeof(NetStat)); 39 | 40 | if (sscanf(buffer, "%s %ld %ld %ld %ld %ld %ld %ld %ld %ld %s", 41 | ns->iface, 42 | &(ns->mtu), 43 | &(ns->rx_ok), 44 | &(ns->rx_err), 45 | &(ns->rx_drp), 46 | &(ns->rx_ovr), 47 | &(ns->tx_ok), 48 | &(ns->tx_err), 49 | &(ns->tx_drp), 50 | &(ns->tx_ovr), 51 | ns->flg) > 11) 52 | ereport(ERROR, 53 | (errcode(ERRCODE_DATA_EXCEPTION), 54 | errmsg("unexpected output format: \"%s\"", cmd), 55 | errdetail("number of fields is not corresponding"))); 56 | 57 | netstat = lappend(netstat, ns); 58 | 59 | } 60 | 61 | 62 | pclose(fp); 63 | 64 | return netstat; 65 | } 66 | -------------------------------------------------------------------------------- /iostat.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * iostat.c 4 | * Show iostat info on Linux 5 | * 6 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 7 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | 12 | #include "postgres.h" 13 | #include "nodes/pg_list.h" 14 | 15 | #include "iostat.h" 16 | 17 | 18 | List * 19 | get_iostat(List *iostat) 20 | { 21 | FILE *fp; 22 | char buffer[256]; 23 | char *cmd = "/usr/bin/iostat"; 24 | bool reading = false; 25 | 26 | if ((fp = popen(cmd, "r")) == NULL) 27 | ereport(ERROR, 28 | (errcode_for_file_access(), 29 | errmsg("could not execute \"%s\": ", cmd))); 30 | 31 | 32 | while (fgets(buffer, sizeof(buffer), fp) != NULL) 33 | { 34 | IoStat *is; 35 | 36 | if (reading == true && strlen(buffer) > 1) 37 | { 38 | is = (IoStat *) palloc0(sizeof(IoStat)); 39 | 40 | if (sscanf(buffer, "%s %f %f %f %f %ld %ld %ld", 41 | is->device, 42 | &(is->tps), 43 | &(is->kb_read_s), 44 | &(is->kb_wrtn_s), 45 | &(is->kb_dscd_s), 46 | &(is->kb_read), 47 | &(is->kb_wrtn), 48 | &(is->kb_dscd)) > 8) 49 | ereport(ERROR, 50 | (errcode(ERRCODE_DATA_EXCEPTION), 51 | errmsg("unexpected output format: \"%s\"", cmd), 52 | errdetail("number of fields is not corresponding"))); 53 | 54 | iostat = lappend(iostat, is); 55 | } 56 | 57 | if (strlen(buffer) == 0) 58 | continue; 59 | else if (strncmp(buffer, "Device", 6) == 0) 60 | { 61 | reading = true; 62 | continue; 63 | } 64 | 65 | } 66 | 67 | 68 | pclose(fp); 69 | 70 | return iostat; 71 | } 72 | -------------------------------------------------------------------------------- /vmstat.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * vmstat.c 4 | * Show vmstat info on Linux 5 | * 6 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 7 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | 12 | #include "postgres.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "vmstat.h" 20 | 21 | bool 22 | get_vmstat(struct VmStat *vmstat) 23 | { 24 | FILE *fp; 25 | char buffer[256]; 26 | char *cmd = "/usr/bin/vmstat"; 27 | 28 | /* extract loadavg information */ 29 | if ((fp = popen(cmd, "r")) == NULL) 30 | ereport(ERROR, 31 | (errcode_for_file_access(), 32 | errmsg("could not execute \"%s\": ", cmd))); 33 | 34 | while (fgets(buffer, sizeof(buffer), fp) != NULL) 35 | { 36 | if (strncmp(buffer, "procs", 5) == 0 || *buffer == 'r') 37 | continue; 38 | 39 | if (sscanf(buffer, "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld", 40 | &(vmstat->r), 41 | &(vmstat->b), 42 | &(vmstat->swpd), 43 | &(vmstat->free), 44 | &(vmstat->buff), 45 | &(vmstat->cache), 46 | &(vmstat->si), 47 | &(vmstat->so), 48 | &(vmstat->bi), 49 | &(vmstat->bo), 50 | &(vmstat->in), 51 | &(vmstat->cs), 52 | &(vmstat->us), 53 | &(vmstat->sy), 54 | &(vmstat->id), 55 | &(vmstat->wa), 56 | &(vmstat->st)) > 18) 57 | ereport(ERROR, 58 | (errcode(ERRCODE_DATA_EXCEPTION), 59 | errmsg("unexpected ouput format: \"%s\"", cmd), 60 | errdetail("number of fields is not corresponding"))); 61 | 62 | } 63 | 64 | pclose(fp); 65 | 66 | return true; 67 | } 68 | -------------------------------------------------------------------------------- /free.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * free.c 4 | * Show free info on Linux 5 | * 6 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 7 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | 12 | #include "postgres.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "free.h" 20 | 21 | bool 22 | get_free(struct Free *free) 23 | { 24 | FILE *fp; 25 | char buffer[256]; 26 | char *cmd = "/usr/bin/free"; 27 | 28 | if ((fp = popen(cmd, "r")) == NULL) 29 | ereport(ERROR, 30 | (errcode_for_file_access(), 31 | errmsg("could not execute \"%s\": ", cmd))); 32 | 33 | while (fgets(buffer, sizeof(buffer), fp) != NULL) 34 | { 35 | if (strncmp(buffer, "Mem:", 4) == 0) 36 | { 37 | if (sscanf(buffer, "Mem: %ld %ld %ld %ld %ld %ld", 38 | &(free->total), 39 | &(free->used), 40 | &(free->free), 41 | &(free->shared), 42 | &(free->buff), 43 | &(free->available)) > 6) 44 | ereport(ERROR, 45 | (errcode(ERRCODE_DATA_EXCEPTION), 46 | errmsg("unexpected ouput format: \"%s\"", cmd), 47 | errdetail("number of fields is not corresponding"))); 48 | } 49 | else if (strncmp(buffer, "Swap:", 5) == 0) 50 | { 51 | if (sscanf(buffer, "Swap: %ld %ld %ld", 52 | &(free->swap_total), 53 | &(free->swap_used), 54 | &(free->swap_free)) > 3) 55 | ereport(ERROR, 56 | (errcode(ERRCODE_DATA_EXCEPTION), 57 | errmsg("unexpected ouput format: \"%s\"", cmd), 58 | errdetail("number of fields is not corresponding"))); 59 | } 60 | } 61 | 62 | pclose(fp); 63 | 64 | return true; 65 | } 66 | -------------------------------------------------------------------------------- /mpstat.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * mpstat.c 4 | * Show mpstat info on Linux 5 | * 6 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 7 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | 12 | #include "postgres.h" 13 | #include "nodes/pg_list.h" 14 | 15 | #include "mpstat.h" 16 | 17 | 18 | List * 19 | get_mpstat(List *mpstat) 20 | { 21 | FILE *fp; 22 | char buffer[256]; 23 | char *cmd = "/usr/bin/mpstat -P ALL"; 24 | bool reading = false; 25 | 26 | if ((fp = popen(cmd, "r")) == NULL) 27 | ereport(ERROR, 28 | (errcode_for_file_access(), 29 | errmsg("could not execute \"%s\": ", cmd))); 30 | 31 | 32 | while (fgets(buffer, sizeof(buffer), fp) != NULL) 33 | { 34 | MpStat *ms; 35 | char dummy1[16]; 36 | char dummy2[16]; 37 | 38 | if (reading == true) 39 | { 40 | ms = (MpStat *) palloc0(sizeof(MpStat)); 41 | 42 | if (sscanf(buffer, "%s %s %f %f %f %f %f %f %f %f %f %f", 43 | dummy1, 44 | ms->cpu, 45 | &(ms->usr), 46 | &(ms->nice), 47 | &(ms->sys), 48 | &(ms->iowait), 49 | &(ms->irq), 50 | &(ms->soft), 51 | &(ms->steal), 52 | &(ms->guest), 53 | &(ms->gnice), 54 | &(ms->idle)) > 12) 55 | ereport(ERROR, 56 | (errcode(ERRCODE_DATA_EXCEPTION), 57 | errmsg("unexpected output format: \"%s\"", cmd), 58 | errdetail("number of fields is not corresponding"))); 59 | 60 | mpstat = lappend(mpstat, ms); 61 | } 62 | else /* reading == false */ 63 | { 64 | /*-- 65 | * strlen("CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle") = 66 66 | */ 67 | if (strlen(buffer) < 66) 68 | continue; 69 | if (sscanf(buffer, "%s %s", dummy1, dummy2) < 2) 70 | continue; 71 | if (strncmp(dummy2, "CPU", 3) == 0) 72 | reading = true; 73 | } 74 | } 75 | 76 | 77 | pclose(fp); 78 | 79 | return mpstat; 80 | } 81 | -------------------------------------------------------------------------------- /iostat.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * iostat.h 4 | * Show iostat info on Linux 5 | * 6 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 7 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | #include "postgres.h" 12 | 13 | #ifndef __IOSTAT_H__ 14 | #define __IOSTAT_H__ 15 | 16 | /* 17 | $ iostat 18 | Linux 5.15.0-94-generic (ubuntu-jammy) 03/27/24 _x86_64_ (2 CPU) 19 | 20 | avg-cpu: %user %nice %system %iowait %steal %idle 21 | 0.15 0.00 0.12 0.01 0.00 99.72 22 | 23 | Device tps kB_read/s kB_wrtn/s kB_dscd/s kB_read kB_wrtn kB_dscd 24 | loop0 0.00 0.00 0.00 0.00 397 0 0 25 | loop1 0.00 0.01 0.00 0.00 44412 0 0 26 | loop2 0.01 0.23 0.00 0.00 716922 0 0 27 | loop3 0.00 0.00 0.00 0.00 1173 0 0 28 | loop4 0.00 0.13 0.00 0.00 409440 0 0 29 | loop5 0.00 0.10 0.00 0.00 298739 0 0 30 | loop6 0.00 0.00 0.00 0.00 1182 0 0 31 | loop7 0.00 0.00 0.00 0.00 97 0 0 32 | loop8 0.00 0.00 0.00 0.00 111 0 0 33 | sda 1.75 27.94 27.31 0.00 86603577 84648397 0 34 | sdb 0.00 0.00 0.00 0.00 3226 0 0 35 | */ 36 | 37 | typedef struct IoStat 38 | { 39 | char device[16]; 40 | float4 tps; 41 | float4 kb_read_s; 42 | float4 kb_wrtn_s; 43 | float4 kb_dscd_s; 44 | int64 kb_read; 45 | int64 kb_wrtn; 46 | int64 kb_dscd; 47 | } IoStat; 48 | 49 | 50 | extern List *get_iostat(List *iostat); 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /pg_linux_stats--1.0.sql: -------------------------------------------------------------------------------- 1 | /* pg_linux_stats--1.0.sql */ 2 | 3 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 4 | \echo Use "CREATE EXTENSION pg_linux_stats" to load this file. \quit 5 | 6 | -- Register functions. 7 | 8 | 9 | CREATE OR REPLACE FUNCTION pg_vmstat( 10 | OUT r int, 11 | OUT b int, 12 | OUT swpd int, 13 | OUT free int, 14 | OUT buff int, 15 | OUT cache int, 16 | OUT si int, 17 | OUT so int, 18 | OUT bi int, 19 | OUT bo int, 20 | OUT interrupts int, 21 | OUT cs int, 22 | OUT us int, 23 | OUT sy int, 24 | OUT id int, 25 | OUT wa int, 26 | OUT st int 27 | ) 28 | RETURNS SETOF record 29 | AS 'MODULE_PATHNAME' 30 | LANGUAGE C IMMUTABLE STRICT; 31 | 32 | CREATE OR REPLACE FUNCTION pg_free( 33 | OUT total int, 34 | OUT used int, 35 | OUT free int, 36 | OUT shared int, 37 | OUT buff int, 38 | OUT available int, 39 | OUT swap_total int, 40 | OUT swap_used int, 41 | OUT swap_free int 42 | ) 43 | RETURNS SETOF record 44 | AS 'MODULE_PATHNAME' 45 | LANGUAGE C IMMUTABLE STRICT; 46 | 47 | 48 | CREATE OR REPLACE FUNCTION pg_netstat( 49 | OUT iface text, 50 | OUT mtu int, 51 | OUT rx_ok int, 52 | OUT rx_err int, 53 | OUT rx_drp int, 54 | OUT rx_ovr int, 55 | OUT tx_ok int, 56 | OUT tx_err int, 57 | OUT tx_drp int, 58 | OUT tx_ovr int, 59 | OUT flg text 60 | ) 61 | RETURNS SETOF record 62 | AS 'MODULE_PATHNAME' 63 | LANGUAGE C IMMUTABLE STRICT; 64 | 65 | 66 | CREATE OR REPLACE FUNCTION pg_iostat( 67 | OUT device text, 68 | OUT tps real, 69 | OUT kb_read_s real, 70 | OUT kb_wrtn_s real, 71 | OUT kb_dscd_s real, 72 | OUT kb_read int, 73 | OUT kb_wrtn int, 74 | OUT kb_dscd int 75 | ) 76 | RETURNS SETOF record 77 | AS 'MODULE_PATHNAME' 78 | LANGUAGE C IMMUTABLE STRICT; 79 | 80 | 81 | CREATE OR REPLACE FUNCTION pg_mpstat( 82 | OUT cpu text, 83 | OUT usr real, 84 | OUT nice real, 85 | OUT sys real, 86 | OUT iowait real, 87 | OUT irq real, 88 | OUT soft real, 89 | OUT steal real, 90 | OUT guest real, 91 | OUT gnice real, 92 | OUT idle real 93 | ) 94 | RETURNS SETOF record 95 | AS 'MODULE_PATHNAME' 96 | LANGUAGE C IMMUTABLE STRICT; 97 | 98 | 99 | -- GRANT SELECT ON pg_show_vm TO PUBLIC; 100 | 101 | -- Don't want this to be available to non-superusers. 102 | --REVOKE ALL ON FUNCTION pg_show_vm() FROM PUBLIC; 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pg_linux_stats 2 | 3 | `pg_linux_stats` is a set of PostgreSQL functions that provides information similar to the Linux commands vmstat, iostat, netstat and mpstat. 4 | 5 | It was developed to simplify OS statistics monitoring without requiring the use of heavyweight solutions like Zabbix and Prometheus. 6 | 7 | This extension supports PostgreSQL versions 16 and 17. 8 | 9 | ### Limitation 10 | 11 | `pg_linux_stats` can only run on Linux. 12 | 13 | ## Installation 14 | 15 | You can install it to do the usual way shown below. 16 | 17 | ``` 18 | $ tar xvfj postgresql-16.0.tar.bz2 19 | $ cd postgresql-16.0/contrib 20 | $ git clone https://github.com/s-hironobu/pg_linux_stats.git 21 | $ cd pg_linux_stats 22 | $ make && make install 23 | ``` 24 | 25 | You must add the line shown below in your postgresql.conf. 26 | 27 | ``` 28 | shared_preload_libraries = 'pg_linux_stats' 29 | ``` 30 | 31 | After starting your server, you must issue `CREATE EXTENSION` statement shown below. 32 | 33 | ``` 34 | postgres=# CREATE EXTENSION pg_linux_stats; 35 | ``` 36 | 37 | 38 | The `sysstat` module is required for `mpstat` command on some Linux distributions. 39 | 40 | Installation: 41 | 42 | ``` 43 | $ sudo apt install sysstat 44 | ``` 45 | 46 | 47 | ## How to use 48 | 49 | ### pg_vmstat 50 | 51 | ``` 52 | testdb=# select * from pg_vmstat(); 53 | r | b | swpd | free | buff | cache | si | so | bi | bo | interrupts | cs | us | sy | id | wa | st 54 | ---+---+------+--------+-------+--------+----+----+----+----+------------+----+----+----+-----+----+---- 55 | 0 | 0 | 0 | 191608 | 30304 | 500916 | 0 | 0 | 14 | 14 | 0 | 5 | 0 | 0 | 100 | 0 | 0 56 | (1 row) 57 | ``` 58 | 59 | ### pg_netstat 60 | 61 | ``` 62 | testdb=# select * from pg_netstat(); 63 | iface | mtu | rx_ok | rx_err | rx_drp | rx_ovr | tx_ok | tx_err | tx_drp | tx_ovr | flg 64 | --------+-------+---------+--------+--------+--------+---------+--------+--------+--------+------ 65 | enp0s3 | 1500 | 1187590 | 0 | 0 | 0 | 221529 | 0 | 0 | 0 | BMRU 66 | enp0s8 | 1500 | 20223 | 0 | 0 | 0 | 895 | 0 | 0 | 0 | BMRU 67 | enp0s9 | 1500 | 2825614 | 0 | 0 | 0 | 1911326 | 0 | 0 | 0 | BMRU 68 | lo | 65536 | 2835 | 0 | 0 | 0 | 2835 | 0 | 0 | 0 | LRU 69 | (4 rows) 70 | ``` 71 | 72 | ### pg_iostat 73 | 74 | ``` 75 | testdb=# select * from pg_iostat(); 76 | device | tps | kb_read_s | kb_wrtn_s | kb_dscd_s | kb_read | kb_wrtn | kb_dscd 77 | --------+------+-----------+-----------+-----------+----------+----------+--------- 78 | loop0 | 0 | 0 | 0 | 0 | 397 | 0 | 0 79 | loop1 | 0 | 0.02 | 0 | 0 | 48131 | 0 | 0 80 | loop2 | 0.01 | 0.23 | 0 | 0 | 716922 | 0 | 0 81 | loop3 | 0 | 0 | 0 | 0 | 1173 | 0 | 0 82 | loop4 | 0.01 | 0.15 | 0 | 0 | 459545 | 0 | 0 83 | loop5 | 0 | 0.1 | 0 | 0 | 308971 | 0 | 0 84 | loop6 | 0 | 0 | 0 | 0 | 1182 | 0 | 0 85 | loop7 | 0 | 0 | 0 | 0 | 97 | 0 | 0 86 | loop8 | 0 | 0 | 0 | 0 | 111 | 0 | 0 87 | sda | 1.74 | 27.74 | 27.32 | 0 | 87167977 | 85825845 | 0 88 | sdb | 0 | 0 | 0 | 0 | 3226 | 0 | 0 89 | (11 rows) 90 | ``` 91 | 92 | ### pg_mpstat 93 | 94 | ``` 95 | testdb=# select * from pg_mpstat(); 96 | cpu | usr | nice | sys | iowait | irq | soft | steal | guest | gnice | idle 97 | -----+------+------+------+--------+-----+------+-------+-------+-------+------- 98 | all | 0.15 | 0.01 | 0.1 | 0.01 | 0 | 0.02 | 0 | 0 | 0 | 99.72 99 | 0 | 0.16 | 0 | 0.11 | 0.01 | 0 | 0.01 | 0 | 0 | 0 | 99.71 100 | 1 | 0.14 | 0.01 | 0.09 | 0 | 0 | 0.03 | 0 | 0 | 0 | 99.73 101 | (3 rows) 102 | ``` 103 | ### pg_free 104 | 105 | ``` 106 | testdb=# select * from pg_free(); 107 | total | used | free | shared | buff | available | swap_total | swap_used | swap_free 108 | --------+--------+--------+--------+--------+-----------+------------+-----------+----------- 109 | 980320 | 257292 | 191608 | 48124 | 531420 | 510420 | 0 | 0 | 0 110 | (1 row) 111 | ``` 112 | 113 | ## Change Log 114 | - 16 Sep, 2024: Supported PG17. 115 | - 28 Mar, 2024: Version 1.0 Released. 116 | -------------------------------------------------------------------------------- /pg_linux_stats.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * pg_linux_stats.c 4 | * run XXstat commands on Linux 5 | * 6 | * Copyright (c) 2008-2025, PostgreSQL Global Development Group 7 | * Copyright (c) 2024-2025, Hironobu Suzuki @ interdb.jp 8 | * 9 | *------------------------------------------------------------------------- 10 | */ 11 | #include "postgres.h" 12 | 13 | #include "nodes/pg_list.h" 14 | #include "utils/builtins.h" 15 | #include "funcapi.h" 16 | #include "tcop/utility.h" 17 | #include "pgstat.h" 18 | 19 | #include "vmstat.h" 20 | #include "free.h" 21 | #include "netstat.h" 22 | #include "iostat.h" 23 | #include "mpstat.h" 24 | 25 | 26 | PG_MODULE_MAGIC; 27 | 28 | /* Function declarations */ 29 | void _PG_init(void); 30 | void _PG_fini(void); 31 | 32 | Datum pg_vmstat(PG_FUNCTION_ARGS); 33 | Datum pg_free(PG_FUNCTION_ARGS); 34 | Datum pg_netstat(PG_FUNCTION_ARGS); 35 | Datum pg_iostat(PG_FUNCTION_ARGS); 36 | Datum pg_mpstat(PG_FUNCTION_ARGS); 37 | 38 | PG_FUNCTION_INFO_V1(pg_vmstat); 39 | PG_FUNCTION_INFO_V1(pg_free); 40 | PG_FUNCTION_INFO_V1(pg_netstat); 41 | PG_FUNCTION_INFO_V1(pg_iostat); 42 | PG_FUNCTION_INFO_V1(pg_mpstat); 43 | 44 | /* Module callback */ 45 | void 46 | _PG_init(void) 47 | { 48 | if (!process_shared_preload_libraries_in_progress) 49 | return; 50 | 51 | EmitWarningsOnPlaceholders("pg_linux_stats"); 52 | } 53 | 54 | void 55 | _PG_fini(void) 56 | { 57 | ; 58 | } 59 | 60 | /* 61 | * Display vmstat 62 | */ 63 | #define NUM_VMSTAT_COLS 17 64 | 65 | Datum 66 | pg_vmstat(PG_FUNCTION_ARGS) 67 | { 68 | TupleDesc tupdesc; 69 | HeapTuple tuple; 70 | Datum values[NUM_VMSTAT_COLS]; 71 | bool nulls[NUM_VMSTAT_COLS]; 72 | VmStat vmstat; 73 | int i = 0; 74 | 75 | /* Build a tuple descriptor for our result type */ 76 | if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) 77 | elog(ERROR, "return type must be a row type"); 78 | 79 | Assert(tupdesc->natts == lengthof(values)); 80 | 81 | get_vmstat(&vmstat); 82 | 83 | memset(nulls, 0, sizeof(nulls)); 84 | memset(values, 0, sizeof(values)); 85 | 86 | values[i++] = Int64GetDatum(vmstat.r); 87 | values[i++] = Int64GetDatum(vmstat.b); 88 | values[i++] = Int64GetDatum(vmstat.swpd); 89 | values[i++] = Int64GetDatum(vmstat.free); 90 | values[i++] = Int64GetDatum(vmstat.buff); 91 | values[i++] = Int64GetDatum(vmstat.cache); 92 | values[i++] = Int64GetDatum(vmstat.si); 93 | values[i++] = Int64GetDatum(vmstat.so); 94 | values[i++] = Int64GetDatum(vmstat.bi); 95 | values[i++] = Int64GetDatum(vmstat.bo); 96 | values[i++] = Int64GetDatum(vmstat.in); 97 | values[i++] = Int64GetDatum(vmstat.cs); 98 | values[i++] = Int64GetDatum(vmstat.us); 99 | values[i++] = Int64GetDatum(vmstat.sy); 100 | values[i++] = Int64GetDatum(vmstat.id); 101 | values[i++] = Int64GetDatum(vmstat.wa); 102 | values[i++] = Int64GetDatum(vmstat.st); 103 | 104 | tuple = heap_form_tuple(tupdesc, values, nulls); 105 | 106 | return HeapTupleGetDatum(tuple); 107 | } 108 | 109 | 110 | /* 111 | * Display free 112 | */ 113 | #define NUM_FREE_COLS 17 114 | 115 | Datum 116 | pg_free(PG_FUNCTION_ARGS) 117 | { 118 | TupleDesc tupdesc; 119 | HeapTuple tuple; 120 | Datum values[NUM_FREE_COLS]; 121 | bool nulls[NUM_FREE_COLS]; 122 | Free free; 123 | int i = 0; 124 | 125 | /* Build a tuple descriptor for our result type */ 126 | if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) 127 | elog(ERROR, "return type must be a row type"); 128 | 129 | Assert(tupdesc->natts == lengthof(values)); 130 | 131 | get_free(&free); 132 | 133 | memset(nulls, 0, sizeof(nulls)); 134 | memset(values, 0, sizeof(values)); 135 | 136 | values[i++] = Int64GetDatum(free.total); 137 | values[i++] = Int64GetDatum(free.used); 138 | values[i++] = Int64GetDatum(free.free); 139 | values[i++] = Int64GetDatum(free.shared); 140 | values[i++] = Int64GetDatum(free.buff); 141 | values[i++] = Int64GetDatum(free.available); 142 | values[i++] = Int64GetDatum(free.swap_total); 143 | values[i++] = Int64GetDatum(free.swap_used); 144 | values[i++] = Int64GetDatum(free.swap_free); 145 | 146 | tuple = heap_form_tuple(tupdesc, values, nulls); 147 | 148 | return HeapTupleGetDatum(tuple); 149 | } 150 | 151 | 152 | /* 153 | * Display netstat -i 154 | */ 155 | #define NUM_NETSTAT_COLS 11 156 | 157 | Datum 158 | pg_netstat(PG_FUNCTION_ARGS) 159 | { 160 | ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; 161 | TupleDesc tupdesc; 162 | Tuplestorestate *tupstore; 163 | MemoryContext per_query_ctx; 164 | MemoryContext oldcontext; 165 | Datum values[NUM_NETSTAT_COLS]; 166 | bool nulls[NUM_NETSTAT_COLS]; 167 | List *netstat = NIL; 168 | ListCell *lc; 169 | 170 | /* check to see if caller supports us returning a tuplestore */ 171 | if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) 172 | ereport(ERROR, 173 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 174 | errmsg("set-valued function called in context that cannot accept a set"))); 175 | if (!(rsinfo->allowedModes & SFRM_Materialize)) 176 | ereport(ERROR, 177 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 178 | errmsg("materialize mode required, but it is not " \ 179 | "allowed in this context"))); 180 | 181 | 182 | /* Build a tuple descriptor for our result type */ 183 | if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) 184 | elog(ERROR, "return type must be a row type"); 185 | Assert(tupdesc->natts == NUM_NETSTAT_COLS); 186 | 187 | /* Switch into long-lived context to construct returned data structures */ 188 | per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; 189 | oldcontext = MemoryContextSwitchTo(per_query_ctx); 190 | 191 | tupstore = tuplestore_begin_heap(true, false, work_mem); 192 | rsinfo->returnMode = SFRM_Materialize; 193 | rsinfo->setResult = tupstore; 194 | rsinfo->setDesc = tupdesc; 195 | 196 | MemoryContextSwitchTo(oldcontext); 197 | 198 | netstat = get_netstat(netstat); 199 | 200 | foreach(lc, netstat) 201 | { 202 | NetStat *ns = (NetStat *) lfirst(lc); 203 | int i; 204 | 205 | memset(values, 0, sizeof(values)); 206 | memset(nulls, false, sizeof(nulls)); 207 | 208 | i = 0; 209 | values[i++] = CStringGetTextDatum(ns->iface); 210 | values[i++] = Int64GetDatum(ns->mtu); 211 | values[i++] = Int64GetDatum(ns->rx_ok); 212 | values[i++] = Int64GetDatum(ns->rx_err); 213 | values[i++] = Int64GetDatum(ns->rx_drp); 214 | values[i++] = Int64GetDatum(ns->rx_ovr); 215 | values[i++] = Int64GetDatum(ns->tx_ok); 216 | values[i++] = Int64GetDatum(ns->tx_err); 217 | values[i++] = Int64GetDatum(ns->tx_drp); 218 | values[i++] = Int64GetDatum(ns->tx_ovr); 219 | values[i++] = CStringGetTextDatum(ns->flg); 220 | 221 | Assert(i == NUM_NETSTAT_COLS); 222 | tuplestore_putvalues(tupstore, tupdesc, values, nulls); 223 | pfree(ns); 224 | } 225 | 226 | return (Datum) 0; 227 | } 228 | 229 | /* 230 | * Display iostat 231 | */ 232 | #define NUM_IOSTAT_COLS 8 233 | 234 | Datum 235 | pg_iostat(PG_FUNCTION_ARGS) 236 | { 237 | ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; 238 | TupleDesc tupdesc; 239 | Tuplestorestate *tupstore; 240 | MemoryContext per_query_ctx; 241 | MemoryContext oldcontext; 242 | Datum values[NUM_IOSTAT_COLS]; 243 | bool nulls[NUM_IOSTAT_COLS]; 244 | List *iostat = NIL; 245 | ListCell *lc; 246 | 247 | /* check to see if caller supports us returning a tuplestore */ 248 | if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) 249 | ereport(ERROR, 250 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 251 | errmsg("set-valued function called in context that cannot accept a set"))); 252 | if (!(rsinfo->allowedModes & SFRM_Materialize)) 253 | ereport(ERROR, 254 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 255 | errmsg("materialize mode required, but it is not " \ 256 | "allowed in this context"))); 257 | 258 | 259 | /* Build a tuple descriptor for our result type */ 260 | if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) 261 | elog(ERROR, "return type must be a row type"); 262 | Assert(tupdesc->natts == NUM_IOSTAT_COLS); 263 | 264 | /* Switch into long-lived context to construct returned data structures */ 265 | per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; 266 | oldcontext = MemoryContextSwitchTo(per_query_ctx); 267 | 268 | tupstore = tuplestore_begin_heap(true, false, work_mem); 269 | rsinfo->returnMode = SFRM_Materialize; 270 | rsinfo->setResult = tupstore; 271 | rsinfo->setDesc = tupdesc; 272 | 273 | MemoryContextSwitchTo(oldcontext); 274 | 275 | iostat = get_iostat(iostat); 276 | 277 | foreach(lc, iostat) 278 | { 279 | IoStat *is = (IoStat *) lfirst(lc); 280 | int i; 281 | 282 | memset(values, 0, sizeof(values)); 283 | memset(nulls, false, sizeof(nulls)); 284 | 285 | i = 0; 286 | values[i++] = CStringGetTextDatum(is->device); 287 | 288 | values[i++] = Float4GetDatum(is->tps); 289 | values[i++] = Float4GetDatum(is->kb_read_s); 290 | values[i++] = Float4GetDatum(is->kb_wrtn_s); 291 | values[i++] = Float4GetDatum(is->kb_dscd_s); 292 | 293 | values[i++] = Int64GetDatum(is->kb_read); 294 | values[i++] = Int64GetDatum(is->kb_wrtn); 295 | values[i++] = Int64GetDatum(is->kb_dscd); 296 | 297 | Assert(i == NUM_IOSTAT_COLS); 298 | tuplestore_putvalues(tupstore, tupdesc, values, nulls); 299 | pfree(is); 300 | } 301 | 302 | return (Datum) 0; 303 | } 304 | 305 | 306 | /* 307 | * Display mpstat -P ALL 308 | */ 309 | #define NUM_MPSTAT_COLS 11 310 | 311 | Datum 312 | pg_mpstat(PG_FUNCTION_ARGS) 313 | { 314 | ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; 315 | TupleDesc tupdesc; 316 | Tuplestorestate *tupstore; 317 | MemoryContext per_query_ctx; 318 | MemoryContext oldcontext; 319 | Datum values[NUM_MPSTAT_COLS]; 320 | bool nulls[NUM_MPSTAT_COLS]; 321 | List *mpstat = NIL; 322 | ListCell *lc; 323 | 324 | /* check to see if caller supports us returning a tuplestore */ 325 | if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) 326 | ereport(ERROR, 327 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 328 | errmsg("set-valued function called in context that cannot accept a set"))); 329 | if (!(rsinfo->allowedModes & SFRM_Materialize)) 330 | ereport(ERROR, 331 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 332 | errmsg("materialize mode required, but it is not " \ 333 | "allowed in this context"))); 334 | 335 | 336 | /* Build a tuple descriptor for our result type */ 337 | if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) 338 | elog(ERROR, "return type must be a row type"); 339 | Assert(tupdesc->natts == NUM_MPSTAT_COLS); 340 | 341 | /* Switch into long-lived context to construct returned data structures */ 342 | per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; 343 | oldcontext = MemoryContextSwitchTo(per_query_ctx); 344 | 345 | tupstore = tuplestore_begin_heap(true, false, work_mem); 346 | rsinfo->returnMode = SFRM_Materialize; 347 | rsinfo->setResult = tupstore; 348 | rsinfo->setDesc = tupdesc; 349 | 350 | MemoryContextSwitchTo(oldcontext); 351 | 352 | mpstat = get_mpstat(mpstat); 353 | 354 | foreach(lc, mpstat) 355 | { 356 | MpStat *ms = (MpStat *) lfirst(lc); 357 | int i; 358 | 359 | memset(values, 0, sizeof(values)); 360 | memset(nulls, false, sizeof(nulls)); 361 | 362 | i = 0; 363 | values[i++] = CStringGetTextDatum(ms->cpu); 364 | 365 | values[i++] = Float4GetDatum(ms->usr); 366 | values[i++] = Float4GetDatum(ms->nice); 367 | values[i++] = Float4GetDatum(ms->sys); 368 | values[i++] = Float4GetDatum(ms->iowait); 369 | values[i++] = Float4GetDatum(ms->irq); 370 | values[i++] = Float4GetDatum(ms->soft); 371 | values[i++] = Float4GetDatum(ms->steal); 372 | values[i++] = Float4GetDatum(ms->guest); 373 | values[i++] = Float4GetDatum(ms->gnice); 374 | values[i++] = Float4GetDatum(ms->idle); 375 | 376 | Assert(i == NUM_MPSTAT_COLS); 377 | tuplestore_putvalues(tupstore, tupdesc, values, nulls); 378 | pfree(ms); 379 | } 380 | 381 | return (Datum) 0; 382 | } 383 | --------------------------------------------------------------------------------