/*
 * \brief  Application to show highest CPU consumer per CPU via LOG session
 * \author Norman Feske
 *         Alexander Boettcher
 * \date   2015-06-15
 */

/*
 * Copyright (C) 2015-2017 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

/* Genode includes */
#include <trace_session/connection.h>
#include <timer_session/connection.h>
#include <base/component.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <os/reporter.h>

enum SORT_TIME { EC_TIME = 0, SC_TIME = 1};

struct Trace_subject_registry
{
	private:

		struct Entry : Genode::List<Entry>::Element
		{
			Genode::Trace::Subject_id const id;

			Genode::Trace::Subject_info info { };

			/**
			 * Execution time during the last period
			 */
			Genode::uint64_t recent_time[2] = { 0, 0 };

			Entry(Genode::Trace::Subject_id id) : id(id) { }

			void update(Genode::Trace::Subject_info const &new_info)
			{
				if (new_info.execution_time().thread_context < info.execution_time().thread_context)
					recent_time[EC_TIME] = 0;
				else
					recent_time[EC_TIME] = new_info.execution_time().thread_context -
				                           info.execution_time().thread_context;

				if (new_info.execution_time().scheduling_context < info.execution_time().scheduling_context)
					recent_time[SC_TIME] = 0;
				else
					recent_time[SC_TIME] = new_info.execution_time().scheduling_context -
				                           info.execution_time().scheduling_context;

				info = new_info;
			}
		};

		Genode::List<Entry> _entries { };

		Entry *_lookup(Genode::Trace::Subject_id const id)
		{
			for (Entry *e = _entries.first(); e; e = e->next())
				if (e->id == id)
					return e;

			return nullptr;
		}

		enum { MAX_CPUS_X = 16, MAX_CPUS_Y = 4, MAX_ELEMENTS_PER_CPU = 6};

		/* accumulated execution time on all CPUs */
		unsigned long long total_first [MAX_CPUS_X][MAX_CPUS_Y];
		unsigned long long total_second [MAX_CPUS_X][MAX_CPUS_Y];

		/* most significant consumer per CPU */
		Entry const * load[MAX_CPUS_X][MAX_CPUS_Y][MAX_ELEMENTS_PER_CPU];

	public:

		bool update(Genode::Trace::Connection &trace,
		            Genode::Allocator &alloc)
		{
			auto res = trace.for_each_subject_info([&](Genode::Trace::Subject_id const &id,
			                                           Genode::Trace::Subject_info const &info)
			{
				Entry *e = _lookup(id);
				if (!e) {
					e = new (alloc) Entry(id);
					_entries.insert(e);
				}

				e->update(info);

				/* remove dead threads which did not run in the last period */
				if (e->info.state() == Genode::Trace::Subject_info::DEAD &&
				    !e->recent_time[EC_TIME] && !e->recent_time[SC_TIME]) {

					trace.free(e->id);
					_entries.remove(e);
					Genode::destroy(alloc, e);
				}
			});

			return res.count < res.limit;
		}

		void flush(Genode::Trace::Connection &trace, Genode::Allocator &alloc)
		{
			while (Entry * const e = _entries.first()) {
					trace.free(e->id);
					_entries.remove(e);
					Genode::destroy(alloc, e);
			}
		}

