text: /*
   * PgBouncer - Lightweight connection pooler for PostgreSQL.
   * 
   * Copyright (c) 2007-2009  Marko Kreen, Skype Technologies OÜ
   * 
   * Permission to use, copy, modify, and/or distribute this software for any
   * purpose with or without fee is hereby granted, provided that the above
   * copyright notice and this permission notice appear in all copies.
   * 
   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   */
  
  #include "bouncer.h"
  
  static struct event ev_stats;
  static usec_t old_stamp, new_stamp;
  
  static void reset_stats(PgStats *stat)
  {
  	stat->server_bytes = 0;
  	stat->client_bytes = 0;
  	stat->request_count = 0;
  	stat->query_time = 0;
  }
  
  static void stat_add(PgStats *total, PgStats *stat)
  {
  	total->server_bytes += stat->server_bytes;
  	total->client_bytes += stat->client_bytes;
  	total->request_count += stat->request_count;
  	total->query_time += stat->query_time;
  }
  
  static void calc_average(PgStats *avg, PgStats *cur, PgStats *old)
  {
  	uint64_t qcount;
  	usec_t dur = get_cached_time() - old_stamp;
  
  	reset_stats(avg);
  
  	if (dur <= 0)
  		return;
  
  	avg->request_count = USEC * (cur->request_count - old->request_count) / dur;
  	avg->client_bytes = USEC * (cur->client_bytes - old->client_bytes) / dur;
  	avg->server_bytes = USEC * (cur->server_bytes - old->server_bytes) / dur;
  	qcount = cur->request_count - old->request_count;
  	if (qcount > 0)
  		avg->query_time = (cur->query_time - old->query_time) / qcount;
  }
  
  static void write_stats(PktBuf *buf, PgStats *stat, PgStats *old, char *dbname)
  {
  	PgStats avg;
  	calc_average(&avg, stat, old);
  	pktbuf_write_DataRow(buf, "sqqqqqqqq", dbname,
  			     stat->request_count, stat->client_bytes,
  			     stat->server_bytes, stat->query_time,
  			     avg.request_count, avg.client_bytes,
  			     avg.server_bytes, avg.query_time);
  }
  
  bool admin_database_stats(PgSocket *client, StatList *pool_list)
  {
  	PgPool *pool;
  	List *item;
  	PgDatabase *cur_db = NULL;
  	PgStats st_total, st_db, old_db, old_total;
  	int rows = 0;
  	PktBuf *buf;
  
  	reset_stats(&st_total);
  	reset_stats(&st_db);
  	reset_stats(&old_db);
  	reset_stats(&old_total);
  
  	buf = pktbuf_dynamic(512);
  	if (!buf) {
  		admin_error(client, "no mem");
  		return true;
  	}
  
  	pktbuf_write_RowDescription(buf, "sqqqqqqqq", "database",
  				    "total_requests", "total_received",
  				    "total_sent", "total_query_time",
  				    "avg_req", "avg_recv", "avg_sent",
  				    "avg_query");
  	statlist_for_each(item, pool_list) {
  		pool = container_of(item, PgPool, head);
  
  		if (!cur_db)
  			cur_db = pool->db;
  
  		if (pool->db != cur_db) {
  			write_stats(buf, &st_db, &old_db, cur_db->name);
  
  			rows ++;
  			cur_db = pool->db;
  			stat_add(&st_total, &st_db);
  			stat_add(&old_total, &old_db);
  			reset_stats(&st_db);
  			reset_stats(&old_db);
  		}
  
  		stat_add(&st_db, &pool->stats);
  		stat_add(&old_db, &pool->older_stats);
  	}
  	if (cur_db) {
  		write_stats(buf, &st_db, &old_db, cur_db->name);
  		stat_add(&st_total, &st_db);
  		stat_add(&old_total, &old_db);
  		rows ++;
  	}
  	admin_flush(client, buf, "SHOW");
  
  	return true;
  }
  
  bool show_stat_totals(PgSocket *client, StatList *pool_list)
  {
  	PgPool *pool;
  	List *item;
  	PgStats st_total, old_total, avg;
  	PktBuf *buf;
  
  	reset_stats(&st_total);
  	reset_stats(&old_total);
  
  	buf = pktbuf_dynamic(512);
  	if (!buf) {
  		admin_error(client, "no mem");
  		return true;
  	}
  
  
  	statlist_for_each(item, pool_list) {
  		pool = container_of(item, PgPool, head);
  		stat_add(&st_total, &pool->stats);
  		stat_add(&old_total, &pool->older_stats);
  	}
  
  	calc_average(&avg, &st_total, &old_total);
  
  	pktbuf_write_RowDescription(buf, "sq", "name", "value");
  
  #define WTOTAL(name) pktbuf_write_DataRow(buf, "sq", "total_" #name, st_total.name)
  #define WAVG(name) pktbuf_write_DataRow(buf, "sq", "avg_" #name, avg.name)
  
  	WTOTAL(request_count);
  	WTOTAL(client_bytes);
  	WTOTAL(server_bytes);
  	WTOTAL(query_time);
  	WAVG(request_count);
  	WAVG(client_bytes);
  	WAVG(server_bytes);
  	WAVG(query_time);
  
  	admin_flush(client, buf, "SHOW");
  	return true;
  }
  
  static void refresh_stats(int s, short flags, void *arg)
  {
  	List *item;
  	PgPool *pool;
  	struct timeval period = { cf_stats_period, 0 };
  	PgStats old_total, cur_total, avg;
  
  	reset_stats(&old_total);
  	reset_stats(&cur_total);
  
  	old_stamp = new_stamp;
  	new_stamp = get_cached_time();
  
  	statlist_for_each(item, &pool_list) {
  		pool = container_of(item, PgPool, head);
  		pool->older_stats = pool->newer_stats;
  		pool->newer_stats = pool->stats;
  
  		stat_add(&cur_total, &pool->stats);
  		stat_add(&old_total, &pool->older_stats);
  	}
  	calc_average(&avg, &cur_total, &old_total);
  	/* send totals to logfile */
  	log_info("Stats: %" PRIu64 " req/s,"
  		 " in %" PRIu64 " b/s,"
  		 " out %" PRIu64 " b/s,"
  		 "query %" PRIu64 " us",
  		 avg.request_count, avg.client_bytes,
  		 avg.server_bytes, avg.query_time);
  
  	safe_evtimer_add(&ev_stats, &period);
  }
  
  void stats_setup(void)
  {
  	struct timeval period = { cf_stats_period, 0 };
  
  	new_stamp = get_cached_time();
  	old_stamp = new_stamp - USEC;
  
  	/* launch stats */
  	evtimer_set(&ev_stats, refresh_stats, NULL);
  	safe_evtimer_add(&ev_stats, &period);
  }
  
  