#!/usr/bin/env python

"""
pg_activity utility
version: 1.0.2
author: Julien Tachoires <julmon@gmail.com>
license: New BSD License

Copyright (c) 2012 - 2013, Julien Tachoires
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of pg_activity nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL JULIEN TACHOIRES BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

PGTOP_VERSION = "1.0.2"

import os
import sys
if os.name != 'posix':
    sys.exit("FATAL: Platform not supported.")
import signal
import time
import curses
from datetime import timedelta
import psutil
import psycopg2
import psycopg2.extras
import re
from optparse import OptionParser, OptionGroup
import socket
import getpass

# Curses initialization
curses.setupterm()
PGTOP_WIN = curses.initscr()
PGTOP_WIN.keypad(1)
curses.noecho()
curses.cbreak()

# Some terms like vt100 do not support colors,
# in that case, curses.curs_set() and all curses functions
# related to colors (and probably others) raise an ERROR.
PGTOP_SYS_COLOR = True

try:
    # deactivate cursor
    curses.curs_set(0)
    # use colors
    curses.start_color()
    curses.use_default_colors()
except Exception:
    # Terminal doesn't support curs_set() and colors
    PGTOP_SYS_COLOR = False

PGTOP_LINENO = 0
curses.endwin()
PGTOP_WIN.scrollok(0)
PGTOP_LINES = []

# define some color pairs
C_BLACK_GREEN = 1
C_CYAN =        2
C_RED =         3
C_GREEN =       4
C_YELLOW =      5
C_MAGENTA =     6
C_WHITE =       7
C_BLACK_CYAN =  8
C_RED_BLACK =   9
C_GRAY =        10
# Columns
PGTOP_FLAG_DATABASE =   1
PGTOP_FLAG_CLIENT =     2
PGTOP_FLAG_CPU =        4
PGTOP_FLAG_MEM =        8
PGTOP_FLAG_READ =       16
PGTOP_FLAG_WRITE =      32
PGTOP_FLAG_TIME =       64
PGTOP_FLAG_WAIT =       128
PGTOP_FLAG_RELATION =   256
PGTOP_FLAG_TYPE =       512
PGTOP_FLAG_MODE =       1024
PGTOP_FLAG_IOWAIT =     2048
PGTOP_FLAG_NONE =       None

def get_curses_color(color):
    """
    Wrapper around curses.color_pair()
    """
    if PGTOP_SYS_COLOR:
        return curses.color_pair(color)
    else:
        return 0

# Maximum number of columns
PGTOP_MAX_NCOL = 12

PGTOP_COLS = {
    'activities': {
        'pid'       : {'n':  1, 'name': 'PID', 'template_h': '%-6s ', 'flag': PGTOP_FLAG_NONE, 'mandatory': True},
        'database'  : {'n':  2, 'name': 'DATABASE', 'template_h': '%-16s ', 'flag': PGTOP_FLAG_DATABASE, 'mandatory': False},
        'client'    : {'n':  3, 'name': 'CLIENT', 'template_h': '%16s ', 'flag': PGTOP_FLAG_CLIENT, 'mandatory': False},
        'cpu'       : {'n':  4, 'name': 'CPU%', 'template_h': '%6s ', 'flag': PGTOP_FLAG_CPU, 'mandatory': False},
        'mem'       : {'n':  5, 'name': 'MEM%', 'template_h': '%4s ', 'flag': PGTOP_FLAG_MEM, 'mandatory': False},
        'read'      : {'n':  6, 'name': 'READ/s', 'template_h': '%8s ', 'flag': PGTOP_FLAG_READ, 'mandatory': False},
        'write'     : {'n':  7, 'name': 'WRITE/s', 'template_h': '%8s ', 'flag': PGTOP_FLAG_WRITE, 'mandatory': False},
        'time'      : {'n':  8, 'name': 'TIME+', 'template_h': '%9s ', 'flag': PGTOP_FLAG_TIME, 'mandatory': False},
        'wait'      : {'n':  9, 'name': 'W', 'template_h': '%2s ', 'flag': PGTOP_FLAG_WAIT, 'mandatory': False},
        'iowait'    : {'n': 10, 'name': 'IOW', 'template_h': '%4s ', 'flag': PGTOP_FLAG_IOWAIT, 'mandatory': False},
        'query'     : {'n': 11, 'name': 'Query', 'template_h': ' %2s', 'flag': PGTOP_FLAG_NONE, 'mandatory': True},
    },
    'waiting': {
        'pid'       : {'n': 1, 'name': 'PID', 'template_h': '%-6s ', 'flag': PGTOP_FLAG_NONE, 'mandatory': True},
        'database'  : {'n': 2, 'name': 'DATABASE', 'template_h': '%-16s ', 'flag': PGTOP_FLAG_DATABASE, 'mandatory': False},
        'relation'  : {'n': 3, 'name': 'RELATION', 'template_h': '%9s ', 'flag': PGTOP_FLAG_RELATION, 'mandatory': False},
        'type'      : {'n': 4, 'name': 'TYPE', 'template_h': '%16s ', 'flag': PGTOP_FLAG_TYPE, 'mandatory': False},
        'mode'      : {'n': 5, 'name': 'MODE', 'template_h': '%16s ', 'flag': PGTOP_FLAG_MODE, 'mandatory': False},
        'time'      : {'n': 6, 'name': 'TIME+', 'template_h': '%9s ', 'flag': PGTOP_FLAG_TIME, 'mandatory': False},
        'query'     : {'n': 7, 'name': 'Query', 'template_h': ' %2s', 'flag': PGTOP_FLAG_NONE, 'mandatory': True},
    },
    'blocking': {
        'pid'       : {'n': 1, 'name': 'PID', 'template_h': '%-6s ', 'flag': PGTOP_FLAG_NONE, 'mandatory': True},
        'database'  : {'n': 2, 'name': 'DATABASE', 'template_h': '%-16s ', 'flag': PGTOP_FLAG_DATABASE, 'mandatory': False},
        'relation'  : {'n': 3, 'name': 'RELATION', 'template_h': '%9s ', 'flag': PGTOP_FLAG_RELATION, 'mandatory': False},
        'type'      : {'n': 4, 'name': 'TYPE', 'template_h': '%16s ', 'flag': PGTOP_FLAG_TYPE, 'mandatory': False},
        'mode'      : {'n': 5, 'name': 'MODE', 'template_h': '%16s ', 'flag': PGTOP_FLAG_MODE, 'mandatory': False},
        'time'      : {'n': 6, 'name': 'TIME+', 'template_h': '%9s ', 'flag': PGTOP_FLAG_TIME, 'mandatory': False},
        'query'     : {'n': 7, 'name': 'Query', 'template_h': ' %2s', 'flag': PGTOP_FLAG_NONE, 'mandatory': True},
    }
}

PGTOP_LINE_COLORS = {
    'pid'           : {'default': get_curses_color(C_CYAN), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'database'      : {'default': curses.A_BOLD | get_curses_color(C_GRAY), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'client'        : {'default': get_curses_color(C_CYAN), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'cpu'           : {'default': get_curses_color(0), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'mem'           : {'default': get_curses_color(0), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'read'          : {'default': get_curses_color(0), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'write'         : {'default': get_curses_color(0), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'time_red'      : {'default': get_curses_color(C_RED), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'time_yellow'   : {'default': get_curses_color(C_YELLOW), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'time_green'    : {'default': get_curses_color(C_GREEN), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'wait_green'    : {'default': get_curses_color(C_GREEN) | curses.A_BOLD, 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'wait_red'      : {'default': get_curses_color(C_RED) | curses.A_BOLD, 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'query'         : {'default': get_curses_color(0), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'relation'      : {'default': get_curses_color(C_CYAN), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'type'          : {'default': get_curses_color(0), 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'mode_yellow'   : {'default': get_curses_color(C_YELLOW)|curses.A_BOLD, 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE},
    'mode_red'      : {'default': get_curses_color(C_RED)|curses.A_BOLD, 'cursor': get_curses_color(C_CYAN)|curses.A_REVERSE}
}

# display query mode
PGTOP_TRUNCATE = 1
PGTOP_WRAP_NOINDENT = 2
PGTOP_WRAP = 3
# default
PGTOP_VERBOSE_MODE = 2
# max IOPS
PGTOP_MAX_IOPS = 0
# sort
PGTOP_SORT = 't'
# color ?
PGTOP_COLOR = True
# default mode : activites, waiting, blocking
PGTOP_MODE = 'activities'
# Does pg_activity is connected to a local PG server ?
PGTOP_IS_LOCAL = True
# Start line
PGTOP_START_LINE = 5
# Window's size
(PGTOP_MAXY, PGTOP_MAXX) = PGTOP_WIN.getmaxyx()
# init buffer
PGTOP_BUFFER = None
# refresh time
PGTOP_REFRESH_TIME = 2
# Maximum DATABASE columns header length
PGTOP_MAX_DB_LENGTH = 16

def set_max_db_length(new_length):
    """
    Set new DATABASE column length
    """
    global PGTOP_MAX_DB_LENGTH
    global PGTOP_COLS
    if new_length > 16:
        new_length = 16
    if new_length < 8:
        new_length = 8

    PGTOP_MAX_DB_LENGTH = new_length
    PGTOP_COLS['activities']['database']['template_h'] = '%-'+str(new_length)+'s '
    PGTOP_COLS['waiting']['database']['template_h'] = '%-'+str(new_length)+'s '
    PGTOP_COLS['blocking']['database']['template_h'] = '%-'+str(new_length)+'s '

class pgtop_proc:
    """
    Simple class used for process management.
    """
    def __init__(self, pid = None, database = None, client = None, cpu = None, mem = None, read = None, write = None, query = None, duration = None, wait = None, extras = None):
        self.pid = pid
        self.database = database
        self.client = client
        self.cpu = cpu
        self.mem = mem
        self.read = read
        self.write = write 
        self.query = query
        self.duration = duration
        self.wait = wait
        self.extras = extras

    def setExtra(self, key, value):
        self.extras[key] = value
    
    def getExtra(self, key):
        if self.extras is not None and self.extras.has_key(key):
            return self.extras[key]

def at_exit_curses():
    """
    Called at exit time.
    Rollback to default values.
    """
    try:
        PGTOP_WIN.keypad(0)
        PGTOP_WIN.move(0, 0)
        PGTOP_WIN.erase()
    except KeyboardInterrupt:
        pass
    curses.nocbreak()
    curses.echo()
    try:
        curses.curs_set(1)
    except Exception:
        pass
    curses.endwin()

def signal_handler(signal, frame):
    """
    Function called on a process kill.
    """
    at_exit_curses()
    print "FATAL: Killed with signal %s ." % (str(signal),)
    print "%s" % (str(frame),)
    sys.exit(0)

def set_nocolor():
    """
    Replace colors by white.
    """
    if not PGTOP_SYS_COLOR:
        return
    global PGTOP_COLOR
    PGTOP_COLOR = False
    curses.init_pair(C_BLACK_GREEN, curses.COLOR_BLACK, curses.COLOR_WHITE)
    curses.init_pair(C_CYAN, curses.COLOR_WHITE, -1)
    curses.init_pair(C_RED, curses.COLOR_WHITE, -1)
    curses.init_pair(C_RED_BLACK, curses.COLOR_WHITE, curses.COLOR_BLACK)
    curses.init_pair(C_GREEN, curses.COLOR_WHITE, -1)
    curses.init_pair(C_YELLOW, curses.COLOR_WHITE, -1)
    curses.init_pair(C_MAGENTA, curses.COLOR_WHITE, -1)
    curses.init_pair(C_WHITE, curses.COLOR_WHITE, -1)
    curses.init_pair(C_BLACK_CYAN, curses.COLOR_WHITE, -1)
    curses.init_pair(C_GRAY, curses.COLOR_WHITE, -1)

def set_color():
    """
    Set colors.
    """
    if not PGTOP_SYS_COLOR:
        return
    global PGTOP_COLOR
    PGTOP_COLOR = True
    curses.init_pair(C_BLACK_GREEN, curses.COLOR_BLACK, curses.COLOR_GREEN)
    curses.init_pair(C_CYAN, curses.COLOR_CYAN, -1)
    curses.init_pair(C_RED, curses.COLOR_RED, -1)
    curses.init_pair(C_RED_BLACK, curses.COLOR_RED, curses.COLOR_BLACK)
    curses.init_pair(C_GREEN, curses.COLOR_GREEN, -1)
    curses.init_pair(C_YELLOW, curses.COLOR_YELLOW, -1)
    curses.init_pair(C_MAGENTA, curses.COLOR_MAGENTA, -1)
    curses.init_pair(C_WHITE, curses.COLOR_WHITE, -1)
    curses.init_pair(C_BLACK_CYAN, curses.COLOR_BLACK, curses.COLOR_CYAN)
    curses.init_pair(C_GRAY, 0, -1)

def clean_str(string):
    """
    Strip and replace some special characters.
    """
    msg = str(string)
    msg = msg.replace("\n", " ")
    msg = re.sub(r"\s+", r" ", msg)
    msg = msg.replace("FATAL:", "")
    msg = re.sub(r"^\s", r"", msg)
    msg = re.sub(r"\s$", r"", msg)
    return msg

def get_pgpass(pgpass = None):
    """
    Get postgres' password using pgpass file.

    http://www.postgresql.org/docs/9.2/static/libpq-pgpass.html
    http://wiki.postgresql.org/wiki/Pgpass
    """
    if pgpass is None:
        from os.path import expanduser
        home = expanduser("~")
        pgpass = "%s/.pgpass" % (str(home),)
    ret = []
    with open(pgpass, 'r') as fp:
        content = fp.readlines()
        for line in content:
            res = None
            res = re.match(r"^([^:]+):([^:]+):([^:]+):([^:]+):(.*)$", line)
            if res is not None:
                ret.append(res.group(1, 2, 3, 4, 5))
        return ret
    raise Exception("pgpass file not found")

def pg_connect(host = 'localhost', port = 5432, user = 'postgres', password = None, database = 'postgres'):
    """
    Connect to a PostgreSQL server and return
    cursor & connector.
    """
    conn = psycopg2.connect(
            database = database,
            host = host,
            port = port,
            user = user,
            password = str(password),
            connection_factory=psycopg2.extras.DictConnection
        )
    conn.set_isolation_level(0)
    cur = conn.cursor()
    cur.execute("SELECT current_setting('is_superuser')")
    ret = cur.fetchone()
    if ret[0] != "on":
        raise Exception("Must be run with database superuser privileges.")
    return conn

def pg_is_local_access():
    """
    Verify if the user running pg_activity can acces 
    system informations for a postgres process.
    """
    for p in psutil.process_iter():
        if p.name == 'postgres' or p.name == 'postmaster':
            try:
                proc = psutil.Process(p.pid)
                proc.get_io_counters()
                proc.get_cpu_times()
                return True
            except psutil.AccessDenied:
                return False
            except Exception:
                return False
    return False

def pg_get_version(conn):
    """
    Get PostgreSQL server version.
    """
    query = "SELECT version() AS pg_version"
    cur = conn.cursor()
    cur.execute(query)
    ret = cur.fetchone()
    return ret['pg_version']

def pg_terminate_backend(conn, pid, pg_num_version):
    """
    Terminate a backend
    """
    if pg_num_version >= 80400:
        query = "SELECT pg_terminate_backend(%s) AS terminated"
    else:
        query = "SELECT pg_cancel_backend(%s) AS terminated"
    cur = conn.cursor()
    cur.execute(query, (pid,))
    ret = cur.fetchone()
    return ret['terminated']

def pg_get_num_version(text_version):
    """
    Get PostgreSQL short & numeric version from
    a string (SELECT version()).
    """
    res = re.match(r"^(PostgreSQL|EnterpriseDB) ([0-9]+)\.([0-9]+)\.([0-9]+)", text_version)
    if res is not None:
        r = res.group(2)
        if int(res.group(3)) < 10:
            r += '0'
        r += res.group(3)
        if int(res.group(4)) < 10:
            r += '0'
        r += res.group(4)
        return (res.group(0), int(r))
    return pg_get_num_dev_version(text_version)

def pg_get_num_dev_version(text_version):
    """
    Get PostgreSQL short & numeric devel. or beta version
    from a string (SELECT version()). 
    """
    res = re.match(r"^(PostgreSQL|EnterpriseDB) ([0-9]+)\.([0-9]+)(devel|beta[0-9]+)", text_version)
    if res is not None:
        r = res.group(2)
        if int(res.group(3)) < 10:
            r += '0'
        r += res.group(3)
        r += '00'
        return (res.group(0), int(r))
    return None


def pg_get_snapshot_infos(conn,):
    """
    Get current sum of transactions, total size and  timestamp.
    """
    query = "SELECT EXTRACT(EPOCH FROM NOW()) AS timestamp, SUM(pg_stat_get_db_xact_commit(oid)+pg_stat_get_db_xact_rollback(oid))::BIGINT AS no_xact, SUM(pg_database_size(datname)) AS total_size, MAX(LENGTH(datname)) AS max_length FROM pg_database"
    cur = conn.cursor()
    cur.execute(query,)
    ret = cur.fetchone()
    return (ret['timestamp'], ret['no_xact'], ret['total_size'], ret['max_length'])

def pg_get_activities(conn, pg_num_version):
    """
    Get activity from pg_stat_activity view.
    """
    if pg_num_version >= 90200:
        # PostgreSQL 9.2.0 and more
        query = """