		void top(enum SORT_TIME sorting)
		{
			/* clear old calculations */
			Genode::bzero(total_first,  sizeof(total_first));
			Genode::bzero(total_second, sizeof(total_second));
			Genode::bzero(load,         sizeof(load));

			unsigned const first  = sorting == EC_TIME ? EC_TIME : SC_TIME;
			unsigned const second = sorting == EC_TIME ? SC_TIME : EC_TIME;

			for (Entry const *e = _entries.first(); e; e = e->next()) {

				/* collect highest execution time per CPU */
				unsigned const x = e->info.affinity().xpos();
				unsigned const y = e->info.affinity().ypos();
				if (x >= MAX_CPUS_X || y >= MAX_CPUS_Y) {
					Genode::error("cpu ", e->info.affinity().xpos(), ".",
					              e->info.affinity().ypos(), " is outside "
					              "supported range ",
					              (int)MAX_CPUS_X, ".", (int)MAX_CPUS_Y);
					continue;
				}

				total_first[x][y] += e->recent_time[first];
				total_second[x][y] += e->recent_time[second];

				enum { NONE = ~0U };
				unsigned replace = NONE;

				for (unsigned i = 0; i < MAX_ELEMENTS_PER_CPU; i++) {
					if (load[x][y][i])
						continue;

					replace = i;
					break;
				}

				if (replace != NONE) {
					load[x][y][replace] = e;
					continue;
				}

				for (unsigned i = 0; i < MAX_ELEMENTS_PER_CPU; i++) {
					if (e->recent_time[first]
					    <= load[x][y][i]->recent_time[first])
						continue;

					if (replace == NONE) {
						replace = i;
						continue;
					}
					if (load[x][y][replace]->recent_time[first]
					    > load[x][y][i]->recent_time[first])
						replace = i;
				}

				if (replace != NONE)
					load[x][y][replace] = e;
			}

			/* sort */
			for (unsigned x = 0; x < MAX_CPUS_X; x++) {
				for (unsigned y = 0; y < MAX_CPUS_Y; y++) {
					for (unsigned k = 0; k < MAX_ELEMENTS_PER_CPU;) {
						if (!load[x][y][k])
							break;

						unsigned i = k;
						for (unsigned j = i; j < MAX_ELEMENTS_PER_CPU; j++) {
							if (!load[x][y][j])
								break;

							if (load[x][y][i]->recent_time[first]
							    < load[x][y][j]->recent_time[first]) {

								Entry const * tmp = load[x][y][j];
								load[x][y][j] = load[x][y][i];
								load[x][y][i] = tmp;

								i++;
								if (i >= MAX_ELEMENTS_PER_CPU || !load[x][y][i])
									break;
							}
						}
						if (i == k)
							k++;
					}
				}
			}

			for (unsigned x = 0; x < MAX_CPUS_X; x++) {
				for (unsigned y = 0; y < MAX_CPUS_Y; y++) {
					for (unsigned i = 0; i < MAX_ELEMENTS_PER_CPU; i++) {

						if (!load[x][y][i])
							continue;

						Entry const &entry = *load[x][y][i];

						bool guessed = false;
						unsigned ec_percent = 0;
						unsigned ec_rest    = 0;
						if (total_first[x][y]) {
							ec_percent = (unsigned)(entry.recent_time[first] * 100   / total_first[x][y]);
							ec_rest    = (unsigned)(entry.recent_time[first] * 10000 / total_first[x][y] - (ec_percent * 100));
						} else {
							guessed = true;
							if (entry.info.thread_name()   == "idle" ||
							    entry.info.session_label() == "kernel") {
								ec_percent = 100;
							}
						}

						unsigned sc_percent = 0;
						unsigned sc_rest    = 0;
						if (total_second[x][y]) {
							sc_percent = (unsigned)(entry.recent_time[second] * 100   / total_second[x][y]);
							sc_rest    = (unsigned)(entry.recent_time[second] * 10000 / total_second[x][y] - (sc_percent * 100));
						}

						enum { NAME_SPACE = 24 };
						static char space[NAME_SPACE];
						for (char &c : space) c = ' ';
						space[NAME_SPACE - 1] = 0;

						Genode::size_t const thread_name_len = entry.info.thread_name().length();
						if (!thread_name_len)
							space[NAME_SPACE - 1] = 0;
						else
						if (thread_name_len >= NAME_SPACE)
							space[0] = 0;
						else
							space[NAME_SPACE - thread_name_len] = 0;

						Genode::String<NAME_SPACE> space_string(space);

						using Genode::log;
						log("cpu=", entry.info.affinity().xpos(),
						    ".", entry.info.affinity().ypos(),
						    " ", _align_right<4>(entry.info.execution_time().priority),
						    " ", _align_right<6>(entry.info.execution_time().quantum),
						    " ", _align_right<4>(ec_percent),
						    ".", _align_right<3>(ec_rest, true), "%",
						    guessed ? "?" : " ",
						    " ", _align_right<4>(sc_percent),
						    ".", _align_right<3>(sc_rest, true), "% "
						    "thread='", entry.info.thread_name(), "' ", space_string,
						    "label='", entry.info.session_label(), "'");
					}
				}
			}
			if (load[0][0][0] && load[0][0][0]->recent_time[first])
				Genode::log("");
		}

