diff --git a/build-docker-timescale.sh b/build-docker-timescale.sh new file mode 100644 index 000000000..13d7a6ad5 --- /dev/null +++ b/build-docker-timescale.sh @@ -0,0 +1,6 @@ +#!/bin/bash +docker build \ + --build-arg GIT_TIME=`git show -s --format=%cI HEAD` \ + --build-arg GIT_HASH=`git show -s --format=%H HEAD` \ + -t cybertec/pgwatch3-timescale:latest \ + -f docker/Dockerfile-timescale . diff --git a/docker/Dockerfile b/docker/Dockerfile index a38f977df..c0d672f8e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,7 @@ # Build web UI FROM node:19 AS uibuilder ADD src/webui /webui -RUN cd webui && yarn install && yarn build +RUN cd webui && yarn install --network-timeout 100000 && yarn build # Build gatherer FROM golang:1.20 as builder diff --git a/docker/Dockerfile-daemon b/docker/Dockerfile-daemon index 7bed0ef92..a1ca7ba4b 100644 --- a/docker/Dockerfile-daemon +++ b/docker/Dockerfile-daemon @@ -1,7 +1,7 @@ # Build web UI FROM node:19 AS uibuilder ADD src/webui /webui -RUN cd webui && yarn install && yarn build +RUN cd webui && yarn install --network-timeout 100000 && yarn build FROM golang:alpine AS builder diff --git a/docker/Dockerfile-timescale b/docker/Dockerfile-timescale new file mode 100644 index 000000000..81cf679b4 --- /dev/null +++ b/docker/Dockerfile-timescale @@ -0,0 +1,88 @@ +# Build web UI +FROM node:19 AS uibuilder +ADD src/webui /webui +RUN cd webui && yarn install --network-timeout 100000 && yarn build + +# Build gatherer +FROM golang:1.20 as builder +ARG GIT_HASH +ARG GIT_TIME +ENV GIT_HASH=${GIT_HASH} +ENV GIT_TIME=${GIT_TIME} + +ADD src /pgwatch3 +COPY --from=uibuilder /webui/build /pgwatch3/webui/build +RUN cd /pgwatch3 && bash build_gatherer.sh + +# Build the final image +FROM ubuntu:22.04 + +RUN apt-get -q update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -qy curl wget apt-transport-https vim git postgresql-common gnupg2 python3-pip \ + && sh /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y \ + && DEBIAN_FRONTEND=noninteractive apt-get install -qy postgresql-15 postgresql-plpython3-15 python3-psutil \ + && echo "deb https://packagecloud.io/timescale/timescaledb/ubuntu/ jammy main" | tee /etc/apt/sources.list.d/timescaledb.list \ + && wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | apt-key add - \ + && apt update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -qy timescaledb-2-postgresql-15 postgresql-15-pg-qualstats \ + && echo "include = 'pgwatch_postgresql.conf'" >> /etc/postgresql/15/main/postgresql.conf + +RUN arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \ + && DEBIAN_FRONTEND=noninteractive apt-get install -qy libfontconfig1 \ + && wget -q -O grafana.deb "https://dl.grafana.com/oss/release/grafana_8.5.20_${arch}.deb" \ + && dpkg -i grafana.deb && rm grafana.deb + + +# Add pgwatch3 sources +ADD src /pgwatch3 +# Copy over the compiled gatherer +COPY --from=builder /pgwatch3/pgwatch3 /pgwatch3 + +# For showing Git versions via :8080/versions or 'pgwatch3 --version' +ARG GIT_HASH +ARG GIT_TIME +ENV GIT_HASH=${GIT_HASH} +ENV GIT_TIME=${GIT_TIME} +RUN echo "${GIT_HASH} ${GIT_TIME}" > /pgwatch3/build_git_version.txt + + +# Grafana config customizations, Python requirements +RUN cp /pgwatch3/bootstrap/grafana_custom_config.ini /etc/grafana/grafana.ini \ + && pip3 install psutil supervisor pyyaml && mkdir /var/log/supervisor + +ADD grafana_dashboards /pgwatch3/grafana_dashboards + + +# Set up supervisord [https://docs.docker.com/engine/admin/using_supervisord/] +COPY docker/supervisord-timescale.conf /etc/supervisor/supervisord.conf + +# NB! When security is a concern one should definitely alter "pgwatch3" password in change_pw.sql and maybe modify pg_hba.conf accordingly +COPY docker/postgresql_timescale.conf /etc/postgresql/15/main/pgwatch_postgresql.conf +COPY docker/pg_hba.conf /etc/postgresql/15/main/pg_hba.conf +COPY docker/docker-launcher-timescale.sh docker/pg_hba.conf /pgwatch3/ + +ENV PW3_DATASTORE postgres +ENV PW3_PG_METRIC_STORE_CONN_STR postgresql://pgwatch3:pgwatch3admin@localhost:5432/pgwatch3_metrics +ENV PW3_PG_SCHEMA_TYPE timescale +ENV PW3_AES_GCM_KEYPHRASE_FILE /pgwatch3/persistent-config/default-password-encryption-key.txt + +# Admin UI for configuring servers to be monitored +EXPOSE 8080 +# Gatherer healthcheck port / metric statistics (JSON) +EXPOSE 8081 +# Postgres DB holding the pgwatch3 config DB / metrics +EXPOSE 5432 +# Grafana UI +EXPOSE 3000 +# Prometheus scraping port +EXPOSE 9187 + +### Volumes for easier updating to newer to newer pgwatch3 containers +### NB! Backwards compatibility is not 100% guaranteed so a backup +### using traditional means is still recommended before updating - see "Updating to a newer Docker version" from README + +VOLUME /pgwatch3/persistent-config +VOLUME /var/lib/postgresql +VOLUME /var/lib/grafana + +CMD ["/pgwatch3/docker-launcher-timescale.sh"] diff --git a/docker/docker-launcher-timescale.sh b/docker/docker-launcher-timescale.sh new file mode 100644 index 000000000..cfbba3570 --- /dev/null +++ b/docker/docker-launcher-timescale.sh @@ -0,0 +1,100 @@ +#!/bin/bash + +mkdir /var/run/grafana && chown grafana /var/run/grafana + +if [ ! -f /pgwatch3/persistent-config/self-signed-ssl.key -o ! -f /pgwatch3/persistent-config/self-signed-ssl.pem ] ; then + openssl req -x509 -newkey rsa:4096 -keyout /pgwatch3/persistent-config/self-signed-ssl.key -out /pgwatch3/persistent-config/self-signed-ssl.pem -days 3650 -nodes -sha256 -subj '/CN=pw2' + cp /pgwatch3/persistent-config/self-signed-ssl.pem /etc/ssl/certs/ssl-cert-snakeoil.pem + cp /pgwatch3/persistent-config/self-signed-ssl.key /etc/ssl/private/ssl-cert-snakeoil.key + chown postgres /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/ssl/private/ssl-cert-snakeoil.key + chmod -R 0600 /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/ssl/private/ssl-cert-snakeoil.key + chmod -R o+rx /pgwatch3/persistent-config +fi + +# enable password encryption by default from v1.8.0 +if [ ! -f /pgwatch3/persistent-config/default-password-encryption-key.txt ]; then + echo -n "${RANDOM}${RANDOM}${RANDOM}${RANDOM}" > /pgwatch3/persistent-config/default-password-encryption-key.txt + chmod 0600 /pgwatch3/persistent-config/default-password-encryption-key.txt +fi + +GRAFANASSL="${PW3_GRAFANASSL,,}" # to lowercase +if [ "$GRAFANASSL" == "1" ] || [ "${GRAFANASSL:0:1}" == "t" ]; then + $(grep -q 'protocol = http$' /etc/grafana/grafana.ini) + if [ "$?" -eq 0 ] ; then + sed -i 's/protocol = http.*/protocol = https/' /etc/grafana/grafana.ini + fi +fi + +if [ -n "$PW3_GRAFANAUSER" ] ; then + sed -i "s/admin_user =.*/admin_user = ${PW3_GRAFANAUSER}/" /etc/grafana/grafana.ini +fi + +if [ -n "$PW3_GRAFANAPASSWORD" ] ; then + sed -i "s/admin_password =.*/admin_password = ${PW3_GRAFANAPASSWORD}/" /etc/grafana/grafana.ini +fi + +if [ -n "$PW3_GRAFANANOANONYMOUS" ] ; then +CFG=$(cat <<-'HERE' +[auth.anonymous] +enabled = false +HERE +) +echo "$CFG" >> /etc/grafana/grafana.ini +fi + +if [ ! -f /pgwatch3/persistent-config/db-bootstrap-done-marker ] ; then + +if [ ! -d /var/lib/postgresql/15 ]; then + mkdir /var/lib/postgresql/15 && chown -R postgres:postgres /var/lib/postgresql/15 + pg_dropcluster 15 main + pg_createcluster --locale en_US.UTF-8 15 main + echo "include = 'pgwatch_postgresql.conf'" >> /etc/postgresql/15/main/postgresql.conf + cp /pgwatch3/postgresql_timescale.conf /etc/postgresql/15/main/pgwatch_postgresql.conf + cp /pgwatch3/pg_hba.conf /etc/postgresql/15/main/pg_hba.conf +fi + +pg_ctlcluster 15 main start -- --wait + +su -c "psql -d postgres -f /pgwatch3/bootstrap/change_pw.sql" postgres +su -c "psql -d postgres -f /pgwatch3/bootstrap/grant_monitor_to_pgwatch3.sql" postgres +su -c "psql -d postgres -f /pgwatch3/bootstrap/create_db_pgwatch.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/bootstrap/revoke_public_create.sql" postgres +su -c "psql -d postgres -f /pgwatch3/bootstrap/create_db_grafana.sql" postgres +su -c "psql -d postgres -f /pgwatch3/bootstrap/create_db_metric_store.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/sql/config_store/config_store.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/sql/config_store/metric_definitions.sql" postgres +su -c "psql -d pgwatch3_metrics -f /pgwatch3/sql/metric_store/00_schema_base.sql" postgres +su -c "psql -d pgwatch3_metrics -f /pgwatch3/sql/metric_store/01_old_metrics_cleanup_procedure.sql" postgres + +su -c "psql -d pgwatch3_metrics -f /pgwatch3/sql/metric_store/timescale/ensure_partition_timescale.sql" postgres +su -c "psql -d pgwatch3_metrics -f /pgwatch3/sql/metric_store/timescale/change_compression_interval.sql" postgres +su -c "psql -d pgwatch3_metrics -f /pgwatch3/sql/metric_store/timescale/metric_store_timescale.sql" postgres +su -c "psql -d pgwatch3_metrics -f /pgwatch3/sql/metric_store/timescale/change_chunk_interval.sql" postgres +su -c "psql -d pgwatch3_metrics -f /pgwatch3/sql/metric_store/metric-time/ensure_partition_metric_time.sql" postgres + +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_load_average/9.1/metric.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_stat_statements/9.4/metric.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_stat_activity/9.2/metric.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_stat_replication/9.2/metric.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_table_bloat_approx/9.5/metric.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_table_bloat_approx_sql/12/metric.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_wal_size/10/metric.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_psutil_cpu/9.1/metric.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_psutil_mem/9.1/metric.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_psutil_disk/9.1/metric.sql" postgres +su -c "psql -d pgwatch3 -f /pgwatch3/metrics/00_helpers/get_psutil_disk_io_total/9.1/metric.sql" postgres +su -c "psql -d pgwatch3 -c 'create extension pg_qualstats'" postgres + +if [ -n "$PW3_TESTDB" ] ; then + su -c "psql -d pgwatch3 -f /pgwatch3/bootstrap/insert_test_monitored_db.sql" postgres +fi + +touch /pgwatch3/persistent-config/db-bootstrap-done-marker + +pg_ctlcluster 15 main stop -- --wait + +fi + +sleep 1 + +exec /usr/local/bin/supervisord --configuration=/etc/supervisor/supervisord.conf --nodaemon diff --git a/docker/postgresql_timescale.conf b/docker/postgresql_timescale.conf new file mode 100644 index 000000000..74c43ca09 --- /dev/null +++ b/docker/postgresql_timescale.conf @@ -0,0 +1,19 @@ +listen_addresses='*' +shared_buffers='64MB' +work_mem='16MB' +checkpoint_timeout='1h' +ssl=true +track_io_timing=on +shared_preload_libraries = 'pg_stat_statements,timescaledb,pg_qualstats' +track_functions='pl' +wal_compression=zstd +log_destination=csvlog +logging_collector=on +log_directory='/var/log/postgresql' +log_filename='postgresql-%a.log' +log_truncate_on_rotation=on +wal_level=archive +archive_mode=on +archive_command='/bin/true' +max_worker_processes=16 +max_locks_per_transaction=128 diff --git a/docker/supervisord-timescale.conf b/docker/supervisord-timescale.conf new file mode 100644 index 000000000..bac0cd994 --- /dev/null +++ b/docker/supervisord-timescale.conf @@ -0,0 +1,50 @@ +[supervisord] +nodaemon=true +user=root +pidfile=/var/run/supervisord.pid +logfile=/var/log/supervisor/supervisord.log +childlogdir=/var/log/supervisor + +[unix_http_server] +file=/var/run/supervisor.sock + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock + +[program:supervisord_bootstrap] +command=/pgwatch3/bootstrap/supervisord_bootstrap_pg.sh +autorestart=false +startsecs=0 +autostart=true +redirect_stderr=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 + +[program:postgres] +command=/usr/lib/postgresql/15/bin/postgres -D /var/lib/postgresql/15/main -c config_file=/etc/postgresql/15/main/postgresql.conf +user=postgres +startsecs=0 +priority=1 +autostart=false + +[program:pgwatch3] +command=/pgwatch3/pgwatch3 +priority=300 +autostart=false + +[program:grafana] +command=/usr/sbin/grafana-server --homepath=/usr/share/grafana --pidfile=/var/run/grafana/grafana-server.pid --config=/etc/grafana/grafana.ini --packaging=deb cfg:default.paths.provisioning=/etc/grafana/provisioning cfg:default.paths.data=/var/lib/grafana cfg:default.paths.logs=/var/log/grafana cfg:default.paths.plugins=/var/lib/grafana/plugins +user=grafana +startsecs=0 +priority=500 +autostart=false + +[program:grafana_dashboard_setup] +command=/pgwatch3/bootstrap/set_up_grafana_dashboards_pg.sh +priority=600 +autorestart=false +startsecs=0 +autostart=false diff --git a/grafana_dashboards/postgres/v8/stat-activity/dashboard.json b/grafana_dashboards/postgres/v8/stat-activity/dashboard.json index 88e7c0ddc..e90640cbf 100644 --- a/grafana_dashboards/postgres/v8/stat-activity/dashboard.json +++ b/grafana_dashboards/postgres/v8/stat-activity/dashboard.json @@ -112,7 +112,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "select\n $__timeGroup(time, $agg_interval),\n sum((data->>'count')::integer) as \" \",\n data->>'query' as \"query\"\nfrom stat_activity\nwhere dbname = '$dbname' and $__timeFilter(time)\n and case when '$hide_pgwatch_generated' = 'yes' then\n not data->>'query' ~* E'/\\\\*\\\\W*pgwatch2_generated\\\\W*\\\\*/'\n else\n true\n end\ngroup by 1,3\norder by 1;\n\n", + "rawSql": "select\n $__timeGroup(time, $agg_interval),\n sum((data->>'count')::integer) as \" \",\n data->>'query' as \"query\"\nfrom stat_activity\nwhere dbname = '$dbname' and $__timeFilter(time)\n and case when '$hide_pgwatch_generated' = 'yes' then\n not data->>'query' ~* E'/\\\\*\\\\W*pgwatch3_generated\\\\W*\\\\*/'\n else\n true\n end\ngroup by 1,3\norder by 1;\n\n", "refId": "A", "select": [ [ @@ -205,7 +205,7 @@ "group": [], "metricColumn": "none", "rawQuery": true, - "rawSql": "select sum((data->>'count')::integer) as count, data->>'query' as query\nfrom stat_activity\nwhere dbname = '$dbname'\n and $__timeFilter(time)\n and case when '$hide_pgwatch_generated' = 'yes' then\n not data->>'query' ~* E'/\\\\*\\\\W*pgwatch2_generated\\\\W*\\\\*/'\n else\n true\n end\ngroup by 2\norder by 1 desc;", + "rawSql": "select sum((data->>'count')::integer) as count, data->>'query' as query\nfrom stat_activity\nwhere dbname = '$dbname'\n and $__timeFilter(time)\n and case when '$hide_pgwatch_generated' = 'yes' then\n not data->>'query' ~* E'/\\\\*\\\\W*pgwatch3_generated\\\\W*\\\\*/'\n else\n true\n end\ngroup by 2\norder by 1 desc;", "refId": "A", "select": [ [ diff --git a/src/metrics/00_helpers/get_backup_age_pgbackrest/11/metric.sql b/src/metrics/00_helpers/get_backup_age_pgbackrest/11/metric.sql index dd792cbb9..166e4b944 100644 --- a/src/metrics/00_helpers/get_backup_age_pgbackrest/11/metric.sql +++ b/src/metrics/00_helpers/get_backup_age_pgbackrest/11/metric.sql @@ -43,6 +43,6 @@ $$ LANGUAGE plpython3u VOLATILE; /* contacting S3 could be laggy depending on location */ ALTER FUNCTION get_backup_age_pgbackrest() SET statement_timeout TO '30s'; -GRANT EXECUTE ON FUNCTION get_backup_age_pgbackrest() TO pgwatch2; +GRANT EXECUTE ON FUNCTION get_backup_age_pgbackrest() TO pgwatch3; -COMMENT ON FUNCTION get_backup_age_pgbackrest() is 'created for pgwatch2'; +COMMENT ON FUNCTION get_backup_age_pgbackrest() is 'created for pgwatch3'; diff --git a/src/metrics/stat_activity/11/metric.sql b/src/metrics/stat_activity/11/metric.sql index 93ed63c9a..e90ee6000 100644 --- a/src/metrics/stat_activity/11/metric.sql +++ b/src/metrics/stat_activity/11/metric.sql @@ -1,4 +1,4 @@ -select /* pgwatch2_generated */ +select /* pgwatch3_generated */ (extract(epoch from now()) * 1e9)::int8 as epoch_ns, s.query as query, count(*) as count diff --git a/src/metrics/stat_activity/11/metric_su.sql b/src/metrics/stat_activity/11/metric_su.sql index 32faaf668..c230e15ce 100644 --- a/src/metrics/stat_activity/11/metric_su.sql +++ b/src/metrics/stat_activity/11/metric_su.sql @@ -1,4 +1,4 @@ -select /* pgwatch2_generated */ +select /* pgwatch3_generated */ (extract(epoch from now()) * 1e9)::int8 as epoch_ns, s.query as query, count(*) as count diff --git a/src/sql/config_store/metric_definitions.sql b/src/sql/config_store/metric_definitions.sql index dd420bc33..28e4a550b 100644 --- a/src/sql/config_store/metric_definitions.sql +++ b/src/sql/config_store/metric_definitions.sql @@ -5743,7 +5743,7 @@ values ( 'subscription_stats', 15, $sql$ -select /* pgwatch2_generated */ +select /* pgwatch3_generated */ (extract(epoch from now()) * 1e9)::int8 as epoch_ns, subname::text as tag_subname, apply_error_count, @@ -5759,7 +5759,7 @@ values ( 'stat_activity', 11, $sql$ -select /* pgwatch2_generated */ +select /* pgwatch3_generated */ (extract(epoch from now()) * 1e9)::int8 as epoch_ns, s.query as query, count(*) as count @@ -5772,7 +5772,7 @@ where s.datname = current_database() group by s.query; $sql$, $sql$ -select /* pgwatch2_generated */ +select /* pgwatch3_generated */ (extract(epoch from now()) * 1e9)::int8 as epoch_ns, s.query as query, count(*) as count diff --git a/src/webui/src/layout/Logs/LogGrid/index.tsx b/src/webui/src/layout/Logs/LogGrid/index.tsx index cc63bc40b..e466127cd 100644 --- a/src/webui/src/layout/Logs/LogGrid/index.tsx +++ b/src/webui/src/layout/Logs/LogGrid/index.tsx @@ -28,7 +28,7 @@ export default () => { 2022-12-15 10:31:22,588 INFO 84 172.17.0.1 - - [15/Dec/2022:10:31:22] "GET /static/jquery-3.5.1.min.js HTTP/1.1" 200 89476 "http://localhost:8080/dbs" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" 2022-12-15 10:31:22,591 INFO 84 172.17.0.1 - - [15/Dec/2022:10:31:22] "GET /static/logo.png HTTP/1.1" 200 5325 "http://localhost:8080/dbs" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" 2022-12-15 10:31:22,739 INFO 84 172.17.0.1 - - [15/Dec/2022:10:31:22] "GET /favicon.ico HTTP/1.1" 200 1406 "http://localhost:8080/dbs" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" -2022-12-15 10:31:27,457 INFO 84 172.17.0.1 - - [15/Dec/2022:10:31:27] "GET /logs/pgwatch2/200 HTTP/1.1" 200 - "http://localhost:8080/dbs" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"` +2022-12-15 10:31:27,457 INFO 84 172.17.0.1 - - [15/Dec/2022:10:31:27] "GET /logs/pgwatch3/200 HTTP/1.1" 200 - "http://localhost:8080/dbs" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"` }