--[[
Copyright (C) 2013-2014 Draios inc.
Copyright (C) 2015 Brendan Gregg.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
--]]

-- Chisel description
description = "This visualizes the subsecond offset time of system call execution. This allows repetitive patterns to be identified, which are lost when averaging at a one second granularity. The Y axis (vertical) shows the passage of time. The X axis (horizontal) shows the passage of time within whole or fractions of a second. By default, the X axis range is 1000 milliseconds; this can be specified as an argument (try 100). Each bucket of the heat map, or spectrogram, is shown as a colored rectangle. The color shows how many syscalls fell into that time and subsecond offset range. It can be black (no calls), green (tens of calls/s), yellow (hundreds of calls/s) or red (Thousands of calls/s). Use this chisel in conjunction with filters to visualize latencies for certain processes, types of I/O activity, file systems, etc."
short_description = "Visualize subsecond offset execution time."
category = "CPU Usage"

-- Chisel argument list
args = {
	{
		name = "refresh_time",
		description = "chart refresh time in milliseconds",
		argtype = "int",
		optional = true
	},
}

require "common"
terminal = require "ansiterminal"
terminal.enable_color(true)

refresh_time = 1000 * 1000 * 1000
refresh_per_sec = 1 * 1000 * 1000 * 1000 / refresh_time
max_label_len = 0
frequencies = {}
colpalette = {22, 28, 64, 34, 2, 76, 46, 118, 154, 191, 227, 226, 11, 220, 209, 208, 202, 197, 9, 1}

-- Argument initialization
function on_set_arg(name, val)
	if name == "refresh_time" then
		refresh_time = parse_numeric_input(val, name) * 1 * 1000 * 1000
		refresh_per_sec = 1 * 1000 * 1000 * 1000 / refresh_time
		return true
	end

	return false
end

-- Initialization callback
function on_init()
	is_tty = sysdig.is_tty()

	if not is_tty then
		print("This chisel only works on ANSI terminals. Aborting.")
		return false
	end

	tinfo = sysdig.get_terminal_info()
	w = tinfo.width
	h = tinfo.height
	max_label_len = string.len("|" .. (0.9 * refresh_time / 10000000) .. "ms")

	-- trace syscall entry
	chisel.set_filter("evt.dir=>")

	rawtime = chisel.request_field("evt.rawtime")

	terminal.hidecursor()

	print("Tracing syscalls... red (hot) == high frequency <-> green == low frequency.\n")

	return true
end

-- Final chisel initialization
function on_capture_start()
	chisel.set_interval_ns(refresh_time)
	return true
end

-- Event parsing callback
function on_event()
	local subsec = evt.field(rawtime)

	-- subsec is normalized to terminal column location
	subsec = math.floor((((subsec * 1000 / refresh_time) % 1000) / 1000) * w)

	if frequencies[subsec] == nil then
		frequencies[subsec] = 1
	else
		frequencies[subsec] = frequencies[subsec] + 1
	end

	return true
end

function mkcol(n)
	local col = math.floor(math.log10(n * refresh_per_sec + 1) / math.log10(1.6))

	if col < 1 then
		col = 1
	end

	if col > #colpalette then
		col = #colpalette
	end

	return colpalette[col]
end

-- Periodic timeout callback
function on_interval(ts_s, ts_ns, delta)
	terminal.moveup(1)

	for x = 1, w do
		local fr = frequencies[x]

		if fr == nil or fr == 0 then
			terminal.setbgcol(0)
		else
			terminal.setbgcol(mkcol(fr))
		end

		io.write(" ")
	end

	io.write(terminal.reset .. "\n")

	local x = 0
	while true do
		if x >= w then
			break
		end

		local curtime = math.floor(x * 10 / w)
		local prevtime = math.floor((x - 1) * 10 / w)

		if curtime ~= prevtime then
			if (x <= w - max_label_len) then
				local tstr = "|" .. (math.floor(10 * x / w) * refresh_time / 10000000) .. "ms"
				io.write(tstr)
				x = x + string.len(tstr)
			else
				io.write(" ")
				x = x + 1
			end
		else
			io.write(" ")
			x = x + 1
		end
	end

	io.write("\n")

	frequencies = {}

	return true
end

function on_capture_end(ts_s, ts_ns, delta)
	if is_tty then
		print(terminal.reset)
		terminal.showcursor()
	end

	return true
end