SELECT pg_stat_activity.pid AS pid, CASE WHEN LENGTH(pg_stat_activity.datname) > 16 THEN SUBSTRING(pg_stat_activity.datname FROM 0 FOR 6)||'...'||SUBSTRING(pg_stat_activity.datname FROM '........$') ELSE pg_stat_activity.datname END AS database, pg_stat_activity.client_addr AS client, EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) AS duration, pg_stat_activity.waiting AS wait, pg_stat_activity.usename AS user, pg_stat_activity.query AS query FROM pg_stat_activity WHERE state <> 'idle' AND pid <> pg_backend_pid() ORDER BY EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) DESC
        """
    elif pg_num_version < 90200:
        # PostgreSQL prior to 9.2.0
        query = """
SELECT pg_stat_activity.procpid AS pid, CASE WHEN LENGTH(pg_stat_activity.datname) > 16 THEN SUBSTRING(pg_stat_activity.datname FROM 0 FOR 6)||'...'||SUBSTRING(pg_stat_activity.datname FROM '........$') ELSE pg_stat_activity.datname END AS database, pg_stat_activity.client_addr AS client, EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) AS duration, pg_stat_activity.waiting AS wait, pg_stat_activity.usename AS user, pg_stat_activity.current_query AS query FROM pg_stat_activity WHERE current_query <> '<IDLE>' AND procpid <> pg_backend_pid() ORDER BY EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) DESC
        """
    cur = conn.cursor()
    cur.execute(query)
    ret = cur.fetchall()
    return ret

def pg_get_waiting(conn, pg_num_version):
    """
    Get waiting queries.
    """
    if pg_num_version >= 90200:
        query = """
