Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions lib/ecto_psql_extras.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule EctoPSQLExtras do
optional(:args_for_select) => list
}

@callback query :: binary
@callback query(args :: keyword) :: {binary, list}

@type repo :: module() | {module(), node()}

Expand Down Expand Up @@ -138,10 +138,13 @@ defmodule EctoPSQLExtras do
query_module = Map.fetch!(queries(repo), name)
opts = prepare_opts(opts, query_module.info()[:default_args])

{statement, params} = query_module.query(Keyword.fetch!(opts, :args))

result =
query!(
repo,
query_module.query(Keyword.fetch!(opts, :args)),
statement,
params,
Keyword.get(opts, :query_opts, @default_query_opts)
)

Expand All @@ -152,10 +155,10 @@ defmodule EctoPSQLExtras do
)
end

defp query!(repo, query, query_opts \\ @default_query_opts)
defp query!(repo, query, params \\ [], query_opts \\ @default_query_opts)

defp query!({repo, node}, query, query_opts) do
case :rpc.call(node, repo, :query!, [query, [], query_opts]) do
defp query!({repo, node}, query, params, query_opts) do
case :rpc.call(node, repo, :query!, [query, params, query_opts]) do
{:badrpc, {:EXIT, {:undef, _}}} ->
raise "repository is not defined on remote node"

Expand All @@ -167,8 +170,8 @@ defmodule EctoPSQLExtras do
end
end

defp query!(repo, query, query_opts) do
repo.query!(query, [], query_opts)
defp query!(repo, query, params, query_opts) do
repo.query!(query, params, query_opts)
end

