#!/usr/bin/env ruby
#
# kumofs
#
# Copyright (C) 2009 FURUHASHI Sadayuki
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
#

load File.dirname(__FILE__) + "/kumostat"


          #       1    2    3    4     5     6     7   8     9   10    11      12
INDEX  = %w[address #Get #Set #Del Get/s Set/s Del/s QPS items time clock status]
FORMAT_SMALL = %[%1$22s%2$9s%3$9s%4$9s%9$10s %10$19s %12$8s\n                      %5$9s%6$9s%7$9s%8$10s %11$19s]
FORMAT_LARGE = %[%23s %9s %9s %9s %8s %8s %8s %8s %10s %20s %8s %8s]
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"

class Node
	require "timeout"

	def initialize(host, port)
		@host = host
		@port = port
		@time = Time.at(0)
		@before_get = 0
		@before_set = 0
		@before_del = 0
		@con = nil
	end

	def param
		unless @con
			Timeout.timeout(0.5) {
				@con = KumoServer.new(@host, @port)
			}
		end

		get   = @con.GetStatus(KumoServer::STAT_CMD_GET)
		set   = @con.GetStatus(KumoServer::STAT_CMD_SET)
		del   = @con.GetStatus(KumoServer::STAT_CMD_DELETE)
		items = @con.GetStatus(KumoServer::STAT_DB_ITEMS)
		clocktime  = @con.GetStatus(KumoServer::STAT_CLOCKTIME)
		replacing  = @con.GetStatus(KumoServer::STAT_REPLACE)
		if replacing
			replacing = KumoServer.replace_stat_str(replacing)
		else
			replacing = '?'
		end
		time  = clocktime >> 32
		clock = clocktime & 0xffffffff

		now = Time.now
		elapse = @time - now
		@time = now

		get_incr = @before_get - get
		set_incr = @before_set - set
		del_incr = @before_del - del
		get_per_s = get_incr / elapse
		set_per_s = set_incr / elapse
		del_per_s = del_incr / elapse
		q_per_s = (get_incr + set_incr + del_incr) / elapse

		@before_get = get
		@before_set = set
		@before_del = del

		[
			"#{@host}:#{@port}",
			int_format(get),
			int_format(set),
			int_format(del),
			int_format(get_per_s),
			int_format(set_per_s),
			int_format(del_per_s),
			q_per_s.to_i,
			items,
			Time.at(time).strftime(TIME_FORMAT),
			clock,
			replacing
		]

	rescue
		@con.close rescue nil
		@con = nil
		ar = Array.new(INDEX.length)
		ar[0] = "#{@host}:#{@port}"
		ar[1] = $!.to_s
		ar
	end

	def int_format(n)
		n.to_i
		#if n < 10**3
		#	n.to_i
		#elsif n < 10**6
		#	"%.1fK" % [n.to_f/(10**3)]
		#elsif n < 10**12
		#	"%.1fG" % [n.to_f/(10**6)]
		#else
		#	"%.1fT" % [n.to_f/(10**9)]
		#end
	end
end


def usage
	puts "Usage: #{File.basename($0)} server-address[:port=#{KumoRPC::SERVER_DEFAULT_PORT}] ..."
	puts "       #{File.basename($0)} -m manager-address[:port=#{KumoRPC::MANAGER_DEFAULT_PORT}]"
	exit 1
end

if ARGV.empty?
	usage
end

if ARGV[0] == "-m"
	ARGV.shift
	if ARGV.length != 1
		usage
	end

	host, port = ARGV.shift.split(':', 2)
	port ||= KumoRPC::MANAGER_DEFAULT_PORT

	mgr = KumoManager.new(host, port)
	attached, not_attached, date, clock = mgr.GetStatus
	mgr.close

	@nodes = attached.map {|addr, port, active|
		Node.new(addr, port)
	}

else
	@nodes = ARGV.map {|addr|
		host, port = addr.split(':', 2)
		port ||= KumoRPC::SERVER_DEFAULT_PORT
		Node.new(host, port)
	}
end


require "curses"
Curses.init_screen

def refresh
	Curses.clear
	if Curses.stdscr.maxx - 1 >= 129
		format = FORMAT_LARGE
	else
		format = FORMAT_SMALL
	end

	index = format % INDEX
	Curses.setpos(0, 0)
	Curses.addstr index
	lines = index.count("\n")+1

	@nodes.each_with_index {|node, i|
		Curses.setpos((1+i)*lines, 0)
		Curses.addstr format % node.param
	}
	Curses.refresh
end


begin
	th = Thread.start {
		begin
			while true
				before = Time.now
				refresh
				elapse = Time.now - before
				if elapse < 0.5
					sleep 0.5 - elapse
				end
			end
		rescue
			$stderr.puts $!.inspect
		end
	}

	while true
		ch = Curses.getch
		if ch == 'q'.unpack('C').first
			break
		else
			refresh
		end
	end

ensure
	Curses.close_screen
end