SELECT pg_locks.pid AS pid, CASE WHEN LENGTH(pg_stat_activity.datname) > 16 THEN SUBSTRING(pg_stat_activity.datname FROM 0 FOR 6)||'...'||SUBSTRING(pg_stat_activity.datname FROM '........$') ELSE pg_stat_activity.datname END AS database, pg_locks.mode AS mode, pg_locks.locktype AS type, pg_locks.relation::regclass AS relation, EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) AS duration, pg_stat_activity.query AS query FROM pg_catalog.pg_locks JOIN pg_catalog.pg_stat_activity ON(pg_catalog.pg_locks.pid = pg_catalog.pg_stat_activity.pid) WHERE NOT pg_catalog.pg_locks.granted AND pg_catalog.pg_stat_activity.pid <> pg_backend_pid() ORDER BY EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) DESC
        """
    elif pg_num_version < 90200:
        query = """
SELECT pg_locks.pid AS pid, CASE WHEN LENGTH(pg_stat_activity.datname) > 16 THEN SUBSTRING(pg_stat_activity.datname FROM 0 FOR 6)||'...'||SUBSTRING(pg_stat_activity.datname FROM '........$') ELSE pg_stat_activity.datname END AS database, pg_locks.mode AS mode, pg_locks.locktype AS type, pg_locks.relation::regclass AS relation, EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) AS duration, pg_stat_activity.current_query AS query FROM pg_catalog.pg_locks JOIN pg_catalog.pg_stat_activity ON(pg_catalog.pg_locks.pid = pg_catalog.pg_stat_activity.procpid) WHERE NOT pg_catalog.pg_locks.granted AND  pg_catalog.pg_stat_activity.procpid <> pg_backend_pid() ORDER BY EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) DESC
        """
    cur = conn.cursor()
    cur.execute(query)
    ret = cur.fetchall()
    return ret

def pg_get_blocking(conn, pg_num_version):
    """
    Get blocking queries
    """
    if pg_num_version >= 90200:
        query = """
SELECT pid, CASE WHEN LENGTH(datname) > 16 THEN SUBSTRING(datname FROM 0 FOR 6)||'...'||SUBSTRING(datname FROM '........$') ELSE datname END AS database, relation, mode, locktype AS type, duration, query FROM (SELECT blocking.pid, pg_stat_activity.query, blocking.mode, pg_stat_activity.datname, blocking.locktype,EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) AS duration, blocking.relation::regclass AS relation FROM pg_locks AS blocking JOIN (SELECT transactionid FROM pg_locks WHERE NOT granted) AS blocked ON (blocking.transactionid = blocked.transactionid) JOIN pg_stat_activity ON (blocking.pid = pg_stat_activity.pid) WHERE blocking.granted UNION ALL SELECT blocking.pid, pg_stat_activity.query, blocking.mode, pg_stat_activity.datname, blocking.locktype,EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) AS duration, blocking.relation::regclass AS relation FROM pg_locks AS blocking JOIN (SELECT database, relation, mode FROM pg_locks WHERE NOT granted AND relation IS NOT NULL) AS blocked ON (blocking.database = blocked.database AND blocking.relation = blocked.relation) JOIN pg_stat_activity ON (blocking.pid = pg_stat_activity.pid) WHERE blocking.granted) AS sq GROUP BY pid, query, mode, locktype, duration, datname, relation ORDER BY duration DESC
        """
    elif pg_num_version < 90200:
        query = """