@doc """
Expand Down
34 changes: 17 additions & 17 deletions lib/queries/all_locks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@ defmodule EctoPSQLExtras.AllLocks do
end

def query(_args \\ []) do
"""
/* ECTO_PSQL_EXTRAS: Queries with active locks */
{"""
/* ECTO_PSQL_EXTRAS: Queries with active locks */

SELECT
pg_stat_activity.pid,
pg_class.relname,
pg_locks.transactionid,
pg_locks.granted,
pg_locks.mode,
pg_stat_activity.query AS query_snippet,
age(now(),pg_stat_activity.query_start) AS "age"
FROM pg_stat_activity,pg_locks left
OUTER JOIN pg_class
ON (pg_locks.relation = pg_class.oid)
WHERE pg_stat_activity.query <> '<insufficient privilege>'
AND pg_locks.pid = pg_stat_activity.pid
AND pg_stat_activity.pid <> pg_backend_pid() order by query_start;
"""
SELECT
pg_stat_activity.pid,
pg_class.relname,
pg_locks.transactionid,
pg_locks.granted,
pg_locks.mode,
pg_stat_activity.query AS query_snippet,
age(now(),pg_stat_activity.query_start) AS "age"
FROM pg_stat_activity,pg_locks left
OUTER JOIN pg_class
ON (pg_locks.relation = pg_class.oid)
WHERE pg_stat_activity.query <> '<insufficient privilege>'
AND pg_locks.pid = pg_stat_activity.pid
AND pg_stat_activity.pid <> pg_backend_pid() order by query_start;
""", []}
end
end
126 changes: 63 additions & 63 deletions lib/queries/bloat.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,69 +17,69 @@ defmodule EctoPSQLExtras.Bloat do
end

def query(_args \\ []) do
"""
/* ECTO_PSQL_EXTRAS: Table and index bloat in your database ordered by most wasteful */
{"""
/* ECTO_PSQL_EXTRAS: Table and index bloat in your database ordered by most wasteful */

WITH constants AS (
SELECT current_setting('block_size')::numeric AS bs, 23 AS hdr, 4 AS ma
), bloat_info AS (
SELECT
ma,bs,schemaname,tablename,
(datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
(maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
FROM (
SELECT
schemaname, tablename, hdr, ma, bs,
SUM((1-null_frac)*avg_width) AS datawidth,
MAX(null_frac) AS maxfracsum,
hdr+(
SELECT 1+count(*)/8
FROM pg_stats s2
WHERE null_frac<>0 AND s2.schemaname = s.schemaname AND s2.tablename = s.tablename
) AS nullhdr
FROM pg_stats s, constants
GROUP BY 1,2,3,4,5
) AS foo
), table_bloat AS (
SELECT
schemaname, tablename, cc.relpages, bs,
CEIL((cc.reltuples*((datahdr+ma-
(CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)) AS otta
FROM bloat_info
JOIN pg_class cc ON cc.relname = bloat_info.tablename
JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
), index_bloat AS (
SELECT
schemaname, tablename, bs,
COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
FROM bloat_info
JOIN pg_class cc ON cc.relname = bloat_info.tablename
JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
JOIN pg_index i ON indrelid = cc.oid
JOIN pg_class c2 ON c2.oid = i.indexrelid
)
SELECT
type, schemaname, object_name, bloat, waste
FROM
(SELECT
'table' as type,
schemaname,
tablename as object_name,
ROUND(CASE WHEN otta=0 THEN 0.0 ELSE table_bloat.relpages/otta::numeric END,1) AS bloat,
CASE WHEN relpages < otta THEN 0 ELSE (bs*(table_bloat.relpages-otta)::bigint)::bigint END AS waste
FROM
table_bloat
UNION
SELECT
'index' as type,
schemaname,
tablename || '::' || iname as object_name,
ROUND(CASE WHEN iotta=0 OR ipages=0 THEN 0.0 ELSE ipages/iotta::numeric END,1) AS bloat,
CASE WHEN ipages < iotta THEN 0 ELSE (bs*(ipages-iotta))::bigint END AS waste
FROM
index_bloat) bloat_summary
ORDER BY waste DESC, bloat DESC;
"""
WITH constants AS (
SELECT current_setting('block_size')::numeric AS bs, 23 AS hdr, 4 AS ma
), bloat_info AS (
SELECT
ma,bs,schemaname,tablename,
(datawidth+(hdr+ma-(case when hdr%ma=0 THEN ma ELSE hdr%ma END)))::numeric AS datahdr,
(maxfracsum*(nullhdr+ma-(case when nullhdr%ma=0 THEN ma ELSE nullhdr%ma END))) AS nullhdr2
FROM (
SELECT
schemaname, tablename, hdr, ma, bs,
SUM((1-null_frac)*avg_width) AS datawidth,
MAX(null_frac) AS maxfracsum,
hdr+(
SELECT 1+count(*)/8
FROM pg_stats s2
WHERE null_frac<>0 AND s2.schemaname = s.schemaname AND s2.tablename = s.tablename
) AS nullhdr
FROM pg_stats s, constants
GROUP BY 1,2,3,4,5
) AS foo
), table_bloat AS (
SELECT
schemaname, tablename, cc.relpages, bs,
CEIL((cc.reltuples*((datahdr+ma-
(CASE WHEN datahdr%ma=0 THEN ma ELSE datahdr%ma END))+nullhdr2+4))/(bs-20::float)) AS otta
FROM bloat_info
JOIN pg_class cc ON cc.relname = bloat_info.tablename
JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
), index_bloat AS (
SELECT
schemaname, tablename, bs,
COALESCE(c2.relname,'?') AS iname, COALESCE(c2.reltuples,0) AS ituples, COALESCE(c2.relpages,0) AS ipages,
COALESCE(CEIL((c2.reltuples*(datahdr-12))/(bs-20::float)),0) AS iotta -- very rough approximation, assumes all cols
FROM bloat_info
JOIN pg_class cc ON cc.relname = bloat_info.tablename
JOIN pg_namespace nn ON cc.relnamespace = nn.oid AND nn.nspname = bloat_info.schemaname AND nn.nspname <> 'information_schema'
JOIN pg_index i ON indrelid = cc.oid
JOIN pg_class c2 ON c2.oid = i.indexrelid
)
SELECT
type, schemaname, object_name, bloat, waste
FROM
(SELECT
'table' as type,
schemaname,
tablename as object_name,
ROUND(CASE WHEN otta=0 THEN 0.0 ELSE table_bloat.relpages/otta::numeric END,1) AS bloat,
CASE WHEN relpages < otta THEN 0 ELSE (bs*(table_bloat.relpages-otta)::bigint)::bigint END AS waste
FROM
table_bloat
UNION
SELECT
'index' as type,
schemaname,
tablename || '::' || iname as object_name,
ROUND(CASE WHEN iotta=0 OR ipages=0 THEN 0.0 ELSE ipages/iotta::numeric END,1) AS bloat,
CASE WHEN ipages < iotta THEN 0 ELSE (bs*(ipages-iotta))::bigint END AS waste
FROM
index_bloat) bloat_summary
ORDER BY waste DESC, bloat DESC;
""", []}
end
end
34 changes: 17 additions & 17 deletions lib/queries/blocking.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@ defmodule EctoPSQLExtras.Blocking do
end

def query(_args \\ []) do
"""
/* ECTO_PSQL_EXTRAS: Queries holding locks other queries are waiting to be released */
{"""
/* ECTO_PSQL_EXTRAS: Queries holding locks other queries are waiting to be released */

SELECT bl.pid AS blocked_pid,
ka.query AS blocking_statement,
now() - ka.query_start AS blocking_duration,
kl.pid AS blocking_pid,
a.query AS blocked_statement,
now() - a.query_start AS blocked_duration
FROM pg_catalog.pg_locks bl
JOIN pg_catalog.pg_stat_activity a
ON bl.pid = a.pid
JOIN pg_catalog.pg_locks kl
JOIN pg_catalog.pg_stat_activity ka
ON kl.pid = ka.pid
ON bl.transactionid = kl.transactionid AND bl.pid != kl.pid
WHERE NOT bl.granted;
"""
SELECT bl.pid AS blocked_pid,
ka.query AS blocking_statement,
now() - ka.query_start AS blocking_duration,
kl.pid AS blocking_pid,
a.query AS blocked_statement,
now() - a.query_start AS blocked_duration
FROM pg_catalog.pg_locks bl
JOIN pg_catalog.pg_stat_activity a
ON bl.pid = a.pid
JOIN pg_catalog.pg_locks kl
JOIN pg_catalog.pg_stat_activity ka
ON kl.pid = ka.pid
ON bl.transactionid = kl.transactionid AND bl.pid != kl.pid
WHERE NOT bl.granted;
""", []}
end
end
24 changes: 12 additions & 12 deletions lib/queries/cache_hit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ defmodule EctoPSQLExtras.CacheHit do
end

def query(_args \\ []) do
"""
/* ECTO_PSQL_EXTRAS: Index and table hit rate */
{"""
/* ECTO_PSQL_EXTRAS: Index and table hit rate */

SELECT
'index hit rate' AS name,
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) AS ratio
FROM pg_statio_user_indexes
UNION ALL
SELECT
'table hit rate' AS name,
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) AS ratio
FROM pg_statio_user_tables;
"""
SELECT
'index hit rate' AS name,
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) AS ratio
FROM pg_statio_user_indexes
UNION ALL
SELECT
'table hit rate' AS name,
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) AS ratio
FROM pg_statio_user_tables;
""", []}
end
end
25 changes: 12 additions & 13 deletions lib/queries/calls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,18 @@ defmodule EctoPSQLExtras.Calls do
end

def query(args \\ []) do
"""
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */
{"""
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */

SELECT query AS query,
interval '1 millisecond' * total_exec_time AS exec_time,
(total_exec_time/sum(total_exec_time) OVER()) AS exec_time_ratio,
calls,
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
ORDER BY calls DESC
LIMIT <%= limit %>;
"""
|> EEx.eval_string(args)
SELECT query AS query,
interval '1 millisecond' * total_exec_time AS exec_time,
(total_exec_time/sum(total_exec_time) OVER()) AS exec_time_ratio,
calls,
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
ORDER BY calls DESC
LIMIT $1;
""", [args[:limit]]}
end
end
25 changes: 12 additions & 13 deletions lib/queries/calls_17.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,18 @@ defmodule EctoPSQLExtras.Calls17 do
end

def query(args \\ []) do
"""
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */
{"""
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */

SELECT query AS query,
interval '1 millisecond' * total_exec_time AS exec_time,
(total_exec_time/sum(total_exec_time) OVER()) AS exec_time_ratio,
calls,
interval '1 millisecond' * (shared_blk_read_time + shared_blk_write_time) AS sync_io_time
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
ORDER BY calls DESC
LIMIT <%= limit %>;
"""
|> EEx.eval_string(args)
SELECT query AS query,
interval '1 millisecond' * total_exec_time AS exec_time,
(total_exec_time/sum(total_exec_time) OVER()) AS exec_time_ratio,
calls,
interval '1 millisecond' * (shared_blk_read_time + shared_blk_write_time) AS sync_io_time
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
ORDER BY calls DESC
LIMIT $1;
""", [args[:limit]]}
end
end
25 changes: 12 additions & 13 deletions lib/queries/calls_legacy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,18 @@ defmodule EctoPSQLExtras.CallsLegacy do
end

def query(args \\ []) do
"""
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */
{"""
/* ECTO_PSQL_EXTRAS: Queries that have the highest frequency of execution */

SELECT query AS query,
interval '1 millisecond' * total_time AS exec_time,
(total_time/sum(total_time) OVER()) AS exec_time_ratio,
calls,
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
ORDER BY calls DESC
LIMIT <%= limit %>;
"""
|> EEx.eval_string(args)
SELECT query AS query,
interval '1 millisecond' * total_time AS exec_time,
(total_time/sum(total_time) OVER()) AS exec_time_ratio,
calls,
interval '1 millisecond' * (blk_read_time + blk_write_time) AS sync_io_time
FROM pg_stat_statements WHERE userid = (SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1)
AND query NOT LIKE '/* ECTO_PSQL_EXTRAS:%'
ORDER BY calls DESC
LIMIT $1;
""", [args[:limit]]}
end
end
Loading
Loading