		template <int T>
		Genode::String<T> _align_right(Genode::uint64_t value, bool zero = false)
		{
			Genode::String<T> result(value);

			for (Genode::uint64_t i = 1, pow = 10; i < (T - 1); i++, pow *= 10) {
				if (value < pow)
					result = Genode::String<T>(zero ? "0" : " ", result);
			}

			return result;
		}
};


namespace App {

	struct Main;
	using namespace Genode;
}


struct App::Main
{
	Env &_env;

	size_t arg_buffer_ram  { 12 * 4096 }; /* ~ 190 trace Subject_info objects */
	size_t trace_ram_quota { arg_buffer_ram + 4 * 4096 };

	Reconstructible<Trace::Connection> _trace { _env,
	                                            trace_ram_quota,
	                                            arg_buffer_ram };

	static uint64_t _default_period_ms() { return 5000; }

	uint64_t _period_ms = _default_period_ms();

	SORT_TIME _sort { EC_TIME };

	Attached_rom_dataspace _config { _env, "config" };

	Timer::Connection _timer { _env };

	Heap _heap { _env.ram(), _env.rm() };

	Trace_subject_registry _trace_subject_registry { };

	void _handle_config();

	Signal_handler<Main> _config_handler = {
		_env.ep(), *this, &Main::_handle_config};

	void _handle_period();

	Signal_handler<Main> _periodic_handler = {
		_env.ep(), *this, &Main::_handle_period};

	Main(Env &env) : _env(env)
	{
		_config.sigh(_config_handler);
		_handle_config();

		_timer.sigh(_periodic_handler);
	}
};


void App::Main::_handle_config()
{
	_config.update();

	_period_ms = _config.node().attribute_value("period_ms", _default_period_ms());

	String<8> ec_sc(_config.node().attribute_value("sort_time", String<8>("ec")));
	if (ec_sc == "ec")
		_sort = EC_TIME;
	else
		_sort = SC_TIME;

	log("sorting based on ",
	    _sort == EC_TIME ? "execution context (ec) [other option is scheduling context (sc)]"
	                     : "scheduling context (sc) [other option is execution context (ec)]");

	_timer.trigger_periodic(1000*_period_ms);
}


void App::Main::_handle_period()
{
	/* update subject information */
	bool const arg_buffer_sufficient = _trace_subject_registry.update(*_trace,
	                                                                  _heap);
	if (arg_buffer_sufficient) {
		/* show most significant consumers */
		_trace_subject_registry.top(_sort);
		return;
	}

	enum { ARG_RAM_UPGRADE = 4 * 4096 };
	trace_ram_quota += ARG_RAM_UPGRADE;
	arg_buffer_ram  += ARG_RAM_UPGRADE;

	/* by destructing the session we free up the allocated memory in core */
	Genode::warning("re-construct trace session");

	_trace_subject_registry.flush(*_trace, _heap);

	_trace.destruct();
	_trace.construct(_env, trace_ram_quota, arg_buffer_ram);
}


void Component::construct(Genode::Env &env) { static App::Main main(env); }

