From 44f1089d5635db910076997dd61e2cea8ab78cfc Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Fri, 22 Feb 2019 01:34:33 +0300 Subject: [PATCH 01/28] draft --- checkup | 46 +++++++++++++++++++++- pghrep/templates/L003.tpl | 26 +++++++++++++ resources/checks/L003_integer_in_pk.sh | 54 ++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 pghrep/templates/L003.tpl create mode 100755 resources/checks/L003_integer_in_pk.sh diff --git a/checkup b/checkup index 126e17b..c06e1a9 100755 --- a/checkup +++ b/checkup @@ -492,6 +492,44 @@ usage() { exit $exit_code } + +####################################### +# Convert arbitrary text to json +# Globals: +# - +# Arguments: +# check_id, raw_str +# Returns: +# (text) json object +####################################### +convert_text_to_json() { + local check_id="$1" + local raw_str="$2" + + if [[ $check_id == "L003" ]] ; then + # echo "$input_json" + # Table: public.pgbench_branches, Column: bid, Type: int4, Reached value: 1 (0.00%) + local result=$(cat << EOF + { + "public.pgbench_branches":{ + "Table":"public.pgbench_branches", + "Column":"bid", + "Type":"int4", + "Reached value":"1 (0.00%)" + }, + "public.pgbench_accounts":{ + "Table":"public.pgbench_accounts", + "Column":"aid", + "Type":"int4", + "Reached value":"100 (0.01%)" + } + } +EOF +) + fi + echo $result +} + ####################################### # Generate json report # Globals: @@ -499,7 +537,7 @@ usage() { # HOST, JSON_REPORTS_DIR, TIMESTAMP_DIR, # TIMESTAMPTZ, MD_REPORTS_DIR # Arguments: -# input, check_id +# input_json, check_id, check_name # Returns: # (text) stdout/stderr ####################################### @@ -517,6 +555,9 @@ generate_report_json() { local tmp_input_json_fname=$(mktemp "${SCRIPT_DIR}"/artifacts/${check_id}_tmp_XXXXXX) # save function's input as a temporary file + if [[ $check_id == "L003" ]] ; then + input_json=$(convert_text_to_json $check_id "$input_json") + fi echo "$input_json" > "$tmp_input_json_fname" # final report file name @@ -992,7 +1033,8 @@ run_checks() { update_nodes_json # perform a check from file - output=$(set -euo pipefail ; source "$CURRENT_CHECK_FNAME") || check_is_failed="true" + # http://linuxcommand.org/lc3_man_pages/seth.html + output=$(set -euo pipefail ; source "$CURRENT_CHECK_FNAME" 2>&1) || check_is_failed="true" dbg "is check failed?: $check_is_failed" msg "========== End of check ===========" diff --git a/pghrep/templates/L003.tpl b/pghrep/templates/L003.tpl new file mode 100644 index 0000000..e476e16 --- /dev/null +++ b/pghrep/templates/L003.tpl @@ -0,0 +1,26 @@ +# {{ .checkId }} Integer (int2, int4) out-of-range risks in PKs # + +## Observations ## +Data collected: {{ DtFormat .timestamptz }} +Current database: {{ .database }} +{{ if .hosts.master }} +{{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} +### Master (`{{.hosts.master}}`) ### +Table | Column | Type | ▼ Reached value +------|--------|------|--------------------------- +{{ range $i, $key := (index (index (index .results .hosts.master) "data") "_keys") }} +{{- $value := (index (index (index $.results $.hosts.master) "data") $key) -}} +{{ index $value "Table"}} | {{ index $value "Column"}} | {{ index $value "Type"}} | {{ index $value "Reached value"}} +{{ end }} +{{- else -}}{{/*Master data*/}} +No data +{{- end }}{{/*Master data*/}} +{{- else -}}{{/*Master*/}} +No data +{{ end }}{{/*Master*/}} + +## Conclusions ## + + +## Recommendations ## + diff --git a/resources/checks/L003_integer_in_pk.sh b/resources/checks/L003_integer_in_pk.sh new file mode 100755 index 0000000..a5339c1 --- /dev/null +++ b/resources/checks/L003_integer_in_pk.sh @@ -0,0 +1,54 @@ +${CHECK_HOST_CMD} "${_PSQL} -f - " < 'pg_toast' + loop + execute format('select max(%I) from %I.%I;', rec.attname, rec.schema_name, rec.table_name) into val; + if rec.typname = 'int4' then + ratio := (val::numeric / 2^31)::numeric; + elsif rec.typname = 'int2' then + ratio := (val::numeric / 2^15)::numeric; + else + assert false, 'unreachable point'; + end if; + if ratio > 0.00 then -- report only if > 1% of capacity is reached + out := out || format( + e'\nTable: %I.%I, Column: %I, Type: %s, Reached value: %s (%s%%)', + -- e'\n%I.%I, %I, %s, %s (%s%%)', + rec.schema_name, + rec.table_name, + rec.attname, + rec.typname, + val, + round(100 * ratio, 2) + ); + end if; + end loop; + raise info '%', out; +end; +\$$ language plpgsql; +SQL -- GitLab From 8e252bc743f7d70729282b475f347eceb563adf3 Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Mon, 25 Feb 2019 02:14:37 +0300 Subject: [PATCH 02/28] convert_text_to_json --- checkup | 57 ++++++++++++++++---------- resources/checks/L003_integer_in_pk.sh | 4 +- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/checkup b/checkup index c06e1a9..dda3232 100755 --- a/checkup +++ b/checkup @@ -492,6 +492,19 @@ usage() { exit $exit_code } +####################################### +# Get index of substring in string +# Globals: +# - +# Arguments: +# string, substring +# Returns: +# (integer) index +####################################### +strindex() { + x="${1%%$2*}" + [[ "$x" = "$1" ]] && echo -1 || echo "${#x}" +} ####################################### # Convert arbitrary text to json @@ -505,29 +518,28 @@ usage() { convert_text_to_json() { local check_id="$1" local raw_str="$2" - + local result="" + rows=() + if [[ $check_id == "L003" ]] ; then - # echo "$input_json" - # Table: public.pgbench_branches, Column: bid, Type: int4, Reached value: 1 (0.00%) - local result=$(cat << EOF - { - "public.pgbench_branches":{ - "Table":"public.pgbench_branches", - "Column":"bid", - "Type":"int4", - "Reached value":"1 (0.00%)" - }, - "public.pgbench_accounts":{ - "Table":"public.pgbench_accounts", - "Column":"aid", - "Type":"int4", - "Reached value":"100 (0.01%)" - } - } -EOF -) + while IFS=';' read -ra records; do + for record in "${records[@]}"; do + if [ ${#record} -le 2 ]; then + continue + fi + local tmp=$(jq -Rnr --arg key ${record:0:$(strindex "$record" ",")} ' + ( "Table,Column,Type,Reached value" | split(",") ) as $keys | + ( inputs | split(",") ) as $vals | + [[$keys, $vals] | transpose[] | {key:.[0],value:.[1]}] | from_entries + | {($key): .}' <<< ${record}) + rows+=($tmp) + #echo $tmp + done + done <<< "${raw_str:$(strindex "$raw_str" "INFO:")+5:${#raw_str}}" + + fi - echo $result + # echo $result } ####################################### @@ -556,7 +568,8 @@ generate_report_json() { # save function's input as a temporary file if [[ $check_id == "L003" ]] ; then - input_json=$(convert_text_to_json $check_id "$input_json") + # input_json=$(convert_text_to_json $check_id "$input_json") + convert_text_to_json $check_id "$input_json" fi echo "$input_json" > "$tmp_input_json_fname" diff --git a/resources/checks/L003_integer_in_pk.sh b/resources/checks/L003_integer_in_pk.sh index a5339c1..13a9b38 100755 --- a/resources/checks/L003_integer_in_pk.sh +++ b/resources/checks/L003_integer_in_pk.sh @@ -37,8 +37,8 @@ begin end if; if ratio > 0.00 then -- report only if > 1% of capacity is reached out := out || format( - e'\nTable: %I.%I, Column: %I, Type: %s, Reached value: %s (%s%%)', - -- e'\n%I.%I, %I, %s, %s (%s%%)', + -- e'\nTable: %I.%I, Column: %I, Type: %s, Reached value: %s (%s%%);', + e'\n%I.%I,%I,%s,%s (%s%%);', rec.schema_name, rec.table_name, rec.attname, -- GitLab From 4e1f22491c2f5293fff4eacbc18fbfa104bea569 Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Mon, 25 Feb 2019 16:23:05 +0300 Subject: [PATCH 03/28] array of jsons --- checkup | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/checkup b/checkup index dda3232..7285f9c 100755 --- a/checkup +++ b/checkup @@ -519,8 +519,8 @@ convert_text_to_json() { local check_id="$1" local raw_str="$2" local result="" - rows=() - + tbls=() + if [[ $check_id == "L003" ]] ; then while IFS=';' read -ra records; do for record in "${records[@]}"; do @@ -532,11 +532,14 @@ convert_text_to_json() { ( inputs | split(",") ) as $vals | [[$keys, $vals] | transpose[] | {key:.[0],value:.[1]}] | from_entries | {($key): .}' <<< ${record}) - rows+=($tmp) - #echo $tmp + tbls+=("$tmp") done done <<< "${raw_str:$(strindex "$raw_str" "INFO:")+5:${#raw_str}}" + for tbl in "${tbls[@]}" + do + echo $tbl + done fi # echo $result -- GitLab From 951b7a5764418899b6422eedd9eb88aa6263fd3a Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Tue, 26 Feb 2019 02:06:30 +0300 Subject: [PATCH 04/28] changed format --- artifacts/.gitkeep | 0 checkup | 21 ++++++++++++--------- pghrep/templates/L003.tpl | 4 ++-- resources/checks/L003_integer_in_pk.sh | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) delete mode 100644 artifacts/.gitkeep diff --git a/artifacts/.gitkeep b/artifacts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/checkup b/checkup index 7285f9c..e610615 100755 --- a/checkup +++ b/checkup @@ -527,19 +527,23 @@ convert_text_to_json() { if [ ${#record} -le 2 ]; then continue fi - local tmp=$(jq -Rnr --arg key ${record:0:$(strindex "$record" ",")} ' - ( "Table,Column,Type,Reached value" | split(",") ) as $keys | - ( inputs | split(",") ) as $vals | + local tmp=$(jq -Rnr --arg key ${record:0:$(strindex "$record" "@")} ' + ( "Table@PK@Type@Current max value@Capacity used, %" | split("@") ) as $keys | + ( inputs | split("@") ) as $vals | [[$keys, $vals] | transpose[] | {key:.[0],value:.[1]}] | from_entries | {($key): .}' <<< ${record}) tbls+=("$tmp") done done <<< "${raw_str:$(strindex "$raw_str" "INFO:")+5:${#raw_str}}" - for tbl in "${tbls[@]}" - do - echo $tbl - done + local res='' + for tbl in "${tbls[@]}" + do + res+=$tbl + done + + echo $res | jq -s add + # echo $res | jq -s add | jq -s -c 'sort_by(.[]."Capacity used, %")' fi # echo $result @@ -571,8 +575,7 @@ generate_report_json() { # save function's input as a temporary file if [[ $check_id == "L003" ]] ; then - # input_json=$(convert_text_to_json $check_id "$input_json") - convert_text_to_json $check_id "$input_json" + input_json=$(convert_text_to_json $check_id "$input_json") fi echo "$input_json" > "$tmp_input_json_fname" diff --git a/pghrep/templates/L003.tpl b/pghrep/templates/L003.tpl index e476e16..632bd0c 100644 --- a/pghrep/templates/L003.tpl +++ b/pghrep/templates/L003.tpl @@ -6,11 +6,11 @@ Current database: {{ .database }} {{ if .hosts.master }} {{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} ### Master (`{{.hosts.master}}`) ### -Table | Column | Type | ▼ Reached value +Table | PK | Type | Current max value | ▼ Capacity used, % ------|--------|------|--------------------------- {{ range $i, $key := (index (index (index .results .hosts.master) "data") "_keys") }} {{- $value := (index (index (index $.results $.hosts.master) "data") $key) -}} -{{ index $value "Table"}} | {{ index $value "Column"}} | {{ index $value "Type"}} | {{ index $value "Reached value"}} +{{ index $value "Table"}} | {{ index $value "PK"}} | {{ index $value "Type"}} | {{ index $value "Current max value"}} | {{ index $value "Capacity used, %"}} {{ end }} {{- else -}}{{/*Master data*/}} No data diff --git a/resources/checks/L003_integer_in_pk.sh b/resources/checks/L003_integer_in_pk.sh index 13a9b38..f51451b 100755 --- a/resources/checks/L003_integer_in_pk.sh +++ b/resources/checks/L003_integer_in_pk.sh @@ -38,7 +38,7 @@ begin if ratio > 0.00 then -- report only if > 1% of capacity is reached out := out || format( -- e'\nTable: %I.%I, Column: %I, Type: %s, Reached value: %s (%s%%);', - e'\n%I.%I,%I,%s,%s (%s%%);', + e'\n%I.%I@%I@%s@%s@%s;', rec.schema_name, rec.table_name, rec.attname, -- GitLab From 986f0f15ea2ab3732675067e10c3ae043c6c2785 Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Tue, 26 Feb 2019 20:41:04 +0300 Subject: [PATCH 05/28] order by capacity desc --- checkup | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/checkup b/checkup index e610615..bef3243 100755 --- a/checkup +++ b/checkup @@ -542,9 +542,7 @@ convert_text_to_json() { res+=$tbl done - echo $res | jq -s add - # echo $res | jq -s add | jq -s -c 'sort_by(.[]."Capacity used, %")' - + echo $res | jq -cs 'sort_by(-(.[]."Capacity used, %"|tonumber)) | .[]' | jq -s add fi # echo $result } -- GitLab From 76d8f22c803e432dfb08e5c2d2d42a172e04b661 Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Tue, 26 Feb 2019 20:48:15 +0300 Subject: [PATCH 06/28] tpl fix --- pghrep/templates/L003.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pghrep/templates/L003.tpl b/pghrep/templates/L003.tpl index 632bd0c..03b1f4f 100644 --- a/pghrep/templates/L003.tpl +++ b/pghrep/templates/L003.tpl @@ -7,7 +7,7 @@ Current database: {{ .database }} {{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} ### Master (`{{.hosts.master}}`) ### Table | PK | Type | Current max value | ▼ Capacity used, % -------|--------|------|--------------------------- +------|----|------|-------------------|------------------------------- {{ range $i, $key := (index (index (index .results .hosts.master) "data") "_keys") }} {{- $value := (index (index (index $.results $.hosts.master) "data") $key) -}} {{ index $value "Table"}} | {{ index $value "PK"}} | {{ index $value "Type"}} | {{ index $value "Current max value"}} | {{ index $value "Capacity used, %"}} -- GitLab From 062d86fc9a20c124d839a2409876d904fab5e847 Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Wed, 27 Feb 2019 09:20:53 +0300 Subject: [PATCH 07/28] fixed convert_text_to_json --- checkup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checkup b/checkup index bef3243..73a06ac 100755 --- a/checkup +++ b/checkup @@ -519,7 +519,7 @@ convert_text_to_json() { local check_id="$1" local raw_str="$2" local result="" - tbls=() + local tbls=() if [[ $check_id == "L003" ]] ; then while IFS=';' read -ra records; do -- GitLab From 30d08e177e6706c0ab45c511951288d2f1020b96 Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Wed, 27 Feb 2019 13:18:16 +0300 Subject: [PATCH 08/28] .gitkeep --- artifacts/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 artifacts/.gitkeep diff --git a/artifacts/.gitkeep b/artifacts/.gitkeep new file mode 100644 index 0000000..e69de29 -- GitLab From aa93ca1dbac7340ce0deacbda22e91f61391bfc1 Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Wed, 27 Feb 2019 14:48:15 +0300 Subject: [PATCH 09/28] changed json format --- checkup | 71 ++------------------------ pghrep/templates/L003.tpl | 1 - resources/checks/L003_integer_in_pk.sh | 43 +++++++++++----- 3 files changed, 36 insertions(+), 79 deletions(-) diff --git a/checkup b/checkup index 80d649e..6963eb4 100755 --- a/checkup +++ b/checkup @@ -352,7 +352,6 @@ validate_args() { [[ "${PGPORT}" = "None" ]] && export PGPORT=5432 [[ "${DBNAME}" = "None" ]] && export DBNAME=postgres [[ "${USERNAME}" = "None" ]] && export USERNAME="${USER}" - [[ "${STIMEOUT}" = "None" ]] && export STIMEOUT=15 # statement timeout # custom UNIX domain socket directory for PostgreSQL local psql_unix_socket_option="" @@ -373,10 +372,9 @@ validate_args() { local pgpas_subst="" fi - # Construct _PSQL macro for usage inside the check scripts - export PGOPTIONS="-c statement_timeout=${STIMEOUT}s" + # for usage inside the check scripts export PSQL_CONN_OPTIONS="--port=${PGPORT} --dbname=${DBNAME} --username=${USERNAME} ${psql_unix_socket_option}" - export _PSQL="PGOPTIONS=\"${PGOPTIONS}\" ${pgpas_subst}${psql_bin} -1 -X -At -q -v ON_ERROR_STOP=1 -P pager=off ${PSQL_CONN_OPTIONS}" + export _PSQL="${pgpas_subst}${psql_bin} -1 -X -At -q -v ON_ERROR_STOP=1 -P pager=off ${PSQL_CONN_OPTIONS}" dbg "" dbg "PSQL_CONN_OPTIONS: $PSQL_CONN_OPTIONS" @@ -494,61 +492,6 @@ usage() { exit $exit_code } -####################################### -# Get index of substring in string -# Globals: -# - -# Arguments: -# string, substring -# Returns: -# (integer) index -####################################### -strindex() { - x="${1%%$2*}" - [[ "$x" = "$1" ]] && echo -1 || echo "${#x}" -} - -####################################### -# Convert arbitrary text to json -# Globals: -# - -# Arguments: -# check_id, raw_str -# Returns: -# (text) json object -####################################### -convert_text_to_json() { - local check_id="$1" - local raw_str="$2" - local result="" - local tbls=() - - if [[ $check_id == "L003" ]] ; then - while IFS=';' read -ra records; do - for record in "${records[@]}"; do - if [ ${#record} -le 2 ]; then - continue - fi - local tmp=$(jq -Rnr --arg key ${record:0:$(strindex "$record" "@")} ' - ( "Table@PK@Type@Current max value@Capacity used, %" | split("@") ) as $keys | - ( inputs | split("@") ) as $vals | - [[$keys, $vals] | transpose[] | {key:.[0],value:.[1]}] | from_entries - | {($key): .}' <<< ${record}) - tbls+=("$tmp") - done - done <<< "${raw_str:$(strindex "$raw_str" "INFO:")+5:${#raw_str}}" - - local res='' - for tbl in "${tbls[@]}" - do - res+=$tbl - done - - echo $res | jq -cs 'sort_by(-(.[]."Capacity used, %"|tonumber)) | .[]' | jq -s add - fi - # echo $result -} - ####################################### # Generate json report # Globals: @@ -556,7 +499,7 @@ convert_text_to_json() { # HOST, JSON_REPORTS_DIR, TIMESTAMP_DIR, # TIMESTAMPTZ, MD_REPORTS_DIR # Arguments: -# input_json, check_id, check_name +# input, check_id # Returns: # (text) stdout/stderr ####################################### @@ -574,9 +517,6 @@ generate_report_json() { local tmp_input_json_fname=$(mktemp "${SCRIPT_DIR}"/artifacts/${check_id}_tmp_XXXXXX) # save function's input as a temporary file - if [[ $check_id == "L003" ]] ; then - input_json=$(convert_text_to_json $check_id "$input_json") - fi echo "$input_json" > "$tmp_input_json_fname" # final report file name @@ -1049,8 +989,7 @@ run_checks() { update_nodes_json # perform a check from file - # http://linuxcommand.org/lc3_man_pages/seth.html - output=$(set -euo pipefail ; source "$CURRENT_CHECK_FNAME" 2>&1) || check_is_failed="true" + output=$(set -euo pipefail ; source "$CURRENT_CHECK_FNAME") || check_is_failed="true" dbg "is check failed?: $check_is_failed" msg "========== End of check ===========" @@ -1164,4 +1103,4 @@ main() { main "$@" -# last line of the file +# last line of the file \ No newline at end of file diff --git a/pghrep/templates/L003.tpl b/pghrep/templates/L003.tpl index 03b1f4f..b32b6d3 100644 --- a/pghrep/templates/L003.tpl +++ b/pghrep/templates/L003.tpl @@ -23,4 +23,3 @@ No data ## Recommendations ## - diff --git a/resources/checks/L003_integer_in_pk.sh b/resources/checks/L003_integer_in_pk.sh index f51451b..ce10aff 100755 --- a/resources/checks/L003_integer_in_pk.sh +++ b/resources/checks/L003_integer_in_pk.sh @@ -1,12 +1,17 @@ -${CHECK_HOST_CMD} "${_PSQL} -f - " < 0.00 then -- report only if > 1% of capacity is reached - out := out || format( - -- e'\nTable: %I.%I, Column: %I, Type: %s, Reached value: %s (%s%%);', - e'\n%I.%I@%I@%s@%s@%s;', - rec.schema_name, - rec.table_name, - rec.attname, - rec.typname, - val, - round(100 * ratio, 2) - ); + if i > 0 then + out := out || ','; + end if; + i:= i+1; + out := out || '"' || rec.table_name || '":' || json_build_object( + 'Table', + rec.schema_name || '.' || rec.table_name, + 'PK', + rec.attname, + 'Type', + rec.typname, + 'Current max value', + val, + 'Capacity used, %', + round(100 * ratio, 2) + ); end if; end loop; - raise info '%', out; + raise info '{%}', out; end; \$$ language plpgsql; SQL +) >$f_stdout 2>$f_stderr + +result=$(cat $f_stderr) +result=${result:23:$((${#result}))} + +echo "$result" + +rm -f "$f_stderr" "$f_stdout" \ No newline at end of file -- GitLab From 47121ea121915601496db29f90146ef185906e69 Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Wed, 27 Feb 2019 15:09:19 +0300 Subject: [PATCH 10/28] sort --- checkup | 4 ++-- resources/checks/L003_integer_in_pk.sh | 11 ++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/checkup b/checkup index 6963eb4..e920dc5 100755 --- a/checkup +++ b/checkup @@ -517,8 +517,8 @@ generate_report_json() { local tmp_input_json_fname=$(mktemp "${SCRIPT_DIR}"/artifacts/${check_id}_tmp_XXXXXX) # save function's input as a temporary file - echo "$input_json" > "$tmp_input_json_fname" - + # echo "$input_json" > "$tmp_input_json_fname" + echo "$input_json" # final report file name local json_output_fname="${JSON_REPORTS_DIR}/${check_id}_${check_name}.json" diff --git a/resources/checks/L003_integer_in_pk.sh b/resources/checks/L003_integer_in_pk.sh index ce10aff..671de29 100755 --- a/resources/checks/L003_integer_in_pk.sh +++ b/resources/checks/L003_integer_in_pk.sh @@ -41,11 +41,8 @@ begin assert false, 'unreachable point'; end if; if ratio > 0.00 then -- report only if > 1% of capacity is reached - if i > 0 then - out := out || ','; - end if; i:= i+1; - out := out || '"' || rec.table_name || '":' || json_build_object( + out := out || '{"' || rec.table_name || '":' || json_build_object( 'Table', rec.schema_name || '.' || rec.table_name, 'PK', @@ -56,10 +53,10 @@ begin val, 'Capacity used, %', round(100 * ratio, 2) - ); + ) || '}'; end if; end loop; - raise info '{%}', out; + raise info '%', out; end; \$$ language plpgsql; SQL @@ -68,6 +65,6 @@ SQL result=$(cat $f_stderr) result=${result:23:$((${#result}))} -echo "$result" +echo "$result" | jq -cs 'sort_by(-(.[]."Capacity used, %"|tonumber)) | .[]' | jq -s add rm -f "$f_stderr" "$f_stdout" \ No newline at end of file -- GitLab From fe9eba05a412b05a0081df1e0d2eb84064b51344 Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Wed, 27 Feb 2019 15:10:11 +0300 Subject: [PATCH 11/28] undo checkup change --- checkup | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/checkup b/checkup index e920dc5..6963eb4 100755 --- a/checkup +++ b/checkup @@ -517,8 +517,8 @@ generate_report_json() { local tmp_input_json_fname=$(mktemp "${SCRIPT_DIR}"/artifacts/${check_id}_tmp_XXXXXX) # save function's input as a temporary file - # echo "$input_json" > "$tmp_input_json_fname" - echo "$input_json" + echo "$input_json" > "$tmp_input_json_fname" + # final report file name local json_output_fname="${JSON_REPORTS_DIR}/${check_id}_${check_name}.json" -- GitLab From 41208e36addd795c0e6a01947c37303c90d7a8eb Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Wed, 27 Feb 2019 15:38:20 +0300 Subject: [PATCH 12/28] fixed table name --- resources/checks/L003_integer_in_pk.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/checks/L003_integer_in_pk.sh b/resources/checks/L003_integer_in_pk.sh index 671de29..7ef0daf 100755 --- a/resources/checks/L003_integer_in_pk.sh +++ b/resources/checks/L003_integer_in_pk.sh @@ -44,7 +44,7 @@ begin i:= i+1; out := out || '{"' || rec.table_name || '":' || json_build_object( 'Table', - rec.schema_name || '.' || rec.table_name, + coalesce(nullif(quote_ident(rec.schema_name), 'public') || '.', '') || quote_ident(rec.table_name), 'PK', rec.attname, 'Type', -- GitLab From 451a12d75a3c4ce5a6e2eec114bc05fa8584484d Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Wed, 27 Feb 2019 16:24:10 +0300 Subject: [PATCH 13/28] fixed tpl --- pghrep/templates/L003.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pghrep/templates/L003.tpl b/pghrep/templates/L003.tpl index b32b6d3..7adb212 100644 --- a/pghrep/templates/L003.tpl +++ b/pghrep/templates/L003.tpl @@ -10,7 +10,7 @@ Table | PK | Type | Current max value | ▼ Capacity used, % ------|----|------|-------------------|------------------------------- {{ range $i, $key := (index (index (index .results .hosts.master) "data") "_keys") }} {{- $value := (index (index (index $.results $.hosts.master) "data") $key) -}} -{{ index $value "Table"}} | {{ index $value "PK"}} | {{ index $value "Type"}} | {{ index $value "Current max value"}} | {{ index $value "Capacity used, %"}} +{{ index $value "Table"}} | {{ index $value "PK"}} | {{ index $value "Type"}} | {{- RawIntFormat (index $value "Current max value")}} | {{ index $value "Capacity used, %"}} {{ end }} {{- else -}}{{/*Master data*/}} No data -- GitLab From 0e1a1cdc32683a9e4fe98924050158bed7a7678f Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Wed, 27 Feb 2019 16:35:33 +0300 Subject: [PATCH 14/28] merge with master --- checkup | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/checkup b/checkup index 6963eb4..532d8e0 100755 --- a/checkup +++ b/checkup @@ -351,7 +351,8 @@ validate_args() { # fill default (not given) psql connection related variables [[ "${PGPORT}" = "None" ]] && export PGPORT=5432 [[ "${DBNAME}" = "None" ]] && export DBNAME=postgres - [[ "${USERNAME}" = "None" ]] && export USERNAME="${USER}" + [[ "${STIMEOUT}" = "None" ]] && export STIMEOUT=15 # statement timeout + [[ "${USERNAME}" = "None" ]] && export USERNAME="" # custom UNIX domain socket directory for PostgreSQL local psql_unix_socket_option="" @@ -372,9 +373,16 @@ validate_args() { local pgpas_subst="" fi - # for usage inside the check scripts - export PSQL_CONN_OPTIONS="--port=${PGPORT} --dbname=${DBNAME} --username=${USERNAME} ${psql_unix_socket_option}" - export _PSQL="${pgpas_subst}${psql_bin} -1 -X -At -q -v ON_ERROR_STOP=1 -P pager=off ${PSQL_CONN_OPTIONS}" + # use default Postgres username or not + local user_substr="" + if [[ ! -z ${USERNAME+x} ]]; then + user_substr=" -U \"${USERNAME}\" " + fi + + # Construct _PSQL macro for usage inside the check scripts + export PGOPTIONS="-c statement_timeout=${STIMEOUT}s" + export PSQL_CONN_OPTIONS="--port=${PGPORT} --dbname=${DBNAME} ${user_substr} ${psql_unix_socket_option}" + export _PSQL="PGOPTIONS=\"${PGOPTIONS}\" ${pgpas_subst}${psql_bin} -1 -X -At -q -v ON_ERROR_STOP=1 -P pager=off ${PSQL_CONN_OPTIONS}" dbg "" dbg "PSQL_CONN_OPTIONS: $PSQL_CONN_OPTIONS" @@ -436,6 +444,12 @@ usage() { echo " ${SCRIPT_NAME} OPTION [OPTION] ..." >&${out_descriptor} echo " ${SCRIPT_NAME} --help" >&${out_descriptor} + if [[ "$exit_code" -ne "0" ]]; then + exit "$exit_code" + fi + + # Printing CLI options starts here + # calc max size of FULL_NAME[] for text alignment local max_name_len=0 for i in $(seq 0 ${CLI_ARGS_POSSIBLE}); do @@ -478,7 +492,7 @@ usage() { # additional info echo >&${out_descriptor} echo "Example:" >&${out_descriptor} - echo " PGPASSWORD=mypasswd ./${SCRIPT_NAME} -h host_to_connect_via_ssh \\" + echo " PGPASSWORD=mypasswd ./${SCRIPT_NAME} -h [ssh_user]@host_to_connect_via_ssh \\" echo " --username ${USER} --dbname postgres \\" echo " --project dummy ${BOLD}-e %EPOCH_NUMBER%${RESET}" >&${out_descriptor} echo >&${out_descriptor} -- GitLab From a9ef39d1a97da50dedc16e8215f930525038bf4a Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Thu, 28 Feb 2019 02:18:45 +0300 Subject: [PATCH 15/28] test data for L003 --- .ci/test_db_dump.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.ci/test_db_dump.sql b/.ci/test_db_dump.sql index cb02bdf..2733bb7 100644 --- a/.ci/test_db_dump.sql +++ b/.ci/test_db_dump.sql @@ -78,3 +78,13 @@ update t_with_bloat set i = i; -- h002 Supports fk select count(*) from t_slw_q; explain select count(*) from t_slw_q; + +-- L003 +CREATE TABLE test_schema.order +( + id serial, + cnt integer, + CONSTRAINT ordiadjust_pk PRIMARY KEY (id) +); + +INSERT INTO test_schema.order(cnt) select id from generate_series(0, 1000000) _(id); \ No newline at end of file -- GitLab From d4f3d83904a4a072384f19a3e43ed6fe80f93548 Mon Sep 17 00:00:00 2001 From: Victor Yagofarov Date: Fri, 22 Feb 2019 09:32:16 +0000 Subject: [PATCH 16/28] K000, generate separate files with queries --- README.md | 2 +- checkup | 17 ++++++------- pghrep/templates/K003.tpl | 12 ++++----- resources/checks/K000_query_analysis.sh | 33 ++++++++++++++++++++++--- 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 68a7ff0..74ea2a3 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ Open it with your favorite Markdown files viewer or just upload to a service suc # Demo Automatically generated fresh demonstration based on the code in the master -branch (only single node analyzed): https://gitlab.com/postgres-ai-team/postgres-checkup-tests/blob/master/master/e1_full_report.md +branch (only single node analyzed): https://gitlab.com/postgres-ai-team/postgres-checkup-tests/tree/master/master # The Full List of Checks diff --git a/checkup b/checkup index 73a06ac..af75ff5 100755 --- a/checkup +++ b/checkup @@ -12,7 +12,7 @@ CMD_TIMEOUT="10" # timeout for command (ssh, psql, etc.) # TODO(vyagofarov): think about timing # out commands in macOS -FULL_REPORT_FNAME="full_report.md" +FULL_REPORT_FNAME="Full_report.md" # GLOBALS (autoload, do not change) : ${DEBUG:=false} # print debug output @@ -789,7 +789,8 @@ check_bin_deps() { # Integer ####################################### glue_md_reports() { - local out_fname="${PROJECT_DIR}/e${EPOCH}_${FULL_REPORT_FNAME}" + # final report path and name + local out_fname="${MD_REPORTS_DIR}/${FULL_REPORT_FNAME}" # do not re-generate full report if '--file' is given [[ "${FILE}" != "None" ]] && return 0 @@ -907,8 +908,6 @@ update_nodes_json() { MD_REPORTS_DIR="${PROJECT_DIR}/md_reports/${EPOCH}_${TIMESTAMP_DIR}" SHORT_DIR_NAME="${EPOCH}_${TIMESTAMP_DIR}" - mkdir -p "${JSON_REPORTS_DIR}" - local input_json_fname="${SCRIPT_DIR}/resources/templates/nodes.json" # create dirs, etc. @@ -941,15 +940,11 @@ update_nodes_json() { SHORT_DIR_NAME=$(basename "${SHORT_DIR_NAME}") JSON_REPORTS_DIR="${PROJECT_DIR}/json_reports/${SHORT_DIR_NAME}" - mkdir -p "${JSON_REPORTS_DIR}" - MD_REPORTS_DIR="${PROJECT_DIR}/md_reports/${SHORT_DIR_NAME}" else SHORT_DIR_NAME="${EPOCH}_${TIMESTAMP_DIR}" JSON_REPORTS_DIR="${PROJECT_DIR}/json_reports/${SHORT_DIR_NAME}" - mkdir -p "${JSON_REPORTS_DIR}" - MD_REPORTS_DIR="${PROJECT_DIR}/md_reports/${SHORT_DIR_NAME}" fi @@ -959,6 +954,7 @@ update_nodes_json() { export JSON_REPORTS_DIR MD_REPORTS_DIR SHORT_DIR_NAME mkdir -p "${JSON_REPORTS_DIR}" + mkdir -p "${MD_REPORTS_DIR}" local role_changed_at="never" if [[ "${prev_role}" != "${ROLE}" ]] && [[ "$prev_role" != "null" ]]; then @@ -982,8 +978,6 @@ update_nodes_json() { dbg "ALIAS_INDEX: '$ALIAS_INDEX'" dbg "ROLE: '$ROLE'" - mkdir -p "${JSON_REPORTS_DIR}" - # finally, fill nodes.json file local result_fname="${PROJECT_DIR}/nodes.json" local result_fname_tmp=$(mktemp "${PROJECT_DIR}"/nodes.json.tmp_XXXXXX) @@ -999,6 +993,9 @@ update_nodes_json() { "${input_json_fname}" \ > "${result_fname_tmp}" mv "${result_fname_tmp}" "${result_fname}" + + TIMESTAMP_DIRNAME=$(jq -r '.last_check.dir' "${result_fname}") + export TIMESTAMP_DIRNAME } ####################################### diff --git a/pghrep/templates/K003.tpl b/pghrep/templates/K003.tpl index 1eec6f2..b63b53f 100644 --- a/pghrep/templates/K003.tpl +++ b/pghrep/templates/K003.tpl @@ -14,8 +14,8 @@ Period age: {{ (index (index (index .results .hosts.master) "data") "period_age" Error (calls): {{ NumFormat (index (index (index .results .hosts.master) "data") "absolute_error_calls") 2 }} ({{ NumFormat (index (index (index .results .hosts.master) "data") "relative_error_calls") 2 }}%) Error (total time): {{ NumFormat (index (index (index .results .hosts.master) "data") "absolute_error_total_time") 2 }} ({{ NumFormat (index (index (index .results .hosts.master) "data") "relative_error_total_time") 2 }}%) -\# | Calls | ▼ Total time | Rows | shared_blks_hit | shared_blks_read | shared_blks_dirtied | shared_blks_written | blk_read_time | blk_write_time | kcache_reads | kcache_writes | kcache_user_time_ms | kcache_system_time | Query -----|-------|------------|------|-----------------|------------------|---------------------|---------------------|---------------|----------------|--------------|---------------|---------------------|--------------------|------- +\# | Calls | ▼ Total time | Rows | shared_blks_hit | shared_blks_read | shared_blks_dirtied | shared_blks_written | blk_read_time | blk_write_time | kcache_reads | kcache_writes | kcache_user_time_ms | kcache_system_time |Query +----|-------|------------|------|-----------------|------------------|---------------------|---------------------|---------------|----------------|--------------|---------------|---------------------|--------------------|------- {{ range $i, $key := (index (index (index (index .results .hosts.master) "data") "queries") "_keys") }} {{- $value := (index (index (index (index $.results $.hosts.master) "data") "queries") $key) -}} {{- $key}} | @@ -32,7 +32,7 @@ Error (total time): {{ NumFormat (index (index (index .results .hosts.master) "d {{- NumFormat $value.diff_kcache_writes 2 }} bytes
{{ NumFormat $value.per_sec_kcache_writes 2 }} bytes/sec
{{ NumFormat $value.per_call_kcache_writes 2 }} bytes/call
{{ NumFormat $value.ratio_kcache_writes 2 }}% | {{- RawFloatFormat $value.diff_kcache_user_time_ms 2 }} ms
{{ MsFormat $value.per_sec_kcache_user_time_ms }}/sec
{{ MsFormat $value.per_call_kcache_user_time_ms }}/call
{{ NumFormat $value.ratio_kcache_user_time_ms 2 }}% | {{- RawFloatFormat $value.diff_kcache_system_time_ms 2 }} ms
{{ MsFormat $value.per_sec_kcache_system_time_ms }}/sec
{{ MsFormat $value.per_call_kcache_system_time_ms }}/call
{{ NumFormat $value.ratio_kcache_system_time_ms 2 }}% | -{{- Nobr (LimitStr $value.query 2000 ) }} +{{- Nobr (LimitStr $value.query 2000 ) }}
[full query (50k symbols)]({{ $value.link }}) {{ end }}{{/* range */}} {{- end }}{{/*Master data*/}} {{ end }}{{/*Master*/}} @@ -47,8 +47,8 @@ End: {{ (index (index (index $.results $host) "data") "end_timestamptz") }} Period seconds: {{ (index (index (index $.results $host) "data") "period_seconds") }} Period age: {{ (index (index (index $.results $host) "data") "period_age") }} -\# | Calls | ▼ Total time | Rows | shared_blks_hit | shared_blks_read | shared_blks_dirtied | shared_blks_written | blk_read_time | blk_write_time | kcache_reads | kcache_writes | kcache_user_time_ms | kcache_system_time | Query -----|-------|------------|------|-----------------|------------------|---------------------|---------------------|---------------|----------------|--------------|---------------|---------------------|--------------------|------- +\# | Calls | ▼ Total time | Rows | shared_blks_hit | shared_blks_read | shared_blks_dirtied | shared_blks_written | blk_read_time | blk_write_time | kcache_reads | kcache_writes | kcache_user_time_ms | kcache_system_time |Query +----|-------|------------|------|-----------------|------------------|---------------------|---------------------|---------------|----------------|--------------|---------------|---------------------|--------------------|------- {{ range $i, $key := (index (index (index (index $.results $host) "data") "queries") "_keys") }} {{- $value := (index (index (index (index $.results $host) "data") "queries") $key) -}} {{- $key}} | @@ -65,7 +65,7 @@ Period age: {{ (index (index (index $.results $host) "data") "period_age") }} {{- NumFormat $value.diff_kcache_writes 2 }} bytes
{{ NumFormat $value.per_sec_kcache_writes 2 }} bytes/sec
{{ NumFormat $value.per_call_kcache_writes 2 }} bytes/call
{{ NumFormat $value.ratio_kcache_writes 2 }}% | {{- RawFloatFormat $value.diff_kcache_user_time_ms 2 }} ms
{{ MsFormat $value.per_sec_kcache_user_time_ms }}/sec
{{ MsFormat $value.per_call_kcache_user_time_ms }}/call
{{ NumFormat $value.ratio_kcache_user_time_ms 2 }}% | {{- RawFloatFormat $value.diff_kcache_system_time_ms 2 }} ms
{{ MsFormat $value.per_sec_kcache_system_time_ms }}/sec
{{ MsFormat $value.per_call_kcache_system_time_ms }}/call
{{ NumFormat $value.ratio_kcache_system_time_ms 2 }}% | -{{- Nobr (LimitStr $value.query 2000 ) }} +{{- Nobr (LimitStr $value.query 2000 ) }}
[full query (50k symbols)]({{ $value.link }}) {{ end }}{{/* range */}} {{- else -}}{{/* if host data */}} No data diff --git a/resources/checks/K000_query_analysis.sh b/resources/checks/K000_query_analysis.sh index c28a9d2..833e5eb 100644 --- a/resources/checks/K000_query_analysis.sh +++ b/resources/checks/K000_query_analysis.sh @@ -1,4 +1,3 @@ - # Generates JSON for three type of reports: # - K001 - Globally aggregated # - K002 - Workload type (first word analysis) @@ -91,6 +90,7 @@ if [[ "${err_code}" -ne "0" ]]; then temp_blks_written, blk_read_time, blk_write_time, + queryid, /* save hash */ @@ -129,6 +129,7 @@ else temp_blks_written, blk_read_time, blk_write_time, + queryid, /* kcache part */ k.reads as kcache_reads, @@ -316,11 +317,13 @@ sql=" select ${sub_sql} s1.md5 as md5, + s1.obj->>'queryid' as queryid, s1.obj->>'query' as query from s1 join s2 using(md5) - group by s1.md5, s1.obj->>'query' + group by s1.md5, s1.obj->>'queryid', s1.obj->>'query' ), queries as ( + -- K003 select row_number() over(order by diff_total_time desc) as rownum, * @@ -372,9 +375,31 @@ sql=" ) from queries " -# sql result to stdout -${CHECK_HOST_CMD} "${_PSQL} -f -" </dev/null 2>&1 || true + echo "-- queryid: ${queryid}" > "${JSON_REPORTS_DIR}/K_query_groups/${query_num}.sql" + echo "$query_text" >> "${JSON_REPORTS_DIR}/K_query_groups/${query_num}.sql" + + # Generate link to a full text + link="../../json_reports/${TIMESTAMP_DIRNAME}/K_query_groups/${query_num}.sql" + + # add link into the object + JSON=$(jq --arg link $link -r '.queries."'$query_num'" += { "link": $link }' <<<${JSON}) +done + +# print resulting JSON to stdout +echo "${JSON}" -- GitLab From 4587cddcecae72b62ebc1652b3ef748cb8503556 Mon Sep 17 00:00:00 2001 From: dmius Date: Wed, 20 Feb 2019 12:50:02 +0300 Subject: [PATCH 17/28] F002, F003, F004 overrided table settings --- pghrep/src/main.go | 3 +-- pghrep/src/reportutils.go | 7 ++++++ pghrep/templates/F002.tpl | 3 ++- pghrep/templates/F003.tpl | 3 ++- pghrep/templates/F004.tpl | 3 ++- pghrep/templates/F005.tpl | 3 ++- .../checks/F002_autovacuum_wraparound.sh | 15 +++++++++++-- .../checks/F003_autovacuum_dead_tuples.sh | 15 +++++++++++-- resources/checks/F004_heap_bloat.sh | 17 +++++++++++--- resources/checks/F005_index_bloat.sh | 22 ++++++++++++++----- 10 files changed, 73 insertions(+), 18 deletions(-) diff --git a/pghrep/src/main.go b/pghrep/src/main.go index 52bda5c..365f12a 100644 --- a/pghrep/src/main.go +++ b/pghrep/src/main.go @@ -24,7 +24,6 @@ import ( "sort" "strconv" "./orderedmap" - "./fmtutils" ) var DEBUG bool = false @@ -153,7 +152,7 @@ func loadTemplates() *template.Template { tplFuncMap["Code"] = Code tplFuncMap["Nobr"] = Nobr tplFuncMap["Br"] = Br - tplFuncMap["ByteFormat"] = fmtutils.ByteFormat + tplFuncMap["ByteFormat"] = ByteFormat tplFuncMap["UnitValue"] = UnitValue tplFuncMap["RawIntUnitValue"] = RawIntUnitValue tplFuncMap["RoundUp"] = Round diff --git a/pghrep/src/reportutils.go b/pghrep/src/reportutils.go index 2214d1f..e6e2583 100644 --- a/pghrep/src/reportutils.go +++ b/pghrep/src/reportutils.go @@ -168,3 +168,10 @@ func Int(value interface{}) int { } return 0 } + +func ByteFormat(value interface{}, places interface{}) string { + val := pyraconv.ToFloat64(value) + pl := pyraconv.ToInt64(places) + result := fmtutils.ByteFormat(val, int(pl)); + return Nobr(result); +} diff --git a/pghrep/templates/F002.tpl b/pghrep/templates/F002.tpl index 03f547e..9c46e3a 100644 --- a/pghrep/templates/F002.tpl +++ b/pghrep/templates/F002.tpl @@ -26,7 +26,7 @@ Current database: {{ .database }} ----------|-----|------------------|---------|-----------------|-------------------- {{ range $i, $key := (index (index (index (index .results .hosts.master) "data") "per_database") "_keys") }} {{- $value := (index (index (index (index $.results $.hosts.master) "data") "per_database") $key) -}} -{{ index $value "relation"}} | +{{ index $value "relation"}}{{if $value.overrided_settings}}*{{ end }} | {{- NumFormat (index $value "age") -1 }} | {{- index $value "capacity_used"}} | {{- if (index $value "warning") }} ⚠ {{ else }} {{ end }} | @@ -34,6 +34,7 @@ Current database: {{ .database }} {{- NumFormat (index $value "toast_relfrozenxid") -1}} | {{ end }}{{/* range */}} {{/*- end -*/}}{{/* if per_instance exists */}} +* This table has specific autovacuum settings. See 'F001 Autovacuum: Current settings' {{- else -}}{{/*Master data*/}} No data diff --git a/pghrep/templates/F003.tpl b/pghrep/templates/F003.tpl index 275bdbc..d2b7bd8 100644 --- a/pghrep/templates/F003.tpl +++ b/pghrep/templates/F003.tpl @@ -14,7 +14,7 @@ Stats reset: {{ (index (index (index .results .hosts.master) "data") "database_s ----------|------|-----------------------|-------------------|----------|---------|-----------|-----------|-----------|--------------------|------------|------------|----------- {{ range $i, $key := (index (index (index (index .results .hosts.master) "data") "dead_tuples") "_keys") }} {{- $value := (index (index (index (index $.results $.hosts.master) "data") "dead_tuples") $key) -}} -{{ index $value "relation"}} | +{{ index $value "relation"}}{{if $value.overrided_settings}}*{{ end }} | {{- index $value "relkind"}} | {{- index $value "since_last_autovacuum"}} | {{- index $value "since_last_vacuum"}} | @@ -28,6 +28,7 @@ Stats reset: {{ (index (index (index .results .hosts.master) "data") "database_s {{- NumFormat (index $value "n_dead_tup") -1 }} | {{- if ge (Int (index $value "dead_ratio")) 10 }} **{{ (index $value "dead_ratio")}}** {{else}} {{ (index $value "dead_ratio")}} {{end}} {{ end }} +* This table has specific autovacuum settings. See 'F001 Autovacuum: Current settings' {{- else -}}{{/* dead_tuples */}} No data {{- end }}{{/* dead_tuples */}} diff --git a/pghrep/templates/F004.tpl b/pghrep/templates/F004.tpl index 1683901..ef20462 100644 --- a/pghrep/templates/F004.tpl +++ b/pghrep/templates/F004.tpl @@ -19,7 +19,7 @@ Current database: {{ .database }} {{- if (index (index (index (index $.results $.hosts.master) "data") "heap_bloat_total") "Bloat ratio") }}{{- if ge (Int (index (index (index (index $.results $.hosts.master) "data") "heap_bloat_total") "Bloat ratio" )) $minRatioWarning }}**{{- RawFloatFormat (index (index (index (index $.results $.hosts.master) "data") "heap_bloat_total") "Bloat ratio" ) 2 }}**{{else}}{{- RawFloatFormat (index (index (index (index $.results $.hosts.master) "data") "heap_bloat_total") "Bloat ratio") 2 }}{{ end }}|||{{ end }}{{ end }} {{ range $i, $key := (index (index (index (index .results .hosts.master) "data") "heap_bloat") "_keys") }} {{- $value := (index (index (index (index $.results $.hosts.master) "data") "heap_bloat") $key ) -}} -{{ $key }} | +{{ $key }}{{if $value.overrided_settings}}*{{ end }} | {{- ByteFormat ( index $value "Real size bytes" ) 2 }} | {{- "~" }}{{ ByteFormat ( index $value "Extra size bytes" ) 2 }} ({{- NumFormat ( index $value "Extra_ratio" ) 2 }}%)| {{- if ( index $value "Bloat size bytes")}}{{ ByteFormat ( index $value "Bloat size bytes") 2 }}{{end}} | @@ -29,6 +29,7 @@ Current database: {{ .database }} {{- if (index $value "Last Vaccuum") }} {{ ( index $value "Last Vaccuum") }} {{ end }} | {{- ( index $value "Fillfactor") }} {{ end }} {{/*range*/}} +* This table has specific autovacuum settings. See 'F001 Autovacuum: Current settings' {{- else }}{{/* if heap_bloat */}} No data {{- end -}}{{/* if heap_bloat */}} diff --git a/pghrep/templates/F005.tpl b/pghrep/templates/F005.tpl index 9530edc..98568d4 100644 --- a/pghrep/templates/F005.tpl +++ b/pghrep/templates/F005.tpl @@ -18,7 +18,7 @@ Current database: {{ .database }} {{ range $i, $key := (index (index (index (index .results .hosts.master) "data") "index_bloat") "_keys") }} {{- $value := (index (index (index (index $.results $.hosts.master) "data") "index_bloat") $key) -}} {{- $tableIndex := Split $key "\n" -}} -{{ $table := Trim (index $tableIndex 1) " ()"}}{{ (index $tableIndex 0) }} ({{ $table }}) | +{{ $table := Trim (index $tableIndex 1) " ()"}}{{ (index $tableIndex 0) }} ({{ $table }}{{if $value.overrided_settings}}*{{ end }}) | {{- ByteFormat ( index $value "Real size bytes") 2 }} | {{- if ( index $value "Extra size bytes")}}{{- "~" }}{{ ByteFormat ( index $value "Extra size bytes" ) 2 }} ({{- NumFormat ( index $value "Extra_ratio" ) 2 }}%){{end}} | {{- if ( index $value "Bloat size bytes")}}{{ ByteFormat ( index $value "Bloat size bytes") 2 }}{{end}} | @@ -27,6 +27,7 @@ Current database: {{ .database }} {{- "~" }}{{ ByteFormat ( index $value "Live bytes" ) 2 }} | {{- ( index $value "fillfactor") }} {{ end }} +* This table has specific autovacuum settings. See 'F001 Autovacuum: Current settings' {{- else -}}{{/*Master data*/}} No data {{- end }}{{/*Master data*/}} diff --git a/resources/checks/F002_autovacuum_wraparound.sh b/resources/checks/F002_autovacuum_wraparound.sh index f7269ef..4bd9860 100755 --- a/resources/checks/F002_autovacuum_wraparound.sh +++ b/resources/checks/F002_autovacuum_wraparound.sh @@ -1,7 +1,16 @@ # how close to wraparound ${CHECK_HOST_CMD} "${_PSQL} -f - " < 1200000000)::int as warning + (greatest(age(c.relfrozenxid), age(t.relfrozenxid)) > 1200000000)::int as warning, + case when ot.table_id is not null then true else false end as overrided_settings from pg_class c join pg_namespace n on c.relnamespace = n.oid left join pg_class t ON c.reltoastrelid = t.oid + left join overrided_tables ot on ot.table_id = c.oid where c.relkind IN ('r', 'm') and not (n.nspname = 'pg_catalog' and c.relname <> 'pg_class') and n.nspname <> 'information_schema' order by 3 desc diff --git a/resources/checks/F003_autovacuum_dead_tuples.sh b/resources/checks/F003_autovacuum_dead_tuples.sh index ce6bb38..fe304b2 100755 --- a/resources/checks/F003_autovacuum_dead_tuples.sh +++ b/resources/checks/F003_autovacuum_dead_tuples.sh @@ -1,7 +1,16 @@ # Collect autovacuum dead tuples ${CHECK_HOST_CMD} "${_PSQL} -f - " < 10000 order by 13 desc limit 50 ), dead_tuples as ( diff --git a/resources/checks/F004_heap_bloat.sh b/resources/checks/F004_heap_bloat.sh index b344717..4f69132 100755 --- a/resources/checks/F004_heap_bloat.sh +++ b/resources/checks/F004_heap_bloat.sh @@ -1,6 +1,15 @@ ${CHECK_HOST_CMD} "${_PSQL} -f -" <= 0 @@ -103,8 +112,10 @@ with data as ( coalesce(substring(array_to_string(reloptions, ' ') from 'fillfactor=([0-9]+)')::smallint, 100) from pg_class where oid = tblid - ) as "Fillfactor" + ) as "Fillfactor", + case when ot.table_id is not null then true else false end as overrided_settings from step4 + left join overrided_tables ot on ot.table_id = step4.tblid order by bloat_size desc nulls last ), limited_data as ( select * from data limit 100 diff --git a/resources/checks/F005_index_bloat.sh b/resources/checks/F005_index_bloat.sh index 207694b..69bb77c 100755 --- a/resources/checks/F005_index_bloat.sh +++ b/resources/checks/F005_index_bloat.sh @@ -1,7 +1,17 @@ ${CHECK_HOST_CMD} "${_PSQL} -f -" < 0 - group by 1, 2, 3, 4, 5, 6, 7, 8, 9 + group by 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ), step2 as ( select *, @@ -92,7 +102,7 @@ with data as ( \$out$%s (%s)\$out$, left(index_name, 50) || case when length(index_name) > 50 then '…' else '' end, - coalesce(nullif(schema_name, 'public') || '.', '') || table_name + coalesce(nullif(step4.schema_name, 'public') || '.', '') || step4.table_name ) as "Index (Table)", real_size as "Real size bytes", pg_size_pretty(real_size::numeric) as "Size", @@ -132,8 +142,10 @@ with data as ( then (real_size - bloat_size)::numeric else null end as "Live bytes", - fillfactor + fillfactor, + case when ot.table_id is not null then true else false end as overrided_settings from step4 + left join overrided_tables ot on ot.table_id = step4.tblid order by real_size desc nulls last ), limited_data as ( select * from data limit 100 -- GitLab From c6e6175f5fb7cf794e8cae5437cf7f2ce2a6a5ed Mon Sep 17 00:00:00 2001 From: dmius Date: Wed, 20 Feb 2019 14:26:47 +0300 Subject: [PATCH 18/28] F001 template fixed --- pghrep/templates/F001.tpl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pghrep/templates/F001.tpl b/pghrep/templates/F001.tpl index 058c374..6608579 100644 --- a/pghrep/templates/F001.tpl +++ b/pghrep/templates/F001.tpl @@ -15,7 +15,7 @@ Data collected: {{ DtFormat .timestamptz }} {{ end }}{{/* range */}} {{ if (index (index (index (index .results .hosts.master) "data") "settings") "table_settings") }} -#### Tables settings override #### +#### Tuned tables #### ▼ Namespace | Relation | Options ----------|----------|------ {{ range $i, $key := (index (index (index (index (index .results .hosts.master) "data") "settings") "table_settings") "_keys") }} @@ -24,6 +24,8 @@ Data collected: {{ DtFormat .timestamptz }} {{- $value.relname }} | {{- range $j, $valopt := $value.reloptions }} {{ $valopt }}
{{ end }} {{ end }}{{/* range */}} +{{else}} +No tuned tables are found {{- end -}}{{/* if table_settings */}} {{- end }}{{/*Master data*/}} {{ end }}{{/*Master*/}} -- GitLab From 681c31962938a3d4d5e3e1d337422b2d7e486eb7 Mon Sep 17 00:00:00 2001 From: dmius Date: Wed, 20 Feb 2019 14:32:48 +0300 Subject: [PATCH 19/28] F001 template fixed --- pghrep/templates/F001.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pghrep/templates/F001.tpl b/pghrep/templates/F001.tpl index 6608579..51ccc5e 100644 --- a/pghrep/templates/F001.tpl +++ b/pghrep/templates/F001.tpl @@ -14,8 +14,8 @@ Data collected: {{ DtFormat .timestamptz }} {{ end -}} {{ end }}{{/* range */}} -{{ if (index (index (index (index .results .hosts.master) "data") "settings") "table_settings") }} #### Tuned tables #### +{{ if (index (index (index (index .results .hosts.master) "data") "settings") "table_settings") }} ▼ Namespace | Relation | Options ----------|----------|------ {{ range $i, $key := (index (index (index (index (index .results .hosts.master) "data") "settings") "table_settings") "_keys") }} -- GitLab From 1665b936717effc6ede11797dcb0ceeefbe492d5 Mon Sep 17 00:00:00 2001 From: dmius Date: Wed, 20 Feb 2019 14:38:50 +0300 Subject: [PATCH 20/28] test db dump improved --- .ci/test_db_dump.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.ci/test_db_dump.sql b/.ci/test_db_dump.sql index 2e83576..6d9ce14 100644 --- a/.ci/test_db_dump.sql +++ b/.ci/test_db_dump.sql @@ -37,3 +37,6 @@ delete from bloated where i % 2 = 0; create table t_with_bloat as select i from generate_series(1, 1000000) _(i); update t_with_bloat set i = i; +-- Table with tuned autovacuum settings +ALTER TABLE t_with_bloat SET (autovacuum_vacuum_scale_factor=0.01); + -- GitLab From 2af33fd54d7880b56b12a193f505e9b4853c4762 Mon Sep 17 00:00:00 2001 From: dmius Date: Fri, 22 Feb 2019 18:26:26 +0300 Subject: [PATCH 21/28] Show remark only if tuned tables found --- pghrep/templates/F002.tpl | 3 ++- pghrep/templates/F003.tpl | 2 ++ pghrep/templates/F004.tpl | 2 ++ pghrep/templates/F005.tpl | 2 ++ resources/checks/F002_autovacuum_wraparound.sh | 4 +++- resources/checks/F003_autovacuum_dead_tuples.sh | 4 +++- resources/checks/F004_heap_bloat.sh | 5 ++++- resources/checks/F005_index_bloat.sh | 5 ++++- 8 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pghrep/templates/F002.tpl b/pghrep/templates/F002.tpl index 9c46e3a..c390728 100644 --- a/pghrep/templates/F002.tpl +++ b/pghrep/templates/F002.tpl @@ -34,8 +34,9 @@ Current database: {{ .database }} {{- NumFormat (index $value "toast_relfrozenxid") -1}} | {{ end }}{{/* range */}} {{/*- end -*/}}{{/* if per_instance exists */}} +{{- if gt (Int (index (index (index .results .hosts.master) "data") "overrided_settings_count")) 0 }} * This table has specific autovacuum settings. See 'F001 Autovacuum: Current settings' - +{{- end }} {{- else -}}{{/*Master data*/}} No data {{- end }}{{/*Master data*/}} diff --git a/pghrep/templates/F003.tpl b/pghrep/templates/F003.tpl index d2b7bd8..a5b8f3d 100644 --- a/pghrep/templates/F003.tpl +++ b/pghrep/templates/F003.tpl @@ -28,7 +28,9 @@ Stats reset: {{ (index (index (index .results .hosts.master) "data") "database_s {{- NumFormat (index $value "n_dead_tup") -1 }} | {{- if ge (Int (index $value "dead_ratio")) 10 }} **{{ (index $value "dead_ratio")}}** {{else}} {{ (index $value "dead_ratio")}} {{end}} {{ end }} +{{- if gt (Int (index (index (index .results .hosts.master) "data") "overrided_settings_count")) 0 }} * This table has specific autovacuum settings. See 'F001 Autovacuum: Current settings' +{{- end }} {{- else -}}{{/* dead_tuples */}} No data {{- end }}{{/* dead_tuples */}} diff --git a/pghrep/templates/F004.tpl b/pghrep/templates/F004.tpl index ef20462..ade5042 100644 --- a/pghrep/templates/F004.tpl +++ b/pghrep/templates/F004.tpl @@ -29,7 +29,9 @@ Current database: {{ .database }} {{- if (index $value "Last Vaccuum") }} {{ ( index $value "Last Vaccuum") }} {{ end }} | {{- ( index $value "Fillfactor") }} {{ end }} {{/*range*/}} +{{- if gt (Int (index (index (index .results .hosts.master) "data") "overrided_settings_count")) 0 }} * This table has specific autovacuum settings. See 'F001 Autovacuum: Current settings' +{{- end }} {{- else }}{{/* if heap_bloat */}} No data {{- end -}}{{/* if heap_bloat */}} diff --git a/pghrep/templates/F005.tpl b/pghrep/templates/F005.tpl index 98568d4..75f75bd 100644 --- a/pghrep/templates/F005.tpl +++ b/pghrep/templates/F005.tpl @@ -27,7 +27,9 @@ Current database: {{ .database }} {{- "~" }}{{ ByteFormat ( index $value "Live bytes" ) 2 }} | {{- ( index $value "fillfactor") }} {{ end }} +{{- if gt (Int (index (index (index .results .hosts.master) "data") "overrided_settings_count")) 0 }} * This table has specific autovacuum settings. See 'F001 Autovacuum: Current settings' +{{- end }} {{- else -}}{{/*Master data*/}} No data {{- end }}{{/*Master data*/}} diff --git a/resources/checks/F002_autovacuum_wraparound.sh b/resources/checks/F002_autovacuum_wraparound.sh index 4bd9860..6d5156f 100755 --- a/resources/checks/F002_autovacuum_wraparound.sh +++ b/resources/checks/F002_autovacuum_wraparound.sh @@ -51,6 +51,8 @@ select 'per_instance', (select json_object_agg(i.datname, i) from per_instance i), 'per_database', - (select json_object_agg(d.relation, d) from per_database d) + (select json_object_agg(d.relation, d) from per_database d), + 'overrided_settings_count', + (select count(1) from per_database where overrided_settings = true) ); SQL diff --git a/resources/checks/F003_autovacuum_dead_tuples.sh b/resources/checks/F003_autovacuum_dead_tuples.sh index fe304b2..f7ecef2 100755 --- a/resources/checks/F003_autovacuum_dead_tuples.sh +++ b/resources/checks/F003_autovacuum_dead_tuples.sh @@ -52,6 +52,8 @@ select 'dead_tuples', (select * from dead_tuples), 'database_stat', - (select * from database_stat) + (select * from database_stat), + 'overrided_settings_count', + (select count(1) from data where overrided_settings = true) ); SQL diff --git a/resources/checks/F004_heap_bloat.sh b/resources/checks/F004_heap_bloat.sh index 4f69132..21ce7d3 100755 --- a/resources/checks/F004_heap_bloat.sh +++ b/resources/checks/F004_heap_bloat.sh @@ -136,6 +136,9 @@ select 'heap_bloat', (select * from limited_json_data), 'heap_bloat_total', - (select row_to_json(total_data) from total_data) + (select row_to_json(total_data) from total_data), + 'overrided_settings_count', + (select count(1) from limited_data where overrided_settings = true) + ) SQL diff --git a/resources/checks/F005_index_bloat.sh b/resources/checks/F005_index_bloat.sh index 69bb77c..953eace 100755 --- a/resources/checks/F005_index_bloat.sh +++ b/resources/checks/F005_index_bloat.sh @@ -166,7 +166,10 @@ select 'index_bloat', (select * from limited_json_data), 'index_bloat_total', - (select row_to_json(total_data) from total_data) + (select row_to_json(total_data) from total_data), + 'overrided_settings_count', + (select count(1) from limited_data where overrided_settings = true) + ) SQL -- GitLab From bee8250a65dd05797fc5f5f2d6bdab93b9ccb685 Mon Sep 17 00:00:00 2001 From: dmius Date: Fri, 22 Feb 2019 14:58:22 +0300 Subject: [PATCH 22/28] A004: per day values added --- resources/checks/A004_cluster_info.sh | 116 +++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 3 deletions(-) diff --git a/resources/checks/A004_cluster_info.sh b/resources/checks/A004_cluster_info.sh index df0ad51..fd4cf2a 100755 --- a/resources/checks/A004_cluster_info.sh +++ b/resources/checks/A004_cluster_info.sh @@ -26,13 +26,123 @@ fi ${CHECK_HOST_CMD} "${_PSQL} -f - " < Date: Fri, 22 Feb 2019 15:36:27 +0300 Subject: [PATCH 23/28] Division on 0: fixed --- resources/checks/A004_cluster_info.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/resources/checks/A004_cluster_info.sh b/resources/checks/A004_cluster_info.sh index fd4cf2a..5185034 100755 --- a/resources/checks/A004_cluster_info.sh +++ b/resources/checks/A004_cluster_info.sh @@ -1,6 +1,4 @@ # Collect pg cluster info -main_sql=$(curl -s -L https://raw.githubusercontent.com/NikolayS/postgres_dba/5.0/sql/0_node.sql | awk '{gsub("; *$", "", $0); print $0}') - pgver=$(${CHECK_HOST_CMD} "${_PSQL} -c \"SHOW server_version\"") vers=(${pgver//./ }) @@ -120,7 +118,12 @@ with data as ( union all select 'Temp Files: total number of files per day', - (temp_files / (((extract(epoch from now()) - extract(epoch from data.stats_reset))/86400)::int))::text + case + when (((extract(epoch from now()) - extract(epoch from data.stats_reset))/86400)::int) <> 0 then + (temp_files / (((extract(epoch from now()) - extract(epoch from data.stats_reset))/86400)::int))::text + else + null + end from data union all select 'Temp Files: avg file size', pg_size_pretty(temp_bytes::numeric / nullif(temp_files, 0))::text from data @@ -129,7 +132,12 @@ with data as ( union all select 'Deadlocks per day', - (deadlocks / (((extract(epoch from now()) - extract(epoch from data.stats_reset))/86400)::int))::text + case + when ((extract(epoch from now()) - extract(epoch from data.stats_reset))/86400)::int <> 0 then + (deadlocks / (((extract(epoch from now()) - extract(epoch from data.stats_reset))/86400)::int))::text + else + null + end from data ), general_info_json as ( select json_object_agg(data.metric, data) as json from data where data.metric not like '------%' -- GitLab From 329a2d358ac34cc6078d7ac378acf215d66f5514 Mon Sep 17 00:00:00 2001 From: dmius Date: Fri, 22 Feb 2019 15:55:53 +0300 Subject: [PATCH 24/28] per day value fixed if less 1 day --- resources/checks/A004_cluster_info.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/checks/A004_cluster_info.sh b/resources/checks/A004_cluster_info.sh index 5185034..b791217 100755 --- a/resources/checks/A004_cluster_info.sh +++ b/resources/checks/A004_cluster_info.sh @@ -122,7 +122,7 @@ with data as ( when (((extract(epoch from now()) - extract(epoch from data.stats_reset))/86400)::int) <> 0 then (temp_files / (((extract(epoch from now()) - extract(epoch from data.stats_reset))/86400)::int))::text else - null + temp_files::text end from data union all @@ -136,7 +136,7 @@ with data as ( when ((extract(epoch from now()) - extract(epoch from data.stats_reset))/86400)::int <> 0 then (deadlocks / (((extract(epoch from now()) - extract(epoch from data.stats_reset))/86400)::int))::text else - null + deadlocks::text end from data ), general_info_json as ( -- GitLab From effbe8f54a41af8fa1035cc825b20eed2ac9eb9e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 25 Feb 2019 09:59:46 +0000 Subject: [PATCH 25/28] Resolve "Improve H002 (unused and redundant indexes). Also adjust H001 (invalid indexes)" --- .ci/test_db_dump.sql | 48 ++- README.md | 2 +- pghrep/plugins/H002.go | 131 +------- pghrep/src/main.go | 1 + pghrep/src/reportutils.go | 14 +- pghrep/templates/H001.tpl | 46 ++- pghrep/templates/H002.tpl | 122 ++++---- pghrep/templates/K001.tpl | 4 +- pghrep/templates/K002.tpl | 4 +- pghrep/templates/K003.tpl | 4 +- resources/checks/H001_invalid_indexes.sh | 78 ++++- resources/checks/H002_unused_indexes.sh | 376 +++++++++++++++++++++-- 12 files changed, 564 insertions(+), 266 deletions(-) diff --git a/.ci/test_db_dump.sql b/.ci/test_db_dump.sql index 6d9ce14..cb02bdf 100644 --- a/.ci/test_db_dump.sql +++ b/.ci/test_db_dump.sql @@ -6,6 +6,10 @@ alter user checkup_test_user set lock_timeout to '3s'; alter database checkup_test_db set lock_timeout = '4s'; --alter database checkup_test_db RESET configuration_parameter +-- rarely used indexes +create table t_rar_q as select id, (random() * 1000000)::int8 as t_dat from generate_series(1, 1000000) _(id); +create index t_rar_q_idx on t_rar_q(id); + -- Fillfactor create table t_fillfactor (i int) with (fillfactor=60); @@ -17,9 +21,10 @@ create index concurrently i_redundant_1 on t_with_redundant_index(i); create index concurrently i_redundant_2 on t_with_redundant_index(i); -- H001 invalid indexes -create table t_with_invalid_index as select i from generate_series(1, 1000000) _(i); -set statement_timeout to '10ms'; -create index concurrently i_invalid on t_with_invalid_index(i); +create schema test_schema; +create table test_schema.t_with_invalid_index as select i from generate_series(1, 1000000) _(i); +set statement_timeout to '20ms'; +create index concurrently i_invalid on test_schema.t_with_invalid_index(i); set statement_timeout to 0; -- H003 non indexed fks @@ -35,8 +40,41 @@ delete from bloated where i % 2 = 0; -- F004 create table t_with_bloat as select i from generate_series(1, 1000000) _(i); -update t_with_bloat set i = i; --- Table with tuned autovacuum settings +-- h002 Supports fk +create table t_red_fk_1 as select id::int8 from generate_series(0, 1000000) _(id); +alter table t_red_fk_1 add primary key (id); +create index r_red_fk_1_id_idx on t_red_fk_1(id); +create index r_red_fk_1_X_idx on t_red_fk_1(id); + +create table t_red_fk_2 as select id, (random() * 1000000)::int8 as r_t1_id from generate_series(1, 1000000) _(id); +alter table t_red_fk_2 add constraint fk_red_fk2_t1 foreign key (r_t1_id) references t_red_fk_1(id); +create index r_red_fk_2_fk_idx on t_red_fk_2(r_t1_id); + +-- altered settings +alter system set random_page_cost = 2.22; +select pg_reload_conf(); + +--slow query +create table t_slw_q as select id::int8 from generate_series(0, 10000000) _(id); ALTER TABLE t_with_bloat SET (autovacuum_vacuum_scale_factor=0.01); +VACUUM ANALYZE; + +-- rarely used indexes +select * from t_rar_q where id = 23211; +update t_rar_q set t_dat=100 where t_dat between 553432 and 155343; +update t_rar_q set t_dat=200 where t_dat between 155343 and 255343; +update t_rar_q set t_dat=300 where t_dat between 255343 and 355343; +update t_rar_q set t_dat=400 where t_dat between 455343 and 555343; +update t_rar_q set t_dat=500 where t_dat between 555343 and 655343; +update t_rar_q set t_dat=600 where t_dat between 655343 and 755343; +update t_rar_q set t_dat=700 where t_dat between 755343 and 855343; +update t_rar_q set t_dat=800 where t_dat between 855343 and 955343; +update t_rar_q set t_dat=900 where t_dat between 955343 and 1055343; +-- F004 +update t_with_bloat set i = i; + +-- h002 Supports fk +select count(*) from t_slw_q; +explain select count(*) from t_slw_q; diff --git a/README.md b/README.md index 74ea2a3..7346dd4 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ branch (only single node analyzed): https://gitlab.com/postgres-ai-team/postgres ## H. Index Analysis - [x] H001 Indexes: invalid #192, #51 -- [x] H002 < H001 Indexes: unused, redundant #51, #180, #170, #168 +- [x] H002 < H001 Unused and redundant indexes #51, #180, #170, #168, #322 - [x] H003 < H002 Missing FK indexes #52, #142, #173 ## J. Capacity Planning diff --git a/pghrep/plugins/H002.go b/pghrep/plugins/H002.go index 3e5b73b..2650bc6 100644 --- a/pghrep/plugins/H002.go +++ b/pghrep/plugins/H002.go @@ -2,8 +2,6 @@ package main import ( "../src/pyraconv" - "strings" - "../src/orderedmap" ) var Data map[string]interface{} @@ -34,135 +32,8 @@ func preCheckHosts(data map[string]interface{}) { hosts["replicas"] = replicas } -func compareHostsData(data map[string]interface{}) { - preCheckHosts(data) - hosts := pyraconv.ToInterfaceMap(data["hosts"]) - master := pyraconv.ToString(hosts["master"]) - replicas := pyraconv.ToStringArray(hosts["replicas"]) - resultData := orderedmap.New() //make(map[string]interface{}) - - results := pyraconv.ToInterfaceMap(data["results"]) - masterData := pyraconv.ToInterfaceMap(results[master]) - masterData = pyraconv.ToInterfaceMap(masterData["data"]) - - allUnusedIndexes := make(map[string]bool) - uIndexesData := pyraconv.ToInterfaceMap(masterData["unused_indexes"]) - uIndexes := orderedmap.New() - uKeys := pyraconv.ToStringArray(uIndexesData["_keys"]) - for i := 0; i < len(uKeys); i++ { - indexName := uKeys[i] - value := uIndexesData[indexName] - if (indexName == "_keys") { - continue - } - valueData := pyraconv.ToInterfaceMap(value); - idxScanValue := pyraconv.ToInt64(valueData["idx_scan"]) - idxScanSum := idxScanValue - - indexData := make(map[string]interface{}) - indexData["master"] = valueData - for _, replica := range replicas { - hostIndexData := getReplicaIndex(data, replica, "unused_indexes", indexName) - hostIdxScanValue := pyraconv.ToInt64(hostIndexData["idx_scan"]) - idxScanSum = idxScanSum + hostIdxScanValue - indexData[replica] = hostIndexData - } - indexData["usage"] = idxScanSum > 0 - if idxScanSum == 0 { - allUnusedIndexes[indexName] = true - } - uIndexes.Set(indexName, indexData) - } - resultData.Set("unused_indexes", *uIndexes) - - rIndexesData := pyraconv.ToInterfaceMap(masterData["redundant_indexes"]) - rIndexes := orderedmap.New() - rKeys := pyraconv.ToStringArray(rIndexesData["_keys"]) - for i := 0; i < len(rKeys); i++ { - indexName := rKeys[i] - value := uIndexesData[indexName] - if (indexName == "_keys") { - continue - } - valueData := pyraconv.ToInterfaceMap(value); - idxScanValue := pyraconv.ToInt64(valueData["index_usage"]) - idxScanSum := idxScanValue - - indexData := make(map[string]interface{}) - indexData["master"] = valueData - for _, replica := range replicas { - hostIndexData := getReplicaIndex(data, replica, "redundant_indexes", indexName) - hostIdxScanValue := pyraconv.ToInt64(hostIndexData["index_usage"]) - idxScanSum = idxScanSum + hostIdxScanValue - indexData[replica] = hostIndexData - } - indexData["usage"] = idxScanSum > 0 - if idxScanSum == 0 { - allUnusedIndexes[indexName] = true - } - rIndexes.Set(indexName, indexData) - } - resultData.Set("redundant_indexes", *rIndexes) - - dropCode := make(map[string]interface{}) - revertCode := make(map[string]interface{}) - // enum only not used indexes - for indexName, _ := range allUnusedIndexes { - dropIndexCode := getIndexCode(data, indexName, "drop") - if len(dropIndexCode) > 0 { - dropCode[indexName] = dropIndexCode - } - revertIndexCode := getIndexCode(data, indexName, "revert") - if len(revertIndexCode) > 0 { - revertCode[indexName] = revertIndexCode - } - } - - resultData.Set("drop_code", dropCode) - resultData.Set("revert_code", revertCode) - - rd := resultData.ToInterfaceArray() - data["resultData"] = rd -} - -func getReplicaIndexUsage(data map[string]interface{}, replica string, indexName string) (int64) { - results := pyraconv.ToInterfaceMap(data["results"]) - hostData := pyraconv.ToInterfaceMap(results[replica]) - hostData = pyraconv.ToInterfaceMap(hostData["data"]) - uIndexesData := pyraconv.ToInterfaceMap(hostData["unused_uIndexes"]) - indexData := pyraconv.ToInterfaceMap(uIndexesData[indexName]) - return pyraconv.ToInt64(indexData["idx_scan"]) -} - -func getReplicaIndex(data map[string]interface{}, replica string, indexType string, indexName string) map[string]interface{} { - results := pyraconv.ToInterfaceMap(data["results"]) - hostData := pyraconv.ToInterfaceMap(results[replica]) - hostData = pyraconv.ToInterfaceMap(hostData["data"]) - uIndexesData := pyraconv.ToInterfaceMap(hostData[indexType]) - indexData := pyraconv.ToInterfaceMap(uIndexesData[indexName]) - return indexData -} - -func getIndexCode(data map[string]interface{}, indexName string, op string) string { - hosts := pyraconv.ToInterfaceMap(data["hosts"]) - master := pyraconv.ToString(hosts["master"]) - - results := pyraconv.ToInterfaceMap(data["results"]) - hostData := pyraconv.ToInterfaceMap(results[master]) - hostData = pyraconv.ToInterfaceMap(hostData["data"]) - codeData := pyraconv.ToInterfaceArray(hostData[op + "_code"]) - for _, codeValue := range codeData { - sql := pyraconv.ToString(codeValue) - if strings.Contains(sql, indexName) { - return sql - } - } - return "" -} - - func (g prepare) Prepare(data map[string]interface{}) map[string]interface{} { - compareHostsData(data) + preCheckHosts(data) return data } diff --git a/pghrep/src/main.go b/pghrep/src/main.go index 365f12a..4349872 100644 --- a/pghrep/src/main.go +++ b/pghrep/src/main.go @@ -149,6 +149,7 @@ func loadTemplates() *template.Template { tplFuncMap := make(template.FuncMap) tplFuncMap["Split"] = Split tplFuncMap["Trim"] = Trim + tplFuncMap["Replace"] = Replace tplFuncMap["Code"] = Code tplFuncMap["Nobr"] = Nobr tplFuncMap["Br"] = Br diff --git a/pghrep/src/reportutils.go b/pghrep/src/reportutils.go index e6e2583..8d6df67 100644 --- a/pghrep/src/reportutils.go +++ b/pghrep/src/reportutils.go @@ -10,8 +10,10 @@ import ( "time" ) -func Split(s string, d string) []string { - arr := strings.Split(s, d) +func Split(s interface{}, d interface{}) []string { + str1 := pyraconv.ToString(s) + str2 := pyraconv.ToString(d) + arr := strings.Split(str1, str2) return arr } @@ -19,6 +21,14 @@ func Trim(s string, d string) string { return strings.Trim(s, d) } +func Replace(str interface{}, src interface{}, dst interface{}) string { + str1 := pyraconv.ToString(str) + src1 := pyraconv.ToString(src) + dst1 := pyraconv.ToString(dst) + return strings.Replace(str1, src1, dst1, -1) +} + + func Nobr(s interface{}) string { str := pyraconv.ToString(s) str = strings.Join(strings.Split(str, "\n"), "") diff --git a/pghrep/templates/H001.tpl b/pghrep/templates/H001.tpl index b10def7..0758c6b 100644 --- a/pghrep/templates/H001.tpl +++ b/pghrep/templates/H001.tpl @@ -4,43 +4,55 @@ Data collected: {{ DtFormat .timestamptz }} Current database: {{ .database }} {{ if .hosts.master }} -{{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} +{{ if (index .results .hosts.master) }} ### Master (`{{.hosts.master}}`) ### - {{ if (index (index .results .hosts.master) "data") }} -\# | Schema name | Table name | Index name | Index size -----|-------------|------------|------------|------------ -{{ range $i, $key := (index (index (index .results .hosts.master) "data") "_keys") }} - {{- $value := (index (index (index $.results $.hosts.master) "data") $key) -}} - {{ $key }} | - {{- $value.schema_name }} | - {{- $value.table_name }} | - {{- $value.index_name }} | - {{- $value.index_size }} +{{ if (index (index (index .results .hosts.master) "data") "invalid_indexes") }} +\# | Table | Index name | Index size | Supports FK +---|-------|------------|------------|---------- + |=====TOTAL=====||{{- ByteFormat (index (index (index (index $.results $.hosts.master) "data") "invalid_indexes_total") "index_size_bytes_sum") 2 }} | +{{ range $i, $key := (index (index (index (index .results .hosts.master) "data") "invalid_indexes") "_keys") }} + {{- $value := (index (index (index (index $.results $.hosts.master) "data") "invalid_indexes") $key) -}} + {{ $value.num }} | + {{- $value.formated_relation_name }} | + {{- $value.formated_index_name }} | + {{- ByteFormat $value.index_size_bytes 2 }} | + {{- if $value.supports_fk }}Yes{{ end }} {{ end }}{{/* range */}} {{- else -}} Invalid indexes not found {{- end -}}{{/* if data */}} {{- else -}} +Invalid indexes not found +{{- end -}}{{/* if data */}} +{{- else -}} No data {{- end -}}{{/* if .host.master data */}} {{- else -}} No data {{- end -}}{{/* if .host.master */}} + ## Conclusions ## ## Recommendations ## -{{ if (index .resultData "repair_code") }} -#### "DO" database migration code #### +{{- if .hosts.master }} +{{- if (index .results .hosts.master) }} +{{- if (index (index (index .results .hosts.master) "data") "invalid_indexes") }} +#### Rebuild invalid indexes #### ``` -- Call each line separately. "CONCURRENTLY" queries cannot be -- combined in multi-statement requests. -{{ range $i, $code := (index .resultData "repair_code") }} -{{ $code.drop_code }} -{{ $code.revert_code }} + +{{ range $i, $key := (index (index (index (index .results .hosts.master) "data") "invalid_indexes") "_keys") }} +{{- $value := (index (index (index (index $.results $.hosts.master) "data") "invalid_indexes") $key) -}} +{{ $value.drop_code }} +{{ $value.revert_code }} + {{ end }} ``` -{{ end }} +{{- end -}}{{/* if data */}} +{{- end -}}{{/* if data */}} +{{- end -}}{{/* if .host.master */}} diff --git a/pghrep/templates/H002.tpl b/pghrep/templates/H002.tpl index 5d437b5..ed1c7eb 100644 --- a/pghrep/templates/H002.tpl +++ b/pghrep/templates/H002.tpl @@ -1,89 +1,83 @@ -# {{ .checkId }} Unused/Rarely Used Indexes # +# {{ .checkId }} Unused and redundant indexes # ## Observations ## Data collected: {{ DtFormat .timestamptz }} Current database: {{ .database }} -{{ if .resultData }} -{{ if .hosts.master }} -{{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} -Stats reset: {{ (index (index (index .results .hosts.master) "data") "database_stat").stats_age }} ago ({{ DtFormat (index (index (index .results .hosts.master) "data") "database_stat").stats_reset }}) -{{ if .resultData.unused_indexes }} +{{- if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} +Stats reset: {{ (index (index (index .results .hosts.master) "data") "database_stat").stats_age }} ago ({{ DtFormat (index (index (index .results .hosts.master) "data") "database_stat").stats_reset }}) +{{- if le (Int (index (index (index .results .hosts.master) "data") "database_stat").days) 30 }} +:warning: Statistics age is less than 30 days. Make decisions on index cleanup with caution! +{{- end }} ### Never Used Indexes ### -Index | {{.hosts.master}} usage {{ range $skey, $host := .hosts.replicas }}| {{ $host }} usage {{ end }}| ▼ Index size | Usage ---------|-------{{ range $skey, $host := .hosts.replicas }}|--------{{ end }}|-----|----- -{{ range $i, $key := (index (index .resultData "unused_indexes") "_keys") }} -{{- $value := (index (index $.resultData "unused_indexes") $key) -}} -{{- if ne $key "_keys" -}} -{{- if eq $value.master.reason "Never Used Indexes" -}} -{{- if $value.usage -}} -{{- else -}} -{{- $key }} | -{{- $value.master.idx_scan }}{{ range $skey, $host := $.hosts.replicas }}| -{{- (index $value $host).idx_scan }}{{- end -}} | -{{- "Index size:"}} {{ Nobr $value.master.index_size }}
Table size: {{ Nobr $value.master.table_size }} | -{{- if $value.usage }} Used{{ else }}Not used {{ end }} -{{/* new line */}} -{{- end -}}{{/* value.usage */}} -{{- end -}}{{/**/}} -{{- end }}{{/* in ! _keys */}} -{{- end }}{{/* range unused_indexes */}} +\#| Table | Index | {{.hosts.master}} usage {{ range $skey, $host := .hosts.replicas }}| {{ $host }} usage {{ end }}| ▼ Index size | Table size | Supports FK +--|-------|-------|----{{ range $skey, $host := .hosts.replicas }}|--------{{ end }}|-----|-----|----- + |=====TOTAL=====||{{ range $skey, $host := .hosts.replicas }}|{{ end }}|{{ ByteFormat ((index (index (index .results .hosts.master) "data") "never_used_indexes_total").index_size_bytes_sum) 2 }}|{{ ByteFormat ((index (index (index .results .hosts.master) "data") "never_used_indexes_total").table_size_bytes_sum) 2 }}| +{{ range $i, $key := (index (index (index (index .results .hosts.master) "data") "never_used_indexes") "_keys") }} +{{- $value:=(index (index (index (index $.results $.hosts.master) "data") "never_used_indexes") $key) -}} +{{- $value.num}}| +{{- $value.formated_relation_name}}| +{{- $value.formated_index_name}}| +{{- RawIntFormat $value.idx_scan }}{{ range $skey, $host := $.hosts.replicas }}|{{ if (index (index (index (index $.results $host) "data") "never_used_indexes") $key) }}{{ RawIntFormat ((index (index (index (index $.results $host) "data") "never_used_indexes") $key).idx_scan) }}{{end}}{{ end }}| +{{- ByteFormat $value.index_size_bytes 2}}| +{{- ByteFormat $value.table_size_bytes 2}}| +{{- if $value.supports_fk }}Yes{{end}} +{{ end }}{{/* range */}} -### Other unused indexes ### -Index | Reason |{{.hosts.master}} {{ range $skey, $host := .hosts.replicas }}| {{ $host }} {{ end }}| Usage -------|--------|-------{{ range $skey, $host := .hosts.replicas }}|--------{{ end }}|----- -{{ range $i, $key := (index (index .resultData "unused_indexes") "_keys") }} -{{- $value := (index (index $.resultData "unused_indexes") $key) -}} -{{- if ne $key "_keys" -}} -{{- if ne $value.master.reason "Never Used Indexes" -}} -{{ $key }} | {{ $value.master.reason }} | Usage: {{ $value.master.idx_scan }}
Index size:{{ Nobr $value.master.index_size }}
Table size:{{ Nobr $value.master.table_size }} {{ range $skey, $host := $.hosts.replicas }} | Usage: {{ (index $value $host).idx_scan }}
Index size:{{ Nobr (index $value $host).index_size }}
Table size:{{ Nobr (index $value $host).table_size }}{{- end -}} | {{ if $value.usage }} Used{{ else }}Not used {{ end }} -{{/* new line */}} -{{- end -}} -{{- end }}{{/* ! "_keys" */}} -{{- end }}{{/* range unused_indexes */}} -{{ end }}{{/* if unused_indexes */}} - -{{- if .resultData.redundant_indexes -}} +{{- if and (index (index (index .results .hosts.master) "data") "rarely_used_indexes") (index (index (index .results .hosts.master) "data") "rarely_used_indexes_total") }} +### Rarely Used Indexes ### +\#| Table | Index | {{.hosts.master}} usage {{ range $skey, $host := .hosts.replicas }}| {{ $host }} usage {{ end }}| ▼ Index size | Table size | Comment | Supports FK +--|-------|-------|-----{{ range $skey, $host := .hosts.replicas }}|--------{{ end }}|-----|-----|----|----- + |=====TOTAL=====||{{ range $skey, $host := .hosts.replicas }}|{{ end }}|{{ ByteFormat ((index (index (index .results .hosts.master) "data") "rarely_used_indexes_total").index_size_bytes_sum) 2 }}|{{ ByteFormat ((index (index (index .results .hosts.master) "data") "rarely_used_indexes_total").table_size_bytes_sum) 2 }}|| +{{ range $i, $key := (index (index (index (index .results .hosts.master) "data") "rarely_used_indexes") "_keys") }} +{{- $value:=(index (index (index (index $.results $.hosts.master) "data") "rarely_used_indexes") $key) -}} +{{- $value.num}}| +{{- $value.formated_relation_name}}| +{{- $value.formated_index_name}}| +{{- "scans:" }} {{ RawIntFormat $value.idx_scan }}\/hour, writes: {{ RawIntFormat $value.writes }}\/hour{{ range $skey, $host := $.hosts.replicas }}|{{ if (index (index (index (index $.results $host) "data") "rarely_used_indexes") $key) }}scans: {{ RawIntFormat ((index (index (index (index $.results $host) "data") "rarely_used_indexes") $key).idx_scan) }}\/hour, writes: {{ RawIntFormat ((index (index (index (index $.results $host) "data") "rarely_used_indexes") $key).writes) }}\/hour{{end}}{{ end }}| +{{- ByteFormat $value.index_size_bytes 2}}| +{{- ByteFormat $value.table_size_bytes 2}}| +{{- $value.reason}}| +{{- if $value.supports_fk }}Yes{{end}} +{{ end }}{{/* range */}} +{{ end }}{{/* rarely used indexes found */}} +{{- if and (index (index (index .results .hosts.master) "data") "redundant_indexes") (index (index (index .results .hosts.master) "data") "redundant_indexes_total") -}} ### Redundant indexes ### +\#| Table | Index | Redundant to |{{.hosts.master}} usage {{ range $skey, $host := .hosts.replicas }}| {{ $host }} usage {{ end }}| ▼ Index size | Table size | Supports FK +--|-------|-------|--------------|--{{ range $skey, $host := .hosts.replicas }}|--------{{ end }}|-----|-----|----- + |=====TOTAL=====|||{{ range $skey, $host := .hosts.replicas }}|{{ end }}|{{ ByteFormat ((index (index (index .results .hosts.master) "data") "redundant_indexes_total").index_size_bytes_sum) 2 }}|{{ ByteFormat ((index (index (index .results .hosts.master) "data") "redundant_indexes_total").table_size_bytes_sum) 2 }}| +{{ range $i, $key := (index (index (index (index .results .hosts.master) "data") "redundant_indexes") "_keys") }} +{{- $value:=(index (index (index (index $.results $.hosts.master) "data") "redundant_indexes") $key) -}} +{{- $value.num}}| +{{- $value.formated_relation_name}}| +{{- $value.formated_index_name}}| +{{- $rinexes := Split $value.reason ", " -}}{{ range $r, $rto:= $rinexes }}{{$rto}}
{{end}}| +{{- RawIntFormat $value.idx_scan }}{{ range $skey, $host := $.hosts.replicas }}|{{ if (index (index (index (index $.results $host) "data") "never_used_indexes") $key) }}{{ RawIntFormat ((index (index (index (index $.results $host) "data") "redundant_indexes") $key).idx_scan) }}{{end}}{{ end }}| +{{- ByteFormat $value.index_size_bytes 2}}| +{{- ByteFormat $value.table_size_bytes 2}}| +{{- if $value.supports_fk }}Yes{{end}} +{{ end }}{{/* range */}} +{{ end }}{{/* redundant indexes found */}} -Index | {{.hosts.master}} usage {{ range $skey, $host := .hosts.replicas }}| {{ $host }} usage {{ end }}| Usage | Index size ---------|-------{{ range $skey, $host := .hosts.replicas }}|--------{{ end }}|-----|----- -{{ range $i, $key := (index (index .resultData "redundant_indexes") "_keys") }} -{{- $value := (index (index $.resultData "redundant_indexes") $key) -}} -{{- if ne $key "_keys" -}} -{{- if $value.usage -}} {{- else -}} -{{ $key }} | {{ $value.master.index_usage }}{{ range $skey, $host := $.hosts.replicas }}|{{ (index $value $host).index_usage }}{{- end -}} | {{ if $value.usage }} Used{{ else }}Not used {{ end }} | {{ $value.master.index_size }} -{{/* new line */}} -{{- end -}}{{/* value.usage */}} -{{- end }}{{/* in ! _keys */}} -{{- end }}{{/* range redundant_indexes */}} -{{end}}{{/* if redundant_indexes */}} - -{{end}}{{/* master data */}} -{{end}}{{/* master */}} +No data +{{end}}{{/* end if master data*/}} ## Conclusions ## ## Recommendations ## -{{ if .resultData.drop_code }} +{{ if and (index (index .results .hosts.master) "data") (index (index (index .results .hosts.master) "data") "do") (index (index (index .results .hosts.master) "data") "undo") }} #### "DO" database migration code #### ``` -{{ range $i, $drop_code := (index .resultData "drop_code") }}{{ $drop_code }} +{{ range $i, $drop_code := (index (index (index .results .hosts.master) "data") "do") }}{{ $drop_code }} {{ end }} ``` -{{ end }} -{{ if .resultData.revert_code }} #### "UNDO" database migration code #### ``` -{{ range $i, $revert_code := (index .resultData "revert_code") }}{{ $revert_code }} +{{ range $i, $revert_code := (index (index (index .results .hosts.master) "data") "undo") }}{{ $revert_code }} {{ end }} ``` -{{ end }} - -{{- else -}} -No data -{{- end }} +{{ end }}{{/* do and undo found */}} diff --git a/pghrep/templates/K001.tpl b/pghrep/templates/K001.tpl index 630b039..98c1a6d 100644 --- a/pghrep/templates/K001.tpl +++ b/pghrep/templates/K001.tpl @@ -4,7 +4,8 @@ Data collected: {{ DtFormat .timestamptz }} Current database: {{ .database }} {{ if .hosts.master }} -{{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} +{{ if (index .results .hosts.master) }} +{{ if (index (index .results .hosts.master) "data") }} ### Master (`{{.hosts.master}}`) ### Start: {{ (index (index (index .results .hosts.master) "data") "start_timestamptz") }} End: {{ (index (index (index .results .hosts.master) "data") "end_timestamptz") }} @@ -33,6 +34,7 @@ Calls | Total time | Rows | shared_blks_hit | shared_blks_read | shared_blk {{- RawFloatFormat $value.diff_kcache_system_time_ms 2 }} ms
{{ MsFormat $value.per_sec_kcache_system_time_ms }}/sec
{{ MsFormat $value.per_call_kcache_system_time_ms }}/call
{{ NumFormat $value.ratio_kcache_system_time_ms 2 }}% {{ end }}{{/* range */}} {{- end }}{{/*Master data*/}} +{{- end }}{{/*Master data*/}} {{ end }}{{/*Master*/}} {{ if gt (len .hosts.replicas) 0 }} diff --git a/pghrep/templates/K002.tpl b/pghrep/templates/K002.tpl index 0cd9090..8a3ea65 100644 --- a/pghrep/templates/K002.tpl +++ b/pghrep/templates/K002.tpl @@ -4,7 +4,8 @@ Data collected: {{ DtFormat .timestamptz }} Current database: {{ .database }} {{ if .hosts.master }} -{{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} +{{ if (index .results .hosts.master) }} +{{ if (index (index .results .hosts.master) "data") }} ### Master (`{{.hosts.master}}`) ### Start: {{ (index (index (index .results .hosts.master) "data") "start_timestamptz") }} End: {{ (index (index (index .results .hosts.master) "data") "end_timestamptz") }} @@ -35,6 +36,7 @@ Error (total time): {{ NumFormat (index (index (index .results .hosts.master) "d {{- RawFloatFormat $value.diff_kcache_system_time_ms 2 }} ms
{{ MsFormat $value.per_sec_kcache_system_time_ms }}/sec
{{ MsFormat $value.per_call_kcache_system_time_ms }}/call
{{ NumFormat $value.ratio_kcache_system_time_ms 2 }}% {{ end }}{{/* range */}} {{- end }}{{/*Master data*/}} +{{- end }}{{/*Master data*/}} {{ end }}{{/*Master*/}} {{ if gt (len .hosts.replicas) 0 }} diff --git a/pghrep/templates/K003.tpl b/pghrep/templates/K003.tpl index b63b53f..0eeefc4 100644 --- a/pghrep/templates/K003.tpl +++ b/pghrep/templates/K003.tpl @@ -4,7 +4,8 @@ Data collected: {{ DtFormat .timestamptz }} Current database: {{ .database }} {{ if .hosts.master }} -{{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} +{{ if (index .results .hosts.master) }} +{{ if (index (index .results .hosts.master) "data") }} ### Master (`{{.hosts.master}}`) ### Start: {{ (index (index (index .results .hosts.master) "data") "start_timestamptz") }} End: {{ (index (index (index .results .hosts.master) "data") "end_timestamptz") }} @@ -35,6 +36,7 @@ Error (total time): {{ NumFormat (index (index (index .results .hosts.master) "d {{- Nobr (LimitStr $value.query 2000 ) }}
[full query (50k symbols)]({{ $value.link }}) {{ end }}{{/* range */}} {{- end }}{{/*Master data*/}} +{{- end }}{{/*Master data*/}} {{ end }}{{/*Master*/}} {{ if gt (len .hosts.replicas) 0 }} diff --git a/resources/checks/H001_invalid_indexes.sh b/resources/checks/H001_invalid_indexes.sh index 06e4f22..73d098c 100755 --- a/resources/checks/H001_invalid_indexes.sh +++ b/resources/checks/H001_invalid_indexes.sh @@ -1,14 +1,72 @@ # Invalid keys - -sql=$(curl -s -L https://raw.githubusercontent.com/NikolayS/postgres_dba/5.0/sql/i4_invalid_indexes.sql | awk '{gsub("; *$", "", $0); print $0}') - ${CHECK_HOST_CMD} "${_PSQL} -f -" < 0 + and writes > 100 + and idx_is_btree + union all + select + 'Seldom Used Large Indexes' as reason, + *, + 2 as grp + from index_ratios + where + index_scan_pct < 5 + and scans_per_write > 1 + and idx_scan > 0 + and idx_is_btree + and index_size_bytes > 100000000 + union all + select + 'High-Write Large Non-Btree' as reason, + index_ratios.*, + 3 as grp + from index_ratios, all_writes + where + ( writes::numeric / ( total_writes + 1 ) ) > 0.02 + and not idx_is_btree + and index_size_bytes > 100000000 + order by grp, index_size_bytes desc +), rarely_used_indexes_num as ( + select row_number() over () num, rui.* from rarely_used_indexes rui +), rarely_used_indexes_total as ( + select + sum(index_size_bytes) as index_size_bytes_sum, + sum(table_size_bytes) as table_size_bytes_sum + from rarely_used_indexes +), rarely_used_indexes_json as ( + select + json_object_agg(ruin.schema_name || '.' || ruin.index_name, ruin) as json + from rarely_used_indexes_num ruin + limit 100 +), +-- Redundant indexes +index_data as ( + select + *, + indkey::text as columns, + array_to_string(indclass, ', ') as opclasses + from pg_index + where indisvalid = true ), redundant_indexes as ( - $redundantSql -), migrations as ( - $migrationSql -), deploy as ( - select * from (select * from migrations limit (select count(1) from migrations)/2) as docode where docode.run_in_separate_transactions not like '--%' -), revert as ( - select * from (select * from migrations offset ((select count(1) from migrations)/2 + 1)) as revertcode where revertcode.run_in_separate_transactions not like '--%' -), deploy_code as ( - select json_agg(jsondata.json) from (select run_in_separate_transactions as json from deploy) jsondata -), revert_code as ( - select json_agg(jsondata.json) from (select run_in_separate_transactions as json from revert) jsondata -), unsed as ( - select json_object_agg(i."index_name", i) as json from indexes i -), redundant as ( - select json_object_agg(ri."index_name", ri) as json from redundant_indexes ri + select + i2.indexrelid as index_id, + tnsp.nspname AS schema_name, + trel.relname AS table_name, + pg_relation_size(trel.oid) as table_size_bytes, + irel.relname AS index_name, + am1.amname as access_method, + (i1.indexrelid::regclass)::text as reason, + pg_get_indexdef(i1.indexrelid) main_index_def, + pg_size_pretty(pg_relation_size(i1.indexrelid)) main_index_size, + pg_get_indexdef(i2.indexrelid) index_def, + pg_relation_size(i2.indexrelid) index_size_bytes, + s.idx_scan as index_usage, + quote_ident(tnsp.nspname) as formated_schema_name, + quote_ident(irel.relname) as formated_index_name, + quote_ident(trel.relname) AS formated_table_name, + coalesce(nullif(quote_ident(tnsp.nspname), 'public') || '.', '') || quote_ident(trel.relname) as formated_relation_name, + i2.opclasses + from + index_data as i1 + join index_data as i2 on ( + i1.indrelid = i2.indrelid -- same table + and i1.indexrelid <> i2.indexrelid -- NOT same index + ) + inner join pg_opclass op1 on i1.indclass[0] = op1.oid + inner join pg_opclass op2 on i2.indclass[0] = op2.oid + inner join pg_am am1 on op1.opcmethod = am1.oid + inner join pg_am am2 on op2.opcmethod = am2.oid + join pg_stat_user_indexes as s on s.indexrelid = i2.indexrelid + join pg_class as trel on trel.oid = i2.indrelid + join pg_namespace as tnsp on trel.relnamespace = tnsp.oid + join pg_class as irel on irel.oid = i2.indexrelid + where + not i1.indisprimary -- index 1 is not primary + and not ( -- skip if index1 is (primary or uniq) and is NOT (primary and uniq) + (i1.indisprimary or i1.indisunique) + and (not i2.indisprimary or not i2.indisunique) + ) + and am1.amname = am2.amname -- same access type + and ( + i2.columns like (i1.columns || '%') -- index 2 includes all columns from index 1 + or i1.columns = i2.columns -- index1 and index 2 includes same columns + ) + and ( + i2.opclasses like (i1.opclasses || '%') + or i1.opclasses = i2.opclasses + ) + -- index expressions is same + and pg_get_expr(i1.indexprs, i1.indrelid) is not distinct from pg_get_expr(i2.indexprs, i2.indrelid) + -- index predicates is same + and pg_get_expr(i1.indpred, i1.indrelid) is not distinct from pg_get_expr(i2.indpred, i2.indrelid) +), redundant_indexes_fk as ( + select + ri.*, + case when fi.index_name is not null then true else false end as supports_fk + from redundant_indexes ri + left join fk_indexes fi on + fi.fk_table_ref = ri.table_name + and fi.opclasses like (ri.opclasses || '%') +), redundant_indexes_grouped as ( + select + index_id, + schema_name, + table_name, + table_size_bytes, + index_name, + access_method, + string_agg(reason, ', ') as reason, + string_agg(main_index_def, ', ') as main_index_def, + string_agg(main_index_size, ', ') as main_index_size, + index_def, + index_size_bytes, + index_usage, + formated_index_name, + formated_schema_name, + formated_table_name, + formated_relation_name, + supports_fk + from redundant_indexes_fk + group by + index_id, + table_size_bytes, + schema_name, + table_name, + index_name, + access_method, + index_def, + index_size_bytes, + index_usage, + formated_index_name, + formated_schema_name, + formated_table_name, + formated_relation_name, + supports_fk + order by index_size_bytes desc +), redundant_indexes_num as ( + select row_number() over () num, rig.* from redundant_indexes_grouped rig +), +redundant_indexes_json as ( + select + json_object_agg(rin.schema_name || '.' || rin.index_name, rin) as json + from redundant_indexes_num rin + limit 100 +), redundant_indexes_total as ( + select + sum(index_size_bytes) as index_size_bytes_sum, + sum(table_size_bytes) as table_size_bytes_sum + from redundant_indexes_grouped +), +-- Do and Undo code generation +together as ( + select + reason::text, + index_id::text, + schema_name::text, + table_name::text, + index_name::text, + pg_size_pretty(index_size_bytes)::text as index_size, + index_def::text, + null as main_index_def, + formated_index_name::text, + formated_schema_name::text, + formated_table_name::text, + formated_relation_name::text + from never_used_indexes + union all + select + reason::text, + index_id::text, + schema_name::text, + table_name::text, + index_name::text, + pg_size_pretty(index_size_bytes)::text as index_size, + index_def::text, + main_index_def::text, + formated_index_name::text, + formated_schema_name::text, + formated_table_name::text, + formated_relation_name::text + from redundant_indexes +), do_lines as ( + select format('DROP INDEX CONCURRENTLY %s; -- %s, %s, table %s', max(formated_index_name), max(index_size), string_agg(reason, ', '), table_name) as line + from together t1 + group by table_name, index_name + order by table_name, index_name +), undo_lines as ( + select + replace( + format('%s; -- table %s', max(index_def), table_name), + 'CREATE INDEX', + 'CREATE INDEX CONCURRENTLY' + )as line + from together t2 + group by table_name, index_name + order by table_name, index_name ), database_stat as ( select row_to_json(dbstat) @@ -30,21 +327,32 @@ with indexes as ( age( date_trunc('minute',now()), date_trunc('minute',sd.stats_reset) - ) as stats_age + ) as stats_age, + ((extract(epoch from now()) - extract(epoch from sd.stats_reset))/86400)::int as days from pg_stat_database sd where datname = current_database() ) dbstat ) -select json_build_object( - 'unused_indexes', - (select * from unsed), - 'redundant_indexes', - (select * from redundant), - 'drop_code', - (select * from deploy_code), - 'revert_code', - (select * from revert_code), - 'database_stat', - (select * from database_stat) - ); +-- summarize data +select + json_build_object( + 'never_used_indexes', + (select * from never_used_indexes_json), + 'never_used_indexes_total', + (select row_to_json(nuit) from never_used_indexes_total as nuit), + 'rarely_used_indexes', + (select * from rarely_used_indexes_json), + 'rarely_used_indexes_total', + (select row_to_json(ruit) from rarely_used_indexes_total as ruit), + 'redundant_indexes', + (select * from redundant_indexes_json), + 'redundant_indexes_total', + (select row_to_json(rit) from redundant_indexes_total as rit), + 'do', + (select json_agg(dl.line) from do_lines as dl), + 'undo', + (select json_agg(ul.line) from undo_lines as ul), + 'database_stat', + (select * from database_stat) + ); SQL -- GitLab From 29e50abb4026ceb0f7b3933756c1805ce95598da Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 26 Feb 2019 14:57:30 +0000 Subject: [PATCH 26/28] =?UTF-8?q?"A001=20=D0=A1=D0=B2=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=BE=20=20Linux=20versions=20+=20kernel=20versions"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pghrep/templates/A001.tpl | 39 +++++++---- pghrep/templates/A008.tpl | 80 +++++++++++++++++----- resources/checks/A001_system_info.sh | 12 +--- resources/checks/A008_disk_usage_fstype.sh | 33 ++++++++- 4 files changed, 119 insertions(+), 45 deletions(-) mode change 100644 => 100755 resources/checks/A008_disk_usage_fstype.sh diff --git a/pghrep/templates/A001.tpl b/pghrep/templates/A001.tpl index 9c4f686..0e01d0c 100644 --- a/pghrep/templates/A001.tpl +++ b/pghrep/templates/A001.tpl @@ -2,6 +2,30 @@ ## Observations ## Data collected: {{ DtFormat .timestamptz }} + +{{ if gt (len .results) 2 }} {{/* Min 2 hosts + "_keys" item */}} +### Operating System by hosts ### + +Host| Operating System | Kernel +----|------------------|-------- +{{- if and (index .results .hosts.master) (index (index .results .hosts.master) "data") (index (index (index .results .hosts.master) "data").virtualization) }} +{{ .hosts.master }}| +{{- (index (index (index (index .results .hosts.master) "data").virtualization) "Operating System") }} | +{{- (index (index (index (index .results .hosts.master) "data").virtualization) "Kernel") }} +{{- end -}} +{{- if gt (len .hosts.replicas) 0 -}} + {{- range $key, $host := .hosts.replicas -}} + {{- if (index $.results $host) -}} + {{- if and (index $.results $host) (index (index $.results $host) "data") (index (index (index $.results $host) "data").virtualization) }} +{{ $host }} | +{{- (index (index (index (index $.results $host) "data").virtualization) "Operating System") }} | +{{- (index (index (index (index $.results $host) "data").virtualization) "Kernel") }} + {{- end -}} + {{- end -}} + {{- end }} +{{ end }} +{{ end }} + {{ if .hosts.master }} {{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} ### Master (`{{.hosts.master}}`) ### @@ -29,13 +53,6 @@ Data collected: {{ DtFormat .timestamptz }} {{ if (index (index .results .hosts.master) "data").disk.raw }} **Disk** -``` -{{ (index (index .results .hosts.master) "data").disk.raw}} -``` -{{ end }}{{/* disk */}} -{{ if (index (index .results .hosts.master) "data").virtualization.raw }} -**Virtualization** - ``` {{ (index (index .results .hosts.master) "data").virtualization.raw }} ``` @@ -72,13 +89,6 @@ Data collected: {{ DtFormat .timestamptz }} {{ if (index (index $.results $value) "data").disk.raw }} **Disk** -``` -{{ (index (index $.results $value) "data").disk.raw }} -``` -{{ end }} -{{ if (index (index $.results $value) "data").virtualization.raw }} -**Virtualization** - ``` {{ (index (index $.results $value) "data").virtualization.raw }} ``` @@ -91,4 +101,3 @@ Data collected: {{ DtFormat .timestamptz }} ## Recommendations ## - diff --git a/pghrep/templates/A008.tpl b/pghrep/templates/A008.tpl index 9af86bd..f5dc3ce 100644 --- a/pghrep/templates/A008.tpl +++ b/pghrep/templates/A008.tpl @@ -4,34 +4,82 @@ Output of `df -TPh` (follows symlinks) ## Observations ## Data collected: {{ DtFormat .timestamptz }} {{ if .hosts.master }} -{{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} + {{ if and (index .results .hosts.master) (index (index .results .hosts.master) "data") }} ### Master (`{{.hosts.master}}`) ### -Name | FS Type | Size | Available | Use | Used | Mount Point | Path | Device + +#### System directories #### +Device | FS Type | Size | Available | Usage | Used | Mount Point +-------|---------|------|-----------|-----|------|------------- +{{ range $i, $name := (index (index (index (index .results .hosts.master) "data") "fs_data") "_keys") -}} + {{- $value := (index (index (index (index $.results $.hosts.master) "data") "fs_data") $name) -}} + {{ $value.device}}| + {{- $value.fstype}}| + {{- $value.size}}| + {{- $value.avail}}| + {{- $value.use_percent}}| + {{- $value.used}}| + {{- $value.mount_point}} +{{ end }}{{/* end of range $i, $name := */}} + +#### Database directories #### +Name | FS Type | Size | Available | Usage | Used | Mount Point | Path | Device -----|---------|------|-----------|-----|------|-------------|------|------- -{{ range $i, $name := (index (index (index .results .hosts.master) "data") "_keys") -}} -{{ $name }} {{ range $k, $val_name := (index (index (index (index $.results $.hosts.master) "data") $name) "_keys") -}} - | {{ (index (index (index (index $.results $.hosts.master) "data") $name) $val_name) }} {{ end }}{{/* end of range $k, $val_name */}} +{{ range $i, $name := (index (index (index (index .results .hosts.master) "data") "db_data") "_keys") -}} + {{- $value := (index (index (index (index $.results $.hosts.master) "data") "db_data") $name) -}} + {{ $name }}| + {{- $value.fstype}}| + {{- $value.size}}| + {{- $value.avail}}| + {{- $value.use_percent}}| + {{- $value.used}}| + {{- $value.mount_point}}| + {{- $value.path}}| + {{- $value.device}} {{ end }}{{/* end of range $i, $name := */}} -{{ end }}{{/* end of if .hosts.master data */}} + + {{ end }}{{/* end of if .hosts.master data */}} {{ end }}{{/* end of if .hosts.master */}} {{ if gt (len .hosts.replicas) 0 }} ### Replica servers: ### - {{ range $skey, $host := .hosts.replicas }} + {{ range $skey, $host := .hosts.replicas }} + {{- if (index $.results $host) }} #### Replica (`{{ $host }}`) #### -Name | FS Type | Size | Available | Use | Used | Mount Point | Path | Device + +#### System directories #### +Device | FS Type | Size | Available | Usage | Used | Mount Point +-------|---------|------|-----------|-----|------|------------- +{{ range $i, $name := (index (index (index (index $.results $host) "data") "fs_data") "_keys") -}} + {{- $value := (index (index (index (index $.results $host) "data") "fs_data") $name) -}} + {{ $value.device}}| + {{- $value.fstype}}| + {{- $value.size}}| + {{- $value.avail}}| + {{- $value.use_percent}}| + {{- $value.used}}| + {{- $value.mount_point}} +{{ end }}{{/* range $i, $name := */}} + +#### Database directories #### +Name | FS Type | Size | Available | Usage | Used | Mount Point | Path | Device -----|---------|------|-----------|-----|------|-------------|------|------- -{{- if (index $.results $host) }} -{{ range $i, $name := (index (index (index $.results $host) "data") "_keys") -}} -{{ $name }} {{ range $k, $val_name := (index (index (index (index $.results $host) "data") $name) "_keys") -}} - | {{ (index (index (index (index $.results $host) "data") $name) $val_name) }}{{ " " }} -{{- end }} {{/* range $k, $val_name : */}} -{{ end }}{{/* if (index $.results $host) */}} +{{ range $i, $name := (index (index (index (index $.results $host) "data") "db_data") "_keys") -}} + {{- $value := (index (index (index (index $.results $host) "data") "db_data") $name) -}} + {{ $name }}| + {{- $value.fstype}}| + {{- $value.size}}| + {{- $value.avail}}| + {{- $value.use_percent}}| + {{- $value.used}}| + {{- $value.mount_point}}| + {{- $value.path}}| + {{- $value.device}} {{ end }}{{/* range $i, $name := */}} -{{ end }}{{/* range $skey, $host := */}} + + {{ end }}{{/* if (index $.results $host) */}} + {{ end }}{{/* if (index $.results $host) */}} {{ end }}{{/* if gt (len .hosts.replicas) 0 */}} ## Conclusions ## ## Recommendations ## - diff --git a/resources/checks/A001_system_info.sh b/resources/checks/A001_system_info.sh index fe59b50..c6f7009 100755 --- a/resources/checks/A001_system_info.sh +++ b/resources/checks/A001_system_info.sh @@ -63,23 +63,13 @@ function get_ctl_info() { CTL_INFO=$res_obj #$(jq -n "$res_obj") } -function get_disk_info() { - local disk_info="$(${CHECK_HOST_CMD} "df -hT")" - #local disk_info="$(df -T | sed 's/"/\\"/g')" - res_obj="{\"cmd2check\": \"df -T\", \"raw\": \"$disk_info\"}" - DISK_INFO=$res_obj #$(jq -n "$res_obj") -} - not_first=false get_cpu_info get_mem_info get_system_info -get_disk_info get_ctl_info -host_obj="{\"cpu\": $CPU_INFO, \"ram\": $MEM_INFO, \"system\": $OS_INFO, \"disk\": $DISK_INFO}" -host_obj="{\"cpu\": $CPU_INFO, \"ram\": $MEM_INFO, \"system\": $OS_INFO, \"disk\": $DISK_INFO, \"virtualization\": $CTL_INFO }" +host_obj="{\"cpu\": $CPU_INFO, \"ram\": $MEM_INFO, \"system\": $OS_INFO, \"virtualization\": $CTL_INFO }" result="${host_obj}" result=$(jq -n "$result") echo "$result" - diff --git a/resources/checks/A008_disk_usage_fstype.sh b/resources/checks/A008_disk_usage_fstype.sh old mode 100644 new mode 100755 index 7d62c29..d44dea0 --- a/resources/checks/A008_disk_usage_fstype.sh +++ b/resources/checks/A008_disk_usage_fstype.sh @@ -1,5 +1,10 @@ # Check disk space and file system type for important Postgres-related disk partitions +if [[ "${SSH_SUPPORT}" = "false" ]]; then + echo "SSH is not supported, skipping..." >&2 + exit 1 +fi + PG_MAJOR_VER=$(${CHECK_HOST_CMD} "${_PSQL} -f -" </dev/null 2>&1"); then print_df "$PG_LOG_DIR" fi -echo "}" - +echo "}," +echo "\"fs_data\":{" + +i=0 +points=$(${CHECK_HOST_CMD} "sudo df -TPh | tail -n +2") +while read -r line; do + if [[ $i -gt 0 ]]; then + echo ",\"$i\":{" + else + echo "\"$i\":{" + fi + let i=$i+1 + params=($line) + echo " \"fstype\": \"${params[1]}\", + \"size\": \"${params[2]}\", + \"avail\": \"${params[4]}\", + \"used\": \"${params[3]}\", + \"use_percent\": \"${params[5]}\", + \"mount_point\": \"${params[6]}\", + \"path\": \"${params[6]}\", + \"device\": \"${params[0]}\" +}" +done <<< "$points" +echo "}}" -- GitLab From 1c43eebd425a62f9ef4ee712e11bc876db065f57 Mon Sep 17 00:00:00 2001 From: Victor Yagofarov Date: Wed, 27 Feb 2019 09:25:14 +0000 Subject: [PATCH 27/28] --statement-timeout option --- checkup | 6 ++++-- resources/cli.conf | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/checkup b/checkup index af75ff5..80d649e 100755 --- a/checkup +++ b/checkup @@ -352,6 +352,7 @@ validate_args() { [[ "${PGPORT}" = "None" ]] && export PGPORT=5432 [[ "${DBNAME}" = "None" ]] && export DBNAME=postgres [[ "${USERNAME}" = "None" ]] && export USERNAME="${USER}" + [[ "${STIMEOUT}" = "None" ]] && export STIMEOUT=15 # statement timeout # custom UNIX domain socket directory for PostgreSQL local psql_unix_socket_option="" @@ -372,9 +373,10 @@ validate_args() { local pgpas_subst="" fi - # for usage inside the check scripts + # Construct _PSQL macro for usage inside the check scripts + export PGOPTIONS="-c statement_timeout=${STIMEOUT}s" export PSQL_CONN_OPTIONS="--port=${PGPORT} --dbname=${DBNAME} --username=${USERNAME} ${psql_unix_socket_option}" - export _PSQL="${pgpas_subst}${psql_bin} -1 -X -At -q -v ON_ERROR_STOP=1 -P pager=off ${PSQL_CONN_OPTIONS}" + export _PSQL="PGOPTIONS=\"${PGOPTIONS}\" ${pgpas_subst}${psql_bin} -1 -X -At -q -v ON_ERROR_STOP=1 -P pager=off ${PSQL_CONN_OPTIONS}" dbg "" dbg "PSQL_CONN_OPTIONS: $PSQL_CONN_OPTIONS" diff --git a/resources/cli.conf b/resources/cli.conf index a7c2685..750e94b 100644 --- a/resources/cli.conf +++ b/resources/cli.conf @@ -1,6 +1,6 @@ # csv-like with "|" field separator # File format: -# section|short_name|long_name|INTERNAL_NAME|arg_type|(no_)mandatory|section|text_description +# section|short_name|long_name|INTERNAL_NAME|arg_type|mandatory/optional|text_description # # 'None' means "no value" # supported arg_types: word, text, number, alnum, uri, filepath @@ -14,6 +14,7 @@ Connection options|U|username|USERNAME|word|optional|database user name (default Connection options|h|hostname|HOST|text|mandatory|database and ssh server host Connection options|s|pg-socket-dir|PGSOCKET|text|optional|PostgreSQL domain socket dir (default: psql's default) Connection options|None|psql-binary|PSQLBINARY|text|optional|psql utility path (default: from $PATH) +Connection options|S|statement-timeout|STIMEOUT|number|optional|statement timeout for sql commands (default: 15 seconds) General options|e|epoch|EPOCH|number|mandatory|epoch of check in integer General options|None|project|PROJECT|word|mandatory|project name -- GitLab From 3663d9f0ee5d605fc741b47ba89d12e1af27ddba Mon Sep 17 00:00:00 2001 From: Oleg Gurov Date: Wed, 27 Feb 2019 14:48:15 +0300 Subject: [PATCH 28/28] L003 Integer (int2, int4) out-of-range risks in PKs --- .ci/test_db_dump.sql | 10 ++++ checkup | 83 ++++++-------------------- pghrep/templates/L003.tpl | 3 +- resources/checks/L003_integer_in_pk.sh | 38 ++++++++---- 4 files changed, 56 insertions(+), 78 deletions(-) diff --git a/.ci/test_db_dump.sql b/.ci/test_db_dump.sql index cb02bdf..2733bb7 100644 --- a/.ci/test_db_dump.sql +++ b/.ci/test_db_dump.sql @@ -78,3 +78,13 @@ update t_with_bloat set i = i; -- h002 Supports fk select count(*) from t_slw_q; explain select count(*) from t_slw_q; + +-- L003 +CREATE TABLE test_schema.order +( + id serial, + cnt integer, + CONSTRAINT ordiadjust_pk PRIMARY KEY (id) +); + +INSERT INTO test_schema.order(cnt) select id from generate_series(0, 1000000) _(id); \ No newline at end of file diff --git a/checkup b/checkup index 80d649e..532d8e0 100755 --- a/checkup +++ b/checkup @@ -351,8 +351,8 @@ validate_args() { # fill default (not given) psql connection related variables [[ "${PGPORT}" = "None" ]] && export PGPORT=5432 [[ "${DBNAME}" = "None" ]] && export DBNAME=postgres - [[ "${USERNAME}" = "None" ]] && export USERNAME="${USER}" [[ "${STIMEOUT}" = "None" ]] && export STIMEOUT=15 # statement timeout + [[ "${USERNAME}" = "None" ]] && export USERNAME="" # custom UNIX domain socket directory for PostgreSQL local psql_unix_socket_option="" @@ -373,9 +373,15 @@ validate_args() { local pgpas_subst="" fi + # use default Postgres username or not + local user_substr="" + if [[ ! -z ${USERNAME+x} ]]; then + user_substr=" -U \"${USERNAME}\" " + fi + # Construct _PSQL macro for usage inside the check scripts export PGOPTIONS="-c statement_timeout=${STIMEOUT}s" - export PSQL_CONN_OPTIONS="--port=${PGPORT} --dbname=${DBNAME} --username=${USERNAME} ${psql_unix_socket_option}" + export PSQL_CONN_OPTIONS="--port=${PGPORT} --dbname=${DBNAME} ${user_substr} ${psql_unix_socket_option}" export _PSQL="PGOPTIONS=\"${PGOPTIONS}\" ${pgpas_subst}${psql_bin} -1 -X -At -q -v ON_ERROR_STOP=1 -P pager=off ${PSQL_CONN_OPTIONS}" dbg "" @@ -438,6 +444,12 @@ usage() { echo " ${SCRIPT_NAME} OPTION [OPTION] ..." >&${out_descriptor} echo " ${SCRIPT_NAME} --help" >&${out_descriptor} + if [[ "$exit_code" -ne "0" ]]; then + exit "$exit_code" + fi + + # Printing CLI options starts here + # calc max size of FULL_NAME[] for text alignment local max_name_len=0 for i in $(seq 0 ${CLI_ARGS_POSSIBLE}); do @@ -480,7 +492,7 @@ usage() { # additional info echo >&${out_descriptor} echo "Example:" >&${out_descriptor} - echo " PGPASSWORD=mypasswd ./${SCRIPT_NAME} -h host_to_connect_via_ssh \\" + echo " PGPASSWORD=mypasswd ./${SCRIPT_NAME} -h [ssh_user]@host_to_connect_via_ssh \\" echo " --username ${USER} --dbname postgres \\" echo " --project dummy ${BOLD}-e %EPOCH_NUMBER%${RESET}" >&${out_descriptor} echo >&${out_descriptor} @@ -494,61 +506,6 @@ usage() { exit $exit_code } -####################################### -# Get index of substring in string -# Globals: -# - -# Arguments: -# string, substring -# Returns: -# (integer) index -####################################### -strindex() { - x="${1%%$2*}" - [[ "$x" = "$1" ]] && echo -1 || echo "${#x}" -} - -####################################### -# Convert arbitrary text to json -# Globals: -# - -# Arguments: -# check_id, raw_str -# Returns: -# (text) json object -####################################### -convert_text_to_json() { - local check_id="$1" - local raw_str="$2" - local result="" - local tbls=() - - if [[ $check_id == "L003" ]] ; then - while IFS=';' read -ra records; do - for record in "${records[@]}"; do - if [ ${#record} -le 2 ]; then - continue - fi - local tmp=$(jq -Rnr --arg key ${record:0:$(strindex "$record" "@")} ' - ( "Table@PK@Type@Current max value@Capacity used, %" | split("@") ) as $keys | - ( inputs | split("@") ) as $vals | - [[$keys, $vals] | transpose[] | {key:.[0],value:.[1]}] | from_entries - | {($key): .}' <<< ${record}) - tbls+=("$tmp") - done - done <<< "${raw_str:$(strindex "$raw_str" "INFO:")+5:${#raw_str}}" - - local res='' - for tbl in "${tbls[@]}" - do - res+=$tbl - done - - echo $res | jq -cs 'sort_by(-(.[]."Capacity used, %"|tonumber)) | .[]' | jq -s add - fi - # echo $result -} - ####################################### # Generate json report # Globals: @@ -556,7 +513,7 @@ convert_text_to_json() { # HOST, JSON_REPORTS_DIR, TIMESTAMP_DIR, # TIMESTAMPTZ, MD_REPORTS_DIR # Arguments: -# input_json, check_id, check_name +# input, check_id # Returns: # (text) stdout/stderr ####################################### @@ -574,9 +531,6 @@ generate_report_json() { local tmp_input_json_fname=$(mktemp "${SCRIPT_DIR}"/artifacts/${check_id}_tmp_XXXXXX) # save function's input as a temporary file - if [[ $check_id == "L003" ]] ; then - input_json=$(convert_text_to_json $check_id "$input_json") - fi echo "$input_json" > "$tmp_input_json_fname" # final report file name @@ -1049,8 +1003,7 @@ run_checks() { update_nodes_json # perform a check from file - # http://linuxcommand.org/lc3_man_pages/seth.html - output=$(set -euo pipefail ; source "$CURRENT_CHECK_FNAME" 2>&1) || check_is_failed="true" + output=$(set -euo pipefail ; source "$CURRENT_CHECK_FNAME") || check_is_failed="true" dbg "is check failed?: $check_is_failed" msg "========== End of check ===========" @@ -1164,4 +1117,4 @@ main() { main "$@" -# last line of the file +# last line of the file \ No newline at end of file diff --git a/pghrep/templates/L003.tpl b/pghrep/templates/L003.tpl index 03b1f4f..7adb212 100644 --- a/pghrep/templates/L003.tpl +++ b/pghrep/templates/L003.tpl @@ -10,7 +10,7 @@ Table | PK | Type | Current max value | ▼ Capacity used, % ------|----|------|-------------------|------------------------------- {{ range $i, $key := (index (index (index .results .hosts.master) "data") "_keys") }} {{- $value := (index (index (index $.results $.hosts.master) "data") $key) -}} -{{ index $value "Table"}} | {{ index $value "PK"}} | {{ index $value "Type"}} | {{ index $value "Current max value"}} | {{ index $value "Capacity used, %"}} +{{ index $value "Table"}} | {{ index $value "PK"}} | {{ index $value "Type"}} | {{- RawIntFormat (index $value "Current max value")}} | {{ index $value "Capacity used, %"}} {{ end }} {{- else -}}{{/*Master data*/}} No data @@ -23,4 +23,3 @@ No data ## Recommendations ## - diff --git a/resources/checks/L003_integer_in_pk.sh b/resources/checks/L003_integer_in_pk.sh index f51451b..7ef0daf 100755 --- a/resources/checks/L003_integer_in_pk.sh +++ b/resources/checks/L003_integer_in_pk.sh @@ -1,12 +1,17 @@ -${CHECK_HOST_CMD} "${_PSQL} -f - " < 0.00 then -- report only if > 1% of capacity is reached - out := out || format( - -- e'\nTable: %I.%I, Column: %I, Type: %s, Reached value: %s (%s%%);', - e'\n%I.%I@%I@%s@%s@%s;', - rec.schema_name, - rec.table_name, - rec.attname, - rec.typname, - val, - round(100 * ratio, 2) - ); + i:= i+1; + out := out || '{"' || rec.table_name || '":' || json_build_object( + 'Table', + coalesce(nullif(quote_ident(rec.schema_name), 'public') || '.', '') || quote_ident(rec.table_name), + 'PK', + rec.attname, + 'Type', + rec.typname, + 'Current max value', + val, + 'Capacity used, %', + round(100 * ratio, 2) + ) || '}'; end if; end loop; raise info '%', out; end; \$$ language plpgsql; SQL +) >$f_stdout 2>$f_stderr + +result=$(cat $f_stderr) +result=${result:23:$((${#result}))} + +echo "$result" | jq -cs 'sort_by(-(.[]."Capacity used, %"|tonumber)) | .[]' | jq -s add + +rm -f "$f_stderr" "$f_stdout" \ No newline at end of file -- GitLab