UNPKG

heroku

Version:

CLI to interact with Heroku

100 lines (99 loc) 4.21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const command_1 = require("@heroku-cli/command"); const core_1 = require("@oclif/core"); const fetcher_1 = require("../../lib/pg/fetcher"); const psql_1 = require("../../lib/pg/psql"); const tsheredoc_1 = require("tsheredoc"); const nls_1 = require("../../nls"); class Outliers extends command_1.Command { async run() { const { flags, args } = await this.parse(Outliers); const { app, reset, truncate, num } = flags; const db = await (0, fetcher_1.database)(this.heroku, app, args.database); const version = await (0, psql_1.fetchVersion)(db); await this.ensurePGStatStatement(db); if (reset) { await (0, psql_1.exec)(db, 'SELECT pg_stat_statements_reset();'); return; } let limit = 10; if (num) { if (/^(\d+)$/.exec(num)) { limit = Number.parseInt(num, 10); } else { core_1.ux.error(`Cannot parse num param value "${num}" to a number`); } } const query = this.outliersQuery(version, limit, truncate); const output = await (0, psql_1.exec)(db, query); core_1.ux.log(output); } async ensurePGStatStatement(db) { const query = (0, tsheredoc_1.default) ` SELECT exists( SELECT 1 FROM pg_extension e LEFT JOIN pg_namespace n ON n.oid = e.extnamespace WHERE e.extname = 'pg_stat_statements' AND n.nspname IN ('public', 'heroku_ext') ) AS available; `; const output = await (0, psql_1.exec)(db, query); if (!output.includes('t')) { core_1.ux.error((0, tsheredoc_1.default) ` pg_stat_statements extension need to be installed first. You can install it by running: CREATE EXTENSION pg_stat_statements WITH SCHEMA heroku_ext; `); } } outliersQuery(version, limit, truncate) { const truncatedQueryString = truncate ? (0, tsheredoc_1.default) ` CASE WHEN length(query) <= 40 THEN query ELSE substr(query, 0, 39) || '…' END ` : 'query'; let totalExecTimeField = ''; if (version && Number.parseInt(version, 10) >= 13) { totalExecTimeField = 'total_exec_time'; } else { totalExecTimeField = 'total_time'; } let blkReadTimeField = ''; let blkWriteTimeField = ''; if (version && Number.parseInt(version, 10) >= 17) { blkReadTimeField = 'shared_blk_read_time'; blkWriteTimeField = 'shared_blk_write_time'; } else { blkReadTimeField = 'blk_read_time'; blkWriteTimeField = 'blk_write_time'; } return (0, tsheredoc_1.default) ` SELECT interval '1 millisecond' * ${totalExecTimeField} AS total_exec_time, to_char((${totalExecTimeField}/sum(${totalExecTimeField}) OVER()) * 100, 'FM90D0') || '%' AS prop_exec_time, to_char(calls, 'FM999G999G999G990') AS ncalls, interval '1 millisecond' * (${blkReadTimeField} + ${blkWriteTimeField}) AS sync_io_time, ${truncatedQueryString} AS query FROM pg_stat_statements WHERE userid = ( SELECT usesysid FROM pg_user WHERE usename = current_user LIMIT 1 ) ORDER BY ${totalExecTimeField} DESC LIMIT ${limit}; `; } } exports.default = Outliers; Outliers.topic = 'pg'; Outliers.description = 'show 10 queries that have longest execution time in aggregate'; Outliers.flags = { reset: command_1.flags.boolean({ description: 'resets statistics gathered by pg_stat_statements' }), truncate: command_1.flags.boolean({ char: 't', description: 'truncate queries to 40 characters' }), num: command_1.flags.string({ char: 'n', description: 'the number of queries to display (default: 10)' }), app: command_1.flags.app({ required: true }), remote: command_1.flags.remote(), }; Outliers.args = { database: core_1.Args.string({ description: `${(0, nls_1.nls)('pg:database:arg:description')} ${(0, nls_1.nls)('pg:database:arg:description:default:suffix')}` }), };