pax_global_header00006660000000000000000000000064144671700640014523gustar00rootroot0000000000000052 comment=752d769aa4d7a438c708c40485e0733501d71389 pgnodemx-1.6/000077500000000000000000000000001446717006400132125ustar00rootroot00000000000000pgnodemx-1.6/.gitignore000066400000000000000000000000111446717006400151720ustar00rootroot00000000000000*.o *.so pgnodemx-1.6/LICENSE.md000066400000000000000000000250331446717006400146210ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2020-2023 Crunchy Data Solutions, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. pgnodemx-1.6/Makefile000066400000000000000000000023061446717006400146530ustar00rootroot00000000000000ifdef USE_PGXS PG_CONFIG = pg_config datadir := $(shell $(PG_CONFIG) --sharedir) else top_builddir = ../.. include $(top_builddir)/src/Makefile.global endif MODULE_big = pgnodemx OBJS = pgnodemx.o cgroup.o envutils.o fileutils.o genutils.o kdapi.o parseutils.o procfunc.o PG_CPPFLAGS = -I$(libpq_srcdir) PATH_TO_FILE = $(datadir)/extension/pg_proctab.control ifeq ($(shell test -e $(PATH_TO_FILE) && echo -n yes),yes) EXTENSION = pgnodemx pg_proctab--0.0.10-compat else EXTENSION = pgnodemx pg_proctab--0.0.10-compat pg_proctab endif DATA = pgnodemx--1.0--1.1.sql pgnodemx--1.1--1.2.sql pgnodemx--1.2--1.3.sql pgnodemx--1.3--1.4.sql pgnodemx--1.4--1.5.sql pgnodemx--1.5--1.6.sql pgnodemx--1.6.sql pg_proctab--0.0.10-compat.sql GHASH := $(shell git rev-parse --short HEAD) ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) else subdir = contrib/pgnodemx top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif ifeq ($(strip $(VSTR)),) ifneq ($(strip $(GHASH)),) override CPPFLAGS += -DGIT_HASH=\"$(GHASH)\" else override CPPFLAGS += -DGIT_HASH=\"none\" endif else override CPPFLAGS += -DGIT_HASH=\"$(VSTR)\" endif pgnodemx-1.6/README.md000066400000000000000000000323601446717006400144750ustar00rootroot00000000000000# pgnodemx ## Overview SQL functions that allow capture of node OS metrics from PostgreSQL ## Security Executing role must have been granted pg_monitor membership (pgmonitor for PostgreSQL version 9.6 and below - see Compatibility section below). ## cgroup Related Functions For detailed information about the various virtual files available on the cgroup file system, see: * cgroup v1: https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/index.html * cgroup v2: https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html ### General Access Functions cgroup virtual files fall into (at least) the following general categories, each with a generic SQL access function: * BIGINT single line scalar values - ```SELECT cgroup_scalar_bigint(filename);``` * cgroup v1 examples: blkio.leaf_weight, blkio.weight, cpuacct.usage, cpuacct.usage_percpu, cpuacct.usage_percpu_sys, cpuacct.usage_percpu_user, cpuacct.usage_sys, cpuacct.usage_user, cpu.cfs_period_us, cpu.cfs_quota_us, cpu.rt_period_us, cpu.rt_runtime_us, cpu.shares, cpuacct.usage, memory.failcnt, memory.kmem.failcnt, memory.kmem.limit_in_bytes, memory.kmem.max_usage_in_bytes, memory.kmem.tcp.failcnt, memory.kmem.tcp.limit_in_bytes, memory.kmem.tcp.max_usage_in_bytes, memory.kmem.tcp.usage_in_bytes, memory.kmem.usage_in_bytes, memory.limit_in_bytes, memory.max_usage_in_bytes, memory.memsw.failcnt, memory.memsw.limit_in_bytes, memory.memsw.max_usage_in_bytes, memory.memsw.usage_in_bytes, memory.move_charge_at_immigrate, memory.soft_limit_in_bytes, memory.usage_in_bytes, net_cls.classid, net_prio.prioidx * cgroup v2 examples: cgroup.freeze, cgroup.max.depth, cgroup.max.descendants, cpu.weight, cpu.weight.nice, memory.current, memory.high, memory.low, memory.max, memory.min, memory.oom.group, memory.swap.current, memory.swap.max, pids.current, pids.max * FLOAT8 single line scalar values - ```SELECT cgroup_scalar_float8(filename);``` * cgroup v1 examples: (none known) * cgroup v2 examples: cpu.uclamp.max, cpu.uclamp.min * TEXT single line scalar values - ```SELECT cgroup_scalar_text(filename);``` * cgroup v1 examples: (none known) * cgroup v2 examples: cgroup.type * SETOF(BIGINT) multiline scalar values - ```SELECT * FROM cgroup_setof_bigint(filename);``` * cgroup v1 examples: cgroup.procs * cgroup v2 examples: cgroup.procs, cgroup.threads * SETOF(TEXT) multiline scalar values - ```SELECT * FROM cgroup_setof_text(filename);``` * cgroup v1 examples: (none known) * cgroup v2 examples: (none known) * ARRAY[BIGINT] space separated values - ```SELECT cgroup_array_bigint(filename);``` * cgroup v1 examples: (none known) * cgroup v2 examples: cpu.max * ARRAY[TEXT] space separated values - ```SELECT cgroup_array_text(filename)``` * cgroup v1 examples: cpuacct.usage_all (sort of) * cgroup v2 examples: cgroup.controllers, cgroup.subtree_control * SETOF(TEXT, BIGINT) flat keyed - ```SELECT * FROM cgroup_setof_kv(filename);``` * cgroup v1 examples: cpuacct.stat, cpu.stat, cpuacct.stat, memory.oom_control, memory.stat, net_prio.ifpriomap, blkio.io_merged, blkio.io_merged_recursive, blkio.io_queued, blkio.io_queued_recursive, blkio.io_service_bytes, blkio.io_service_bytes_recursive, blkio.io_serviced, blkio.io_serviced_recursive, blkio.io_service_time, blkio.io_service_time_recursive, blkio.io_wait_time, blkio.io_wait_time_recursive * cgroup v2 examples: cgroup.events, cgroup.stat, cpu.stat, io.pressure, io.weight, memory.events, memory.events.local, memory.stat, memory.swap.events, pids.events * SETOF(TEXT, TEXT, BIGINT) key/subkey/value space separated - ```SELECT * FROM cgroup_setof_ksv(filename);``` * cgroup v1 examples: blkio.throttle.io_service_bytes, blkio.throttle.io_serviced * cgroup v2 examples: (none known) * SETOF(TEXT, TEXT, FLOAT8) nested keyed - ```SELECT * FROM cgroup_setof_nkv(filename);``` * cgroup v1 examples: (none known) * cgroup v2 examples: memory.pressure, cpu.pressure, io.max, io.stat In each case, the filename must be in the form ```.```, e.g. ```memory.stat```. ### Get status of cgroup support ``` SELECT current_setting('pgnodemx.cgroup_enabled'); ``` * Returns boolean result ("on"/"off"). * This value may be explicitly set in postgresql.conf * However the extension will disable it at runtime if the location pointed to by pgnodemx.cgrouproot does not exist or is not a valid cgroup v1 or v2 mount. ### Get current cgroup mode ``` SELECT cgroup_mode(); ``` * Returns the current cgroup mode. Possible values are "legacy", "unified", "hybrid", and "disabled". These correspond to cgroup v1, cgroup v2, mixed, and disabled, respectively. * Currently "hybrid" mode is not supported; it might be in the future. ### Determine if Running Containerized ``` SELECT current_setting('pgnodemx.containerized'); ``` * Returns boolean result ("on"/"off"). The extension attempts to heuristically determine whether PostgreSQL is running under a container, but this value may be explicitly set in postgresql.conf to override the heuristically determined value. The value of this setting influences the cgroup paths which are used to read the cgroup controller files. ### Get cgroup Paths ``` SELECT controller, path FROM cgroup_path(); ``` * Returns the path to each supported cgroup controller. ### Get cgroup process count ``` SELECT cgroup_process_count(); ``` * Returns the number of processes assigned to the cgroup * For cgroup v1, based on the "memory" controller cgroup.procs file. For cgroup v2, based on the unified cgroup.procs file. ## Environment Variable Related Functions ### Get Environment Variable as TEXT ``` SELECT envvar_text('PGDATA'); ``` * Returns the value of requested environment variable as TEXT ### Get Environment Variable as BIGINT ``` SELECT envvar_bigint('PGPORT'); ``` * Returns the value of requested environment variable as BIGINT ## ```/proc``` Related Functions For more detailed information about the /proc file system virtual files, please see: https://www.kernel.org/doc/html/latest/filesystems/proc.html ### Get "/proc/diskstats" as a virtual table ``` SELECT * FROM proc_diskstats(); ``` ### Get "/proc/self/mountinfo" as a virtual table ``` SELECT * FROM proc_mountinfo(); ``` ### Get "/proc/meminfo" as a virtual table ``` SELECT * FROM proc_meminfo(); ``` ### Get "/proc/self/net/dev" as a virtual table ``` SELECT * FROM proc_network_stats(); ``` ### Get "/proc/\/io" for all PostgreSQL processes as a virtual table ``` SELECT * FROM proc_pid_io(); ``` ### Get the full command line, uid, and username for all PostgreSQL processes as a virtual table ``` SELECT * FROM proc_pid_cmdline(); ``` ### Get "/proc/\/stat" for all PostgreSQL processes as a virtual table ``` SELECT * FROM proc_pid_stat(); ``` ### Get first line of "/proc/stat" as a virtual table ``` SELECT * FROM proc_cputime(); ``` ### Get first line of "/proc/loadavg" as a virtual table ``` SELECT * FROM proc_loadavg(); ``` ## pg_proctab Compatibility Functions for use with pg_top Five functions are provided in an extension that match the SQL interface presented by the pg_proctab extension. ``` CREATE EXTENSION pg_proctab VERSION "0.0.10-compat"; SELECT * FROM pg_cputime(); SELECT * FROM pg_loadavg(); SELECT * FROM pg_memusage(); SELECT * FROM pg_diskusage(); SELECT * FROM pg_proctab(); ``` These functions are not installed by default. They may be installed by installing pg_proctab VERSION "0.0.10-compat" after installing the pgnodemx extension. ## System Information Related Functions ### Get file system information as a virtual table ``` SELECT * FROM fsinfo(path text); ``` * Returns major_number, minor_number, type, block_size, blocks, total_bytes, free_blocks, free_bytes, available_blocks, available_bytes, total_file_nodes, free_file_nodes, and mount_flags for the file system on which ```path``` is mounted. Note: Some filesystems can return unexpected values (like MAX_UINT64) if these numbers cannot be determined. ### Get current FIPS mode ``` SELECT fips_mode(); ``` * Returns TRUE if openssl is currently running in FIPS mode, otherwise FALSE. ### Get openssl version string ``` select openssl_version(); ``` ### Get source C library path for a function symbol ``` SELECT symbol_filename(sym_name text); ``` * Returns the source C library from whence the C function sym_name comes. Returns NULL on any errors. ### Convert number of kernel memory pages to bytes ``` SELECT kpages_to_bytes(num_k_pages numeric); ``` ## Kubernetes DownwardAPI Related Functions For more detailed information about the Kubernetes DownwardAPI please see: https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/ ### Get status of kdapi_enabled ``` SELECT current_setting('pgnodemx.kdapi_enabled'); ``` * Returns boolean result ("on"/"off"). * This value may be explicitly set in postgresql.conf * However the extension will disable it at runtime if the location pointed to by pgnodemx.kdapi_path does not exist. ### Access "key equals quoted value" files ``` SELECT * FROM kdapi_setof_kv('filename'); ``` ### Get scalar BIGINT from file ``` SELECT kdapi_scalar_bigint('filename text'); ``` ## General Information Functions ### Get pgnodemx version information ``` SELECT pgnodemx_version(); ``` * If VSTR environment variable is set at compile time, returns that value * Otherwise returns the value of the short git hash * If not compiling from the git repository and VSTR is unset, returns "none" ### Get currently running PostgreSQL executable path ``` SELECT exec_path(); ``` ### Get uid, username, gid, groupname, and filemode for a file ``` SELECT * FROM stat_file(filename); ``` ## Configuration * Add pgnodemx to shared_preload_libraries in postgresql.conf. ``` shared_preload_libraries = 'pgnodemx' ``` * The following custom parameters may be set. The values shown are defaults. If the default values work for you, there is no need to add these to ```postgresql.conf```. ``` # enable or disable the cgroup facility pgnodemx.cgroup_enabled = on # force use of "containerized" assumptions for cgroup file paths pgnodemx.containerized = off # specify location of cgroup mount pgnodemx.cgrouproot = '/sys/fs/cgroup' # enable cgroup functions pgnodemx.cgroup_enabled = on # enable or disable the Kubernetes DownwardAPI facility pgnodemx.kdapi_enabled = on # specify location of Kubernetes DownwardAPI files pgnodemx.kdapi_path = '/etc/podinfo' ``` Notes: * If pgnodemx.cgroup_enabled is defined in ```postgresql.conf```, and set to ```off``` (or ```false```), then all cgroup* functions will return NULL, or zero rows, except cgroup_mode() which will return "disabled". * If ```pgnodemx.containerized``` is defined in ```postgresql.conf```, that value will override pgnodemx heuristics. When not specified, pgnodemx heuristics will determine if the value should be ```on``` or ```off``` at runtime. * If the location specified by ```pgnodemx.cgrouproot```, default or as set in ```postgresql.conf```, is not accessible (does not exist, or otherwise causes an error when accessed), then pgnodemx.cgroup_enabled is forced to ```off``` at runtime and all cgroup* functions will return NULL, or zero rows, except cgroup_mode() which will return "disabled". * If the location specified by ```pgnodemx.kdapi_path```, default or as set in ```postgresql.conf```, is not accessible (does not exist, or otherwise causes an error when accessed), then pgnodemx.kdapi_enabled is forced to ```off``` at runtime and all kdapi* functions will return NULL, or zero rows. ## Installation ### Compatibility * PostgreSQL version 9.5 or newer is required. * On PostgreSQL version 9.6 or earlier, a role called pgmonitor must be created, and the user calling these functions must be granted that role. ### Compile and Install Clone PostgreSQL repository: ```bash $> git clone https://github.com/postgres/postgres.git ``` Checkout REL_12_STABLE (for example) branch: ```bash $> git checkout REL_12_STABLE ``` Make PostgreSQL: ```bash $> ./configure $> make install -s ``` Change to the contrib directory: ```bash $> cd contrib ``` Clone ```pgnodemx``` extension: ```bash $> git clone https://github.com/crunchydata/pgnodemx ``` Change to ```pgnodemx``` directory: ```bash $> cd pgnodemx ``` Build ```pgnodemx```: ```bash $> make ``` Install ```pgnodemx```: ```bash $> make install ``` #### Using PGXS If an instance of PostgreSQL is already installed, then PGXS can be utilized to build and install ```pgnodemx```. Ensure that PostgreSQL binaries are available via the ```$PATH``` environment variable then use the following commands. ```bash $> make USE_PGXS=1 $> make USE_PGXS=1 install ``` ### Configure The following bash commands should configure your system to utilize pgnodemx. Replace all paths as appropriate. It may be prudent to visually inspect the files afterward to ensure the changes took place. ###### Initialize PostgreSQL (if needed): ```bash $> initdb -D /path/to/data/directory ``` ###### Create Target Database (if needed): ```bash $> createdb ``` ###### Install ```pgnodemx``` functions: Edit postgresql.conf and add ```pgnodemx``` to the shared_preload_libraries line, and change custom settings as mentioned above. Finally, restart PostgreSQL (method may vary): ``` $> service postgresql restart ``` Install the extension into your database: ```bash psql CREATE EXTENSION pgnodemx; ``` ## TODO * Map more ```/proc``` files to virtual tables * Add support for "hybrid" cgroup mode pgnodemx-1.6/cgroup.c000066400000000000000000000610451446717006400146630ustar00rootroot00000000000000/* * cgroup.c * * Functions specific to capture and manipulation of cgroup virtual files * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #include "postgres.h" #include #include #ifndef CGROUP2_SUPER_MAGIC #define CGROUP2_SUPER_MAGIC 0x63677270 #endif #include #include #if PG_VERSION_NUM < 150000 #include "utils/int8.h" #endif #if PG_VERSION_NUM >= 110000 #include "catalog/pg_type_d.h" #else #include "catalog/pg_type.h" #endif #include "fmgr.h" #if PG_VERSION_NUM >= 130000 #include "lib/qunique.h" #else /* did not exist prior to pg13; use local copy */ #include "qunique.h" #endif #include "lib/stringinfo.h" #include "utils/builtins.h" #include "utils/guc_tables.h" #include "utils/memutils.h" #if PG_VERSION_NUM >= 100000 #include "utils/varlena.h" #endif #include "fileutils.h" #include "genutils.h" #include "parseutils.h" #include "cgroup.h" #define DEFCONTROLLER "memory" /* context gathering functions */ static void create_default_cgpath(char *str, int curlen); static void init_or_reset_cgpath(void); static StringInfo candidate_controller_path(char *controller, char *r); static StringInfo check_and_fix_controller_path(char *controller, char *r); /* custom GUC vars */ bool containerized = false; char *cgrouproot = NULL; bool cgroup_enabled = true; /* module globals */ char *cgmode = NULL; kvpairs *cgpath = NULL; /* * Take input filename from caller, make sure it is acceptable * (not absolute, no relative parent references, caller belongs * to correct role), and concatenates it with the path to the * related controller in the cgroup filesystem. The returned * value is a "fully qualified" path to the file of interest * for the purposes of cgroup virtual files. */ char * get_fq_cgroup_path(FunctionCallInfo fcinfo) { StringInfo ftr = makeStringInfo(); char *fname = convert_and_check_filename(PG_GETARG_TEXT_PP(0), false); char *p = strchr(fname, '.'); Size len; char *controller; if (!p) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: missing \".\" in filename %s", PROC_CGROUP_FILE))); len = (p - fname); controller = pnstrdup(fname, len); appendStringInfo(ftr, "%s/%s", get_cgpath_value(controller), fname); return pstrdup(ftr->data); } /* * Find out all the pids in a cgroup. * * In cgroup v2 (at least) cgroup.procs is not sorted or guaranteed unique. * Remedy that. *pids is set to point to a palloc'd array containing * distinct pids in sorted order. The length of the array is the * function result. Cribbed from aclmembers. */ int cgmembers(int64 **pids) { int64 *list; int i; StringInfo ftr = makeStringInfo(); int nlines; char **lines; appendStringInfo(ftr, "%s/%s", get_cgpath_value("cgroup"), "cgroup.procs"); lines = read_nlsv(ftr->data, &nlines); if (nlines == 0) { /* * This should never happen, by definition. If it does * die horribly... */ ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no cgroup procs found in file %s", ftr->data))); } /* Allocate the worst-case space requirement */ list = (int64 *) palloc(nlines * sizeof(int64)); /* * Walk the string array collecting PIDs. */ for (i = 0; i < nlines; i++) { bool success = false; int64 result; #if PG_VERSION_NUM >= 150000 char *endptr = NULL; #endif #if PG_VERSION_NUM < 150000 success = scanint8(lines[i], true, &result); #else errno = 0; result = strtoi64(lines[i], &endptr, 10); if (errno == 0 && *endptr == '\0') success = true; #endif if (!success) ereport(ERROR, (errcode_for_file_access(), errmsg("contents not an integer, file \"%s\"", ftr->data))); list[i] = result; } /* Sort the array */ qsort(list, nlines, sizeof(int64), int64_cmp); /* * We could repalloc the array down to minimum size, but it's hardly worth * it since it's only transient memory. */ *pids = list; /* Remove duplicates from the array, returns new size */ return qunique(list, nlines, sizeof(int64), int64_cmp); } /* * Determine whether running inside a container. * * Of particular interest to us is whether our cgroup vfs has been mounted * at /sys/fs/cgroup for us. Inside a container that is what we expect, * but outside of a container it will be where PROC_CGROUP_FILE tells * us to find it. */ void set_containerized(void) { /* * If containerized was explicitly set in postgresql.conf, allow that * value to preside. */ struct config_generic *record; #if PG_VERSION_NUM < 160000 record = find_option("pgnodemx.containerized"); #else record = find_option("pgnodemx.containerized", false, false, ERROR); #endif if (record->source == PGC_S_FILE) return; /* * Check to see if path referenced in PROC_CGROUP_FILE exists. * If it does, we are presumably not in a container, else we are. * In either case, the important distinction is whether we will * find the controller files in that location. If the location * does not exist the files are found under cgrouproot directly. */ if (is_cgroup_v1 || is_cgroup_v2) { StringInfo str = makeStringInfo(); /* cgroup v1 and v2 will have differences we need to account for */ if (is_cgroup_v1) { int nlines; char **lines = read_nlsv(PROC_CGROUP_FILE, &nlines); if (nlines > 0) { int i; for (i = 0; i < nlines; ++i) { /* use the DEFCONTROLLER controller path to test with */ char *line = lines[i]; char *p = strchr(line, ':'); /* advance past the colon */ if (p) p += 1; if (strncmp(p, DEFCONTROLLER, 6) == 0) { p = strchr(p, ':'); /* advance past the colon and "/" */ if (p) p += 2; appendStringInfo(str, "%s/%s/%s", cgrouproot, DEFCONTROLLER, p); break; } } } else ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no cgroup paths found in file %s", PROC_CGROUP_FILE))); if (access(str->data, F_OK) != -1) containerized = false; else containerized = true; } else if (is_cgroup_v2) { char *rawstr; /* in cgroup v2 there should only be one entry */ rawstr = read_one_nlsv(PROC_CGROUP_FILE); appendStringInfo(str, "%s/%s", cgrouproot, (rawstr + 4)); } if (access(str->data, F_OK) != -1) containerized = false; else containerized = true; return; } else { /* hybrid mode; means not in a container */ containerized = false; return; } } /* * Determine whether running with cgroup v1, v2, or systemd hybrid mode */ bool set_cgmode(void) { /* * From: https://systemd.io/CGROUP_DELEGATION/ * * To detect which of three modes is currently used, use statfs() * on /sys/fs/cgroup/. If it reports CGROUP2_SUPER_MAGIC in its * .f_type field, then you are in unified mode. If it reports * TMPFS_MAGIC then you are either in legacy or hybrid mode. To * distinguish these two cases, run statfs() again on * /sys/fs/cgroup/unified/. If that succeeds and reports * CGROUP2_SUPER_MAGIC you are in hybrid mode, otherwise not. */ struct statfs buf; int ret; /* * If requested, directly set cgmode to disabled before * doing anything else. */ if (!cgroup_enabled) { cgmode = MemoryContextStrdup(TopMemoryContext, CGROUP_DISABLED); return false; } ret = statfs(cgrouproot, &buf); if (ret == -1) { /* * If we have an error trying to stat cgrouproot, there is not * much else we can do besides disabling cgroup access. */ ereport(WARNING, (errcode_for_file_access(), errmsg("pgnodemx: statfs error on cgroup mount %s: %m", cgrouproot), errdetail("disabling cgroup virtual file system access"))); cgmode = MemoryContextStrdup(TopMemoryContext, CGROUP_DISABLED); return false; } if (buf.f_type == CGROUP2_SUPER_MAGIC) /* cgroup v2 */ { char *ftr = PROC_CGROUP_FILE; int nlines; /* * From what I have read, this should not ever happen. * However it was reported from the field, so apparently * it *can* happen. * * In any case, it seems to indicate hybrid mode is in effect. */ read_nlsv(ftr, &nlines); if (nlines != 1) { cgmode = MemoryContextStrdup(TopMemoryContext, CGROUP_HYBRID); return false; } cgmode = MemoryContextStrdup(TopMemoryContext, CGROUP_V2); return true; } else if (buf.f_type == TMPFS_MAGIC) { StringInfo str = makeStringInfo(); appendStringInfo(str, "%s/%s", cgrouproot, "unified"); ret = statfs(str->data, &buf); if (ret == 0 && buf.f_type == CGROUP2_SUPER_MAGIC) /* hybrid mode */ { cgmode = MemoryContextStrdup(TopMemoryContext, CGROUP_HYBRID); return false; } else /* cgroup v1 */ { cgmode = MemoryContextStrdup(TopMemoryContext, CGROUP_V1); return true; } } else { /* * If cgrouproot is not actually a cgroup mount, there is not * much else we can do besides disabling cgroup access. */ ereport(WARNING, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: unexpected mount type on cgroup root %s", cgrouproot), errdetail("disabling cgroup virtual file system access"))); cgmode = MemoryContextStrdup(TopMemoryContext, CGROUP_DISABLED); return false; } } /* * Expand cgpath by one element and populate with a default * path. str is the path to use for the default and curlen * is the pre-expanded number of kv pairs. */ static void create_default_cgpath(char *str, int curlen) { /* add room */ cgpath->nkvp = curlen + 1; cgpath->keys = (char **) repalloc(cgpath->keys, cgpath->nkvp * sizeof(char *)); cgpath->values = (char **) repalloc(cgpath->values, cgpath->nkvp * sizeof(char *)); /* create the default record */ cgpath->keys[cgpath->nkvp - 1] = MemoryContextStrdup(TopMemoryContext, "cgroup"); if (str != NULL) cgpath->values[cgpath->nkvp - 1] = MemoryContextStrdup(TopMemoryContext, str); else cgpath->values[cgpath->nkvp - 1] = MemoryContextStrdup(TopMemoryContext, "Default_Controller_Not_Found"); } static void init_or_reset_cgpath(void) { if (cgpath == NULL) { /* initialize in TopMemoryContext */ cgpath = (kvpairs *) MemoryContextAlloc(TopMemoryContext, sizeof(kvpairs)); cgpath->nkvp = 0; cgpath->keys = (char **) MemoryContextAlloc(TopMemoryContext, 0); cgpath->values = (char **) MemoryContextAlloc(TopMemoryContext, 0); } else { int i; /* deep clear any existing info */ for (i = 0; i < cgpath->nkvp; ++i) { if (cgpath->keys[i]) pfree(cgpath->keys[i]); if (cgpath->values[i]) pfree(cgpath->values[i]); } if (cgpath->keys) cgpath->keys = (char **) repalloc(cgpath->keys, 0); if (cgpath->values) cgpath->values = (char **) repalloc(cgpath->values, 0); cgpath->nkvp = 0; } } /* * Take an array of int, and copy it. */ static int * intarr_copy(int *oldarr, size_t oldsize) { int *newarr; int sizebytes = oldsize * sizeof(int); Assert(oldarr != NULL); Assert(sizebytes != 0); newarr = (int *) palloc(sizebytes); memcpy(newarr, oldarr, sizebytes); return newarr; } static void swap (int *arr, int a, int b) { int t = arr[a]; arr[a] = arr[b]; arr[b] = t; } /* * Generate permutations of origarr indexes, and return them as an array * of integer array. Each array represents the indexes for a different * permutation. Use's "heap's algorithm". * See https://en.wikipedia.org/wiki/Heap%27s_algorithm */ static void heap_permute(int *origarr, size_t origarrsize, size_t level, int **arrofpermarr, int *nrow) { int i; if (level == 1) { /* * We have recursed to the end of the original array of indexes, * so attach our permutation to the array of arrays and return it. */ arrofpermarr[*nrow] = intarr_copy(origarr, origarrsize); ++(*nrow); } else { /* * Generate permutations with levelth unaltered * Initially level == length(origarr) == origarrsize */ heap_permute(origarr, origarrsize, level - 1, arrofpermarr, nrow); for (i = 0; i < level - 1; ++i) { /* Swap choice dependent on parity of level (even or odd) */ if (level % 2 == 0) { /* * If level is even, swap ith and * (level-1)th i.e (last) element */ swap(origarr, i, level-1); } else { /* * If level is odd, swap 0th i.e (first) and (level-1)th * i.e (last) element */ swap(origarr, 0, level-1); } heap_permute(origarr, origarrsize, level - 1, arrofpermarr, nrow); } } } /* * Accept a string list (comma delimited list of items) * and return an array of strings representing all of the * different permutation of the original string list. */ #define MAX_PERM_ARRLEN 10 static char *** get_list_permutations(char *controller, int ncol, int *nrow) { char *rawstring = pstrdup(controller); List *origlist = NIL; ListCell *l; int *origarr = NULL; char **origarr_str = NULL; size_t origarrsize = 0; int **arrofpermarr = NULL; int i; char ***values; int cntr; int fact = 1; StringInfo str = makeStringInfo(); /* * If the controller name includes one or more ",", we need * to check all orderings to see which is the actual path. * * Parse the list into individual tokens */ if (!SplitIdentifierString(rawstring, ',', &origlist)) { elog(WARNING, "failed to parse controller string: %s", controller); return NULL; } origarrsize = list_length(origlist); if (origarrsize > MAX_PERM_ARRLEN) { elog(WARNING, "too many elements in controller string: %s", controller); return NULL; } origarr_str = (char **) palloc(origarrsize * sizeof(char *)); i = 0; foreach(l, origlist) { origarr_str[i] = pstrdup((char *) lfirst(l)); ++i; } origarr = (int *) palloc(origarrsize * sizeof(int)); for (i = 0; i < origarrsize; ++i) origarr[i] = i; /* precalculate how many permutations we should get back */ for (cntr = 1; cntr <= origarrsize; cntr++) fact = fact * cntr; /* make space for the permutation arrays */ arrofpermarr = (int **) palloc(fact * sizeof(int *)); /* get list of permutation indexes */ heap_permute(origarr, origarrsize, origarrsize, arrofpermarr, nrow); if (*nrow != fact) elog(WARNING, "expected %d permutations, got %d", fact, *nrow); /* make space for the return tuples */ values = (char ***) palloc((*nrow) * sizeof(char **)); /* map the original list back to the permuted indexes */ for (i = 0; i < (*nrow); ++i) { int *pidx = arrofpermarr[i]; int j; resetStringInfo(str); for(j = 0; j < origarrsize; ++j) { char *tok = origarr_str[pidx[j]]; if (j == 0) appendStringInfo(str, "%s", tok); else appendStringInfo(str, ",%s", tok); } values[i] = (char **) palloc(ncol * sizeof(char *)); values[i][0] = pstrdup(str->data); pfree(arrofpermarr[i]); } pfree(arrofpermarr); return values; } /* * Create candidate path based on controller string taking into account * whether we are "containerized" or not. */ static StringInfo candidate_controller_path(char *controller, char *r) { StringInfo str = makeStringInfo(); if (!containerized) { /* * not containerized: controller files are in path contained * in PROC_CGROUP_FILE concatenated to "//" */ appendStringInfo(str, "%s/%s/%s", cgrouproot, controller, r); } else { /* * containerized: controller files are in path contained * in "//" directly */ appendStringInfo(str, "%s/%s", cgrouproot, controller); } return str; } /* * Attempt to determine and return a valid path for a cgroup controller. * * If no directories are found, return "Controller_Not_Found" as the * path. If we were to raise an ERROR it would prevent Postgres from starting * since this extension is preloaded, which seems less friendly than causing * later queries to generate errors. For example: * * could not open file "Controller_Not_Found/cpuacct.usage" * for reading: No such file or directory * * At least would clue us in that something went wrong without causing an * outage of postgres itself. */ static StringInfo check_and_fix_controller_path(char *controller, char *r) { StringInfo str = candidate_controller_path(controller, r); if (strchr(controller, ',') == NULL) { /* * The controller name does not include "," and is therefore * a single controller. */ /* * Should not happen (I think), but if the directory does * not exist, mark it as such for debugging purposes. * But avoid throwing an error, which would prevent Postgres * from starting up entirely. */ if (access(str->data, F_OK) != 0) { resetStringInfo(str); appendStringInfoString(str, "Controller_Not_Found"); } return str; } else { /* * The controller name includes "," and is therefore a list * of controllers. It turns out that the list ordering in * /proc/self/cgroup might not match the list ordering used * for the cgroupfs in some circumstances. But first check the * proposed path based on /proc/self/cgroup to see if it * actually exists. If so, return that. */ if (access(str->data, F_OK) == 0) return str; else { /* if not, try the alternative orderings */ char ***values; int nrow = 0; int ncol = 1; int i; values = get_list_permutations(controller, ncol, &nrow); for (i = 0; i < nrow; ++i) { char *pcontroller = values[i][0]; resetStringInfo(str); str = candidate_controller_path(pcontroller, r); if (access(str->data, F_OK) == 0) return str; } /* none of the candidates were valid */ resetStringInfo(str); appendStringInfoString(str, "Controller_Not_Found"); return str; } } } /* CREATE FUNCTION permute_list(TEXT) RETURNS SETOF TEXT AS '$libdir/pgnodemx', 'pgnodemx_permute_list' LANGUAGE C STABLE STRICT; */ /* function return signatures */ Oid cg_text_sig[] = {TEXTOID}; /* debug function */ PG_FUNCTION_INFO_V1(pgnodemx_permute_list); Datum pgnodemx_permute_list(PG_FUNCTION_ARGS) { char *controller = text_to_cstring(PG_GETARG_TEXT_PP(0)); char ***values; int nrow = 0; int ncol = 1; values = get_list_permutations(controller, ncol, &nrow); return form_srf(fcinfo, values, nrow, ncol, cg_text_sig); } void set_cgpath(void) { char *ftr = PROC_CGROUP_FILE; init_or_reset_cgpath(); /* obtain a list of cgroup controllers */ if (is_cgroup_v1) { /* * In cgroup v1 the active controllers for the * cgroup are listed in PROC_CGROUP_FILE. We will * need to read these whether "containerized" or not, * in order to get a complete list of controllers * available. */ int nlines; char **lines; StringInfo str; int i; char *defpath = NULL; lines = read_nlsv(PROC_CGROUP_FILE, &nlines); if (nlines == 0) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no cgroup paths found in file %s", PROC_CGROUP_FILE))); cgpath->nkvp = nlines; cgpath->keys = (char **) repalloc(cgpath->keys, cgpath->nkvp * sizeof(char *)); cgpath->values = (char **) repalloc(cgpath->values, cgpath->nkvp * sizeof(char *)); for (i = 0; i < nlines; ++i) { /* * The lines in PROC_CGROUP_FILE look like: * #::/ * e.g. 2:memory:/foo/bar * Sometimes the part is further divided * into key-value, e.g. "name=systemd" in which case * "systemd" actually corresponds to the directory name. * * Sometimes the part is further divided * into a list of controllers, e.g. "cpu,cpuacct" in which case * the directory name might be based on either ordering of * "cpu" and "cpuacct". In this case more work is required to * discover the actual path in use. */ char *line = lines[i]; char *p = strchr(line, ':'); char *r; char *q; Size len; char *controller; if (p) p += 1; else ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: malformed cgroup path found in file %s", PROC_CGROUP_FILE))); r = strchr(p, ':'); /* advance past the ":" and also the "/" */ if (r) r += 2; else ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: malformed cgroup path found in file %s", PROC_CGROUP_FILE))); len = ((r - p) - 2); controller = pnstrdup(p, len); q = strchr(controller, '='); if (q) controller = q + 1; /* get valid path to controller */ str = check_and_fix_controller_path(controller, r); cgpath->keys[i] = MemoryContextStrdup(TopMemoryContext, controller); cgpath->values[i] = MemoryContextStrdup(TopMemoryContext, str->data); if (strcasecmp(controller, DEFCONTROLLER) == 0) defpath = cgpath->values[i]; } create_default_cgpath(defpath, nlines); } else if (is_cgroup_v2) { /* * In v2 the active controllers for the * cgroup are listed in cgroup.controllers */ StringInfo fname = makeStringInfo(); StringInfo str = makeStringInfo(); int nvals; char **controllers; char *rawstr; char *defpath = NULL; int i; /* read PROC_CGROUP_FILE, which for v2 has one line */ rawstr = read_one_nlsv(ftr); if (!containerized) { /* * not containerized: controller files are in path contained * in PROC_CGROUP_FILE * * cgroup v2 PROC_CGROUP_FILE has one line * that always starts "0::/", so skip that * in order to get the relative path to the * unified set of cgroup controllers */ appendStringInfo(str, "%s/%s", cgrouproot, (rawstr + 4)); defpath = str->data; } else { /* containerized: controller files in cgrouproot directly */ defpath = cgrouproot; } /* * In cgroup v2 all the controllers are in the * same cgroup dir, but we need to determine which * controllers are present in the current cgroup. * It is simpler to just repeat the same path for * each controller in order to maintain consistency * with the cgroup v1 case. */ appendStringInfo(fname, "%s/%s", defpath, "cgroup.controllers"); controllers = parse_space_sep_val_file(fname->data, &nvals); cgpath->nkvp = nvals; cgpath->keys = (char **) repalloc(cgpath->keys, cgpath->nkvp * sizeof(char *)); cgpath->values = (char **) repalloc(cgpath->values, cgpath->nkvp * sizeof(char *)); for (i = 0; i < cgpath->nkvp; ++i) { cgpath->keys[i] = MemoryContextStrdup(TopMemoryContext, controllers[i]); cgpath->values[i] = MemoryContextStrdup(TopMemoryContext, defpath); } create_default_cgpath(defpath, nvals); } else /* unsupported */ { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: unsupported cgroup configuration"))); } } /* * Look up the cgroup path by controller name * Since this should never be a long list, just * do brute force lookup. */ char * get_cgpath_value(char *key) { int i; for (i = 0; i < cgpath->nkvp; ++i) { char *p; char *controller = cgpath->keys[i]; char *path = cgpath->values[i]; /* * If controller name cgpath->keys[i] includes ",", * split into multiple subkeys and check each one. */ p = strchr(controller, ','); if (!p) { /* no subkeys, just do it */ if (strcmp(controller, key) == 0) return pstrdup(path); } else { /* * Multiple subkeys. Check each one, but first get a * copy we can mutate. */ char *buf = pstrdup(controller); char *token; char *lstate; for (token = strtok_r(buf, ",", &lstate); token; token = strtok_r(NULL, ",", &lstate)) { if (strcmp(token, key) == 0) return pstrdup(path); } } } /* bad request if not found */ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("failed to find controller %s", key))); /* unreached */ return "unknown"; } pgnodemx-1.6/cgroup.h000066400000000000000000000040611446717006400146630ustar00rootroot00000000000000/* * cgroup.h * * Functions specific to capture and manipulation of cgroup virtual files * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #ifndef CGROUP_H #define CGROUP_H #include "fmgr.h" #include "parseutils.h" #define PROC_CGROUP_FILE "/proc/self/cgroup" #define CGROUP_V1 "legacy" #define CGROUP_V2 "unified" #define CGROUP_HYBRID "hybrid" #define CGROUP_DISABLED "disabled" #define is_cgroup_v1 (strcmp(cgmode, CGROUP_V1) == 0) #define is_cgroup_v2 (strcmp(cgmode, CGROUP_V2) == 0) #define is_cgroup_hy (strcmp(cgmode, CGROUP_HYBRID) == 0) extern bool set_cgmode(void); extern void set_containerized(void); extern void set_cgpath(void); extern int cgmembers(int64 **pids); extern char *get_cgpath_value(char *key); extern char *get_fq_cgroup_path(FunctionCallInfo fcinfo); /* exported globals */ extern char *cgmode; extern kvpairs *cgpath; extern char *cgrouproot; extern bool containerized; extern bool cgroup_enabled; #endif /* CGROUP_H */ pgnodemx-1.6/envutils.c000066400000000000000000000031371446717006400152330ustar00rootroot00000000000000/* * envutils.c * * Functions specific to capture and manipulation of environment vars * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #include "postgres.h" #include "envutils.h" char * get_string_from_env(char *varname) { const char *value = getenv(varname); if (value) return pstrdup(value); else ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: environment variable not found: %s", varname))); /* never reached */ return "not found"; } pgnodemx-1.6/envutils.h000066400000000000000000000025401446717006400152350ustar00rootroot00000000000000/* * envutils.h * * Functions specific to capture and manipulation of environment vars * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #ifndef ENVUTILS_H #define ENVUTILS_H char *get_string_from_env(char *varname); #endif /* ENVUTILS_H */ pgnodemx-1.6/fileutils.c000066400000000000000000000246611446717006400153670ustar00rootroot00000000000000/* * fileutils.c * * Functions specific to accessing the file system * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #include "postgres.h" #include #ifndef CGROUP2_SUPER_MAGIC #define CGROUP2_SUPER_MAGIC 0x63677270 #endif #ifndef XFS_SUPER_MAGIC #define XFS_SUPER_MAGIC 0x58465342 #endif #include #include #include #include #include #include #include "catalog/pg_authid.h" #include "miscadmin.h" #include "storage/fd.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/memutils.h" #if PG_VERSION_NUM < 100000 #include "access/htup_details.h" #include "utils/syscache.h" #endif #include "fileutils.h" #include "genutils.h" uint64 magic_ids[] = {ADFS_SUPER_MAGIC,AFFS_SUPER_MAGIC,AFS_SUPER_MAGIC, ANON_INODE_FS_MAGIC,AUTOFS_SUPER_MAGIC,BDEVFS_MAGIC, BINFMTFS_MAGIC,BPF_FS_MAGIC, BTRFS_SUPER_MAGIC,BTRFS_TEST_MAGIC,CGROUP_SUPER_MAGIC, CGROUP2_SUPER_MAGIC,CODA_SUPER_MAGIC, CRAMFS_MAGIC,DEBUGFS_MAGIC, DEVPTS_SUPER_MAGIC,ECRYPTFS_SUPER_MAGIC, EFIVARFS_MAGIC,EFS_SUPER_MAGIC, EXT2_SUPER_MAGIC,EXT3_SUPER_MAGIC, EXT4_SUPER_MAGIC,F2FS_SUPER_MAGIC, FUTEXFS_SUPER_MAGIC,HOSTFS_SUPER_MAGIC, HPFS_SUPER_MAGIC,HUGETLBFS_MAGIC,ISOFS_SUPER_MAGIC, JFFS2_SUPER_MAGIC,MINIX_SUPER_MAGIC, MINIX_SUPER_MAGIC2,MINIX2_SUPER_MAGIC,MINIX2_SUPER_MAGIC2, MINIX3_SUPER_MAGIC,MSDOS_SUPER_MAGIC, MTD_INODE_FS_MAGIC,NCP_SUPER_MAGIC,NFS_SUPER_MAGIC, NILFS_SUPER_MAGIC, OPENPROM_SUPER_MAGIC, OVERLAYFS_SUPER_MAGIC,PIPEFS_MAGIC,PROC_SUPER_MAGIC, PSTOREFS_MAGIC,QNX4_SUPER_MAGIC,QNX6_SUPER_MAGIC, RAMFS_MAGIC,REISERFS_SUPER_MAGIC, SECURITYFS_MAGIC,SELINUX_MAGIC,SMACK_MAGIC,SMB_SUPER_MAGIC, SOCKFS_MAGIC,SQUASHFS_MAGIC,SYSFS_MAGIC, TMPFS_MAGIC, USBDEVICE_SUPER_MAGIC,V9FS_MAGIC, XENFS_SUPER_MAGIC,XFS_SUPER_MAGIC,0}; /* keep in sync with magic_ids above */ char *magic_names[] = {"adfs","affs","afs", "anon_inode_fs","autofs","bdevfs", "binfmtfs","bpf_fs", "btrfs","btrfs_test","cgroup", "cgroup2","coda", "cramfs","debugfs", "devpts","ecryptfs", "efivarfs","efs", "ext2","ext3", "ext4","f2fs", "futexfs","hostfs", "hpfs","hugetlbfs","isofs", "jffs2","minix", "minix12","minix2","minix22", "minix3","msdos", "mtd_inode_fs","ncp","nfs", "nilfs", "openprom", "overlayfs","pipefs","proc", "pstorefs","qnx4","qnx6", "ramfs","reiserfs", "securityfs","selinux","smack","smb", "sockfs","squashfs","sysfs", "tmpfs", "usbdevice","v9fs", "xenfs","xfs",NULL}; /* possible mount flags */ uint64 mflags[] = {ST_MANDLOCK,ST_NOATIME,ST_NODEV,ST_NODIRATIME,ST_NOEXEC, ST_NOSUID,ST_RDONLY,ST_RELATIME,ST_SYNCHRONOUS,0}; /* keep in sync with mflags above */ char *mflagnames[] = {"mandlock","noatime","nodev","nodiratime","noexec", "nosuid","rdonly","relatime","synchronous",NULL}; static char *magic_get_name(uint64 magic_id); static char *mount_flags_to_string(uint64 flags); #if PG_VERSION_NUM < 100000 /* * Get role oid from role name, returns NULL for nonexistent role name * if noerr is true. */ static Oid GetRoleIdFromName(char *rolename, bool noerr) { HeapTuple tuple; Oid result; tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename)); if (!HeapTupleIsValid(tuple)) { if (!noerr) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", rolename))); result = InvalidOid; } else { result = HeapTupleGetOid(tuple); ReleaseSysCache(tuple); } return result; } #endif void pgnodemx_check_role(void) { #if PG_VERSION_NUM < 100000 #define PGNODEMX_MONITOR_ROLE "pgmonitor" Oid checkoid = GetRoleIdFromName(PGNODEMX_MONITOR_ROLE, true); if (checkoid == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("role %s does not exist", PGNODEMX_MONITOR_ROLE))); #else /* PG_VERSION_NUM >= 100000 */ #define PGNODEMX_MONITOR_ROLE "pg_monitor" #if PG_VERSION_NUM >= 140000 Oid checkoid = ROLE_PG_MONITOR; #else /* PG_VERSION_NUM < 140000 */ Oid checkoid = DEFAULT_ROLE_MONITOR; #endif /* >= 140000 */ #endif /* PG_VERSION_NUM < 100000 */ /* Limit use to members of the specified role */ if (!is_member_of_role(GetUserId(), checkoid)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be member of %s role", PGNODEMX_MONITOR_ROLE))); } /* * Simplified/modified version of same named function in genfile.c. * Be careful not to call during _PG_init() because * is_member_of_role does not play nice with shared_preload_libraries. */ char * convert_and_check_filename(text *arg, bool allow_abs) { char *filename; /* Limit use to members of special role */ pgnodemx_check_role(); filename = text_to_cstring(arg); canonicalize_path(filename); /* filename can change length here */ /* Disallow absolute paths */ if (!allow_abs && is_absolute_path(filename)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("reference to absolute path not allowed"))); /* Disallow references to parent directory */ if (path_contains_parent_reference(filename)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("reference to parent directory (\"..\") not allowed"))); return filename; } /* * read_vfs(): stripped down copy of read_binary_file() from * genfile.c */ /* Minimum amount to read at a time */ #define MIN_READ_SIZE 4096 char * read_vfs(char *filename) { char *buf; size_t nbytes = 0; FILE *file; StringInfoData sbuf; if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" for reading: %m", filename))); initStringInfo(&sbuf); while (!(feof(file) || ferror(file))) { size_t rbytes; /* * If not at end of file, and sbuf.len is equal to * MaxAllocSize - 1, then either the file is too large, or * there is nothing left to read. Attempt to read one more * byte to see if the end of file has been reached. If not, * the file is too large; we'd rather give the error message * for that ourselves. */ if (sbuf.len == MaxAllocSize - 1) { char rbuf[1]; if (fread(rbuf, 1, 1, file) != 0 || !feof(file)) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("file length too large"))); else break; } /* OK, ensure that we can read at least MIN_READ_SIZE */ enlargeStringInfo(&sbuf, MIN_READ_SIZE); /* * stringinfo.c likes to allocate in powers of 2, so it's likely * that much more space is available than we asked for. Use all * of it, rather than making more fread calls than necessary. */ rbytes = fread(sbuf.data + sbuf.len, 1, (size_t) (sbuf.maxlen - sbuf.len - 1), file); sbuf.len += rbytes; nbytes += rbytes; } /* * Keep a trailing null in place, same as what * appendBinaryStringInfo() would do. */ sbuf.data[sbuf.len] = '\0'; /* Now we can commandeer the stringinfo's buffer as the result */ buf = sbuf.data; if (ferror(file)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", filename))); FreeFile(file); return buf; } /* * Convert statfs and stat structs for given path (at least the * interesting bits) to a string matrix of key/value pairs, suitable * for use in constructing a tuplestore for returning to the client. */ char *** get_statfs_path(char *pname, int *nrow, int *ncol) { struct statfs buf; struct stat fs; int ret; int i; char ***values; *nrow = 1; *ncol = 13; ret = stat(pname, &fs); if (ret == -1) { ereport(ERROR, (errcode_for_file_access(), errmsg("pgnodemx: stat error on path %s: %m", pname))); } ret = statfs(pname, &buf); if (ret == -1) { ereport(ERROR, (errcode_for_file_access(), errmsg("pgnodemx: statfs error on path %s: %m", pname))); } values = (char ***) palloc((*nrow) * sizeof(char **)); for (i = 0; i < (*nrow); ++i) values[i] = (char **) palloc((*ncol) * sizeof(char *)); values[0][0] = uint64_to_string((uint64) major(fs.st_dev)); values[0][1] = uint64_to_string((uint64) minor(fs.st_dev)); values[0][2] = magic_get_name((uint64) buf.f_type); values[0][3] = uint64_to_string((uint64) buf.f_bsize); values[0][4] = uint64_to_string((uint64) buf.f_blocks); values[0][5] = uint64_to_string((uint64) (buf.f_blocks * buf.f_bsize)); values[0][6] = uint64_to_string((uint64) buf.f_bfree); values[0][7] = uint64_to_string((uint64) (buf.f_bfree * buf.f_bsize)); values[0][8] = uint64_to_string((uint64) buf.f_bavail); values[0][9] = uint64_to_string((uint64) (buf.f_bavail * buf.f_bsize)); values[0][10] = uint64_to_string((uint64) buf.f_files); values[0][11] = uint64_to_string((uint64) buf.f_ffree); values[0][12] = mount_flags_to_string((uint64) buf.f_flags); return values; } static char * magic_get_name(uint64 magic_id) { int i = 0; while (magic_names[i] != NULL) { if (magic_ids[i] == magic_id) return pstrdup(magic_names[i]); ++i; } return pstrdup("unknown"); } static char * mount_flags_to_string(uint64 flags) { StringInfo mflag_str = makeStringInfo(); int i = 0; bool found = false; while (mflagnames[i] != NULL) { if ((flags & mflags[i]) == mflags[i]) { if (found) appendStringInfo(mflag_str, ",%s", mflagnames[i]); else appendStringInfo(mflag_str, "%s", mflagnames[i]); found = true; } ++i; } if (!found) appendStringInfo(mflag_str, "%s", "none"); return mflag_str->data; } pgnodemx-1.6/fileutils.h000066400000000000000000000027741446717006400153750ustar00rootroot00000000000000/* * fileutils.h * * Functions specific to accessing the file system * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #ifndef FILEUTILS_H #define FILEUTILS_H extern void pgnodemx_check_role(void); extern char *convert_and_check_filename(text *arg, bool allow_abs); extern char *read_vfs(char *filename); extern char ***get_statfs_path(char *pname, int *nrow, int *ncol); #endif /* FILEUTILS_H */ pgnodemx-1.6/genutils.c000066400000000000000000000532511446717006400152160ustar00rootroot00000000000000/* * genutils.h * * General utility functions * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #include "postgres.h" #include #include #include #include #include #include #if PG_VERSION_NUM >= 110000 #include "catalog/pg_collation_d.h" #include "catalog/pg_type_d.h" #else #include "catalog/pg_collation.h" #include "catalog/pg_type.h" #endif #include "fmgr.h" #include "funcapi.h" #include "miscadmin.h" #include "utils/array.h" #include "utils/builtins.h" #if PG_VERSION_NUM < 130000 /* currently in builtins.h but locally defined prior to pg13 */ #define MAXINT8LEN 25 #endif /* PG_VERSION_NUM < 130000 */ #include "utils/guc_tables.h" #include "utils/lsyscache.h" #include "utils/numeric.h" #include "fileutils.h" #include "genutils.h" #include "parseutils.h" #include "srfsigs.h" #if PG_VERSION_NUM < 160000 /* These two functions are used via custom find_option() function Once custom find_option is dropped in PG16+, remove these unused functions here and full definitions below https://github.com/CrunchyData/pgnodemx/pull/18 */ static int guc_var_compare(const void *a, const void *b); static int guc_name_compare(const char *namea, const char *nameb); #endif #if PG_VERSION_NUM < 140000 static Numeric int64_to_numeric(int64 v) { Datum d = Int64GetDatum(v); return DatumGetNumeric(DirectFunctionCall1(int8_numeric, d)); } #endif /* PG_VERSION_NUM < 140000 */ #if PG_VERSION_NUM < 130000 /* * A table of all two-digit numbers. This is used to speed up decimal digit * generation by copying pairs of digits into the final output. */ static const char DIGIT_TABLE[200] = "00" "01" "02" "03" "04" "05" "06" "07" "08" "09" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30" "31" "32" "33" "34" "35" "36" "37" "38" "39" "40" "41" "42" "43" "44" "45" "46" "47" "48" "49" "50" "51" "52" "53" "54" "55" "56" "57" "58" "59" "60" "61" "62" "63" "64" "65" "66" "67" "68" "69" "70" "71" "72" "73" "74" "75" "76" "77" "78" "79" "80" "81" "82" "83" "84" "85" "86" "87" "88" "89" "90" "91" "92" "93" "94" "95" "96" "97" "98" "99"; static int pg_ulltoa_n(uint64 value, char *a); #if PG_VERSION_NUM >= 120000 #include "port/pg_bitutils.h" #else /* * Array giving the position of the left-most set bit for each possible * byte value. We count the right-most position as the 0th bit, and the * left-most the 7th bit. The 0th entry of the array should not be used. * * Note: this is not used by the functions in pg_bitutils.h when * HAVE__BUILTIN_CLZ is defined, but we provide it anyway, so that * extensions possibly compiled with a different compiler can use it. */ static const uint8 pg_leftmost_one_pos[256] = { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }; /* * pg_leftmost_one_pos64 * As above, but for a 64-bit word. */ static inline int pg_leftmost_one_pos64(uint64 word) { #ifdef HAVE__BUILTIN_CLZ Assert(word != 0); #if defined(HAVE_LONG_INT_64) return 63 - __builtin_clzl(word); #elif defined(HAVE_LONG_LONG_INT_64) return 63 - __builtin_clzll(word); #else #error must have a working 64-bit integer datatype #endif #else /* !HAVE__BUILTIN_CLZ */ int shift = 64 - 8; Assert(word != 0); while ((word >> shift) == 0) shift -= 8; return shift + pg_leftmost_one_pos[(word >> shift) & 255]; #endif /* HAVE__BUILTIN_CLZ */ } #endif /* PG_VERSION_NUM >= 120000 */ static inline int decimalLength64(const uint64 v) { int t; static const uint64 PowersOfTen[] = { UINT64CONST(1), UINT64CONST(10), UINT64CONST(100), UINT64CONST(1000), UINT64CONST(10000), UINT64CONST(100000), UINT64CONST(1000000), UINT64CONST(10000000), UINT64CONST(100000000), UINT64CONST(1000000000), UINT64CONST(10000000000), UINT64CONST(100000000000), UINT64CONST(1000000000000), UINT64CONST(10000000000000), UINT64CONST(100000000000000), UINT64CONST(1000000000000000), UINT64CONST(10000000000000000), UINT64CONST(100000000000000000), UINT64CONST(1000000000000000000), UINT64CONST(10000000000000000000) }; /* * Compute base-10 logarithm by dividing the base-2 logarithm by a * good-enough approximation of the base-2 logarithm of 10 */ t = (pg_leftmost_one_pos64(v) + 1) * 1233 / 4096; return t + (v >= PowersOfTen[t]); } #endif /* PG_VERSION_NUM < 130000 */ /* * Convert a 2D array of strings into a tuplestore and return it * as an SRF result. * * fcinfo is the called SQL facing function call info * values is the 2D array of strings to convert * nrow and ncol provide the array dimensions * dtypes is an array of data type oids for the output tuple * * If nrow is 0 or values is NULL, return an empty tuplestore * to the caller (empty result set). */ Datum form_srf(FunctionCallInfo fcinfo, char ***values, int nrow, int ncol, Oid *dtypes) { ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; Tuplestorestate *tupstore; HeapTuple tuple; TupleDesc tupdesc; AttInMetadata *attinmeta; MemoryContext per_query_ctx; MemoryContext oldcontext; int i; /* check to see if caller supports us returning a tuplestore */ if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize)) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("materialize mode required, but it is not " "allowed in this context"))); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); /* get the requested return tuple description */ tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); /* * Check to make sure we have a reasonable tuple descriptor */ if (tupdesc->natts != ncol) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("query-specified return tuple and " "function return type are not compatible"), errdetail("Number of columns mismatch"))); } else { for (i = 0; i < ncol; ++i) { Oid tdtyp = TupleDescAttr(tupdesc, i)->atttypid; if (tdtyp != dtypes[i]) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("query-specified return tuple and " "function return type are not compatible"), errdetail("Expected %s, got %s", format_type_be(dtypes[i]), format_type_be(tdtyp)))); } } /* OK to use it */ attinmeta = TupleDescGetAttInMetadata(tupdesc); /* let the caller know we're sending back a tuplestore */ rsinfo->returnMode = SFRM_Materialize; /* initialize our tuplestore */ tupstore = tuplestore_begin_heap(true, false, work_mem); if (nrow > 0 && values != NULL) { for (i = 0; i < nrow; ++i) { char **rowvals = values[i]; tuple = BuildTupleFromCStrings(attinmeta, rowvals); tuplestore_puttuple(tupstore, tuple); } } /* * no longer need the tuple descriptor reference created by * TupleDescGetAttInMetadata() */ ReleaseTupleDesc(tupdesc); tuplestore_donestoring(tupstore); rsinfo->setResult = tupstore; /* * SFRM_Materialize mode expects us to return a NULL Datum. The actual * tuples are in our tuplestore and passed back through rsinfo->setResult. * rsinfo->setDesc is set to the tuple description that we actually used * to build our tuples with, so the caller can verify we did what it was * expecting. */ rsinfo->setDesc = tupdesc; MemoryContextSwitchTo(oldcontext); return (Datum) 0; } /* * Convert multiline scalar file into setof scalar resultset */ Datum setof_scalar_internal(FunctionCallInfo fcinfo, char *fqpath, Oid *srf_sig) { char **lines; int nrow; int ncol = 1; lines = read_nlsv(fqpath, &nrow); if (nrow > 0) { char ***values; int i; values = (char ***) palloc(nrow * sizeof(char **)); for (i = 0; i < nrow; ++i) { values[i] = (char **) palloc(ncol * sizeof(char *)); /* if bigint, deal with "max" */ if (srf_sig[0] == INT8OID && strcasecmp(lines[i], "max") == 0) { char buf[MAXINT8LEN + 1]; int len; #if PG_VERSION_NUM >= 140000 len = pg_lltoa(PG_INT64_MAX, buf) + 1; #else pg_lltoa(PG_INT64_MAX, buf); len = strlen(buf) + 1; #endif values[i][0] = palloc(len); memcpy(values[i][0], buf, len); } else values[i][0] = pstrdup(lines[i]); } return form_srf(fcinfo, values, nrow, ncol, srf_sig); } else { /* return empty result set */ return form_srf(fcinfo, NULL, 0, ncol, srf_sig); } } /* return simple, one dimensional array */ Datum string_get_array_datum(char **values, int nvals, Oid typelem, bool *isnull) { const char *value; int16 typlen; bool typbyval; char typdelim; Oid typinput, typioparam; FmgrInfo in_func; char typalign; int i; Datum *dvalues = NULL; ArrayType *arr; if (nvals == 0) { *isnull = true; return (Datum) 0; } else *isnull = false; /* * get the element type's in_func */ get_type_io_data(typelem, IOFunc_input, &typlen, &typbyval, &typalign, &typdelim, &typioparam, &typinput); fmgr_info(typinput, &in_func); dvalues = (Datum *) palloc(nvals * sizeof(Datum)); for (i = 0; i < nvals; i++) { value = values[i]; dvalues[i] = FunctionCall1(&in_func, CStringGetDatum(value)); } arr = construct_array(dvalues, nvals, typelem, typlen, typbyval, typalign); return PointerGetDatum(arr); } /* qsort comparison function for int64 */ int int64_cmp(const void *p1, const void *p2) { int64 v1 = *((const int64 *) p1); int64 v2 = *((const int64 *) p2); if (v1 < v2) return -1; if (v1 > v2) return 1; return 0; } /* * Functions for obtaining the context within which we are operating */ #if PG_VERSION_NUM < 160000 /* Remove this custom find_option() function once PG16+ is the lowest * supported PG version * https://github.com/CrunchyData/pgnodemx/pull/18 * * Look up GUC option NAME. If it exists, return a pointer to its record, * else return NULL. This is cribbed from guc.c -- unfortunately there * seems to be no exported functionality to get the entire record by name. */ struct config_generic * find_option(const char *name) { const char **key = &name; struct config_generic **res; struct config_generic **guc_vars; int numOpts; Assert(name); guc_vars = get_guc_variables(); numOpts = GetNumConfigOptions(); /* * By equating const char ** with struct config_generic *, we are assuming * the name field is first in config_generic. */ res = (struct config_generic **) bsearch((void *) &key, (void *) guc_vars, numOpts, sizeof(struct config_generic *), guc_var_compare); if (res) return *res; /* Unknown name */ return NULL; } #endif /* * Additional utility functions cribbed from guc.c */ #if PG_VERSION_NUM < 160000 /* * comparator for qsorting and bsearching guc_variables array */ static int guc_var_compare(const void *a, const void *b) { const struct config_generic *confa = *(struct config_generic *const *) a; const struct config_generic *confb = *(struct config_generic *const *) b; return guc_name_compare(confa->name, confb->name); } /* * the bare comparison function for GUC names */ static int guc_name_compare(const char *namea, const char *nameb) { /* * The temptation to use strcasecmp() here must be resisted, because the * array ordering has to remain stable across setlocale() calls. So, build * our own with a simple ASCII-only downcasing. */ while (*namea && *nameb) { char cha = *namea++; char chb = *nameb++; if (cha >= 'A' && cha <= 'Z') cha += 'a' - 'A'; if (chb >= 'A' && chb <= 'Z') chb += 'a' - 'A'; if (cha != chb) return cha - chb; } if (*namea) return 1; /* a is longer */ if (*nameb) return -1; /* b is longer */ return 0; } #endif char * int64_to_string(int64 val) { char buf[MAXINT8LEN + 1]; int len; char *value; #if PG_VERSION_NUM >= 140000 len = pg_lltoa(val, buf) + 1; #else pg_lltoa(val, buf); len = strlen(buf) + 1; #endif value = palloc(len); memcpy(value, buf, len); return value; } /* * pg_ulltoa: converts an unsigned 64-bit integer to its string representation and * returns strlen(a). Copied and modified version of PostgreSQL's pg_lltoa(). * * Caller must ensure that 'a' points to enough memory to hold the result * (at least MAXINT8LEN + 1 bytes, counting a trailing NUL). */ int pg_ulltoa(uint64 uvalue, char *a) { int len = 0; len += pg_ulltoa_n(uvalue, a); a[len] = '\0'; return len; } char * uint64_to_string(uint64 val) { char buf[MAXINT8LEN + 1]; int len; char *value; len = pg_ulltoa(val, buf) + 1; value = palloc(len); memcpy(value, buf, len); return value; } #if PG_VERSION_NUM < 90600 /* * Convert a human-readable size to a size in bytes. * Copied from src/backend/utils/adt/dbsize.c and * modified to take a simple char* string and return * int64 instead of Datum. */ int64 size_bytes(char *str) { char *strptr, *endptr; char saved_char; Numeric num; int64 result; bool have_digits = false; /* Skip leading whitespace */ strptr = str; while (isspace((unsigned char) *strptr)) strptr++; /* Check that we have a valid number and determine where it ends */ endptr = strptr; /* Part (1): sign */ if (*endptr == '-' || *endptr == '+') endptr++; /* Part (2): main digit string */ if (isdigit((unsigned char) *endptr)) { have_digits = true; do endptr++; while (isdigit((unsigned char) *endptr)); } /* Part (3): optional decimal point and fractional digits */ if (*endptr == '.') { endptr++; if (isdigit((unsigned char) *endptr)) { have_digits = true; do endptr++; while (isdigit((unsigned char) *endptr)); } } /* Complain if we don't have a valid number at this point */ if (!have_digits) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid size: \"%s\"", str))); /* Part (4): optional exponent */ if (*endptr == 'e' || *endptr == 'E') { long exponent; char *cp; /* * Note we might one day support EB units, so if what follows 'E' * isn't a number, just treat it all as a unit to be parsed. */ exponent = strtol(endptr + 1, &cp, 10); (void) exponent; /* Silence -Wunused-result warnings */ if (cp > endptr + 1) endptr = cp; } /* * Parse the number, saving the next character, which may be the first * character of the unit string. */ saved_char = *endptr; *endptr = '\0'; num = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(strptr), ObjectIdGetDatum(InvalidOid), Int32GetDatum(-1))); *endptr = saved_char; /* Skip whitespace between number and unit */ strptr = endptr; while (isspace((unsigned char) *strptr)) strptr++; /* Handle possible unit */ if (*strptr != '\0') { int64 multiplier = 0; /* Trim any trailing whitespace */ endptr = str + strlen(str) - 1; while (isspace((unsigned char) *endptr)) endptr--; endptr++; *endptr = '\0'; /* Parse the unit case-insensitively */ if (pg_strcasecmp(strptr, "bytes") == 0) multiplier = (int64) 1; else if (pg_strcasecmp(strptr, "kb") == 0) multiplier = (int64) 1024; else if (pg_strcasecmp(strptr, "mb") == 0) multiplier = ((int64) 1024) * 1024; else if (pg_strcasecmp(strptr, "gb") == 0) multiplier = ((int64) 1024) * 1024 * 1024; else if (pg_strcasecmp(strptr, "tb") == 0) multiplier = ((int64) 1024) * 1024 * 1024 * 1024; else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid size: \"%s\"", str), errdetail("Invalid size unit: \"%s\".", strptr), errhint("Valid units are \"bytes\", \"kB\", \"MB\", \"GB\", and \"TB\"."))); if (multiplier > 1) { Numeric mul_num; mul_num = DatumGetNumeric(DirectFunctionCall1(int8_numeric, Int64GetDatum(multiplier))); num = DatumGetNumeric(DirectFunctionCall2(numeric_mul, NumericGetDatum(mul_num), NumericGetDatum(num))); } } result = DatumGetInt64(DirectFunctionCall1(numeric_int8, NumericGetDatum(num))); return result; } #endif #if PG_VERSION_NUM < 130000 /* * Get the decimal representation, not NUL-terminated, and return the length of * same. Caller must ensure that a points to at least MAXINT8LEN bytes. * Pulled from PostgreSQL 13 numutils.c since it does not exist before that. */ static int pg_ulltoa_n(uint64 value, char *a) { int olength, i = 0; uint32 value2; /* Degenerate case */ if (value == 0) { *a = '0'; return 1; } olength = decimalLength64(value); /* Compute the result string. */ while (value >= 100000000) { const uint64 q = value / 100000000; uint32 value2 = (uint32) (value - 100000000 * q); const uint32 c = value2 % 10000; const uint32 d = value2 / 10000; const uint32 c0 = (c % 100) << 1; const uint32 c1 = (c / 100) << 1; const uint32 d0 = (d % 100) << 1; const uint32 d1 = (d / 100) << 1; char *pos = a + olength - i; value = q; memcpy(pos - 2, DIGIT_TABLE + c0, 2); memcpy(pos - 4, DIGIT_TABLE + c1, 2); memcpy(pos - 6, DIGIT_TABLE + d0, 2); memcpy(pos - 8, DIGIT_TABLE + d1, 2); i += 8; } /* Switch to 32-bit for speed */ value2 = (uint32) value; if (value2 >= 10000) { const uint32 c = value2 - 10000 * (value2 / 10000); const uint32 c0 = (c % 100) << 1; const uint32 c1 = (c / 100) << 1; char *pos = a + olength - i; value2 /= 10000; memcpy(pos - 2, DIGIT_TABLE + c0, 2); memcpy(pos - 4, DIGIT_TABLE + c1, 2); i += 4; } if (value2 >= 100) { const uint32 c = (value2 % 100) << 1; char *pos = a + olength - i; value2 /= 100; memcpy(pos - 2, DIGIT_TABLE + c, 2); i += 2; } if (value2 >= 10) { const uint32 c = value2 << 1; char *pos = a + olength - i; memcpy(pos - 2, DIGIT_TABLE + c, 2); } else *a = (char) ('0' + value2); return olength; } #endif /* PG_VERSION_NUM < 130000 */ /* * Convert number of kernel pages to size in bytes */ PG_FUNCTION_INFO_V1(pgnodemx_pages_to_bytes); Datum pgnodemx_pages_to_bytes(PG_FUNCTION_ARGS) { Numeric num = int64_to_numeric(sysconf(_SC_PAGESIZE)); PG_RETURN_DATUM(DirectFunctionCall2(numeric_mul, PG_GETARG_DATUM(0), NumericGetDatum(num))); } /* * Return the currently running executable full path */ PG_FUNCTION_INFO_V1(pgnodemx_exec_path); Datum pgnodemx_exec_path(PG_FUNCTION_ARGS) { PG_RETURN_TEXT_P(cstring_to_text(my_exec_path)); } #define INTEGER_LEN 64 /* * pgnodemx version of stat a file */ PG_FUNCTION_INFO_V1(pgnodemx_stat_file); Datum pgnodemx_stat_file(PG_FUNCTION_ARGS) { int nrow = 1; int ncol = 5; char ***values = (char ***) palloc(nrow * sizeof(char **)); text *filename_t = PG_GETARG_TEXT_PP(0); char *filename; struct stat fst; mode_t st_mode; /* File type and mode */ uid_t st_uid; /* User ID of owner */ gid_t st_gid; /* Group ID of owner */ char buf[INTEGER_LEN]; char *uidstr; char *gidstr; char *modestr; char *username; char *groupname; struct passwd *pwd; struct group *grp; filename = convert_and_check_filename(filename_t, true); /* stat the file */ if (stat(filename, &fst) < 0) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", filename))); } st_mode = fst.st_mode; st_uid = fst.st_uid; st_gid = fst.st_gid; /* get uid string and username */ snprintf(buf, INTEGER_LEN, "%" PRIuMAX, (uintmax_t) st_uid); uidstr = pstrdup(buf); pwd = getpwuid(st_uid); if (pwd == NULL) username = NULL; else username = pstrdup(pwd->pw_name); /* get gid string and groupname */ snprintf(buf, INTEGER_LEN, "%" PRIuMAX, (uintmax_t) st_gid); gidstr = pstrdup(buf); grp = getgrgid(st_gid); if (grp == NULL) groupname = NULL; else groupname = pstrdup(grp->gr_name); /* get mode string */ snprintf(buf, INTEGER_LEN, "%o", st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); modestr = pstrdup(buf); values[0] = (char **) palloc(ncol * sizeof(char *)); /* uid and username for file */ values[0][0] = uidstr; values[0][1] = username; /* gid and groupname for file */ values[0][2] = gidstr; values[0][3] = groupname; /* one mode */ values[0][4] = modestr; return form_srf(fcinfo, values, nrow, ncol, num_text_num_2_text_sig); } pgnodemx-1.6/genutils.h000066400000000000000000000036311446717006400152200ustar00rootroot00000000000000/* * genutils.h * * General utility functions * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #ifndef GENUTILS_H #define GENUTILS_H extern Datum form_srf(FunctionCallInfo fcinfo, char ***values, int nrow, int ncol, Oid *dtypes); extern Datum setof_scalar_internal(FunctionCallInfo fcinfo, char *fname, Oid *srf_sig); extern Datum string_get_array_datum(char **values, int nvals, Oid typelem, bool *isnull); extern int int64_cmp(const void *p1, const void *p2); #if PG_VERSION_NUM < 160000 extern struct config_generic *find_option(const char *name); #endif extern char *int64_to_string(int64 val); extern int pg_ulltoa(uint64 uvalue, char *a); extern char *uint64_to_string(uint64 val); #if PG_VERSION_NUM < 90600 extern int64 size_bytes(char *str); #endif #endif /* GENUTILS_H */ pgnodemx-1.6/kdapi.c000066400000000000000000000040111446717006400144420ustar00rootroot00000000000000/* * kdapi.h * * Functions specific to capture and manipulation of Kubernetes * Downward API files * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #include "postgres.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "fileutils.h" #include "kdapi.h" #include "parseutils.h" char *kdapi_path = NULL; bool kdapi_enabled = true; /* * Take input filename from caller, make sure it is acceptable * (not absolute, no relative parent references, caller belongs * to correct role), and concatenates it with the path to the * to the kdapi files. The returned value is a "fully qualified" * path to the file of interest for the purposes of kdapi * virtual files. */ char * get_fq_kdapi_path(FunctionCallInfo fcinfo) { StringInfo ftr = makeStringInfo(); char *fname = convert_and_check_filename(PG_GETARG_TEXT_PP(0), false); appendStringInfo(ftr, "%s/%s", kdapi_path, fname); return pstrdup(ftr->data); } pgnodemx-1.6/kdapi.h000066400000000000000000000027131446717006400144560ustar00rootroot00000000000000/* * kdapi.h * * Functions specific to capture and manipulation of Kubernetes * Downward API files * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #ifndef KDAPI_H #define KDAPI_H #include "fmgr.h" char *get_fq_kdapi_path(FunctionCallInfo fcinfo); /* exported globals */ extern char *kdapi_path; extern bool kdapi_enabled; #endif /* KDAPI_H */ pgnodemx-1.6/parseutils.c000066400000000000000000000255331446717006400155610ustar00rootroot00000000000000/* * parseutils.c * * Functions specific to parsing various common string formats * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #include "postgres.h" #include #if PG_VERSION_NUM < 150000 #include "utils/int8.h" #endif #if PG_VERSION_NUM >= 120000 #include "utils/float.h" #else #include "utils/builtins.h" #endif #include "mb/pg_wchar.h" #include "fileutils.h" #include "kdapi.h" #include "parseutils.h" #define is_hex_digit(ch) ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) #define hex_value(ch) ((ch >= '0' && ch <= '9') ? (ch & 0x0F) : (ch & 0x0F) + 9) /* assumes ascii */ /* * Funtions to parse the various virtual file output formats. * See https://www.kernel.org/doc/Documentation/cgroup-v2.txt * for examples of the types of output formats to be parsed. */ /* * Read lines from a "new-line separated values" virtual file. Returns * the lines as an array of strings (char *), and populates nlines * with the line count. */ char ** read_nlsv(char *ftr, int *nlines) { char *rawstr = read_vfs(ftr); char *token; char **lines = (char **) palloc(0); *nlines = 0; for (token = strtok(rawstr, "\n"); token; token = strtok(NULL, "\n")) { lines = repalloc(lines, (*nlines + 1) * sizeof(char *)); lines[*nlines] = pstrdup(token); *nlines += 1; } return lines; } /* * Read one value from a "new-line separated values" virtual file */ char * read_one_nlsv(char *ftr) { int nlines; char **lines = read_nlsv(ftr, &nlines); if (nlines != 1) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: expected 1, got %d, lines from file %s", nlines, ftr))); return lines[0]; } /* * Parse columns from a "nested keyed" virtual file line */ kvpairs * parse_nested_keyed_line(char *line) { char *token; char *lstate; char *subtoken; char *cstate; kvpairs *nkl = (kvpairs *) palloc(sizeof(kvpairs)); nkl->nkvp = 0; nkl->keys = (char **) palloc(0); nkl->values = (char **) palloc(0); for (token = strtok_r(line, " ", &lstate); token; token = strtok_r(NULL, " ", &lstate)) { nkl->keys = repalloc(nkl->keys, (nkl->nkvp + 1) * sizeof(char *)); nkl->values = repalloc(nkl->values, (nkl->nkvp + 1) * sizeof(char *)); if (nkl->nkvp > 0) { subtoken = strtok_r(token, "=", &cstate); if (subtoken) nkl->keys[nkl->nkvp] = pstrdup(subtoken); else ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: missing key in nested keyed line"))); subtoken = strtok_r(NULL, "=", &cstate); if (subtoken) nkl->values[nkl->nkvp] = pstrdup(subtoken); else ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: missing value in nested keyed line"))); } else { /* first column has value only (not in form key=value) */ nkl->keys[nkl->nkvp] = pstrdup("key"); nkl->values[nkl->nkvp] = pstrdup(token); } nkl->nkvp += 1; } return nkl; } /* * Parse tokens from a space separated line. * Return tokens and set ntok to number found. */ char ** parse_ss_line(char *line, int *ntok) { char *token; char *lstate; char **values = (char **) palloc(0); *ntok = 0; for (token = strtok_r(line, " ", &lstate); token; token = strtok_r(NULL, " ", &lstate)) { values = (char **) repalloc(values, (*ntok + 1) * sizeof(char *)); values[*ntok] = pstrdup(token); *ntok += 1; } return values; } /* * parse_quoted_string * * Remove quotes and escapes from the string at **source. Returns a new palloc() string with the contents. * */ char* parse_quoted_string(char **source) { char *src; char *dst; char *ret; bool lastSlash = false; Assert(source != NULL); Assert(*source != NULL); src = *source; ret = dst = palloc0(strlen(src)); if (*src && *src == '"') src++; /* skip leading quote */ while (*src) { char c = *src; pg_wchar cp = 0; int i; if (lastSlash) { switch (c) { case '\\': *dst++ = '\\'; src++; break; case 'a': *dst++ = '\a'; src++; break; case 'b': *dst++ = '\b'; src++; break; case 'f': *dst++ = '\f'; src++; break; case 'n': *dst++ = '\n'; src++; break; case 'r': *dst++ = '\r'; src++; break; case 't': *dst++ = '\t'; src++; break; case 'v': *dst++ = '\v'; src++; break; case '"': *dst++ = '"'; src++; break; case 'x': /* next 2 chars are hex bytes */ if (is_hex_digit(src[1]) && is_hex_digit(src[2])) { *dst++ = (hex_value(src[1])<<4) + hex_value(src[2]); src+=3; } else ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed \\x literal"))); break; case 'u': case 'U': /* unicode code point handling */ for (i = 1; i <= (c == 'u' ? 4 : 8); i++) { if (is_hex_digit(src[i])) { cp <<= 4; cp += hex_value(src[i]); } else ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("malformed unicode literal"))); } src += (c == 'u' ? 5 : 9); /* append our multibyte encoded codepoint */ dst += pg_wchar2mb_with_len(&cp, dst, 1); break; default: /* unrecognized escape just pass through */ *dst++ = '\\'; *dst++ = *src++; break; } lastSlash = false; } else { lastSlash = (c == '\\'); if (c == '"' && src[1] == '\0') { src++; break; /* skip trailing quote without copying */ } if (lastSlash) src++; else *dst++ = *src++; } } *dst = '\0'; *source = src; return ret; } /* * Parse tokens from a "key equals quoted value" line. * Examples (from Kubernetes Downward API): * * cluster="test-cluster1" * rack="rack-22" * zone="us-est-coast" * var="abc=123" * multiline="multi\nline" * quoted="{\"quoted\":\"json\"}" * * Return two tokens; strip the quotes around the second one. * If exactly two tokens are not found, throw an error. */ char ** parse_keqv_line(char *line) { int ntok = 0; char *token; char *lstate; char **values = (char **) palloc(2 * sizeof(char *)); /* find the initial key portion of the code */ token = strtok_r(line, "=", &lstate); /* invalid will fall through */ if (token) { values[ntok++] = pstrdup(token); /* punt the hard work to this routine */ token = parse_quoted_string(&lstate); if (token) { values[ntok++] = pstrdup(token); /* if we have any extra chars, then it's actually a parse error */ if (strlen(lstate)) { ntok++; } } } /* line should have exactly two tokens */ if (ntok != 2) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: incorrect format for key equals quoted value line"), errdetail("pgnodemx: expected 2 tokens, found %d", ntok))); return values; } /* * Read provided file to obtain one int64 value */ int64 get_int64_from_file(char *ftr) { char *rawstr; bool success = false; int64 result; #if PG_VERSION_NUM >= 150000 char *endptr = NULL; #endif rawstr = read_one_nlsv(ftr); /* cgroup v2 reports literal "max" instead of largest possible value */ if (strcasecmp(rawstr, "max") == 0) result = PG_INT64_MAX; else { #if PG_VERSION_NUM < 150000 success = scanint8(rawstr, true, &result); #else errno = 0; result = strtoi64(rawstr, &endptr, 10); if (errno == 0 && *endptr == '\0') success = true; #endif if (!success) ereport(ERROR, (errcode_for_file_access(), errmsg("contents not an integer, file \"%s\"", ftr))); } return result; } /* * Read provided file to obtain one double precision value */ double get_double_from_file(char *ftr) { char *rawstr = read_one_nlsv(ftr); double result; /* cgroup v2 reports literal "max" instead of largest possible value */ if (strcmp(rawstr, "max") == 0) result = DBL_MAX; else #if PG_VERSION_NUM < 160000 result = float8in_internal(rawstr, NULL, "double precision", rawstr); #else result = float8in_internal(rawstr, NULL, "double precision", rawstr, NULL); #endif return result; } /* * Read provided file to obtain one string value */ char * get_string_from_file(char *ftr) { return read_one_nlsv(ftr); } /* * Parse a "space separated values" virtual file. * Must be exactly one line with tokens separated by a space. * Returns tokens as array of strings, and number of tokens * found in nvals. */ char ** parse_space_sep_val_file(char *ftr, int *nvals) { char *line; char *token; char *lstate; char **values = (char **) palloc(0); line = read_one_nlsv(ftr); *nvals = 0; for (token = strtok_r(line, " ", &lstate); token; token = strtok_r(NULL, " ", &lstate)) { values = repalloc(values, (*nvals + 1) * sizeof(char *)); values[*nvals] = pstrdup(token); *nvals += 1; } return values; } /* * Parse a "key value" virtual file. * * Must be one or more lines with 2 tokens separated by a space. * Returns tokens as array of strings, and number of tokens * found in nlines. * * Currently makes no attempt to strip a trailing character from * the "key". Possibly that should be added later. */ char *** read_kv_file(char *fname, int *nlines) { char **lines = read_nlsv(fname, nlines); if (nlines > 0) { char ***values; int nrow = *nlines; int ncol = 2; int i; values = (char ***) palloc(nrow * sizeof(char **)); for (i = 0; i < nrow; ++i) { int ntok; values[i] = parse_ss_line(lines[i], &ntok); /* line should have exactly ncol tokens */ if (ntok != ncol) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: incorrect format for key value line"), errdetail("pgnodemx: expected 2 tokens, found %d, file %s", ntok, fname))); } return values; } return NULL; } pgnodemx-1.6/parseutils.h000066400000000000000000000036521446717006400155640ustar00rootroot00000000000000/* * parseutils.h * * Functions specific to parsing various common string formats * * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #ifndef PARSEUTILS_H #define PARSEUTILS_H typedef struct kvpairs { int nkvp; char **keys; char **values; } kvpairs; extern char **read_nlsv(char *ftr, int *nlines); extern char *read_one_nlsv(char *ftr); extern kvpairs *parse_nested_keyed_line(char *line); extern char **parse_ss_line(char *line, int *ntok); extern char *parse_quoted_string(char **source); extern char **parse_keqv_line(char *line); extern int64 get_int64_from_file(char *ftr); extern double get_double_from_file(char *ftr); extern char *get_string_from_file(char *ftr); extern char **parse_space_sep_val_file(char *filename, int *nvals); extern char ***read_kv_file(char *fname, int *nlines); #endif /* PARSEUTILS_H */ pgnodemx-1.6/pg_proctab--0.0.10-compat.control000066400000000000000000000001761446717006400210100ustar00rootroot00000000000000# pg_proctab--0.0.10-compat extension comment = 'Provide compatibility for pg_proctab' relocatable = true requires = pgnodemx pgnodemx-1.6/pg_proctab--0.0.10-compat.sql000066400000000000000000000122721446717006400201270ustar00rootroot00000000000000/* contrib/pgnodemx/pg_proctab--0.0.10-compat.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_proctab VERSION 0.0.10-compat" to load this file. \quit /* * Functions to provide a pg_proctab compatible interface. * The hope is that this will allow pgnodemx to work with * pg_top as a remote target. */ CREATE FUNCTION pg_cputime( OUT "user" BIGINT, OUT nice BIGINT, OUT system BIGINT, OUT idle BIGINT, OUT iowait BIGINT ) RETURNS SETOF record AS $$ SELECT "user", nice, system, idle, iowait FROM proc_cputime() $$ LANGUAGE sql; CREATE FUNCTION pg_loadavg( OUT load1 FLOAT, OUT load5 FLOAT, OUT load15 FLOAT, OUT last_pid INTEGER ) RETURNS SETOF record AS $$ SELECT load1, load5, load15, last_pid FROM proc_loadavg() $$ LANGUAGE sql; /* * Compatibility note: in the original implementation memshared * is always equal to zero. Here we use the value from Shmem instead. */ CREATE FUNCTION pg_memusage( OUT memused BIGINT, OUT memfree BIGINT, OUT memshared BIGINT, OUT membuffers BIGINT, OUT memcached BIGINT, OUT swapused BIGINT, OUT swapfree BIGINT, OUT swapcached BIGINT) RETURNS SETOF record AS $$ WITH m (key,val) AS ( SELECT key, val FROM proc_meminfo() ) SELECT ((SELECT val FROM m WHERE key = 'MemTotal') - (SELECT val FROM m WHERE key = 'MemFree')) / 1024 as memused, (SELECT val FROM m WHERE key = 'MemFree') / 1024 AS memfree, (SELECT val FROM m WHERE key = 'Shmem') / 1024 AS memshared, (SELECT val FROM m WHERE key = 'Buffers') / 1024 AS membuffers, (SELECT val FROM m WHERE key = 'Cached') / 1024 AS memcached, ((SELECT val FROM m WHERE key = 'SwapTotal') - (SELECT val FROM m WHERE key = 'SwapFree')) / 1024 AS swapused, (SELECT val FROM m WHERE key = 'SwapFree') / 1024 AS swapfree, (SELECT val FROM m WHERE key = 'SwapCached') / 1024 as swapcached $$ LANGUAGE sql; CREATE FUNCTION pg_proctab( OUT pid integer, OUT comm character varying, OUT fullcomm character varying, OUT state character, OUT ppid integer, OUT pgrp integer, OUT session integer, OUT tty_nr integer, OUT tpgid integer, OUT flags integer, OUT minflt bigint, OUT cminflt bigint, OUT majflt bigint, OUT cmajflt bigint, OUT utime bigint, OUT stime bigint, OUT cutime bigint, OUT cstime bigint, OUT priority bigint, OUT nice bigint, OUT num_threads bigint, OUT itrealvalue bigint, OUT starttime bigint, OUT vsize bigint, OUT rss bigint, OUT exit_signal integer, OUT processor integer, OUT rt_priority bigint, OUT policy bigint, OUT delayacct_blkio_ticks bigint, OUT uid integer, OUT username character varying, OUT rchar bigint, OUT wchar bigint, OUT syscr bigint, OUT syscw bigint, OUT reads bigint, OUT writes bigint, OUT cwrites bigint ) RETURNS SETOF record AS $$ SELECT s.pid, comm, fullcomm, state, ppid, pgrp, session, tty_nr, tpgid, flags::integer, /* 10 */ minflt::bigint, cminflt::bigint, majflt::bigint, cmajflt::bigint, utime::bigint, stime::bigint, cutime::bigint, cstime::bigint, priority, nice, /* 20 */ num_threads, itrealvalue, starttime::bigint, vsize::bigint, (kpages_to_bytes(rss))::bigint / 1024 as rss, exit_signal, processor, rt_priority, policy, delayacct_blkio_ticks::bigint, /* 30 */ uid, username, rchar::bigint, wchar::bigint, syscr::bigint, syscw::bigint, reads::bigint, writes::bigint, cwrites::bigint FROM proc_pid_stat() s JOIN proc_pid_cmdline() c ON s.pid = c.pid JOIN proc_pid_io() i ON c.pid = i.pid $$ LANGUAGE sql; CREATE FUNCTION pg_diskusage ( OUT major smallint, OUT minor smallint, OUT devname text, OUT reads_completed bigint, OUT reads_merged bigint, OUT sectors_read bigint, OUT readtime bigint, OUT writes_completed bigint, OUT writes_merged bigint, OUT sectors_written bigint, OUT writetime bigint, OUT current_io bigint, OUT iotime bigint, OUT totaliotime bigint, OUT discards_completed bigint, OUT discards_merged bigint, OUT sectors_discarded bigint, OUT discardtime bigint, OUT flushes_completed bigint, OUT flushtime bigint ) RETURNS SETOF record AS $$ SELECT major_number::smallint AS major, minor_number::smallint AS minor, device_name AS devname, reads_completed_successfully::bigint AS reads_completed, reads_merged::bigint AS reads_merged, sectors_read::bigint AS sectors_read, time_spent_reading_ms AS readtime, writes_completed::bigint AS writes_completed, writes_merged::bigint AS writes_merged, sectors_written::bigint AS sectors_written, time_spent_writing_ms AS writetime, ios_currently_in_progress AS current_io, time_spent_doing_ios_ms AS iotime, weighted_time_spent_doing_ios_ms AS totaliotime, COALESCE(discards_completed_successfully, 0)::bigint AS discards_completed, COALESCE(discards_merged, 0)::bigint AS discards_merged, COALESCE(sectors_discarded, 0)::bigint AS sectors_discarded, COALESCE(time_spent_discarding, 0) AS discardtime, COALESCE(flush_requests_completed_successfully, 0)::bigint AS flushes_completed, COALESCE(time_spent_flushing, 0) AS flushtime FROM proc_diskstats() $$ LANGUAGE sql; pgnodemx-1.6/pg_proctab.control000066400000000000000000000001571446717006400167370ustar00rootroot00000000000000# pgnodemx pg_proctab--0.0.10-compat extension comment = 'Placeholder - see pg_proctab--0.0.10-compat.control' pgnodemx-1.6/pgnodemx--1.0--1.1.sql000066400000000000000000000010511446717006400164740ustar00rootroot00000000000000/* contrib/pgnodemx/pgnodemx--1.0--1.1.sql */ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pgnodemx UPDATE TO '1.1'" to load this file. \quit CREATE FUNCTION fips_mode() RETURNS BOOL AS 'MODULE_PATHNAME', 'pgnodemx_fips_mode' LANGUAGE C STABLE STRICT; CREATE FUNCTION symbol_filename(TEXT) RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_symbol_filename' LANGUAGE C STABLE STRICT; CREATE FUNCTION pgnodemx_version() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_version' LANGUAGE C STABLE STRICT; pgnodemx-1.6/pgnodemx--1.1--1.2.sql000066400000000000000000000012741446717006400165050ustar00rootroot00000000000000/* contrib/pgnodemx/pgnodemx--1.1--1.2.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgnodemx" to load this file. \quit DROP FUNCTION fsinfo (text); CREATE FUNCTION fsinfo ( IN pathname TEXT, OUT major_number NUMERIC, OUT minor_number NUMERIC, OUT type TEXT, OUT block_size NUMERIC, OUT blocks NUMERIC, OUT total_bytes NUMERIC, OUT free_blocks NUMERIC, OUT free_bytes NUMERIC, OUT available_blocks NUMERIC, OUT available_bytes NUMERIC, OUT total_file_nodes NUMERIC, OUT free_file_nodes NUMERIC, OUT mount_flags TEXT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_fsinfo' LANGUAGE C STABLE STRICT; pgnodemx-1.6/pgnodemx--1.2--1.3.sql000066400000000000000000000075431446717006400165140ustar00rootroot00000000000000/* contrib/pgnodemx/pgnodemx--1.1--1.2.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgnodemx" to load this file. \quit CREATE FUNCTION proc_pid_io( OUT pid INTEGER, OUT rchar NUMERIC, OUT wchar NUMERIC, OUT syscr NUMERIC, OUT syscw NUMERIC, OUT reads NUMERIC, OUT writes NUMERIC, OUT cwrites NUMERIC) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_pid_io' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION proc_pid_cmdline( OUT pid INTEGER, OUT fullcomm TEXT, OUT uid INTEGER, OUT username TEXT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_pid_cmdline' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION proc_pid_stat( OUT pid INTEGER, OUT comm TEXT, OUT state TEXT, OUT ppid INTEGER, OUT pgrp INTEGER, OUT session INTEGER, OUT tty_nr INTEGER, OUT tpgid INTEGER, OUT flags BIGINT, OUT minflt NUMERIC, OUT cminflt NUMERIC, OUT majflt NUMERIC, OUT cmajflt NUMERIC, OUT utime NUMERIC, OUT stime NUMERIC, OUT cutime BIGINT, OUT cstime BIGINT, OUT priority BIGINT, OUT nice BIGINT, OUT num_threads BIGINT, OUT itrealvalue BIGINT, OUT starttime NUMERIC, OUT vsize NUMERIC, OUT rss BIGINT, OUT rsslim NUMERIC, OUT startcode NUMERIC, OUT endcode NUMERIC, OUT startstack NUMERIC, OUT kstkesp NUMERIC, OUT kstkeip NUMERIC, OUT signal NUMERIC, OUT blocked NUMERIC, OUT sigignore NUMERIC, OUT sigcatch NUMERIC, OUT wchan NUMERIC, OUT nswap NUMERIC, OUT cnswap NUMERIC, OUT exit_signal INTEGER, OUT processor INTEGER, OUT rt_priority BIGINT, OUT policy BIGINT, OUT delayacct_blkio_ticks NUMERIC, OUT guest_time NUMERIC, OUT cguest_time BIGINT, OUT start_data NUMERIC, OUT end_data NUMERIC, OUT start_brk NUMERIC, OUT arg_start NUMERIC, OUT arg_end NUMERIC, OUT env_start NUMERIC, OUT env_end NUMERIC, OUT exit_code INTEGER) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_pid_stat' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION kpages_to_bytes(NUMERIC) RETURNS NUMERIC AS 'MODULE_PATHNAME', 'pgnodemx_pages_to_bytes' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION proc_cputime( OUT "user" BIGINT, OUT nice BIGINT, OUT system BIGINT, OUT idle BIGINT, OUT iowait BIGINT) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_cputime' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION proc_loadavg( OUT load1 FLOAT, OUT load5 FLOAT, OUT load15 FLOAT, OUT last_pid INTEGER) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_loadavg' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION exec_path() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_exec_path' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION stat_file( IN filename TEXT, OUT uid NUMERIC, OUT username TEXT, OUT gid NUMERIC, OUT groupname TEXT, OUT filemode TEXT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_stat_file' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION openssl_version() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_openssl_version' LANGUAGE C IMMUTABLE STRICT; DROP FUNCTION proc_diskstats(); CREATE FUNCTION proc_diskstats ( OUT major_number BIGINT, OUT minor_number BIGINT, OUT device_name TEXT, OUT reads_completed_successfully NUMERIC, OUT reads_merged NUMERIC, OUT sectors_read NUMERIC, OUT time_spent_reading_ms BIGINT, OUT writes_completed NUMERIC, OUT writes_merged NUMERIC, OUT sectors_written NUMERIC, OUT time_spent_writing_ms BIGINT, OUT ios_currently_in_progress BIGINT, OUT time_spent_doing_ios_ms BIGINT, OUT weighted_time_spent_doing_ios_ms BIGINT, OUT discards_completed_successfully NUMERIC, OUT discards_merged NUMERIC, OUT sectors_discarded NUMERIC, OUT time_spent_discarding BIGINT, OUT flush_requests_completed_successfully NUMERIC, OUT time_spent_flushing BIGINT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_diskstats' LANGUAGE C STABLE STRICT; pgnodemx-1.6/pgnodemx--1.3--1.4.sql000066400000000000000000000001351446717006400165040ustar00rootroot00000000000000-- No SQL code changes. This file is provided to allow extension update path to version 1.4. pgnodemx-1.6/pgnodemx--1.4--1.5.sql000066400000000000000000000001351446717006400165060ustar00rootroot00000000000000-- No SQL code changes. This file is provided to allow extension update path to version 1.5. pgnodemx-1.6/pgnodemx--1.5--1.6.sql000066400000000000000000000001351446717006400165100ustar00rootroot00000000000000-- No SQL code changes. This file is provided to allow extension update path to version 1.6. pgnodemx-1.6/pgnodemx--1.6.sql000066400000000000000000000202121446717006400161300ustar00rootroot00000000000000/* pgnodemx--1.6.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pgnodemx" to load this file. \quit CREATE FUNCTION cgroup_mode() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_mode' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_path ( OUT controller TEXT, OUT path TEXT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_path' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_process_count() RETURNS INT4 AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_process_count' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_scalar_bigint(TEXT) RETURNS BIGINT AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_scalar_bigint' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_scalar_float8(TEXT) RETURNS FLOAT8 AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_scalar_float8' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_scalar_text(TEXT) RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_scalar_text' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_setof_bigint(TEXT) RETURNS SETOF BIGINT AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_setof_bigint' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_setof_text(TEXT) RETURNS SETOF TEXT AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_setof_text' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_array_text(TEXT) RETURNS TEXT[] AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_array_text' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_array_bigint(TEXT) RETURNS BIGINT[] AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_array_bigint' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_setof_kv ( IN filename TEXT, OUT key TEXT, OUT val BIGINT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_setof_kv' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_setof_ksv ( IN filename TEXT, OUT key TEXT, OUT subkey TEXT, OUT val BIGINT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_setof_ksv' LANGUAGE C STABLE STRICT; CREATE FUNCTION cgroup_setof_nkv ( IN filename TEXT, OUT key TEXT, OUT subkey TEXT, OUT val FLOAT8 ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_cgroup_setof_nkv' LANGUAGE C STABLE STRICT; CREATE FUNCTION envvar_text(TEXT) RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_envvar_text' LANGUAGE C STABLE STRICT; CREATE FUNCTION envvar_bigint(TEXT) RETURNS BIGINT AS 'MODULE_PATHNAME', 'pgnodemx_envvar_bigint' LANGUAGE C STABLE STRICT; CREATE FUNCTION kdapi_scalar_bigint(TEXT) RETURNS BIGINT AS 'MODULE_PATHNAME', 'pgnodemx_kdapi_scalar_bigint' LANGUAGE C STABLE STRICT; CREATE FUNCTION kdapi_setof_kv ( IN filename TEXT, OUT key TEXT, OUT val TEXT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_kdapi_setof_kv' LANGUAGE C STABLE STRICT; CREATE FUNCTION fips_mode() RETURNS BOOL AS 'MODULE_PATHNAME', 'pgnodemx_fips_mode' LANGUAGE C STABLE STRICT; CREATE FUNCTION symbol_filename(TEXT) RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_symbol_filename' LANGUAGE C STABLE STRICT; CREATE FUNCTION pgnodemx_version() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_version' LANGUAGE C STABLE STRICT; CREATE FUNCTION proc_diskstats ( OUT major_number BIGINT, OUT minor_number BIGINT, OUT device_name TEXT, OUT reads_completed_successfully NUMERIC, OUT reads_merged NUMERIC, OUT sectors_read NUMERIC, OUT time_spent_reading_ms BIGINT, OUT writes_completed NUMERIC, OUT writes_merged NUMERIC, OUT sectors_written NUMERIC, OUT time_spent_writing_ms BIGINT, OUT ios_currently_in_progress BIGINT, OUT time_spent_doing_ios_ms BIGINT, OUT weighted_time_spent_doing_ios_ms BIGINT, OUT discards_completed_successfully NUMERIC, OUT discards_merged NUMERIC, OUT sectors_discarded NUMERIC, OUT time_spent_discarding BIGINT, OUT flush_requests_completed_successfully NUMERIC, OUT time_spent_flushing BIGINT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_diskstats' LANGUAGE C STABLE STRICT; CREATE FUNCTION proc_mountinfo ( OUT mount_id BIGINT, OUT parent_id BIGINT, OUT major_number BIGINT, OUT minor_number BIGINT, OUT root TEXT, OUT mount_point TEXT, OUT mount_options TEXT, OUT fs_type TEXT, OUT mount_source TEXT, OUT super_options TEXT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_mountinfo' LANGUAGE C STABLE STRICT; CREATE FUNCTION proc_meminfo ( OUT key TEXT, OUT val BIGINT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_meminfo' LANGUAGE C STABLE STRICT; CREATE FUNCTION proc_network_stats ( OUT interface TEXT, OUT rx_bytes BIGINT, OUT rx_packets BIGINT, OUT rx_errs BIGINT, OUT rx_drop BIGINT, OUT rx_fifo BIGINT, OUT rx_frame BIGINT, OUT rx_compressed BIGINT, OUT rx_multicast BIGINT, OUT tx_bytes BIGINT, OUT tx_packets BIGINT, OUT tx_errs BIGINT, OUT tx_drop BIGINT, OUT tx_fifo BIGINT, OUT tx_frame BIGINT, OUT tx_compressed BIGINT, OUT tx_multicast BIGINT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_network_stats' LANGUAGE C STABLE STRICT; CREATE FUNCTION fsinfo ( IN pathname TEXT, OUT major_number NUMERIC, OUT minor_number NUMERIC, OUT type TEXT, OUT block_size NUMERIC, OUT blocks NUMERIC, OUT total_bytes NUMERIC, OUT free_blocks NUMERIC, OUT free_bytes NUMERIC, OUT available_blocks NUMERIC, OUT available_bytes NUMERIC, OUT total_file_nodes NUMERIC, OUT free_file_nodes NUMERIC, OUT mount_flags TEXT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_fsinfo' LANGUAGE C STABLE STRICT; CREATE FUNCTION proc_pid_io( OUT pid INTEGER, OUT rchar NUMERIC, OUT wchar NUMERIC, OUT syscr NUMERIC, OUT syscw NUMERIC, OUT reads NUMERIC, OUT writes NUMERIC, OUT cwrites NUMERIC) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_pid_io' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION proc_pid_cmdline( OUT pid INTEGER, OUT fullcomm TEXT, OUT uid INTEGER, OUT username TEXT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_pid_cmdline' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION proc_pid_stat( OUT pid INTEGER, OUT comm TEXT, OUT state TEXT, OUT ppid INTEGER, OUT pgrp INTEGER, OUT session INTEGER, OUT tty_nr INTEGER, OUT tpgid INTEGER, OUT flags BIGINT, OUT minflt NUMERIC, OUT cminflt NUMERIC, OUT majflt NUMERIC, OUT cmajflt NUMERIC, OUT utime NUMERIC, OUT stime NUMERIC, OUT cutime BIGINT, OUT cstime BIGINT, OUT priority BIGINT, OUT nice BIGINT, OUT num_threads BIGINT, OUT itrealvalue BIGINT, OUT starttime NUMERIC, OUT vsize NUMERIC, OUT rss BIGINT, OUT rsslim NUMERIC, OUT startcode NUMERIC, OUT endcode NUMERIC, OUT startstack NUMERIC, OUT kstkesp NUMERIC, OUT kstkeip NUMERIC, OUT signal NUMERIC, OUT blocked NUMERIC, OUT sigignore NUMERIC, OUT sigcatch NUMERIC, OUT wchan NUMERIC, OUT nswap NUMERIC, OUT cnswap NUMERIC, OUT exit_signal INTEGER, OUT processor INTEGER, OUT rt_priority BIGINT, OUT policy BIGINT, OUT delayacct_blkio_ticks NUMERIC, OUT guest_time NUMERIC, OUT cguest_time BIGINT, OUT start_data NUMERIC, OUT end_data NUMERIC, OUT start_brk NUMERIC, OUT arg_start NUMERIC, OUT arg_end NUMERIC, OUT env_start NUMERIC, OUT env_end NUMERIC, OUT exit_code INTEGER) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_pid_stat' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION kpages_to_bytes(NUMERIC) RETURNS NUMERIC AS 'MODULE_PATHNAME', 'pgnodemx_pages_to_bytes' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION proc_cputime( OUT "user" BIGINT, OUT nice BIGINT, OUT system BIGINT, OUT idle BIGINT, OUT iowait BIGINT) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_cputime' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION proc_loadavg( OUT load1 FLOAT, OUT load5 FLOAT, OUT load15 FLOAT, OUT last_pid INTEGER) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_proc_loadavg' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION exec_path() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_exec_path' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION stat_file( IN filename TEXT, OUT uid NUMERIC, OUT username TEXT, OUT gid NUMERIC, OUT groupname TEXT, OUT filemode TEXT ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pgnodemx_stat_file' LANGUAGE C IMMUTABLE STRICT; CREATE FUNCTION openssl_version() RETURNS TEXT AS 'MODULE_PATHNAME', 'pgnodemx_openssl_version' LANGUAGE C IMMUTABLE STRICT; pgnodemx-1.6/pgnodemx.c000066400000000000000000000467441446717006400152160ustar00rootroot00000000000000/* * pgnodemx * * SQL functions that allow capture of node OS metrics from PostgreSQL * Joe Conway * * This code is released under the PostgreSQL license. * * Copyright 2020-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #include "postgres.h" #if PG_VERSION_NUM < 90500 #error "pgnodemx only builds with PostgreSQL 9.5 or later" #endif #include #ifdef USE_OPENSSL #include #endif #include #if PG_VERSION_NUM < 150000 #include "utils/int8.h" #endif #include "catalog/pg_authid.h" #if PG_VERSION_NUM >= 110000 #include "catalog/pg_type_d.h" #else #include "catalog/pg_type.h" #endif #include "fmgr.h" #include "miscadmin.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/guc_tables.h" #include "cgroup.h" #include "envutils.h" #include "fileutils.h" #include "genutils.h" #include "kdapi.h" #include "parseutils.h" #include "procfunc.h" #include "srfsigs.h" PG_MODULE_MAGIC; /* function return signatures */ Oid text_sig[] = {TEXTOID}; Oid bigint_sig[] = {INT8OID}; Oid text_text_sig[] = {TEXTOID, TEXTOID}; Oid text_bigint_sig[] = {TEXTOID, INT8OID}; Oid text_text_bigint_sig[] = {TEXTOID, TEXTOID, INT8OID}; Oid text_text_float8_sig[] = {TEXTOID, TEXTOID, FLOAT8OID}; Oid _2_numeric_text_9_numeric_text_sig[] = {NUMERICOID, NUMERICOID, TEXTOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, TEXTOID}; Oid _4_bigint_6_text_sig[] = {INT8OID, INT8OID, INT8OID, INT8OID, TEXTOID, TEXTOID, TEXTOID, TEXTOID, TEXTOID, TEXTOID}; Oid text_16_bigint_sig[] = {TEXTOID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID}; Oid _5_bigint_sig[] = { INT8OID, INT8OID, INT8OID, INT8OID, INT8OID }; Oid int_7_numeric_sig[] = { INT4OID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID }; Oid int_text_int_text_sig[] = { INT4OID, TEXTOID, INT4OID, TEXTOID }; Oid load_avg_sig[] = { FLOAT8OID, FLOAT8OID, FLOAT8OID, INT4OID }; /* proc_diskstats is unique enough to have its own sig */ Oid proc_diskstats_sig[] = {INT8OID, INT8OID, TEXTOID, NUMERICOID, NUMERICOID, NUMERICOID, INT8OID, NUMERICOID, NUMERICOID, NUMERICOID, INT8OID, INT8OID, INT8OID, INT8OID, NUMERICOID, NUMERICOID, NUMERICOID, INT8OID, NUMERICOID, INT8OID}; /* proc_pid_stat is unique enough to have its own sig */ Oid proc_pid_stat_sig[] = {INT4OID, TEXTOID, TEXTOID, INT4OID, INT4OID, INT4OID, INT4OID, INT4OID, INT8OID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, INT8OID, NUMERICOID, NUMERICOID, INT8OID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, INT4OID, INT4OID, INT8OID, INT8OID, NUMERICOID, NUMERICOID, INT8OID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, NUMERICOID, INT4OID }; Oid num_text_num_2_text_sig[] = {NUMERICOID, TEXTOID, NUMERICOID, TEXTOID, TEXTOID}; void _PG_init(void); Datum pgnodemx_cgroup_mode(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_path(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_process_count(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_scalar_bigint(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_scalar_float8(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_scalar_text(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_setof_bigint(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_setof_text(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_array_text(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_array_bigint(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_setof_kv(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_setof_ksv(PG_FUNCTION_ARGS); Datum pgnodemx_cgroup_setof_nkv(PG_FUNCTION_ARGS); Datum pgnodemx_envvar_text(PG_FUNCTION_ARGS); Datum pgnodemx_envvar_bigint(PG_FUNCTION_ARGS); Datum pgnodemx_kdapi_setof_kv(PG_FUNCTION_ARGS); Datum pgnodemx_kdapi_scalar_bigint(PG_FUNCTION_ARGS); bool proc_enabled = false; /* * Entrypoint of this module. */ void _PG_init(void) { /* Be sure we do initialization only once */ static bool inited = false; if (inited) return; /* Must be loaded with shared_preload_libraries */ if (!process_shared_preload_libraries_in_progress) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: must be loaded via shared_preload_libraries"))); DefineCustomBoolVariable("pgnodemx.cgroup_enabled", "True if cgroup virtual file system access is enabled", NULL, &cgroup_enabled, true, PGC_POSTMASTER, 0, NULL, NULL, NULL); DefineCustomBoolVariable("pgnodemx.containerized", "True if operating inside a container", NULL, &containerized, false, PGC_POSTMASTER, 0, NULL, NULL, NULL); DefineCustomStringVariable("pgnodemx.cgrouproot", "Path to root cgroup", NULL, &cgrouproot, "/sys/fs/cgroup", PGC_POSTMASTER, 0, NULL, NULL, NULL); DefineCustomBoolVariable("pgnodemx.kdapi_enabled", "True if Kubernetes Downward API file system access is enabled", NULL, &kdapi_enabled, true, PGC_POSTMASTER, 0, NULL, NULL, NULL); DefineCustomStringVariable("pgnodemx.kdapi_path", "Path to Kubernetes Downward API files", NULL, &kdapi_path, "/etc/podinfo", PGC_POSTMASTER, 0, NULL, NULL, NULL); /* don't try to set cgmode unless cgroup is enabled */ if (set_cgmode()) { /* must determine if containerized before setting cgpath */ set_containerized(); set_cgpath(); } else { /* * If cgmode cannot be set, either because cgroup_enabled is * already set to false, or because of an error trying to stat * cgrouproot, then we must force disable cgroup functions. */ cgroup_enabled = false; } /* force kdapi disabled if path does not exist */ if (kdapi_enabled && access(kdapi_path, F_OK) != 0) { /* * If kdapi_path does not exist, there is not * much else we can do besides disabling kdapi access. */ ereport(WARNING, (errcode_for_file_access(), errmsg("pgnodemx: Kubernetes Downward API path %s does not exist: %m", kdapi_path), errdetail("disabling Kubernetes Downward API file system access"))); kdapi_enabled = false; } /* * Check procfs exists. * The "proc" functions are disabled if not. */ proc_enabled = check_procfs(); inited = true; } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_mode); Datum pgnodemx_cgroup_mode(PG_FUNCTION_ARGS) { /* * Do not check cgroup_enabled here; this is the one cgroup * function which *should* work when cgroup is disabled. */ PG_RETURN_TEXT_P(cstring_to_text(cgmode)); } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_path); Datum pgnodemx_cgroup_path(PG_FUNCTION_ARGS) { char ***values; int nrow; int ncol = 2; int i; if (!cgroup_enabled) return form_srf(fcinfo, NULL, 0, ncol, text_text_sig); nrow = cgpath->nkvp; if (nrow < 1) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no lines in cgpath"))); /* never reached */ values = (char ***) palloc(nrow * sizeof(char **)); for (i = 0; i < nrow; ++i) { values[i] = (char **) palloc(ncol * sizeof(char *)); values[i][0] = pstrdup(cgpath->keys[i]); values[i][1] = pstrdup(cgpath->values[i]); } return form_srf(fcinfo, values, nrow, ncol, text_text_sig); } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_process_count); Datum pgnodemx_cgroup_process_count(PG_FUNCTION_ARGS) { int64 *cgpids; if (!cgroup_enabled) PG_RETURN_NULL(); /* cgmembers returns pid count */ PG_RETURN_INT32(cgmembers(&cgpids)); } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_scalar_bigint); Datum pgnodemx_cgroup_scalar_bigint(PG_FUNCTION_ARGS) { char *fqpath; if (!cgroup_enabled) PG_RETURN_NULL(); fqpath = get_fq_cgroup_path(fcinfo); PG_RETURN_INT64(get_int64_from_file(fqpath)); } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_scalar_float8); Datum pgnodemx_cgroup_scalar_float8(PG_FUNCTION_ARGS) { char *fqpath; if (!cgroup_enabled) PG_RETURN_NULL(); fqpath = get_fq_cgroup_path(fcinfo); PG_RETURN_FLOAT8(get_double_from_file(fqpath)); } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_scalar_text); Datum pgnodemx_cgroup_scalar_text(PG_FUNCTION_ARGS) { char *fqpath; if (!cgroup_enabled) PG_RETURN_NULL(); fqpath = get_fq_cgroup_path(fcinfo); PG_RETURN_TEXT_P(cstring_to_text(get_string_from_file(fqpath))); } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_setof_bigint); Datum pgnodemx_cgroup_setof_bigint(PG_FUNCTION_ARGS) { char *fqpath; if (!cgroup_enabled) return form_srf(fcinfo, NULL, 0, 1, bigint_sig); fqpath = get_fq_cgroup_path(fcinfo); return setof_scalar_internal(fcinfo, fqpath, bigint_sig); } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_setof_text); Datum pgnodemx_cgroup_setof_text(PG_FUNCTION_ARGS) { char *fqpath; if (!cgroup_enabled) return form_srf(fcinfo, NULL, 0, 1, text_sig); fqpath = get_fq_cgroup_path(fcinfo); return setof_scalar_internal(fcinfo, fqpath, text_sig); } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_array_text); Datum pgnodemx_cgroup_array_text(PG_FUNCTION_ARGS) { char *fqpath; char **values; int nvals; bool isnull = false; Datum dvalue; if (!cgroup_enabled) PG_RETURN_NULL(); fqpath = get_fq_cgroup_path(fcinfo); values = parse_space_sep_val_file(fqpath, &nvals); dvalue = string_get_array_datum(values, nvals, TEXTOID, &isnull); if (!isnull) return dvalue; else PG_RETURN_NULL(); } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_array_bigint); Datum pgnodemx_cgroup_array_bigint(PG_FUNCTION_ARGS) { char *fqpath; char **values; int nvals; bool isnull = false; Datum dvalue; int i; if (!cgroup_enabled) PG_RETURN_NULL(); fqpath = get_fq_cgroup_path(fcinfo); values = parse_space_sep_val_file(fqpath, &nvals); /* deal with "max" */ for (i = 0; i < nvals; ++i) { if (strcasecmp(values[i], "max") == 0) values[i] = int64_to_string(PG_INT64_MAX); } dvalue = string_get_array_datum(values, nvals, INT8OID, &isnull); if (!isnull) return dvalue; else PG_RETURN_NULL(); } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_setof_kv); Datum pgnodemx_cgroup_setof_kv(PG_FUNCTION_ARGS) { char *fqpath; int nlines; char **lines; int ncol = 2; if (!cgroup_enabled) return form_srf(fcinfo, NULL, 0, ncol, text_bigint_sig); fqpath = get_fq_cgroup_path(fcinfo); lines = read_nlsv(fqpath, &nlines); if (nlines > 0) { char ***values; int nrow = nlines; int i; values = (char ***) palloc(nrow * sizeof(char **)); for (i = 0; i < nrow; ++i) { int ntok; values[i] = parse_ss_line(lines[i], &ntok); if (ntok != ncol) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: expected %d tokens, got %d in flat keyed file %s, line %d", ncol, ntok, fqpath, i + 1))); } return form_srf(fcinfo, values, nrow, ncol, text_bigint_sig); } ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no lines in flat keyed file: %s ", fqpath))); /* never reached */ return (Datum) 0; } /* * Some virtual files have multiple rows of three columns: * (key text, subkey text, value bigint). They essentially * look like nkv, except they only have one subkey and value * and no "=" between them. The columns are space separated. * They also may have a "grand sum" line that only has two * columns because it represents a sum of all the other lines. * Two examples of this are blkio.throttle.io_serviced and * blkio.throttle.io_service_bytes. */ PG_FUNCTION_INFO_V1(pgnodemx_cgroup_setof_ksv); Datum pgnodemx_cgroup_setof_ksv(PG_FUNCTION_ARGS) { char *fqpath; int nlines; char **lines; int ncol = 3; if (!cgroup_enabled) return form_srf(fcinfo, NULL, 0, ncol, text_text_bigint_sig); fqpath = get_fq_cgroup_path(fcinfo); lines = read_nlsv(fqpath, &nlines); if (nlines > 0) { char ***values; int nrow = nlines; int i; values = (char ***) palloc(nrow * sizeof(char **)); for (i = 0; i < nrow; ++i) { int ntok; values[i] = parse_ss_line(lines[i], &ntok); if (ntok > ncol || ntok < ncol - 1) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: expected %d tokens, got %d in flat keyed file %s, line %d", ncol, ntok, fqpath, i + 1))); } else if (ntok == 2) { /* for the two column case, expand and shift the values right */ values[i] = (char **) repalloc(values[i], ncol * sizeof(char *)); values[i][2] = values[i][1]; values[i][1] = values[i][0]; values[i][0] = pstrdup("all"); } } return form_srf(fcinfo, values, nrow, ncol, text_text_bigint_sig); } ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no lines in flat keyed file: %s ", fqpath))); /* never reached */ return (Datum) 0; } PG_FUNCTION_INFO_V1(pgnodemx_cgroup_setof_nkv); Datum pgnodemx_cgroup_setof_nkv(PG_FUNCTION_ARGS) { char *fqpath; int nlines; char **lines; int ncol = 3; if (!cgroup_enabled) return form_srf(fcinfo, NULL, 0, ncol, text_text_float8_sig); fqpath = get_fq_cgroup_path(fcinfo); lines = read_nlsv(fqpath, &nlines); if (nlines > 0) { char ***values; int nrow; kvpairs *nkl; int nkvp; int i; /* * We expect that each line in a "nested keyed" file has the * same number of column. Therefore use the first line of the * parsed file to determine how many columns we have. The entire * line has a key, and each column will consist of a subkey and * value. We will build "number of column" rows from this one line. * Each row in the output will look like (key, subkey, value). */ nkl = parse_nested_keyed_line(pstrdup(lines[0])); nkvp = nkl->nkvp; /* each line expands to nkvp - 1 rows in the output */ nrow = nlines * (nkvp - 1); values = (char ***) palloc(nrow * sizeof(char **)); for (i = 0; i < nlines; ++i) { int j; nkl = parse_nested_keyed_line(lines[i]); if (nkl->nkvp != nkvp) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: not nested keyed file: %s ", fqpath))); for (j = 1; j < nkvp; ++j) { values[(i * (nkvp - 1)) + j - 1] = (char **) palloc(ncol * sizeof(char *)); values[(i * (nkvp - 1)) + j - 1][0] = pstrdup(nkl->values[0]); values[(i * (nkvp - 1)) + j - 1][1] = pstrdup(nkl->keys[j]); values[(i * (nkvp - 1)) + j - 1][2] = pstrdup(nkl->values[j]); } } return form_srf(fcinfo, values, nrow, ncol, text_text_float8_sig); } ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no lines in nested keyed file: %s ", fqpath))); /* never reached */ return (Datum) 0; } PG_FUNCTION_INFO_V1(pgnodemx_envvar_text); Datum pgnodemx_envvar_text(PG_FUNCTION_ARGS) { char *varname = text_to_cstring(PG_GETARG_TEXT_PP(0)); /* Limit use to members of special role */ pgnodemx_check_role(); PG_RETURN_TEXT_P(cstring_to_text(get_string_from_env(varname))); } PG_FUNCTION_INFO_V1(pgnodemx_envvar_bigint); Datum pgnodemx_envvar_bigint(PG_FUNCTION_ARGS) { bool success = false; int64 result; char *varname = text_to_cstring(PG_GETARG_TEXT_PP(0)); char *value = get_string_from_env(varname); #if PG_VERSION_NUM >= 150000 char *endptr = NULL; #endif /* Limit use to members of special role */ pgnodemx_check_role(); #if PG_VERSION_NUM < 150000 success = scanint8(value, true, &result); #else errno = 0; result = strtoi64(value, &endptr, 10); if (errno == 0 && *endptr == '\0') success = true; #endif if (!success) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("contents not an integer: env variable \"%s\"", varname))); PG_RETURN_INT64(result); } PG_FUNCTION_INFO_V1(pgnodemx_kdapi_setof_kv); Datum pgnodemx_kdapi_setof_kv(PG_FUNCTION_ARGS) { char *fqpath; int nlines; char **lines; int ncol = 2; if (!kdapi_enabled) return form_srf(fcinfo, NULL, 0, ncol, text_text_sig); fqpath = get_fq_kdapi_path(fcinfo); lines = read_nlsv(fqpath, &nlines); if (nlines > 0) { char ***values; int nrow = nlines; int i; values = (char ***) palloc(nrow * sizeof(char **)); for (i = 0; i < nrow; ++i) { /* * parse_keqv_line always returns two tokens * or throws an error if it cannot. */ values[i] = parse_keqv_line(lines[i]); } return form_srf(fcinfo, values, nrow, ncol, text_text_sig); } ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no lines in Kubernetes Downward API file: %s ", fqpath))); /* never reached */ return (Datum) 0; } PG_FUNCTION_INFO_V1(pgnodemx_kdapi_scalar_bigint); Datum pgnodemx_kdapi_scalar_bigint(PG_FUNCTION_ARGS) { char *fqpath; if (!kdapi_enabled) PG_RETURN_NULL(); fqpath = get_fq_kdapi_path(fcinfo); PG_RETURN_INT64(get_int64_from_file(fqpath)); } PG_FUNCTION_INFO_V1(pgnodemx_fips_mode); Datum pgnodemx_fips_mode(PG_FUNCTION_ARGS) { /* Limit use to members of special role */ pgnodemx_check_role(); #ifdef USE_OPENSSL #if OPENSSL_VERSION_MAJOR >= 3 if (EVP_default_properties_is_fips_enabled(NULL)) #else if (FIPS_mode()) #endif PG_RETURN_BOOL(true); else PG_RETURN_BOOL(false); #else PG_RETURN_BOOL(false); #endif } PG_FUNCTION_INFO_V1(pgnodemx_openssl_version); Datum pgnodemx_openssl_version(PG_FUNCTION_ARGS) { /* Limit use to members of special role */ pgnodemx_check_role(); #ifdef USE_OPENSSL PG_RETURN_TEXT_P(cstring_to_text(OPENSSL_VERSION_TEXT)); #else PG_RETURN_NULL(); #endif } PG_FUNCTION_INFO_V1(pgnodemx_symbol_filename); Datum pgnodemx_symbol_filename(PG_FUNCTION_ARGS) { const char *sym_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); const void *sym_addr; Dl_info info; int rc; char *msg; /* Limit use to members of special role */ pgnodemx_check_role(); /* according to the man page, clear any residual error message first */ msg = dlerror(); /* grab the symbol address using the default path */ sym_addr = dlsym(RTLD_DEFAULT, sym_name); /* any error means we could not find the symbol by this name */ msg = dlerror(); if (msg) PG_RETURN_NULL(); /* grab the source library */ rc = dladdr(sym_addr, &info); if (!rc) PG_RETURN_NULL(); else { char *tmppath = realpath(info.dli_fname, NULL); char *rpath; if (tmppath == NULL) PG_RETURN_NULL(); rpath = pstrdup(tmppath); free(tmppath); PG_RETURN_TEXT_P(cstring_to_text(rpath)); } } PG_FUNCTION_INFO_V1(pgnodemx_version); Datum pgnodemx_version(PG_FUNCTION_ARGS) { PG_RETURN_TEXT_P(cstring_to_text(GIT_HASH)); } pgnodemx-1.6/pgnodemx.control000066400000000000000000000002651446717006400164400ustar00rootroot00000000000000# pgnodemx extension comment = 'SQL functions that allow capture of node OS metrics from PostgreSQL' default_version = '1.6' module_pathname = '$libdir/pgnodemx' relocatable = true pgnodemx-1.6/procfunc.c000066400000000000000000000545741446717006400152140ustar00rootroot00000000000000/* * * Functions that allow capture procfs metrics from PostgreSQL * Dave Cramer * * This code is released under the PostgreSQL license. * * Copyright 2021-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #include "postgres.h" #include #include #include #include #include #include #include #include #include #include #include #include "fmgr.h" #include "funcapi.h" #include "miscadmin.h" #include "access/htup_details.h" #include "utils/tuplestore.h" #include "storage/fd.h" #include "utils/builtins.h" #include "fileutils.h" #include "genutils.h" #include "parseutils.h" #include "procfunc.h" #include "srfsigs.h" Datum pgnodemx_proc_meminfo(PG_FUNCTION_ARGS); Datum pgnodemx_fsinfo(PG_FUNCTION_ARGS); Datum pgnodemx_network_stats(PG_FUNCTION_ARGS); Datum pgnodemx_proctab(PG_FUNCTION_ARGS); Datum pgnodemx_proc_cputime(PG_FUNCTION_ARGS); Datum pgnodemx_proc_loadavg(PG_FUNCTION_ARGS); static char *get_fullcmd(char *pid); static void get_uid_username( char *pid, char **uid, char **username ); /* human readable to bytes */ #if PG_VERSION_NUM < 90600 #define h2b(arg1) size_bytes(arg1) #else #define h2b(arg1) \ DatumGetInt64(DirectFunctionCall1(pg_size_bytes, PointerGetDatum(cstring_to_text(arg1)))) #endif /* various /proc/ source files */ #define PROCFS "/proc" #define diskstats PROCFS "/diskstats" #define mountinfo PROCFS "/self/mountinfo" #define meminfo PROCFS "/meminfo" #define procstat PROCFS "/stat" #define loadavg PROCFS "/loadavg" #define netstat PROCFS "/self/net/dev" #define pidiofmt PROCFS "/%s/io" #define pidcmdfmt PROCFS "/%s/cmdline" #define childpidsfmt PROCFS "/%d/task/%d/children" #define pidstatfmt PROCFS "/%s/stat" extern bool proc_enabled; /* * "/proc" files: these files have all kinds of formats. For now * at least do not try to create generic parsing functions. Just * create a handful of specific access functions for the most * interesting (to us) files. */ /* * Check to see if procfs exists */ bool check_procfs(void) { struct statfs sb; /* Check if /proc is mounted. */ if (statfs(PROCFS, &sb) < 0 || sb.f_type != PROC_SUPER_MAGIC) return false; else return true; } /* * /proc/diskstats file: * * 1 - major number * 2 - minor mumber * 3 - device name * 4 - reads completed successfully * 5 - reads merged * 6 - sectors read * 7 - time spent reading (ms) * 8 - writes completed * 9 - writes merged * 10 - sectors written * 11 - time spent writing (ms) * 12 - I/Os currently in progress * 13 - time spent doing I/Os (ms) * 14 - weighted time spent doing I/Os (ms) * * Kernel 4.18+ appends four more fields for discard * tracking putting the total at 18: * * 15 - discards completed successfully * 16 - discards merged * 17 - sectors discarded * 18 - time spent discarding * * Kernel 5.5+ appends two more fields for flush requests: * * 19 - flush requests completed successfully * 20 - time spent flushing * * Validate either 14,18, or 20 fields found when * parsing the lines. Unused fields passed as NULL. */ PG_FUNCTION_INFO_V1(pgnodemx_proc_diskstats); Datum pgnodemx_proc_diskstats(PG_FUNCTION_ARGS) { int nrow = 0; int ncol = 20; char ***values = (char ***) palloc(0); char **lines; int nlines; if (!proc_enabled) return form_srf(fcinfo, NULL, 0, ncol, proc_diskstats_sig); /* read /proc/diskstats file */ lines = read_nlsv(diskstats, &nlines); /* * These files have either 14,18, or 20 fields per line. * Validate one of those lengths. The third column is the device name. * Rest of the columns are unsigned long or unsigned int, which * are mapped to postgres numeric or bigints respectively. */ if (nlines > 0) { int j; char **toks; nrow = nlines; values = (char ***) repalloc(values, nrow * sizeof(char **)); for (j = 0; j < nrow; ++j) { int ntok; int k; values[j] = (char **) palloc(ncol * sizeof(char *)); toks = parse_ss_line(lines[j], &ntok); if (ntok != 14 && ntok != 18 && ntok != 20) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: unexpected number of tokens, %d, in file %s, line %d", ntok, diskstats, j + 1))); for (k = 0; k < ncol; ++k) { if (k < ntok) values[j][k] = pstrdup(toks[k]); else values[j][k] = NULL; } } } else ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no data in file: %s ", diskstats))); return form_srf(fcinfo, values, nrow, ncol, proc_diskstats_sig); } /* * 3.5 /proc//mountinfo - Information about mounts * -------------------------------------------------------- * * This file contains lines of the form: * * 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue * (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) * * (1) mount ID: unique identifier of the mount (may be reused after umount) * (2) parent ID: ID of parent (or of self for the top of the mount tree) * (3) major:minor: value of st_dev for files on filesystem * (4) root: root of the mount within the filesystem * (5) mount point: mount point relative to the process's root * (6) mount options: per mount options * (7) optional fields: zero or more fields of the form "tag[:value]" * (8) separator: marks the end of the optional fields * (9) filesystem type: name of filesystem of the form "type[.subtype]" * (10) mount source: filesystem specific information or "none" * (11) super options: per super block options * * Parsers should ignore all unrecognised optional fields. Currently the * possible optional fields are: * * shared:X mount is shared in peer group X * master:X mount is slave to peer group X * propagate_from:X mount is slave and receives propagation from peer group X (*) * unbindable mount is unbindable * -------------------------------------------------------- * * Map fields 1 - 6, skip 7 (one or more) and 8, map 9 - 11 to a virtual * table with 10 columns (split major:minor into two columns) */ PG_FUNCTION_INFO_V1(pgnodemx_proc_mountinfo); Datum pgnodemx_proc_mountinfo(PG_FUNCTION_ARGS) { int nrow = 0; int ncol = 10; char ***values = (char ***) palloc(0); char **lines; int nlines; if (!proc_enabled) return form_srf(fcinfo, NULL, 0, ncol, _4_bigint_6_text_sig); /* read /proc/self/mountinfo file */ lines = read_nlsv(mountinfo, &nlines); /* * These files are complicated - see above. */ if (nlines > 0) { int j; char **toks; nrow = nlines; values = (char ***) repalloc(values, nrow * sizeof(char **)); for (j = 0; j < nrow; ++j) { int ntok; int k; int c = 0; bool sep_found = false; values[j] = (char **) palloc(ncol * sizeof(char *)); toks = parse_ss_line(lines[j], &ntok); /* there shoould be at least 10 tokens */ if (ntok < 10) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: unexpected number of tokens, %d, in file %s, line %d", ntok, mountinfo, j + 1))); /* iterate all found columns and keep the ones we want */ for (k = 0; k < ntok; ++k) { /* grab the first 6 columns */ if (k < 6) { if (k != 2) { values[j][c] = pstrdup(toks[k]); ++c; } else { /* split major:minor into two columns */ char *p = strchr(toks[k], ':'); Size len; if (!p) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: missing \":\" in file %s, line %d", mountinfo, j + 1))); len = (p - toks[k]); values[j][c] = pnstrdup(toks[k], len); ++c; values[j][c] = pstrdup(p + 1); ++c; } } else if (strcmp(toks[k], "-") == 0) /* skip until the separator */ sep_found = true; else if (sep_found) /* all good, grab the remaining columns */ { values[j][c] = pstrdup(toks[k]); ++c; } } /* make sure we found ncol columns */ if (c != ncol) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: malformed line in file %s, line %d", mountinfo, j + 1))); } } else ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no data in file: %s ", mountinfo))); return form_srf(fcinfo, values, nrow, ncol, _4_bigint_6_text_sig); } PG_FUNCTION_INFO_V1(pgnodemx_proc_meminfo); Datum pgnodemx_proc_meminfo(PG_FUNCTION_ARGS) { int nlines; char **lines; int ncol = 2; if (!proc_enabled) return form_srf(fcinfo, NULL, 0, ncol, text_bigint_sig); lines = read_nlsv(meminfo, &nlines); if (nlines > 0) { char ***values; int nrow = nlines; int i; char **fkl; values = (char ***) palloc(nrow * sizeof(char **)); for (i = 0; i < nrow; ++i) { size_t len; StringInfo hbytes = makeStringInfo(); int64 nbytes; int ntok; values[i] = (char **) palloc(ncol * sizeof(char *)); /* * These lines look like ":_some_spaces__ * We usually get back 3 tokens but sometimes 2 (no unit). * In either case we only have two output columns. */ fkl = parse_ss_line(lines[i], &ntok); if (ntok < 2 || ntok > 3) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: unexpected number of tokens, %d, in file %s, line %d", ntok, meminfo, i + 1))); /* token 1 will end with an extraneous colon - strip that */ len = strlen(fkl[0]) - 1; fkl[0][len] = '\0'; values[i][0] = pstrdup(fkl[0]); /* reconstruct tok 2 and 3 and then convert to bytes */ if (ntok == 3) { appendStringInfo(hbytes, "%s %s", fkl[1], fkl[2]); nbytes = h2b(hbytes->data); values[i][1] = int64_to_string(nbytes); } else values[i][1] = fkl[1]; } return form_srf(fcinfo, values, nrow, ncol, text_bigint_sig); } ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no lines in file: %s ", meminfo))); /* never reached */ return (Datum) 0; } PG_FUNCTION_INFO_V1(pgnodemx_fsinfo); Datum pgnodemx_fsinfo(PG_FUNCTION_ARGS) { int nrow; int ncol; char ***values; char *pname = text_to_cstring(PG_GETARG_TEXT_PP(0)); if (!proc_enabled) return form_srf(fcinfo, NULL, 0, ncol, _2_numeric_text_9_numeric_text_sig); values = get_statfs_path(pname, &nrow, &ncol); return form_srf(fcinfo, values, nrow, ncol, _2_numeric_text_9_numeric_text_sig); } #define HDR_LINES 2 PG_FUNCTION_INFO_V1(pgnodemx_network_stats); Datum pgnodemx_network_stats(PG_FUNCTION_ARGS) { int nrow = 0; int ncol = 17; char ***values = (char ***) palloc(0); char **lines; int nlines; if (!proc_enabled) return form_srf(fcinfo, NULL, 0, ncol, text_16_bigint_sig); /* read /proc/self/net/dev file */ lines = read_nlsv(netstat, &nlines); /* * These files have two rows we want to skip at the top. * Lines of interest are 17 space separated columns. * First column is the interface name. It has a trailing colon. * Rest of the columns are bigints. */ if (nlines > HDR_LINES) { int j; char **toks; nrow += (nlines - HDR_LINES); values = (char ***) repalloc(values, nrow * sizeof(char **)); for (j = HDR_LINES; j < nlines; ++j) { size_t len; int ntok; int k; values[j - HDR_LINES] = (char **) palloc(ncol * sizeof(char *)); toks = parse_ss_line(lines[j], &ntok); if (ntok != ncol) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: unexpected number of tokens, %d, in file %s, line %d", ntok, netstat, j + 1))); /* token 1 will end with an extraneous colon - strip that */ len = strlen(toks[0]) - 1; toks[0][len] = '\0'; values[j - HDR_LINES][0] = pstrdup(toks[0]); /* second through seventeenth columns are rx and tx stats */ for (k = 1; k < ncol; ++k) values[j - HDR_LINES][k] = pstrdup(toks[k]); } } else ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no data in file: %s ", netstat))); return form_srf(fcinfo, values, nrow, ncol, text_16_bigint_sig); } PG_FUNCTION_INFO_V1(pgnodemx_proc_pid_io); Datum pgnodemx_proc_pid_io(PG_FUNCTION_ARGS) { int nrow = 0; int ncol = 8; char **child_pids; pid_t ppid; char ***values = (char ***) palloc(0); StringInfo fname = makeStringInfo(); if (!proc_enabled) return form_srf(fcinfo, NULL, 0, ncol, int_7_numeric_sig); /* Get pid of all client connections. */ ppid = getppid(); appendStringInfo(fname, childpidsfmt, ppid, ppid); /* read /proc//task//children file */ child_pids = parse_space_sep_val_file(fname->data, &nrow); if (nrow > 0) { int j; /* nrow is the number of child pids we will be getting stats for */ values = (char ***) repalloc(values, nrow * sizeof(char **)); /* iterate through child pids */ for (j = 0; j < nrow; ++j) { int nlines; char ***iostat; int i; int k = 0; values[j] = (char **) palloc(ncol * sizeof(char *)); /* read io for current child pid */ resetStringInfo(fname); appendStringInfo(fname, pidiofmt, child_pids[j]); /* read "/proc//io file" */ iostat = read_kv_file(fname->data, &nlines); if (nlines != ncol - 1) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: expected %d tokens, got %d in keyed file %s", ncol - 1, nlines, fname->data))); /* inject the current child pid number as first column */ values[j][k++] = pstrdup(child_pids[j]); for (i = 0; i < nlines ; i++ ) { /* * We only care about the values, not the keys * because each key gets its own column in the * output. */ values[j][k++] = pstrdup(iostat[i][1]); } } return form_srf(fcinfo, values, nrow, ncol, int_7_numeric_sig); } ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no lines in flat keyed file: %s ", fname->data))); /* never reached */ return (Datum) 0; } PG_FUNCTION_INFO_V1(pgnodemx_proc_pid_cmdline); Datum pgnodemx_proc_pid_cmdline(PG_FUNCTION_ARGS) { int nrow = 0; int ncol = 4; char **child_pids; pid_t ppid; char ***values = (char ***) palloc(0); StringInfo fname = makeStringInfo(); if (!proc_enabled) return form_srf(fcinfo, NULL, 0, ncol, int_text_int_text_sig); /* Get pid of all client connections. */ ppid = getppid(); appendStringInfo(fname, childpidsfmt, ppid, ppid); /* read /proc//task//children file */ child_pids = parse_space_sep_val_file(fname->data, &nrow); if (nrow > 0) { int j; /* nrow is the number of child pids we will be getting stats for */ values = (char ***) repalloc(values, nrow * sizeof(char **)); /* iterate through child pids */ for (j = 0; j < nrow; ++j) { char *uid; char *username; values[j] = (char **) palloc(ncol * sizeof(char *)); /* inject the current child pid number as first column */ values[j][0] = pstrdup(child_pids[j]); /* full command line as second column */ values[j][1] = get_fullcmd(child_pids[j]); /* get uid and username for process */ get_uid_username(child_pids[j], &uid, &username); values[j][2] = pstrdup(uid); values[j][3] = pstrdup(username); } return form_srf(fcinfo, values, nrow, ncol, int_text_int_text_sig); } ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no lines in space separated file: %s ", fname->data))); /* never reached */ return (Datum) 0; } PG_FUNCTION_INFO_V1(pgnodemx_proc_pid_stat); Datum pgnodemx_proc_pid_stat(PG_FUNCTION_ARGS) { int nrow = 0; int ncol = 52; char **child_pids; pid_t ppid; char ***values = (char ***) palloc(0); StringInfo fname = makeStringInfo(); if (!proc_enabled) return form_srf(fcinfo, NULL, 0, ncol, proc_pid_stat_sig); /* Get pid of all client connections. */ ppid = getppid(); appendStringInfo(fname, childpidsfmt, ppid, ppid); /* read /proc//task//children file */ child_pids = parse_space_sep_val_file(fname->data, &nrow); if (nrow > 0) { int j; /* nrow is the number of child pids we will be getting stats for */ values = (char ***) repalloc(values, nrow * sizeof(char **)); /* iterate through child pids */ for (j = 0; j < nrow; ++j) { int ntok; char **toks; int k; char *rawstr; char *line; char *ptr1; char *ptr2; int ch1 = '('; int ch2 = ')'; /* read stats for current child pid */ resetStringInfo(fname); appendStringInfo(fname, pidstatfmt, child_pids[j]); /* read "/proc//stat file" */ rawstr = get_string_from_file(fname->data); /* * Find the end of the first two fields, and * advance line pointer to correct position in rawstr * for the rest. While at it, also find the first "(" */ /* Find start position of the command string */ ptr1 = strchr(rawstr, ch1 ); /* Find end position of the command string */ ptr2 = strrchr(rawstr, ch2 ); /* * The rest of the line starts 2 bytes after * the command ending parenthesis */ line = ptr2 + 2; /* parse the rest */ toks = parse_ss_line(line, &ntok); /* there should be two columns less in the parsed rest-of-line */ if ((ntok + 2) != ncol) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: expected %d tokens, got %d in space separated file %s", ncol, ntok + 2, fname->data))); values[j] = (char **) palloc(ncol * sizeof(char *)); for (k = 0; k < ncol; ++k) { if ( k == 0 ) { char *p = ptr1 - 1; /* * first column starts at 0 and goes through * ptr1 - 1 */ p[0] = '\0'; values[j][k] = pstrdup(rawstr); } else if ( k == 1 ) { char *p = ptr1 + 1; /* * second column (command) is everything inbetween * ptr1 + 1 to ptr2 (which should be a NULL terminator) */ ptr2[0] = '\0'; values[j][k] = pstrdup(p); } else values[j][k] = pstrdup(toks[k - 2]); } } return form_srf(fcinfo, values, nrow, ncol, proc_pid_stat_sig); } ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: no lines in flat keyed file: %s ", fname->data))); /* never reached */ return (Datum) 0; } /* * Returns full command line of a postgres pid * * Note that this would not work with pids in general * because the /proc/pid/cmdline file typically separates * the command line options using NULL bytes ('\0'). But * for postgres they are rewritten as full strings for * clear ps output. */ static char * get_fullcmd(char *pid) { StringInfo fname = makeStringInfo(); /* calculate filename of interest */ appendStringInfo(fname, pidcmdfmt, pid); /* read /proc//cmdline file */ return get_string_from_file(fname->data); } #define INTEGER_LEN 64 static void get_uid_username( char *pid, char **uid, char **username ) { struct stat stat_struct; char tmp[INTEGER_LEN]; /* Get the uid and username of the pid's owner. */ snprintf(tmp, sizeof(tmp) - 1, "%s/%s", PROCFS, pid); if (stat(tmp, &stat_struct) < 0) { elog(ERROR, "'%s' not found", tmp); *uid = pstrdup("-1"); *username = NULL; } else { struct passwd *pwd; snprintf(tmp, INTEGER_LEN, "%" PRIuMAX, (uintmax_t) stat_struct.st_uid); *uid = pstrdup(tmp); pwd = getpwuid(stat_struct.st_uid); if (pwd == NULL) *username = NULL; else { *username = pstrdup(pwd->pw_name); } } } PG_FUNCTION_INFO_V1(pgnodemx_proc_cputime); Datum pgnodemx_proc_cputime(PG_FUNCTION_ARGS) { int nrow = 1; int ncol = 5; char ***values = (char ***) palloc(0); char **lines; int nlines; char **tokens; int ntok; if (!proc_enabled) return form_srf(fcinfo, NULL, 0, ncol, _5_bigint_sig); lines = read_nlsv(procstat, &nlines); /* currently only interested in the first part of the first line */ if (nlines < 1) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: got too few lines in file %s", procstat))); tokens = parse_ss_line(lines[0], &ntok); if (ntok < (ncol + 1)) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: got too few values in file %s", procstat))); values = (char ***) repalloc(values, nrow * sizeof(char **)); values[0] = (char **) palloc(ncol * sizeof(char *)); values[0][0] = pstrdup(tokens[1]); values[0][1] = pstrdup(tokens[2]); values[0][2] = pstrdup(tokens[3]); values[0][3] = pstrdup(tokens[4]); values[0][4] = pstrdup(tokens[5]); return form_srf(fcinfo, values, nrow, ncol, _5_bigint_sig); } PG_FUNCTION_INFO_V1(pgnodemx_proc_loadavg); Datum pgnodemx_proc_loadavg(PG_FUNCTION_ARGS) { int nrow = 1; int ncol = 4; char ***values = (char ***) palloc(0); char *rawstr; char **tokens; int ntok; if (!proc_enabled) return form_srf(fcinfo, NULL, 0, ncol, load_avg_sig); rawstr = read_one_nlsv(loadavg); tokens = parse_ss_line(rawstr, &ntok); if (ntok < (ncol + 1)) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("pgnodemx: got too few values in file %s", loadavg))); values = (char ***) repalloc(values, nrow * sizeof(char **)); values[0] = (char **) palloc(ncol * sizeof(char *)); values[0][0] = pstrdup(tokens[0]); values[0][1] = pstrdup(tokens[1]); values[0][2] = pstrdup(tokens[2]); /* skip running/tasks */ values[0][3] = pstrdup(tokens[4]); return form_srf(fcinfo, values, nrow, ncol, load_avg_sig); } pgnodemx-1.6/procfunc.h000066400000000000000000000024001446717006400151760ustar00rootroot00000000000000/* * * Return signatures for SRFs * * This code is released under the PostgreSQL license. * * Copyright 2021-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #ifndef _PROCFUNC_H_ #define _PROCFUNC_H_ extern bool check_procfs(void); #endif /* _PROCFUNC_H_ */ pgnodemx-1.6/qunique.h000066400000000000000000000030751446717006400150570ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * qunique.h * inline array unique functions * Portions Copyright (c) 2019-2023, PostgreSQL Global Development Group * * IDENTIFICATION * src/include/lib/qunique.h *------------------------------------------------------------------------- */ #ifndef QUNIQUE_H #define QUNIQUE_H /* * Remove duplicates from a pre-sorted array, according to a user-supplied * comparator. Usually the array should have been sorted with qsort() using * the same arguments. Return the new size. */ static inline size_t qunique(void *array, size_t elements, size_t width, int (*compare) (const void *, const void *)) { char *bytes = (char *) array; size_t i, j; if (elements <= 1) return elements; for (i = 1, j = 0; i < elements; ++i) { if (compare(bytes + i * width, bytes + j * width) != 0 && ++j != i) memcpy(bytes + j * width, bytes + i * width, width); } return j + 1; } /* * Like qunique(), but takes a comparator with an extra user data argument * which is passed through, for compatibility with qsort_arg(). */ static inline size_t qunique_arg(void *array, size_t elements, size_t width, int (*compare) (const void *, const void *, void *), void *arg) { char *bytes = (char *) array; size_t i, j; if (elements <= 1) return elements; for (i = 1, j = 0; i < elements; ++i) { if (compare(bytes + i * width, bytes + j * width, arg) != 0 && ++j != i) memcpy(bytes + j * width, bytes + i * width, width); } return j + 1; } #endif /* QUNIQUE_H */ pgnodemx-1.6/sql/000077500000000000000000000000001446717006400140115ustar00rootroot00000000000000pgnodemx-1.6/sql/pgnodemx_1.sql000066400000000000000000000077341446717006400166060ustar00rootroot00000000000000/* beginnings of a cgroup v1 regression test */ \pset pager off \x auto DROP EXTENSION IF EXISTS pgnodemx; CREATE EXTENSION pgnodemx; SELECT cgroup_mode(); SELECT * FROM cgroup_path(); SELECT cgroup_process_count(); SELECT current_setting('pgnodemx.containerized'); SELECT current_setting('pgnodemx.cgroup_enabled'); SELECT cgroup_scalar_bigint('memory.usage_in_bytes'); SELECT cgroup_scalar_float8('memory.usage_in_bytes'); SELECT cgroup_scalar_text('memory.usage_in_bytes'); SELECT cgroup_scalar_bigint('memory.limit_in_bytes'); SELECT cgroup_scalar_bigint('cpu.cfs_period_us'); SELECT cgroup_scalar_bigint('cpu.cfs_quota_us'); SELECT cgroup_scalar_bigint('cpuacct.usage'); -- should return NULL SELECT cgroup_scalar_bigint(null); -- should fail SELECT cgroup_scalar_bigint('bar/../../etc/memory.usage_in_bytes'); -- should fail SELECT cgroup_scalar_bigint('/memory.usage_in_bytes'); CREATE USER pgnodemx_joe; SET SESSION AUTHORIZATION pgnodemx_joe; -- should fail SELECT cgroup_scalar_bigint('memory.usage_in_bytes'); RESET SESSION AUTHORIZATION; DROP USER pgnodemx_joe; SELECT cgroup_setof_bigint('cgroup.procs'); SELECT cgroup_array_text('cpu.shares'); SELECT cgroup_array_bigint('cpu.shares'); SELECT * FROM cgroup_setof_kv('cpuacct.stat'); SELECT * FROM cgroup_setof_kv('cpu.stat'); SELECT * FROM cgroup_setof_kv('memory.stat'); SELECT * FROM cgroup_setof_ksv('blkio.throttle.io_serviced'); SELECT * FROM cgroup_setof_ksv('blkio.throttle.io_service_bytes'); SELECT envvar_text('PGDATA'); SELECT envvar_text('HOSTNAME'); SELECT envvar_bigint('PGHA_PG_PORT'); SELECT * FROM proc_diskstats(); SELECT * FROM proc_mountinfo(); SELECT * FROM proc_meminfo(); SELECT * FROM fsinfo(current_setting('data_directory')); SELECT pg_size_pretty(total_bytes) AS total_size, pg_size_pretty(available_bytes) AS available_size FROM fsinfo(current_setting('data_directory')); SELECT * FROM proc_network_stats(); SELECT interface, rx_bytes, rx_packets, tx_bytes, tx_packets FROM proc_network_stats(); SELECT current_setting('pgnodemx.kdapi_enabled'); SELECT * FROM kdapi_setof_kv('labels'); SELECT * FROM kdapi_setof_kv('annotations'); SELECT replace(val,'\"','"')::jsonb FROM kdapi_setof_kv('annotations') WHERE key = 'status'; SELECT * FROM kdapi_scalar_bigint('cpu_limit'); SELECT * FROM kdapi_scalar_bigint('cpu_request'); SELECT * FROM kdapi_scalar_bigint('mem_limit'); SELECT * FROM kdapi_scalar_bigint('mem_request'); SELECT * FROM proc_mountinfo() m JOIN proc_diskstats() d USING (major_number, minor_number) JOIN fsinfo(current_setting('data_directory')) f USING (major_number, minor_number); SELECT "user", nice, system, idle, iowait FROM proc_cputime(); SELECT load1, load5, load15, last_pid FROM proc_loadavg(); WITH m (key,val) AS ( SELECT key, val FROM proc_meminfo() ) SELECT ((SELECT val FROM m WHERE key = 'MemTotal') - (SELECT val FROM m WHERE key = 'MemFree')) / 1024 as memused, (SELECT val FROM m WHERE key = 'MemFree') / 1024 AS memfree, (SELECT val FROM m WHERE key = 'Shmem') / 1024 AS memshared, (SELECT val FROM m WHERE key = 'Buffers') / 1024 AS membuffers, (SELECT val FROM m WHERE key = 'Cached') / 1024 AS memcached, ((SELECT val FROM m WHERE key = 'SwapTotal') - (SELECT val FROM m WHERE key = 'SwapFree')) / 1024 AS swapused, (SELECT val FROM m WHERE key = 'SwapFree') / 1024 AS swapfree, (SELECT val FROM m WHERE key = 'SwapCached') / 1024 as swapcached; SELECT s.pid, comm, fullcomm, state, ppid, pgrp, session, tty_nr, tpgid, flags, minflt, cminflt, majflt, cmajflt, utime, stime, cutime, cstime, priority, nice, num_threads, itrealvalue, starttime, vsize, kpages_to_bytes(rss) / 1024 as rss, exit_signal, processor, rt_priority, policy, delayacct_blkio_ticks, uid, username, rchar, wchar, syscr, syscw, reads, writes, cwrites FROM proc_pid_stat() s JOIN proc_pid_cmdline() c ON s.pid = c.pid JOIN proc_pid_io() i ON c.pid = i.pid; SELECT exec_path(), * FROM stat_file(exec_path()); pgnodemx-1.6/sql/pgnodemx_2.sql000066400000000000000000000101611446717006400165730ustar00rootroot00000000000000/* beginnings of a cgroup v2 regression test */ \pset pager off \x auto DROP EXTENSION IF EXISTS pgnodemx; CREATE EXTENSION pgnodemx; SELECT cgroup_mode(); SELECT * FROM cgroup_path(); SELECT cgroup_process_count(); SELECT current_setting('pgnodemx.containerized'); SELECT current_setting('pgnodemx.cgroup_enabled'); SELECT cgroup_scalar_bigint('memory.current'); SELECT cgroup_scalar_float8('memory.current'); SELECT cgroup_scalar_text('memory.current'); SELECT cgroup_scalar_text('cgroup.type'); SELECT cgroup_scalar_bigint('memory.high'); SELECT cgroup_scalar_bigint('memory.max'); SELECT cgroup_scalar_bigint('memory.swap.current'); -- should return NULL SELECT cgroup_scalar_bigint(null); -- should fail SELECT cgroup_scalar_bigint('bar/../../etc/memory.max'); -- should fail SELECT cgroup_scalar_bigint('/memory.max'); CREATE USER pgnodemx_joe; SET SESSION AUTHORIZATION pgnodemx_joe; -- should fail SELECT cgroup_scalar_bigint('memory.current'); RESET SESSION AUTHORIZATION; DROP USER pgnodemx_joe; SELECT cgroup_setof_bigint('cgroup.procs'); SELECT cgroup_array_text('cpu.max'); SELECT cgroup_array_bigint('cpu.max'); SELECT cgroup_array_text('cgroup.controllers'); SELECT * FROM cgroup_setof_kv('memory.stat'); SELECT * FROM cgroup_setof_kv('cgroup.events'); SELECT * FROM cgroup_setof_kv('cgroup.stat'); SELECT * FROM cgroup_setof_kv('cpu.stat'); SELECT * FROM cgroup_setof_kv('io.weight'); SELECT * FROM cgroup_setof_kv('memory.events'); SELECT * FROM cgroup_setof_kv('memory.events.local'); SELECT * FROM cgroup_setof_kv('memory.swap.events'); SELECT * FROM cgroup_setof_kv('pids.events'); SELECT * FROM cgroup_setof_nkv('memory.pressure'); SELECT * FROM cgroup_setof_nkv('io.stat'); SELECT * FROM cgroup_setof_nkv('io.pressure'); SELECT * FROM cgroup_setof_nkv('cpu.pressure'); SELECT envvar_text('PGDATA'); SELECT envvar_bigint('PGPORT'); SELECT * FROM proc_diskstats(); SELECT * FROM proc_mountinfo(); SELECT * FROM proc_meminfo(); SELECT * FROM fsinfo(current_setting('data_directory')); SELECT pg_size_pretty(total_bytes) AS total_size, pg_size_pretty(available_bytes) AS available_size FROM fsinfo(current_setting('data_directory')); SELECT * FROM proc_network_stats(); SELECT interface, rx_bytes, rx_packets, tx_bytes, tx_packets FROM proc_network_stats(); SELECT current_setting('pgnodemx.kdapi_enabled'); SELECT * FROM kdapi_setof_kv('labels'); SELECT * FROM kdapi_scalar_bigint('cpu_limit'); SELECT * FROM kdapi_scalar_bigint('cpu_request'); SELECT * FROM kdapi_scalar_bigint('mem_limit'); SELECT * FROM kdapi_scalar_bigint('mem_request'); SELECT * FROM proc_mountinfo() m JOIN proc_diskstats() d USING (major_number, minor_number) JOIN fsinfo(current_setting('data_directory')) f USING (major_number, minor_number); SELECT "user", nice, system, idle, iowait FROM proc_cputime(); SELECT load1, load5, load15, last_pid FROM proc_loadavg(); WITH m (key,val) AS ( SELECT key, val FROM proc_meminfo() ) SELECT ((SELECT val FROM m WHERE key = 'MemTotal') - (SELECT val FROM m WHERE key = 'MemFree')) / 1024 as memused, (SELECT val FROM m WHERE key = 'MemFree') / 1024 AS memfree, (SELECT val FROM m WHERE key = 'Shmem') / 1024 AS memshared, (SELECT val FROM m WHERE key = 'Buffers') / 1024 AS membuffers, (SELECT val FROM m WHERE key = 'Cached') / 1024 AS memcached, ((SELECT val FROM m WHERE key = 'SwapTotal') - (SELECT val FROM m WHERE key = 'SwapFree')) / 1024 AS swapused, (SELECT val FROM m WHERE key = 'SwapFree') / 1024 AS swapfree, (SELECT val FROM m WHERE key = 'SwapCached') / 1024 as swapcached; SELECT s.pid, comm, fullcomm, state, ppid, pgrp, session, tty_nr, tpgid, flags, minflt, cminflt, majflt, cmajflt, utime, stime, cutime, cstime, priority, nice, num_threads, itrealvalue, starttime, vsize, kpages_to_bytes(rss) / 1024 as rss, exit_signal, processor, rt_priority, policy, delayacct_blkio_ticks, uid, username, rchar, wchar, syscr, syscw, reads, writes, cwrites FROM proc_pid_stat() s JOIN proc_pid_cmdline() c ON s.pid = c.pid JOIN proc_pid_io() i ON c.pid = i.pid; SELECT exec_path(), * FROM stat_file(exec_path()); pgnodemx-1.6/srfsigs.h000066400000000000000000000034061446717006400150460ustar00rootroot00000000000000/* * * Return signatures for SRFs * * This code is released under the PostgreSQL license. * * Copyright 2021-2023 Crunchy Data Solutions, Inc. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without a written * agreement is hereby granted, provided that the above copyright notice * and this paragraph and the following two paragraphs appear in all copies. * * IN NO EVENT SHALL CRUNCHY DATA SOLUTIONS, INC. BE LIABLE TO ANY PARTY * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS * DOCUMENTATION, EVEN IF THE CRUNCHY DATA SOLUTIONS, INC. HAS BEEN ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * THE CRUNCHY DATA SOLUTIONS, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE CRUNCHY DATA SOLUTIONS, INC. HAS NO * OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR * MODIFICATIONS. */ #ifndef _SRFSIGS_H_ #define _SRFSIGS_H_ /* function return signatures */ extern Oid text_sig[]; extern Oid bigint_sig[]; extern Oid text_text_sig[]; extern Oid text_bigint_sig[]; extern Oid text_text_bigint_sig[]; extern Oid text_text_float8_sig[]; extern Oid _2_numeric_text_9_numeric_text_sig[]; extern Oid _4_bigint_6_text_sig[]; extern Oid text_16_bigint_sig[]; extern Oid _5_bigint_sig[]; extern Oid int_7_numeric_sig[]; extern Oid int_text_int_text_sig[]; extern Oid num_text_num_2_text_sig[]; extern Oid load_avg_sig[]; extern Oid proc_diskstats_sig[]; extern Oid proc_pid_stat_sig[]; #endif /* _SRFSIGS_H_ */