SELECT pid, CASE WHEN LENGTH(datname) > 16 THEN SUBSTRING(datname FROM 0 FOR 6)||'...'||SUBSTRING(datname FROM '........$') ELSE datname END AS database, relation, mode, locktype AS type, duration, query FROM (SELECT blocking.pid, pg_stat_activity.current_query AS query, blocking.mode, pg_stat_activity.datname, blocking.locktype,EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) AS duration, blocking.relation::regclass AS relation FROM pg_locks AS blocking JOIN (SELECT transactionid FROM pg_locks WHERE NOT granted) AS blocked ON (blocking.transactionid = blocked.transactionid) JOIN pg_stat_activity ON (blocking.pid = pg_stat_activity.procpid) WHERE blocking.granted UNION ALL SELECT blocking.pid, pg_stat_activity.current_query AS query, blocking.mode, pg_stat_activity.datname, blocking.locktype,EXTRACT(epoch FROM (NOW() - pg_stat_activity.query_start)) AS duration, blocking.relation::regclass AS relation FROM pg_locks AS blocking JOIN (SELECT database, relation, mode FROM pg_locks WHERE NOT granted AND relation IS NOT NULL) AS blocked ON (blocking.database = blocked.database AND blocking.relation = blocked.relation) JOIN pg_stat_activity ON (blocking.pid = pg_stat_activity.procpid) WHERE blocking.granted) AS sq GROUP BY pid, query, mode, locktype, duration, datname, relation ORDER BY duration DESC
        """
    cur = conn.cursor()
    cur.execute(query)
    ret = cur.fetchall()
    return ret

def pg_is_local(conn):
    """
    Does pg_activity is connected localy ?
    """
    query = "SELECT inet_server_addr() AS inet_server_addr, inet_client_addr() AS inet_client_addr"
    cur = conn.cursor()
    cur.execute(query)
    ret = cur.fetchone()
    if len(str(ret['inet_server_addr'])) == 0 or ret['inet_server_addr'] == ret['inet_client_addr']:
        return True
    return False

def get_duration(duration):
    """
    Returns 0 if the given duration is negative
    else, returns the duration
    """
    if float(duration) < 0:
        return 0
    return float(duration)

def sys_get_IOW_status(status):
    if str(status) == 'disk sleep':
        return 'Y'
    else:
        return 'N'

def sys_get_proc(queries):
    """
    Get system informations (CPU, memory, IO read & write)
    for each process PID using psutil module.
    """
    process = {}
    if not PGTOP_IS_LOCAL:
        return process

    for sq in queries:
        try:
            proc = psutil.Process(sq['pid'])
            p = pgtop_proc(pid = sq['pid'], database = sq['database'], client = sq['client'], duration = sq['duration'], wait = sq['wait'], query = clean_str(sq['query']), extras = {})
            p.setExtra('meminfo', proc.get_memory_info())
            p.setExtra('io_counters', proc.get_io_counters())
            p.setExtra('io_time', time.time())
            p.setExtra('mem_percent', proc.get_memory_percent())
            p.setExtra('cpu_percent', proc.get_cpu_percent(interval=0))
            p.setExtra('cpu_times', proc.get_cpu_times())
            p.setExtra('read_delta', 0)
            p.setExtra('write_delta', 0)
            p.setExtra('io_wait', sys_get_IOW_status(str(proc.status)))
            p.setExtra('psutil_proc', proc)
            process[p.pid] = p
        except psutil.NoSuchProcess:
            pass
        except psutil.AccessDenied:
            pass
    return process

def check_window_size():
    """
    Update window's size
    """
    global PGTOP_MAXY
    global PGTOP_MAXX
    (PGTOP_MAXY, PGTOP_MAXX) = PGTOP_WIN.getmaxyx()
    return

def get_pause_msg():
    """
    Returns PAUSE message, depending of the line size
    """
    msg = "PAUSE"
    line = ""
    line += " " * (int(PGTOP_MAXX/2) - len(msg))
    line += msg
    line += " " * (PGTOP_MAXX - len(line) - 0)
    return line

def pause():
    """
    PAUSE mode
    """
    print_string(PGTOP_START_LINE, 0, get_pause_msg(), get_curses_color(C_RED_BLACK)|curses.A_REVERSE|curses.A_BOLD)
    while 1:
        try:
            k = PGTOP_WIN.getch()
        except KeyboardInterrupt as e:
            raise e
        if k == ord('q'):
            curses.endwin()
            exit()
        if k == ord(' '):
            curses.flushinp()
            return 0
        
        if k == curses.KEY_RESIZE and PGTOP_BUFFER is not None and PGTOP_BUFFER.has_key('procs'):
            check_window_size()
            refresh_window(PGTOP_BUFFER['procs'], PGTOP_BUFFER['extras'], PGTOP_BUFFER['flag'], PGTOP_BUFFER['indent'], PGTOP_BUFFER['io'], PGTOP_BUFFER['tps'], PGTOP_BUFFER['size_ev'], PGTOP_BUFFER['total_size'])
            print_string(PGTOP_START_LINE, 0, get_pause_msg(), get_curses_color(C_RED_BLACK)|curses.A_REVERSE|curses.A_BOLD)
        curses.flushinp()

def current_position():
    """
    Display current mode
    """
    if PGTOP_MODE == 'activities':
        msg = "RUNNING QUERIES"
    if PGTOP_MODE == 'waiting':
        msg = "WAITING QUERIES"
    if PGTOP_MODE == 'blocking':
        msg = "BLOCKING QUERIES"
    line = ""
    line += " " * (int(PGTOP_MAXX/2) - len(msg))
    line += msg
    line += " " * (PGTOP_MAXX - len(line) - 0)
    print_string(PGTOP_START_LINE, 0, line, get_curses_color(C_GREEN))

def help_key_interactive():
    """
    Display interactive mode menu bar
    """
    colno = print_string((PGTOP_MAXY - 1), 0, "<k>", get_curses_color(0))
    colno += print_string((PGTOP_MAXY - 1), colno, "Terminate the backend    ", get_curses_color(C_CYAN)|curses.A_REVERSE)
    colno += print_string((PGTOP_MAXY - 1), colno, "<Space>", get_curses_color(0))
    colno += print_string((PGTOP_MAXY - 1), colno, "Back to activity    ", get_curses_color(C_CYAN)|curses.A_REVERSE)
    colno += print_string((PGTOP_MAXY - 1), colno, "<q>", get_curses_color(0))
    colno += print_string((PGTOP_MAXY - 1), colno, "Quit    ", get_curses_color(C_CYAN)|curses.A_REVERSE)
    colno += print_string((PGTOP_MAXY - 1), colno, add_blank(" "), get_curses_color(C_CYAN)|curses.A_REVERSE)

def change_mode_interactive():
    """
    Display change mode menu bar
    """
    colno = print_string((PGTOP_MAXY - 1), 0, "<F1/1>", get_curses_color(0))
    colno += print_string((PGTOP_MAXY - 1), colno, "Running queries    ", get_curses_color(C_CYAN)|curses.A_REVERSE)
    colno += print_string((PGTOP_MAXY - 1), colno, "<F2/2>", get_curses_color(0))
    colno += print_string((PGTOP_MAXY - 1), colno, "Waiting queries    ", get_curses_color(C_CYAN)|curses.A_REVERSE)
    colno += print_string((PGTOP_MAXY - 1), colno, "<F3/3>", get_curses_color(0))
    colno += print_string((PGTOP_MAXY - 1), colno, "Blocking queries ", get_curses_color(C_CYAN)|curses.A_REVERSE)
    colno += print_string((PGTOP_MAXY - 1), colno, "<Space>", get_curses_color(0))
    colno += print_string((PGTOP_MAXY - 1), colno, "Pause    ", get_curses_color(C_CYAN)|curses.A_REVERSE)
    colno += print_string((PGTOP_MAXY - 1), colno, "<q>", get_curses_color(0))
    colno += print_string((PGTOP_MAXY - 1), colno, "Quit    ", get_curses_color(C_CYAN)|curses.A_REVERSE)
    colno += print_string((PGTOP_MAXY - 1), colno, "<h>", get_curses_color(0))
    colno += print_string((PGTOP_MAXY - 1), colno, "Help    ", get_curses_color(C_CYAN)|curses.A_REVERSE)
    colno += print_string((PGTOP_MAXY - 1), colno, add_blank(" "), get_curses_color(C_CYAN)|curses.A_REVERSE)

def ask_terminate_backend(pid, connector, pg_num_version):
    """
    Ask for terminating a backend
    """
    colno = print_string((PGTOP_MAXY - 1), 0, "Terminate backend with PID %s ? <Y/N>" % (str(pid),), get_curses_color(0))
    colno += print_string((PGTOP_MAXY - 1), colno, add_blank(" "), get_curses_color(C_CYAN)|curses.A_REVERSE)
    while 1:
        try:
            k = PGTOP_WIN.getch()
        except KeyboardInterrupt as e:
            raise e
        # quit
        if k == ord('q'):
            curses.endwin()
            exit()
        # yes
        if k == ord('y') or k == ord('Y'):
            pg_terminate_backend(connector, str(pid), pg_num_version)
            return 1
        # no
        if k == ord('n') or k == ord('N') or k == ord(' '):
            return 0
        # resize => exit
        if k == curses.KEY_RESIZE:
            return 0

def interactive(process, flag, indent, connector, pg_num_version):
    """
    Interactive mode trigged on KEY_UP or KEY_DOWN key press
    If no key hit during 3 seconds, exit this mode
    """
    global PGTOP_VERBOSE_MODE
    # Force truncated display
    old_verbose_mode = PGTOP_VERBOSE_MODE
    PGTOP_VERBOSE_MODE = PGTOP_TRUNCATE

    # Refresh lines with this verbose mode    
    scroll_window(process, flag, indent, 0)
    
    help_key_interactive()

    current_pos = 0
    offset = 0
    refresh_line(process[current_pos], flag, indent, 'cursor', PGTOP_LINES[current_pos] - offset)
    PGTOP_WIN.timeout(int(1000))
    nb_nk = 0

    while 1:
        try:
            k = PGTOP_WIN.getch()
        except KeyboardInterrupt as e:
            raise e
        if k == -1:
            nb_nk += 1        
        # quit
        if k == ord('q'):
            curses.endwin()
            exit()
        # terminate the backend attached to this PID
        if k == ord('k'):
            ask_terminate_backend(process[current_pos]['pid'], connector, pg_num_version)
            PGTOP_VERBOSE_MODE = old_verbose_mode
            curses.flushinp()
            return 0
        # Move cursor
        if k == curses.KEY_DOWN or k == curses.KEY_UP:
            nb_nk = 0        
            if k == curses.KEY_UP and current_pos > 0:
                if (PGTOP_LINES[current_pos] - offset) < (PGTOP_START_LINE + 3):
                    offset -= 1
                    scroll_window(process, flag, indent, offset)
                    help_key_interactive()

                if current_pos < len(process):
                    refresh_line(process[current_pos], flag, indent, 'default', PGTOP_LINES[current_pos] - offset)
                current_pos -= 1
            if k == curses.KEY_DOWN and current_pos < (len(process) - 1):
                if (PGTOP_LINES[current_pos] - offset) >= (PGTOP_MAXY - 2):
                    offset += 1
                    scroll_window(process, flag, indent, offset)
                    help_key_interactive()

                if current_pos >= 0:
                    refresh_line(process[current_pos], flag, indent, 'default', PGTOP_LINES[current_pos] - offset)
                current_pos += 1
            refresh_line(process[current_pos], flag, indent, 'cursor', PGTOP_LINES[current_pos] - offset)
            curses.flushinp()
            continue
        # quit interactive mode
        if k == ord(' ') or k == curses.KEY_RESIZE:
            PGTOP_VERBOSE_MODE = old_verbose_mode
            curses.flushinp()
            return 0
        curses.flushinp()
        if nb_nk > 3:
            PGTOP_VERBOSE_MODE = old_verbose_mode
            return 0

def poll(interval, connector, flag, indent, process = None, pg_num_version = None, disp_proc = None):
    """
    Wrapper around polling
    """
    if PGTOP_MODE == 'activities':
        return poll_activities(interval, connector, flag, indent, process, pg_num_version, disp_proc)
    elif PGTOP_MODE == 'waiting' or PGTOP_MODE == 'blocking':
        return poll_waiting_blocking(interval, connector, flag, indent, process, pg_num_version, disp_proc)
 
def poll_activities(interval, connector, flag, indent, process = None, pg_num_version = None, disp_proc = None):
    """
    Poll activities.
    """
    global PGTOP_VERBOSE_MODE
    global PGTOP_SORT
    global PGTOP_REFRESH_TIME
    global PGTOP_MODE

    # Keyboard interactions
    PGTOP_WIN.timeout(int(1000 * PGTOP_REFRESH_TIME * interval))
    t_start = time.time()
    known = False
    do_refresh = False
    try:
        k = PGTOP_WIN.getch()
    except KeyboardInterrupt as e:
        raise e
    if k == ord('q'):
        curses.endwin()
        exit()
    # PAUSE mode
    if k == ord(' '):
        pause()
        do_refresh = True
    # interactive mode
    if (k == curses.KEY_DOWN or k == curses.KEY_UP) and len(disp_proc) > 0:
        interactive(disp_proc, flag, indent, connector, pg_num_version)
        known = True
    # show waiting queries
    if (k == curses.KEY_F2 or k == ord('2')):
        PGTOP_MODE = 'waiting'
        curses.flushinp()
        return poll_waiting_blocking(0, connector, flag, indent, None, pg_num_version, None)
    # show blocking queries
    if (k == curses.KEY_F3 or k == ord('3')):
        PGTOP_MODE = 'blocking'
        curses.flushinp()
        return poll_waiting_blocking(0, connector, flag, indent, None, pg_num_version, None)
    # change verbosity
    if k == ord('v'):
        PGTOP_VERBOSE_MODE += 1
        if PGTOP_VERBOSE_MODE > 3:
            PGTOP_VERBOSE_MODE = 1
        do_refresh = True
    # turn off/on colors
    if k == ord('C'):
        if PGTOP_COLOR is True:
            set_nocolor()
        else:
            set_color()
        do_refresh = True
    # sorts
    if k == ord('c') and (flag & PGTOP_FLAG_CPU) and PGTOP_SORT != 'c':
        PGTOP_SORT = 'c'
        known = True
    if k == ord('m') and (flag & PGTOP_FLAG_MEM) and PGTOP_SORT != 'm':
        PGTOP_SORT = 'm'
        known = True
    if k == ord('r') and (flag & PGTOP_FLAG_READ) and PGTOP_SORT != 'r':
        PGTOP_SORT = 'r'
        known = True
    if k == ord('w') and (flag & PGTOP_FLAG_WRITE) and PGTOP_SORT != 'w':
        PGTOP_SORT = 'w'
        known = True
    if k == ord('t') and PGTOP_SORT != 't':
        PGTOP_SORT = 't'
        known = True
    if k == ord('+') and PGTOP_REFRESH_TIME < 3:
        PGTOP_REFRESH_TIME += 1
        do_refresh = True
    if k == ord('-') and PGTOP_REFRESH_TIME > 1:
        PGTOP_REFRESH_TIME -= 1
        do_refresh = True
    # Refresh
    if k == ord('R'):
        known = True
     
    if k == ord('h'):
        help_window()
        do_refresh = True

    if k == curses.KEY_RESIZE and PGTOP_BUFFER is not None and PGTOP_BUFFER.has_key('procs'):
        do_refresh = True
    
    if do_refresh is True and PGTOP_BUFFER is not None and PGTOP_BUFFER.has_key('procs'):
        check_window_size()
        refresh_window(PGTOP_BUFFER['procs'], PGTOP_BUFFER['extras'], PGTOP_BUFFER['flag'], PGTOP_BUFFER['indent'], PGTOP_BUFFER['io'], PGTOP_BUFFER['tps'], PGTOP_BUFFER['size_ev'], PGTOP_BUFFER['total_size'])

    curses.flushinp()
    t_end = time.time() 
    if k > -1 and not known and (t_end - t_start) < (PGTOP_REFRESH_TIME * interval):
        return poll_activities(((PGTOP_REFRESH_TIME * interval) - (t_end - t_start))/PGTOP_REFRESH_TIME, connector, flag, indent, process, pg_num_version, disp_proc)

    # poll postgresql activity
    queries =  pg_get_activities(connector, pg_num_version)
    if PGTOP_IS_LOCAL:
        # get resource usage for each process
        new_procs = sys_get_proc(queries)

        procs = []
        for pid, new_proc in new_procs.items():
            try:
                if process.has_key(pid):
                    n_io_time = time.time()
                    # Getting informations from the previous loop
                    proc = process[pid]
                    # Update old process with new informations
                    proc.duration = new_proc.duration
                    proc.query = new_proc.query
                    proc.client = new_proc.client
                    proc.wait = new_proc.wait
                    proc.setExtra('io_wait', new_proc.getExtra('io_wait'))
                    proc.setExtra('read_delta', (new_proc.getExtra('io_counters').read_bytes - proc.getExtra('io_counters').read_bytes)/(n_io_time - proc.getExtra('io_time')))
                    proc.setExtra('write_delta', (new_proc.getExtra('io_counters').write_bytes - proc.getExtra('io_counters').write_bytes)/(n_io_time - proc.getExtra('io_time')))
                    proc.setExtra('io_counters', new_proc.getExtra('io_counters'))
                    proc.setExtra('io_time', n_io_time)
                else:
                    # No previous information about this process
                    proc = new_proc
            
                proc.setExtra('mem_percent', proc.getExtra('psutil_proc').get_memory_percent())
                proc.setExtra('cpu_percent', proc.getExtra('psutil_proc').get_cpu_percent(interval=0))
                new_procs[pid] = proc
                procs.append({'pid': pid, 'database': proc.database, 'client': proc.client, 'cpu': proc.getExtra('cpu_percent'), 'mem': proc.getExtra('mem_percent'), 'read': proc.getExtra('read_delta'), 'write': proc.getExtra('write_delta'), 'query': proc.query, 'duration': get_duration(proc.duration), 'wait': proc.wait, 'io_wait': proc.getExtra('io_wait')})

            except psutil.NoSuchProcess:
                pass
            except psutil.AccessDenied:
                pass
            except Exception as e:
                raise e
    else:
        procs = []
        new_procs = None
        for q in queries:
            procs.append({'pid':q['pid'], 'database': q['database'], 'client': q['client'], 'query': q['query'], 'duration': get_duration(q['duration']), 'wait': q['wait']})

    # return processes sorted by query duration
    if PGTOP_SORT == 't':
        # TIME
        disp_procs = sorted(procs, key=lambda p: p['duration'], reverse=True)
    elif PGTOP_SORT == 'c':
        # CPU
        disp_procs = sorted(procs, key=lambda p: p['cpu'], reverse=True)
    elif PGTOP_SORT == 'm':
        # MEM
        disp_procs = sorted(procs, key=lambda p: p['mem'], reverse=True)
    elif PGTOP_SORT == 'r':
        # READ
        disp_procs = sorted(procs, key=lambda p: p['read'], reverse=True)
    elif PGTOP_SORT == 'w':
        # WRITE
        disp_procs = sorted(procs, key=lambda p: p['write'], reverse=True)
    else:
        disp_procs = sorted(procs, key=lambda p: p['duration'], reverse=True)
    
    return (disp_procs, new_procs)

def poll_waiting_blocking(interval, connector, flag, indent, process = None, pg_num_version = None, disp_proc = None):
    """
    Poll waiting or blocking queries
    """
    global PGTOP_VERBOSE_MODE
    global PGTOP_SORT
    global PGTOP_REFRESH_TIME
    global PGTOP_MODE

    t_start = time.time()
    do_refresh = False
    known = False
    # Keyboard interactions
    PGTOP_WIN.timeout(int(1000 * PGTOP_REFRESH_TIME * interval))
    
    try:
        k = PGTOP_WIN.getch()
    except KeyboardInterrupt as e:
        raise e
    if k == ord('q'):
        curses.endwin()
        exit()
    # PAUSE mode
    if k == ord(' '):
        pause()
        do_refresh = True
    # interactive mode
    if (k == curses.KEY_DOWN or k == curses.KEY_UP) and len(disp_proc) > 0:
        interactive(disp_proc, flag, indent, connector, pg_num_version)
        known = True
    # activities mode
    if (k == curses.KEY_F1 or k == ord('1')):
        PGTOP_MODE = 'activities'
        curses.flushinp()
        queries = pg_get_activities(connector, pg_num_version)
        procs = sys_get_proc(queries)
        return poll_activities(0, connector, flag, indent, procs, pg_num_version, None)
    # Waiting queries
    if ((k == curses.KEY_F2 or k == ord('2')) and PGTOP_MODE != 'waiting'):
        PGTOP_MODE = 'waiting'
        curses.flushinp()
        return poll_waiting_blocking(0, connector, flag, indent, None, pg_num_version, None)
    # blocking queries
    if ((k == curses.KEY_F3 or k == ord('3')) and PGTOP_MODE != 'blocking'):
        PGTOP_MODE = 'blocking'
        curses.flushinp()
        return poll_waiting_blocking(0, connector, flag, indent, None, pg_num_version, None)
    # change verbosity
    if k == ord('v'):
        PGTOP_VERBOSE_MODE += 1
        if PGTOP_VERBOSE_MODE > 3:
            PGTOP_VERBOSE_MODE = 1
        do_refresh = True
    # turnoff/on colors
    if k == ord('C'):
        if PGTOP_COLOR is True:
            set_nocolor()
        else:
            set_color()
        do_refresh = True
    # sorts
    if k == ord('t') and PGTOP_SORT != 't':
        PGTOP_SORT = 't'
        known = True
    if k == ord('+') and PGTOP_REFRESH_TIME < 3:
        PGTOP_REFRESH_TIME += 1
    if k == ord('-') and PGTOP_REFRESH_TIME > 1:
        PGTOP_REFRESH_TIME -= 1

    if k == ord('h'):
        help_window()
        do_refresh = True
    
    # Refresh
    if k == ord('R'):
        known = True
    
    if k == curses.KEY_RESIZE and PGTOP_BUFFER is not None and PGTOP_BUFFER.has_key('procs'):
        do_refresh = True
    
    if do_refresh is True and PGTOP_BUFFER is not None and PGTOP_BUFFER.has_key('procs'):
        check_window_size()
        refresh_window(PGTOP_BUFFER['procs'], PGTOP_BUFFER['extras'], PGTOP_BUFFER['flag'], PGTOP_BUFFER['indent'], PGTOP_BUFFER['io'], PGTOP_BUFFER['tps'], PGTOP_BUFFER['size_ev'], PGTOP_BUFFER['total_size'])
     
    curses.flushinp()
    t_end = time.time() 
    if k > -1 and not known and (t_end - t_start) < (PGTOP_REFRESH_TIME * interval):
        return poll_waiting_blocking(((PGTOP_REFRESH_TIME * interval) - (t_end - t_start))/PGTOP_REFRESH_TIME, connector, flag, indent, process, pg_num_version, disp_proc)
 
    # poll postgresql activity
    if PGTOP_MODE == 'waiting':
        queries =  pg_get_waiting(connector, pg_num_version)
    else:
        queries =  pg_get_blocking(connector, pg_num_version)
    
    new_procs = {}
    for q in queries:
        new_procs[q['pid']] = q
        new_procs[q['pid']]['duration'] = get_duration(q['duration'])
    
    # return processes sorted by query duration
    if PGTOP_SORT == 't':
        # TIME
        disp_procs = sorted(queries, key=lambda q: q['duration'], reverse=True)
    else:
        disp_procs = sorted(queries, key=lambda q: q['duration'], reverse=True)
    
    return (disp_procs, new_procs)

def print_string(lineno, colno, word, color = 0):
    """
    Print a string at position (lineno, colno) and returns its length.
    """
    try:
        PGTOP_WIN.addstr(lineno, colno, word, color)
    except curses.error:
        pass
    return len(word)

def add_blank(line, offset = 0): 
    """
    Complete string with white spaces from the end of string to the end of line.
    """
    line += " " * (PGTOP_MAXX - (len(line) + offset))
    return line

def bytes2human(n):
    """
    Convert a size into a human readable format.
    """
    symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
    prefix = {}
    ne = ''
    if n < 0:
        n = n * -1
        ne = '-'
    for i, s in enumerate(symbols):
        prefix[s] = 1 << (i+1)*10
    for s in reversed(symbols):
        if n >= prefix[s]:
            value = "%.2f" % float(float(n) / float(prefix[s]))
            return "%s%s%s" % (ne, value, s)
    return "%s%.2fB" % (ne, n)

def get_indent(flag):
    """
    Returns identation for Query column.
    """
    indent = ''
    r = [0] * PGTOP_MAX_NCOL
    for _, val in PGTOP_COLS[PGTOP_MODE].items():
        if val['mandatory'] or (not val['mandatory'] and val['flag'] & flag):
            r[int(val['n'])] = val
    for val in r:
        if val is not 0:
            if val['name'] is not 'Query':
                indent += val['template_h'] % ' '
    return indent

def print_cols_header(flag):
    """
    Print columns headers
    """
    global PGTOP_LINENO
    line = ''
    disp = ''
    x = 0
    r = [0] * PGTOP_MAX_NCOL
    for _, val in PGTOP_COLS[PGTOP_MODE].items():
        if val['mandatory'] or (not val['mandatory'] and val['flag'] & flag):
            r[int(val['n'])] = val
    for val in r:
        if val is not 0:
            disp = val['template_h'] % val['name']
            if (
                (val['name'] == "CPU%" and PGTOP_SORT == 'c') or
                (val['name'] == "MEM%" and PGTOP_SORT == 'm') or
                (val['name'] == "READ/s" and PGTOP_SORT == 'r') or
                (val['name'] == "WRITE/s" and PGTOP_SORT == 'w') or
                (val['name'] == "TIME+" and PGTOP_SORT == 't')):
                color_highlight = get_curses_color(C_CYAN)
            else:
                color_highlight = get_curses_color(C_GREEN)
            if val['name'] == "Query":
                disp += " " * (PGTOP_MAXX - (len(line) + len(disp)))
            line += disp
            print_string(PGTOP_LINENO, x, disp, color_highlight|curses.A_REVERSE)
            x += len(disp)
    PGTOP_LINENO += 1

def print_header(pg_version, hostname, user, host, port, io, tps, size_ev, total_size):
    """
    Print window header
    """
    global PGTOP_LINENO
    global PGTOP_MAX_IOPS 
    PGTOP_LINENO = 0
    colno = 0
    version = " %s" % (pg_version)
    colno = print_string(PGTOP_LINENO, colno, version)
    colno += print_string(PGTOP_LINENO, colno, " - ")
    colno += print_string(PGTOP_LINENO, colno, hostname, curses.A_BOLD)
    colno += print_string(PGTOP_LINENO, colno, " - ")
    colno += print_string(PGTOP_LINENO, colno, user, get_curses_color(C_CYAN))
    colno += print_string(PGTOP_LINENO, colno, "@")
    colno += print_string(PGTOP_LINENO, colno, host, get_curses_color(C_CYAN))
    colno += print_string(PGTOP_LINENO, colno, ":")
    colno += print_string(PGTOP_LINENO, colno, port, get_curses_color(C_CYAN))
    colno += print_string(PGTOP_LINENO, colno, " - Ref.: %ss" % (PGTOP_REFRESH_TIME,))
    colno = 0
    PGTOP_LINENO += 1   
    colno += print_string(PGTOP_LINENO, colno, "  Size: ")
    colno += print_string(PGTOP_LINENO, colno, "%7s" % (bytes2human(total_size),),)
    colno += print_string(PGTOP_LINENO, colno, " - %9s/s" % (bytes2human(size_ev),),)
    colno += print_string(PGTOP_LINENO, colno, " - TPS: ")
    colno += print_string(PGTOP_LINENO, colno, "%6s" % (tps,), get_curses_color(C_GREEN)|curses.A_BOLD)
 
    # If not local connection, don't get and display system informations
    if not PGTOP_IS_LOCAL:
        return
    
    try:
        # psutil >= 0.6.0
        phymem = psutil.virtual_memory()
        buffers = psutil.virtual_memory().buffers
        cached = psutil.virtual_memory().cached
        vmem = psutil.swap_memory()
    except AttributeError:
        # psutil > 0.4.0 and < 0.6.0
        phymem = psutil.phymem_usage()
        buffers = getattr(psutil, 'phymem_buffers', lambda: 0)()
        cached = getattr(psutil, 'cached_phymem', lambda: 0)()
        vmem = psutil.virtmem_usage()

    used = phymem.total - (phymem.free + buffers + cached)
    PGTOP_LINENO += 1
    line = "  Mem.: %5s0%% - %9s/%s" % (phymem.percent, bytes2human(used), bytes2human(phymem.total))
    colno_io = print_string(PGTOP_LINENO, 0, line)
    
    if (int(io['read_count'])+int(io['write_count'])) > PGTOP_MAX_IOPS:
        PGTOP_MAX_IOPS = (int(io['read_count'])+int(io['write_count']))
    
    line_io = " | IO Disks Max: %s IOPS" % (PGTOP_MAX_IOPS,)
    colno = print_string(PGTOP_LINENO, colno_io, line_io)

    # swap usage
    line = "  Swap: %5s0%% - %9s/%s" % (vmem.percent, bytes2human(vmem.used), bytes2human(vmem.total))
    PGTOP_LINENO += 1
    colno = print_string(PGTOP_LINENO, 0, line)
    line_io = " | Read : %8s/s - %6s IOPS" % (bytes2human(io['read_bytes']), int(io['read_count']),)
    colno = print_string(PGTOP_LINENO, colno_io, line_io)

    # load average, uptime
    av1, av2, av3 = os.getloadavg()
    line = "  Load:   %.2f %.2f %.2f" % (av1, av2, av3)
    PGTOP_LINENO += 1
    colno = print_string(PGTOP_LINENO, 0, line)
    line_io = " | Write: %8s/s - %6s IOPS" % (bytes2human(io['write_bytes']), int(io['write_count']),)
    colno = print_string(PGTOP_LINENO, colno_io, line_io)

def help_window():
    """
    Display help window
    """
    PGTOP_WIN.erase()
    global PGTOP_LINENO
    PGTOP_LINENO = 0
    text = "pg_activity %s - (c) 2012-2013 Julien Tachoires" % (PGTOP_VERSION)
    print_string(PGTOP_LINENO, 0, text, get_curses_color(C_GREEN)|curses.A_BOLD)
    PGTOP_LINENO += 1
    text = "Released under New BSD License."
    print_string(PGTOP_LINENO, 0, text)
    PGTOP_LINENO += 2
    display_help_key(PGTOP_LINENO, 00, "Up/Down", "scroll process list")
    display_help_key(PGTOP_LINENO, 45, "      C", "activate/deactivate colors")
    PGTOP_LINENO += 1
    display_help_key(PGTOP_LINENO, 00, "  Space", "pause")
    display_help_key(PGTOP_LINENO, 45, "      r", "sort by READ/s desc. (activities)")
    PGTOP_LINENO += 1
    display_help_key(PGTOP_LINENO, 00, "      v", "change queries display mode")
    display_help_key(PGTOP_LINENO, 45, "      w", "sort by WRITE/s desc. (activities)")
    PGTOP_LINENO += 1
    display_help_key(PGTOP_LINENO, 00, "      q", "quit")
    display_help_key(PGTOP_LINENO, 45, "      c", "sort by CPU% desc. (activities)")
    PGTOP_LINENO += 1
    display_help_key(PGTOP_LINENO, 00, "      +", "increase refresh time (max:3)")
    display_help_key(PGTOP_LINENO, 45, "      m", "sort by MEM% desc. (activities)")
    PGTOP_LINENO += 1
    display_help_key(PGTOP_LINENO, 00, "      -", "decrease refresh time (min:1)")
    display_help_key(PGTOP_LINENO, 45, "      t", "sort by TIME+ desc. (activities)")
    PGTOP_LINENO += 1
    display_help_key(PGTOP_LINENO, 00, "      R", "force refresh")
    PGTOP_LINENO += 1
    print_string(PGTOP_LINENO, 0, "Mode")
    PGTOP_LINENO += 1
    display_help_key(PGTOP_LINENO, 00, "   F1/1", "running queries")
    PGTOP_LINENO += 1
    display_help_key(PGTOP_LINENO, 00, "   F2/2", "waiting queries")
    PGTOP_LINENO += 1
    display_help_key(PGTOP_LINENO, 00, "   F3/3", "blocking queries")
    
    PGTOP_LINENO += 2
    print_string(PGTOP_LINENO, 0, "Press any key to exit.")    
    PGTOP_WIN.timeout(-1)
    try:
        PGTOP_WIN.getch()
    except KeyboardInterrupt as e:
        raise e

def display_help_key(lineno, colno, key, help):
    """
    Display help key
    """
    l = print_string(lineno, colno, key, get_curses_color(C_CYAN)|curses.A_BOLD)
    l2 = print_string(lineno, colno + l, ": %s" % (help,))
    return (colno + l + l2)

def refresh_window(procs, extras, flag, indent, io, tps, size_ev, total_size):
    """
    Refresh the window
    """
    global PGTOP_LINENO
    global PGTOP_LINES

    PGTOP_LINES = []
    (pg_version, hostname, user, host, port) = extras
    PGTOP_WIN.erase()
    print_header(pg_version, hostname, user, host, port, io, tps, size_ev, total_size)
    PGTOP_LINENO += 2
    line_trunc = PGTOP_LINENO
    current_position()
    print_cols_header(flag)
    for p in procs:
        try:
            refresh_line(p, flag, indent, 'default')
            line_trunc += 1
            PGTOP_LINES.append(line_trunc)
        except curses.error:
            break
        for l in range(PGTOP_LINENO, (PGTOP_MAXX-1)):
            print_string(l, 0, add_blank(" "))
    change_mode_interactive()

def scroll_window(procs, flag, indent, offset = 0):
    """
    Scroll the window
    """
    global PGTOP_LINENO
    PGTOP_LINENO = (PGTOP_START_LINE + 2)
    pos = 0
    for p in procs:
        if pos >= offset:
            try:
                refresh_line(p, flag, indent, 'default')
            except curses.error:
                break
            for l in range(PGTOP_LINENO, (PGTOP_MAXX-1)):
                print_string(l, 0, add_blank(" "))
        pos += 1

def refresh_line(p, flag, indent, typecolor = 'default', line = None):
    """
    Refresh a line for activities mode
    """
    global PGTOP_LINENO
    if line is not None:
        l_lineno = line
    else:
        l_lineno = PGTOP_LINENO
    
    colno = 0
    colno += print_string(l_lineno, colno, "%-6s " % (p['pid'],), PGTOP_LINE_COLORS['pid'][typecolor])
    if flag & PGTOP_FLAG_DATABASE:
        colno += print_string(l_lineno, colno, PGTOP_COLS[PGTOP_MODE]['database']['template_h'] % (p['database'][:16],), PGTOP_LINE_COLORS['database'][typecolor])
    if PGTOP_MODE == 'activities':
        if flag & PGTOP_FLAG_CLIENT:
            colno += print_string(l_lineno, colno, "%16s " % (str(p['client'])[:16],), PGTOP_LINE_COLORS['client'][typecolor])
        if flag & PGTOP_FLAG_CPU:
            colno += print_string(l_lineno, colno, "%6s " % (p['cpu'],), PGTOP_LINE_COLORS['cpu'][typecolor])
        if flag & PGTOP_FLAG_MEM:
            colno += print_string(l_lineno, colno, "%4s " % (round(p['mem'], 1),), PGTOP_LINE_COLORS['mem'][typecolor])
        if flag & PGTOP_FLAG_READ:
            colno += print_string(l_lineno, colno, "%8s " % (bytes2human(p['read']),), PGTOP_LINE_COLORS['read'][typecolor])
        if flag & PGTOP_FLAG_WRITE:
            colno += print_string(l_lineno, colno, "%8s " % (bytes2human(p['write']),), PGTOP_LINE_COLORS['write'][typecolor])
    elif PGTOP_MODE == 'waiting' or PGTOP_MODE == 'blocking':
        if flag & PGTOP_FLAG_RELATION:
            colno += print_string(l_lineno, colno, "%9s " % (str(p['relation'])[:9],), PGTOP_LINE_COLORS['relation'][typecolor])
        if flag & PGTOP_FLAG_TYPE:
            colno += print_string(l_lineno, colno, "%16s " % (str(p['type'])[:16],), PGTOP_LINE_COLORS['type'][typecolor])
        if flag & PGTOP_FLAG_MODE:
            if p['mode'] == 'ExclusiveLock' or p['mode'] == 'RowExclusiveLock' or p['mode'] == 'AccessExclusiveLock':
                colno += print_string(l_lineno, colno, "%16s " % (str(p['mode'])[:16],), PGTOP_LINE_COLORS['mode_red'][typecolor])
            else:
                colno += print_string(l_lineno, colno, "%16s " % (str(p['mode'])[:16],), PGTOP_LINE_COLORS['mode_yellow'][typecolor])

    if flag & PGTOP_FLAG_TIME:
        if p['duration'] >= 1:
            ctime = timedelta(seconds=float(p['duration']))
            mic = '%.6d' % (ctime.microseconds)
            ctime = "%s:%s.%s" % (str((ctime.seconds // 60)).zfill(2), str((ctime.seconds % 60)).zfill(2), str(mic)[:2])
        if p['duration'] < 1:
            colno += print_string(l_lineno, colno, " %.6f " % (p['duration'],), PGTOP_LINE_COLORS['time_green'][typecolor])
        elif p['duration'] >= 1 and p['duration'] < 3:
            colno += print_string(l_lineno, colno, "%9s " % (ctime,), PGTOP_LINE_COLORS['time_yellow'][typecolor])
        else:
            colno += print_string(l_lineno, colno, "%9s " % (ctime,), PGTOP_LINE_COLORS['time_red'][typecolor])
    if PGTOP_MODE == 'activities' and flag & PGTOP_FLAG_WAIT:
        if p['wait']:
            colno += print_string(l_lineno, colno, "%2s " % ('Y',), PGTOP_LINE_COLORS['wait_red'][typecolor])
        else:
            colno += print_string(l_lineno, colno, "%2s " % ('N',), PGTOP_LINE_COLORS['wait_green'][typecolor])
    
    if PGTOP_MODE == 'activities' and flag & PGTOP_FLAG_IOWAIT:
        if p['io_wait'] == 'Y':
            colno += print_string(l_lineno, colno, "%4s " % ('Y',), PGTOP_LINE_COLORS['wait_red'][typecolor])
        else:
            colno += print_string(l_lineno, colno, "%4s " % ('N',), PGTOP_LINE_COLORS['wait_green'][typecolor])


    dif = PGTOP_MAXX - len(indent) - 1
    if PGTOP_VERBOSE_MODE == PGTOP_TRUNCATE:
        query = p['query'][:dif]
        colno += print_string(l_lineno, colno, " %s" % (add_blank(query, len(indent)+1),), PGTOP_LINE_COLORS['query'][typecolor])
    elif PGTOP_VERBOSE_MODE == PGTOP_WRAP or  PGTOP_VERBOSE_MODE == PGTOP_WRAP_NOINDENT:
        query = p['query']
        query_wrote = ''
        offset = 0
        if len(query) > dif and dif > 1:
            query_part = query[offset:dif]
            print_string(l_lineno, colno, " %s" % (add_blank(query_part, len(indent)+1),), PGTOP_LINE_COLORS['query'][typecolor])
            query_wrote += query_part
            offset = len(query_wrote)
            if PGTOP_VERBOSE_MODE == PGTOP_WRAP_NOINDENT:
                dif = PGTOP_MAXX
                p_indent = ""
            else:
                p_indent = indent
            while (len(query) - offset > 0):    
                query_part = query[offset:(dif+offset)]
                l_lineno += 1
                PGTOP_LINENO += 1
                print_string(l_lineno, 0, "%s" % (add_blank(p_indent + " " + query_part, len(indent)+1)), PGTOP_LINE_COLORS['query'][typecolor])
                query_wrote += query_part
                offset = len(query_wrote)
        else:
            colno += print_string(l_lineno, colno, " %s" % (add_blank(query, len(indent)),), PGTOP_LINE_COLORS['query'][typecolor])
    PGTOP_LINENO += 1

# Main function
def main():
    global PGTOP_IS_LOCAL
    global PGTOP_START_LINE
    global PGTOP_BUFFER

    try:
        parser = OptionParser(add_help_option = False, version = "%prog "+PGTOP_VERSION, description = "htop like application for PostgreSQL server activity monitoring.")
        parser.add_option('-U', '--username', dest = 'username', default = getpass.getuser(), help = "Database user name (default: \"%s\")." % (getpass.getuser(),), metavar = 'USERNAME')
        parser.add_option('-p', '--port', dest = 'port', default = '5432', help = "Database server port (default: \"5432\").", metavar = 'PORT')
        parser.add_option('-h', '--host', dest = 'host', help = "Database server host or socket directory (default: \"localhost\").", metavar = 'HOSTNAME', default = 'localhost')
        parser.add_option('-d', '--dbname', dest = 'dbname', help = "Database name to connect to (default: \"postgres\").", metavar = 'DBNAME', default = 'postgres')
        parser.add_option('-C', '--no-color', dest = 'nocolor', action = 'store_true', help = "Disable color usage.", default = 'false')
        group = OptionGroup(parser, "Display Options, you can exclude some columns by using them ")
        group.add_option('--no-database', dest = 'nodb', action = 'store_true', help = "Disable DATABASE.", default = 'false')
        group.add_option('--no-client', dest = 'noclient', action = 'store_true', help = "Disable CLIENT.", default = 'false')
        group.add_option('--no-cpu', dest = 'nocpu', action = 'store_true', help = "Disable CPU%.", default = 'false')
        group.add_option('--no-mem', dest = 'nomem', action = 'store_true', help = "Disable MEM%.", default = 'false')
        group.add_option('--no-read', dest = 'noread', action = 'store_true', help = "Disable READ/s.", default = 'false')
        group.add_option('--no-write', dest = 'nowrite', action = 'store_true', help = "Disable WRITE/s.", default = 'false')
        group.add_option('--no-time', dest = 'notime', action = 'store_true', help = "Disable TIME+.", default = 'false')
        group.add_option('--no-wait', dest = 'nowait', action = 'store_true', help = "Disable W.", default = 'false')
        parser.add_option_group(group)
        parser.add_option('--help', dest = 'help', action = 'store_true', help = "Show this help message and exit.", default = 'false')
        parser.add_option('--debug', dest = 'debug', action = 'store_true', help = "Enable debug mode for traceback tracking.", default = 'false')
    
        (options, args) = parser.parse_args()
        if options.help is True:
            at_exit_curses()
            print(parser.format_help().strip())
            sys.exit(0)
        
        password = os.environ.get('PGPASSWORD')
        if password is None:
            # pgpass file handling
            try:
                for (pgpass_host, pgpass_port, pgpass_database, pgpass_user, pgpass_password) in get_pgpass(os.environ.get('PGPASSFILE')):
                    if (pgpass_host == options.host or pgpass_host == '*') and (pgpass_port == options.port or pgpass_port == '*') and (pgpass_user == options.username or pgpass_user == '*') and (pgpass_database == options.dbname or pgpass_database == '*'):
                        password = pgpass_password
                        continue
            except Exception as e:
                pass
        
        debug = options.debug
        
        try:
            conn = pg_connect(host = options.host, port = options.port, user = options.username, password = password, database = options.dbname)
        except psycopg2.Error as e:
            at_exit_curses()
            sys.exit("FATAL: %s" % (clean_str(str(e),)))
        
        pg_version = pg_get_version(conn)
        (pg_short_version, pg_num_version) = pg_get_num_version(pg_version)
        hostname = socket.gethostname()
       
        # reduce DATABASE column length 
        set_max_db_length(16)
        
        # does pg_activity runing against local PG instance
        if not pg_is_local(conn):
            PGTOP_IS_LOCAL = False
            PGTOP_START_LINE = 2
            hostname = options.host
        # if not connected to a local pg server, then go to degraded mode
        elif not pg_is_local_access():
            PGTOP_IS_LOCAL = False
            PGTOP_START_LINE = 2
            hostname = options.host

        # top part
        interval = 0
        if PGTOP_MODE == 'activities':
            queries =  pg_get_activities(conn, pg_num_version)
            procs = sys_get_proc(queries)
        elif PGTOP_MODE == 'waiting':
            procs = pg_get_waiting(conn, pg_num_version)
        elif PGTOP_MODE == 'blocking':
            procs = pg_get_blocking(conn, pg_num_version)

        # color ?
        if options.nocolor == True:
            set_nocolor()
        else:
            set_color()    
        # default flag
        flag = PGTOP_FLAG_DATABASE | PGTOP_FLAG_CLIENT | PGTOP_FLAG_CPU | PGTOP_FLAG_MEM | PGTOP_FLAG_READ | PGTOP_FLAG_WRITE | PGTOP_FLAG_TIME | PGTOP_FLAG_WAIT | PGTOP_FLAG_RELATION | PGTOP_FLAG_TYPE | PGTOP_FLAG_MODE | PGTOP_FLAG_IOWAIT
        if options.nodb is True:
            flag -= PGTOP_FLAG_DATABASE
        if options.nocpu is True:
            flag -= PGTOP_FLAG_CPU
        if options.noclient is True:
            flag -= PGTOP_FLAG_CLIENT
        if options.nomem is True:
            flag -= PGTOP_FLAG_MEM
        if options.noread is True:
            flag -= PGTOP_FLAG_READ
        if options.nowrite is True:
            flag -= PGTOP_FLAG_WRITE
        if options.notime is True:
            flag -= PGTOP_FLAG_TIME
        if options.nowait is True:
            flag -= PGTOP_FLAG_WAIT
        
        if not PGTOP_IS_LOCAL and (flag & PGTOP_FLAG_CPU):
            flag -= PGTOP_FLAG_CPU
        if not PGTOP_IS_LOCAL and (flag & PGTOP_FLAG_MEM):
            flag -= PGTOP_FLAG_MEM
        if not PGTOP_IS_LOCAL and (flag & PGTOP_FLAG_READ):
            flag -= PGTOP_FLAG_READ
        if not PGTOP_IS_LOCAL and (flag & PGTOP_FLAG_WRITE):
            flag -= PGTOP_FLAG_WRITE
        if not PGTOP_IS_LOCAL and (flag & PGTOP_FLAG_IOWAIT):
            flag -= PGTOP_FLAG_IOWAIT
          
        # main loop
        disks_io = None
        disp_procs = None
        # init num. of TX
        (tps_time, tps_nb_tx, tps_total_size, max_db_length) = pg_get_snapshot_infos(conn)
        set_max_db_length(max_db_length)
        # indentation
        indent = get_indent(flag)
        
        while 1:
            check_window_size()
            
            # poll process
            old_pgtop_mode = PGTOP_MODE
            (disp_procs, new_procs) = poll(interval, conn, flag, indent, procs, pg_num_version, disp_procs)
            if PGTOP_MODE != old_pgtop_mode:
                indent = get_indent(flag)
            
            if PGTOP_IS_LOCAL:
                new_disks_io = {'io': psutil.disk_io_counters(), 'time': time.time()}
                delta_disks_io = {'read_count': 0, 'write_count': 0, 'read_bytes': 0.0, 'write_bytes': 0.0}
                if disks_io is not None:
                    delta_disks_io['read_count'] = (new_disks_io['io'].read_count - disks_io['io'].read_count)/(new_disks_io['time'] - disks_io['time'])
                    delta_disks_io['write_count'] = (new_disks_io['io'].write_count - disks_io['io'].write_count)/(new_disks_io['time'] - disks_io['time'])
                    delta_disks_io['read_bytes'] = (new_disks_io['io'].read_bytes - disks_io['io'].read_bytes)/(new_disks_io['time'] - disks_io['time'])
                    delta_disks_io['write_bytes'] = (new_disks_io['io'].write_bytes - disks_io['io'].write_bytes)/(new_disks_io['time'] - disks_io['time'])
                disks_io = new_disks_io
            else:
                delta_disks_io = None
    
            procs = new_procs
            # refresh the winodw
            (n_tps_time, n_tps_nb_tx, n_tps_total_size, max_db_length) = pg_get_snapshot_infos(conn)
            set_max_db_length(max_db_length)
            
            tps = int((n_tps_nb_tx - tps_nb_tx) / (n_tps_time - tps_time))
            size_ev = float(float(n_tps_total_size - tps_total_size) / (n_tps_time - tps_time))
            tps_time = n_tps_time
            tps_nb_tx = n_tps_nb_tx
            tps_total_size = n_tps_total_size
            PGTOP_BUFFER = {'procs': disp_procs, 'extras': (pg_short_version, hostname, options.username, options.host, options.port), 'flag': flag, 'indent': indent, 'io': delta_disks_io, 'tps': tps, 'size_ev': size_ev, 'total_size': tps_total_size}
            refresh_window(disp_procs, (pg_short_version, hostname, options.username, options.host, options.port), flag, indent, delta_disks_io, tps, size_ev, tps_total_size)
            interval = 1

    except psutil.AccessDenied as e:
        at_exit_curses()
        sys.exit("FATAL: Acces denied for user %s, can't acces system informations for process %s" % (getpass.getuser(), str(e),))
    except curses.error as e:
        at_exit_curses()
        if debug is True:
            import traceback
            exc_type, exc_value, exc_traceback = sys.exc_info()
            traceback.print_exception(
                exc_type,
                exc_value,
                exc_traceback,
                file=sys.stdout)
        sys.exit("FATAL: %s" % (str(e),))
    except KeyboardInterrupt as e:
        at_exit_curses()
        sys.exit(0)
    except Exception as e:
        at_exit_curses()
        # DEBUG
        if debug is True:
            import traceback
            exc_type, exc_value, exc_traceback = sys.exc_info()
            traceback.print_exception(
                exc_type,
                exc_value,
                exc_traceback,
                file=sys.stdout)
        sys.exit("FATAL: %s" % (str(e),))

# Call the main function
if __name__ == '__main__':
    signal.signal(signal.SIGTERM, signal_handler)
    main()
