def test_get_sql_by_pg_version_should_return_newer_when_bigger_than_96( self): cluster = self.cluster.copy() cluster['ver'] = 9.7 collector = PgStatCollector.from_cluster(cluster, 1049) self.assertEqual(SELECT_PGSTAT_NEVER_VERSION, collector.get_sql_pgstat_by_version())
def test_ncurses_produce_prefix_should_return_online_when_pgcon( self, mocked__status, mocked__max_conn): self.cluster['pgcon'].get_parameter_status.return_value = '9.3' collector = PgStatCollector.from_cluster(self.cluster, [1049]) self.assertEqual( '/var/lib/postgresql/9.3/main 9.3 role connections: 0 of 10 allocated, 0 active\n', collector.ncurses_produce_prefix())
def test_get_additional_info_should_update_when_not_backend_and_not_action( self, mocked__get_memory_usage, mocked__get_psinfo): collector = PgStatCollector.from_cluster(self.cluster, 1049) mocked__get_psinfo.return_value = ('vacuum', None) mocked__get_memory_usage.return_value = 10 info = collector.get_additional_proc_info(1049, {'cmdline': ''}, [10]) self.assertEqual({'type': 'vacuum', 'cmdline': '', 'uss': 10}, info)
def test__get_psinfo_should_return_pstype_action_when_cmdline_matches_postgres_process( self): collector = PgStatCollector.from_cluster(self.cluster, 1049) pstype, action = collector._get_psinfo( 'postgres: checkpointer process') self.assertEqual('checkpointer', pstype) self.assertEqual('', action)
def test__get_psinfo_should_return_pstype_action_when_cmdline_matches_autovacuum_worker( self): collector = PgStatCollector.from_cluster(self.cluster, 1049) pstype, action = collector._get_psinfo( 'postgres: autovacuum worker process') self.assertEqual('autovacuum', pstype) self.assertEqual('', action)
def test_get_subprocesses_pid_should_return_empty_when_no_cmd_output( self, mocked_logger, mocked_process): mocked_process.return_value.children.return_value = [] collector = PgStatCollector.from_cluster(self.cluster, 1049) self.assertEqual([], collector.get_subprocesses_pid()) mocked_logger.info.assert_called_with( "Couldn't determine the pid of subprocesses for 1049")
def test_get_sql_by_pg_version_should_return_less_than_96_when_dbver_95( self): cluster = self.cluster.copy() cluster['ver'] = 9.5 collector = PgStatCollector.from_cluster(cluster, 1049) self.assertEqual(SELECT_PGSTAT_VERSION_LESS_THAN_96, collector.get_sql_pgstat_by_version())
def test__read_pg_stat_activity_should_parse_pg_stats_when_ok(self): results = [{ 'datname': 'postgres', 'client_addr': None, 'locked_by': None, 'pid': 11139, 'waiting': False, 'client_port': -1, 'query': 'idle', 'age': None, 'usename': 'postgres' }] self.cluster[ 'pgcon'].cursor.return_value.fetchall.return_value = results collector = PgStatCollector.from_cluster(self.cluster, [1049]) activity_stats = collector._read_pg_stat_activity() expected_stats = { 11139: { 'datname': 'postgres', 'client_addr': None, 'locked_by': None, 'pid': 11139, 'waiting': False, 'client_port': -1, 'query': 'idle', 'age': None, 'usename': 'postgres' } } self.assertEqual(expected_stats, activity_stats)
def test_query_status_fn_should_return_ok_when_no_warning(self): collector = PgStatCollector.from_cluster(self.cluster, 1049) formatter = StatusFormatter(collector) row = [ 11139, None, 'backend', None, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.9, '21:05:45', 'postgres', 'postgres', False, 'ok' ] col = {'warning': 'idle in transaction', 'critical': 'locked', 'out': 'query'} self.assertEqual({-1: 0}, formatter.query_status_fn(row, col))
def test_age_status_fn_should_return_ok_when_age_less_than_warning(self): collector = PgStatCollector.from_cluster(self.cluster, 1049) formatter = StatusFormatter(collector) row = [ 11139, None, 'backend', None, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.9, '0:04:05', 'postgres', 'postgres', False, 'idle in transaction for 20:51:17' ] col = {'warning': 300, 'critical': 500, 'out': 'age'} self.assertEqual({-1: 0}, formatter.age_status_fn(row, col))
def test_get_subprocesses_pid_should_return_subprocesses_when_children_processes( self, mocked_process): subprocesses = [ mock.Mock(pid=1051), mock.Mock(pid=1052), mock.Mock(pid=1206) ] mocked_process.return_value.children.return_value = subprocesses collector = PgStatCollector.from_cluster(self.cluster, 1049) self.assertEqual([1051, 1052, 1206], collector.get_subprocesses_pid())
def test_get_additional_info_should_update_when_backend_and_active_pid_in_track_pids( self, mocked__get_memory_usage, mocked__get_psinfo): collector = PgStatCollector.from_cluster(self.cluster, [1049]) mocked__get_psinfo.return_value = ('vacuum', None) mocked__get_memory_usage.return_value = 10 info = collector.get_additional_proc_info(1049, {'cmdline': ''}, {1049: { 'query': 'idle' }}) self.assertEqual({'type': 'backend', 'cmdline': '', 'uss': 10}, info)
def test_refresh_should_return_results_when_ok( self, mocked__do_refresh, mocked_get_proc_data, mocked_get_subprocesses_pid, mocked__read_pg_stat_activity, mocked___get_memory_usage): mocked_get_proc_data.return_value = { 'read_bytes': 655, 'write_bytes': 1, 'pid': 1049, 'status': 'status', 'utime': 0.0002, 'stime': 0.0001, 'rss': 432, 'priority': 10, 'vsize': 252428288, 'guest_time': 0.0, 'starttime': 911, 'delayacct_blkio_ticks': 1, 'cmdline': 'backend' } mocked__read_pg_stat_activity.return_value = { 11139: { 'datname': 'postgres', 'client_addr': None, 'locked_by': None, 'pid': 11139, 'waiting': False, 'client_port': -1, 'query': 'idle', 'age': None, 'usename': 'postgres' } } collector = PgStatCollector.from_cluster(self.cluster, [1049]) result = collector.refresh() expected_results = [{ 'status': 'status', 'write_bytes': 1, 'vsize': 252428288, 'delayacct_blkio_ticks': 1, 'pid': 1049, 'priority': 10, 'cmdline': 'backend', 'read_bytes': 655, 'uss': 10, 'stime': 0.0001, 'starttime': 911, 'utime': 0.0002, 'type': 'unknown', 'guest_time': 0.0, 'rss': 432 }] self.assertEqual(expected_results, result) mocked__do_refresh.assert_called_with(result)
def test_refresh_should_try_reconnect_whne_no_pgcon( self, mocked__do_refresh, mocked_get_proc_data, mocked_get_subprocesses_pid, mocked__read_pg_stat_activity, mocked___get_memory_usage, mocked_try_reconnect): mocked_get_proc_data.return_value = {} mocked__read_pg_stat_activity.return_value = {} collector = PgStatCollector.from_cluster(self.cluster, [1049]) collector.pgcon = None result = collector.refresh() mocked_try_reconnect.assert_called_with() mocked__do_refresh.assert_called_with(result)
def test_refresh_should_return_none_when_try_reconnect_raises_error( self, mocked__do_refresh, mocked_get_proc_data, mocked_get_subprocesses_pid, mocked__read_pg_stat_activity, mocked___get_memory_usage, mocked_try_reconnect): mocked_get_proc_data.return_value = {} mocked__read_pg_stat_activity.return_value = {} collector = PgStatCollector.from_cluster(self.cluster, [1049]) collector.pgcon = None mocked_try_reconnect.side_effect = psycopg2.OperationalError result = collector.refresh() self.assertIsNone(result) mocked__do_refresh.assert_called_with([])
def test__get_memory_usage_should_return_uss_when_memory_info_ok( self, mocked_psutil_process): mocked_psutil_process.return_value.memory_info.return_value = pmem( rss=1769472, vms=252428288, shared=344064, text=5492736, lib=0, data=1355776, dirty=0) collector = PgStatCollector.from_cluster(self.cluster, 1049) memory_usage = collector._get_memory_usage(1049) self.assertEqual(1425408, memory_usage)
def test__read_proc_should_return_data_when_process_ok( self, mocked_psutil_process, mocked_os): collector = PgStatCollector.from_cluster(self.cluster, 1049) mocked_os.sysconf.return_value.SC_PAGE_SIZE = 4096 mocked_process = mocked_psutil_process.return_value mocked_process.pid = 1049 mocked_process.status.return_value = 'status' mocked_process.io_counters.return_value = pio(read_count=12, write_count=13, read_bytes=655, write_bytes=1) cpu_times = pcputimes(user=0.02, system=0.01, children_user=0.0, children_system=0.0) memory_info = pmem(rss=1769472, vms=252428288, shared=344064, text=5492736, lib=0, data=1355776, dirty=0) mocked_process.cpu_times.return_value = cpu_times mocked_process.memory_info.return_value = memory_info mocked_process.nice.return_value = '10' mocked_process.cmdline.return_value = ['backend \n'] mocked_process.create_time.return_value = 1480777289.0 proc_stats = collector.get_proc_data(1048) expected_proc_stats = { 'read_bytes': 655, 'write_bytes': 1, 'pid': 1049, 'state': 'status', 'utime': 0.0002, 'stime': 0.0001, 'rss': 432, 'priority': 10, 'vsize': 252428288, 'guest_time': 0.0, 'starttime': datetime.datetime.fromtimestamp(1480777289.0), 'delayacct_blkio_ticks': 0, 'cmdline': 'backend' } self.assertEqual(expected_proc_stats, proc_stats)
def test__get_recovery_status_should_return_zero_when_output_ok( self, mocked_execute_fetchone_query): collector = PgStatCollector.from_cluster(self.cluster, 1049) self.assertEqual('role', collector._get_recovery_status()) mocked_execute_fetchone_query.assert_called_with( SELECT_PG_IS_IN_RECOVERY)
def test_kb_pretty_print_should_return_formatted_when_kb(self): collector = PgStatCollector.from_cluster(self.cluster, 1049) formatter = FnFormatter(collector) formatted_kb = formatter.kb_pretty_print(1024) self.assertEqual('1024KB', formatted_kb)
def test__get_psinfo_should_return_unknown_when_cmdline_not_match(self): collector = PgStatCollector.from_cluster(self.cluster, 1049) pstype, action = collector._get_psinfo('postgres1: worker process') self.assertEqual('unknown', pstype) self.assertIsNone(action)
def test_time_interval_pretty_print_should_return_formatted_when_start_time_number(self): collector = PgStatCollector.from_cluster(self.cluster, 1049) formatter = FnFormatter(collector) formatted_time = formatter.time_pretty_print(68852.0) self.assertEqual('19:07:32', formatted_time)
def test_time_interval_pretty_print_should_return_formatted_when_start_time_timedelta(self): collector = PgStatCollector.from_cluster(self.cluster, 1049) formatter = FnFormatter(collector) formatted_time = formatter.time_pretty_print(timedelta(seconds=30)) self.assertEqual('00:30', formatted_time)
def test__get_psinfo_should_return_pstype_action_when_cmdline_matches_postgres( self): collector = PgStatCollector.from_cluster(self.cluster, 1049) pstype, action = collector._get_psinfo('postgres: back') self.assertEqual('backend', pstype) self.assertIsNone(action)
def test__get_psinfo_should_return_empty_when_no_cmdline(self): pstype, action = PgStatCollector.from_cluster(self.cluster, 1049)._get_psinfo('') self.assertEqual('unknown', pstype) self.assertIsNone(action)
def test_time_interval_pretty_print_should_raise_error_when_non_valid_type(self): collector = PgStatCollector.from_cluster(self.cluster, 1049) formatter = FnFormatter(collector) with self.assertRaises(ValueError): formatter.time_pretty_print('None')
def test_ncurses_produce_prefix_should_return_offline_when_no_pgcon(self): self.cluster['pgcon'].get_parameter_status.return_value = '9.3' collector = PgStatCollector.from_cluster(self.cluster, [1049]) collector.pgcon = None self.assertEqual('/var/lib/postgresql/9.3/main 9.3 (offline)\n', collector.ncurses_produce_prefix())
def test_idle_format_fn_should_return_formatted_for_version_less_than_92(self): self.cluster['ver'] = 9.1 collector = PgStatCollector.from_cluster(self.cluster, 1049) formatter = FnFormatter(collector) formatted_idle = formatter.idle_format_fn('idle in transaction 1') self.assertEqual('idle in transaction 00:01 since the last query start', formatted_idle)
def test__get_max_connections_should_return_zero_when_output_ok( self, mocked_execute_fetchone_query): collector = PgStatCollector.from_cluster(self.cluster, 1049) self.assertEqual(1, collector._get_max_connections()) mocked_execute_fetchone_query.assert_called_with(SHOW_MAX_CONNECTIONS)
def main(): global options # bail out if we are not running Linux if platform.system() != 'Linux': print( 'Non Linux database hosts are not supported at the moment. Can not continue' ) sys.exit(243) options, args = parse_args() consts.TICK_LENGTH = options.tick output_method = options.output_method if not output_method_is_valid(output_method): print('Unsupported output method: {0}'.format(output_method)) print('Valid output methods are: {0}'.format(','.join( get_valid_output_methods()))) sys.exit(1) if output_method == OUTPUT_METHOD.curses and not curses_available: print( 'Curses output is selected, but curses are unavailable, falling back to console output' ) output_method = OUTPUT_METHOD.console log_stderr = setup_loggers(options) user_dbname = options.instance user_dbver = options.version clusters = [] # now try to read the configuration file config = (read_configuration(options.config_file) if options.config_file else None) if config: for instance in config: if user_dbname and instance != user_dbname: continue # pass already aquired connections to make sure we only list unique clusters. db_client = DBClient.from_config(config[instance]) try: cluster = db_client.establish_user_defined_connection( instance, clusters) except (NotConnectedError, NoPidConnectionError): msg = 'failed to acquire details about the database cluster {0}, the server will be skipped' loggers.logger.error(msg.format(instance)) except DuplicatedConnectionError: pass else: clusters.append(cluster) elif options.host: # try to connect to the database specified by command-line options instance = options.instance or "default" db_client = DBClient.from_options(options) try: cluster = db_client.establish_user_defined_connection( instance, clusters) except (NotConnectedError, NoPidConnectionError): loggers.logger.error( "unable to continue with cluster {0}".format(instance)) except DuplicatedConnectionError: pass else: clusters.append(cluster) else: # do autodetection postmasters = ProcWorker().get_postmasters_directories() # get all PostgreSQL instances for result_work_dir, connection_params in postmasters.items(): # if user requested a specific database name and version - don't try to connect to others try: validate_autodetected_conn_param(user_dbname, user_dbver, result_work_dir, connection_params) except InvalidConnectionParamError: continue db_client = DBClient.from_postmasters(result_work_dir, connection_params.pid, connection_params.version, options) if db_client is None: continue conn = db_client.connection_builder.build_connection() try: pgcon = psycopg2.connect(**conn) except Exception as e: loggers.logger.error('PostgreSQL exception {0}'.format(e)) pgcon = None if pgcon: desc = make_cluster_desc(name=connection_params.dbname, version=connection_params.version, workdir=result_work_dir, pid=connection_params.pid, pgcon=pgcon, conn=conn) clusters.append(desc) collectors = [] groups = {} try: if not clusters: loggers.logger.error( 'No suitable PostgreSQL instances detected, exiting...') loggers.logger.error( 'hint: use -v for details, or specify connection parameters ' 'manually in the configuration file (-c)') sys.exit(1) # initialize the disks stat collector process and create an exchange queue q = JoinableQueue(1) work_directories = [cl['wd'] for cl in clusters if 'wd' in cl] collector = DetachedDiskStatCollector(q, work_directories) collector.start() consumer = DiskCollectorConsumer(q) collectors.append(HostStatCollector()) collectors.append(SystemStatCollector()) collectors.append(MemoryStatCollector()) for cluster in clusters: partition_collector = PartitionStatCollector.from_cluster( cluster, consumer) pg_collector = PgStatCollector.from_cluster(cluster, options.pid) groups[cluster['wd']] = { 'pg': pg_collector, 'partitions': partition_collector } collectors.append(partition_collector) collectors.append(pg_collector) # we don't want to mix diagnostics messages with useful output, so we log the former into a file. loggers.logger.removeHandler(log_stderr) loop(collectors, consumer, groups, output_method) loggers.logger.addHandler(log_stderr) except KeyboardInterrupt: pass except curses.error: print(traceback.format_exc()) if 'SSH_CLIENT' in os.environ and 'SSH_TTY' not in os.environ: print( 'Unable to initialize curses. Make sure you supply -t option (force psedo-tty allocation) to ssh' ) except: print(traceback.format_exc()) finally: sys.exit(0)
def test_idle_format_fn_should_return_formatted_for_version_bigger_than_92(self): collector = PgStatCollector.from_cluster(self.cluster, 1049) formatter = FnFormatter(collector) formatted_idle = formatter.idle_format_fn('idle in transaction 1') self.assertEqual('idle in transaction for 00:01', formatted_idle)