/*
 *  energy_model.c
 *
 *  Copyright (C) 2014, Linaro Limited.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  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, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Contributors:
 *     Larry Bassel <larry.bassel@linaro.org>
 *     Tuukka Tikkanen <tuukka.tikkanen@linaro.org>
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>

#include "energy_model.h"
#include "idlestat.h"
#include "topology.h"
#include "list.h"
#include "utils.h"

static struct cluster_energy_info *cluster_energy_table;
static unsigned int clusters_in_energy_file = 0;

static int make_energy_model_template(struct program_options *options)
{
	FILE *f;
	struct cpuidle_datas *datas;
	struct cpu_physical *s_phy;
	struct cpu_core     *s_core;
	struct cpu_cpu      *s_cpu;
	unsigned int cluster_count = 0;
	unsigned int cluster_number = 0;
	struct cpu_topology *cpu_topo = NULL;

	f = fopen(options->energy_model_filename, "w+");
	if (!f)
		return -1;
	fprintf(f, "# This is a energy model template generated by idlestat\n");
	fprintf(f, "# Lines starting with # or which are blank are ignored\n");
	fprintf(f, "# Replace ? with correct values\n");

	datas = idlestat_load(options->filename);
	if (is_err(datas)) {
		fclose(f);
		unlink(options->energy_model_filename);
		return -1;
	}
	cpu_topo = datas->topo;

	list_for_each_entry(s_phy, &cpu_topo->physical_head, list_physical)
		cluster_count++;

	fprintf(f, "clusters %d\n", cluster_count);

	list_for_each_entry(s_phy, &cpu_topo->physical_head, list_physical) {
		unsigned int num_cap_states = 0;
		unsigned int num_c_states = 0;
		int i;

		s_core = list_entry((&s_phy->core_head)->prev, struct cpu_core, list_core);
		s_cpu = list_entry((&s_core->cpu_head)->prev, struct cpu_cpu, list_cpu);
		num_c_states = s_cpu->cstates->cstate_max + 1;
		for (i = 0; i < s_cpu->pstates->max; i++) {
			struct cpufreq_pstate *p = &s_cpu->pstates->pstate[i];

			if (p->freq == 0)
				continue;

			num_cap_states++;
		}

		fprintf(f, "\nC-states:\n");

		fprintf(f, "cluster%c: %d cap states %d C states\n\n", cluster_number + 'A',
			num_cap_states, num_c_states);
		fprintf(f, "P-states:\n");
		fprintf(f, "# speed, cluster power, core power\n");

		for (i = 0; i < s_cpu->pstates->max; i++) {
			struct cpufreq_pstate *p = &s_cpu->pstates->pstate[i];

			if (p->freq == 0)
				continue;

			fprintf(f, "%d\t\t?\t?\n", p->freq/1000);
		}
		fprintf(f, "\nC-states:\n");
		fprintf(f, "# name, cluster power, core power\n");
		for (i = 0; i < s_cpu->cstates->cstate_max + 1; i++) {
			struct cpuidle_cstate *c = &s_cpu->cstates->cstate[i];

			fprintf(f, "%s\t\t?\t?\n", c->name);
		}
		fprintf(f, "\n");
		cluster_number++;
	}

	fclose(f);

	release_cpu_topo_cstates(cpu_topo);
	release_cpu_topo_info(cpu_topo);

	return 0;
}

int parse_energy_model(struct program_options *options)
{
	FILE *f;
	char tmp;
	struct cluster_energy_info *clustp = NULL;
	unsigned int number_cap_states, number_c_states;
	int current_cluster = -1;
	unsigned int current_pstate = 0;
	unsigned int current_cstate = 0;
	unsigned int clust_p, core_p;
	char buffer[BUFSIZE];
	char *path = options->energy_model_filename;
	int ret;

	assert(path != NULL);

	f = fopen(path, "r");
	if (!f) {
		if (errno == ENOENT) {
			if (options->mode == TRACE) {
				fprintf(stderr,
					"Energy model file %s does not "
					"exist.\nIdlestat was started in "
					"trace mode. If you wish to "
					"generate energy model\ntemplate, "
					"run idlestat in import mode.\n",
					path);
				return -1;
			}
			ret = make_energy_model_template(options);
			exit(ret ? 1 : 0);
		}
		fprintf(stderr, "%s: failed to open '%s': %m\n", __func__, path);
		return -1;
	}

	while (fgets(buffer, BUFSIZE, f)) {
		if (buffer[0] == '#') continue;
		if (strlen(buffer) == 1) continue;

		if (strstr(buffer, "clusters")) {
			if (clusters_in_energy_file) {
				fprintf(stderr, "%s: number of clusters already specified in %s\n",
					__func__, path);
				fclose(f);
				return -1;
			}
			sscanf(buffer, "%*s %d", &clusters_in_energy_file);
			cluster_energy_table = (struct cluster_energy_info *)realloc(cluster_energy_table,
                                               clusters_in_energy_file * sizeof(struct cluster_energy_info));
			continue;
		}
		if (strstr(buffer, "cluster") && !strstr(buffer, "cluster-")) {
			sscanf(buffer, "cluster%c: %d cap states %d C states", &tmp,
				&number_cap_states, &number_c_states);
			current_cluster = tmp - 'A';
			if (current_cluster >= (int) clusters_in_energy_file) {
				fprintf(stderr, "%s: cluster%c out of range in %s\n",
					__func__, tmp, path);
				fclose(f);
				return -1;
			}
			clustp = cluster_energy_table + current_cluster;
			if (clustp->number_cap_states) {
				fprintf(stderr, "%s: number of cap states for cluster%c already specified in %s\n",
					__func__, tmp, path);
				fclose(f);
				return -1;
			}
			clustp->number_cap_states = number_cap_states;
			clustp->number_c_states = number_c_states;
			clustp->p_energy = calloc(number_cap_states,
				sizeof(struct pstate_energy_info));
			clustp->c_energy = calloc(number_c_states,
				sizeof(struct cstate_energy_info));
			clustp->state = parsed_cluster_info;
			continue;
		}
		if (strstr(buffer, "P-states")) {
			if (current_cluster == -1) {
				fprintf(stderr, "%s: unknown cluster (cap states) in %s\n",
					__func__, path);
				fclose(f);
				return -1;
			}
			if (clustp->state < parsed_cluster_info) {
				fprintf(stderr, "%s: number of cap states for cluster%c not specified in %s\n",
					__func__, current_cluster, path);
				fclose(f);
				return -1;
			}
			current_pstate = 0;
			clustp->state = parsing_cap_states;
			continue;
		}
		if (strstr(buffer, "C-states")) {
			if (current_cluster == -1) {
				fprintf(stderr, "%s: unknown cluster (c states) in %s\n",
					__func__, path);
				fclose(f);
				return -1;
			}
			if (clustp->state < parsed_cluster_info) {
				fprintf(stderr, "%s: number of c states for cluster%c not specified in %s\n",
					__func__, current_cluster, path);
				fclose(f);
				return -1;
			}
			current_cstate = 0;
			clustp->state = parsing_c_states;
			continue;
		}
		if (strstr(buffer, "wakeup")) {
			unsigned int clust_w, core_w;

			if (current_cluster == -1) {
				fprintf(stderr, "%s: unknown cluster (wakeup) in %s\n",
					__func__, path);
				fclose(f);
				return -1;
			}
			sscanf(buffer, "%*s %d %d", &clust_w, &core_w);
			clustp->wakeup_energy.cluster_wakeup_energy = clust_w;
			clustp->wakeup_energy.core_wakeup_energy = core_w;
			continue;
		}
		if (!clustp) {
			fprintf(stderr, "%s: unknown cluster in %s\n",
				__func__, path);
			fclose(f);
			return -1;
			}
		if (clustp->state == parsing_cap_states) {
			struct pstate_energy_info *pp;
			unsigned int speed;

			if (sscanf(buffer, "%d %d %d", &speed, &clust_p, &core_p) != 3) {
				fprintf(stderr, "%s: expected P state (speed cluster core) for cluster%c in %s\n",
					__func__, current_cluster, path);
				fclose(f);
				return -1;
			}

			if (current_pstate >= clustp->number_cap_states) {
				fprintf(stderr, "%s: too many cap states specified for cluster%c in %s\n",
					__func__, current_cluster, path);
				fclose(f);
				return -1;
			}
			pp = &clustp->p_energy[current_pstate++];
			pp->speed = speed;
			pp->cluster_power = clust_p;
			pp->core_power = core_p;
			continue;
		}
		if (clustp->state == parsing_c_states) {
			char name[NAMELEN];
			struct cstate_energy_info *cp;

			if (sscanf(buffer, "%s %d %d", name, &clust_p, &core_p) != 3) {
				fprintf(stderr, "%s: expected C state (name cluster core) for cluster%c in %s\n",
					__func__, current_cluster, path);
				fclose(f);
				return -1;
			}

			if (current_cstate >= clustp->number_c_states) {
				fprintf(stderr, "%s: too many C states specified for cluster%c in %s\n",
					__func__, current_cluster, path);
				fclose(f);
				return -1;
			}
			cp = &clustp->c_energy[current_cstate++];
			strncpy(cp->cstate_name, name, NAMELEN);
			cp->cluster_idle_power = clust_p;
			cp->core_idle_power = core_p;
			continue;
		}
	}

	fclose(f);
	printf("Parsed energy model file successfully\n");

	return 0;
}

static struct cstate_energy_info *find_cstate_energy_info(const unsigned int cluster, const char *name)
{
	struct cluster_energy_info *clustp;
	struct cstate_energy_info *cp;
	unsigned int i;

	clustp = cluster_energy_table + cluster;
	cp = &clustp->c_energy[0];
	for (i = 0; i < clustp->number_c_states; i++, cp++) {
		if (!strcmp(cp->cstate_name, name)) return cp;
	}
	return NULL;
}

static struct pstate_energy_info *find_pstate_energy_info(const unsigned int cluster, const unsigned int speed)
{
	struct cluster_energy_info *clustp;
	struct pstate_energy_info *pp;
	unsigned int i;

	clustp = cluster_energy_table + cluster;
	pp = &clustp->p_energy[0];
	for (i = 0; i < clustp->number_cap_states; i++, pp++) {
		if (speed == pp->speed) return pp;
	}
	return NULL;
}

#define US_TO_SEC(US) (US / 1e6)

void calculate_energy_consumption(struct cpu_topology *cpu_topo)
{
	struct cpu_physical *s_phy;
	struct cpu_core     *s_core;
	struct cpu_cpu      *s_cpu;

	/* Overall energy breakdown */
	double total_energy = 0.0;
	double total_cap = 0.0;
	double total_idl = 0.0;

	/* Per cluster energy breakdown  */
	double cluster_energy;
	double cluster_cap;
	double cluster_idl;

	int i, j;
	unsigned int current_cluster;
	struct cstate_energy_info *cp;
	struct pstate_energy_info *pp;

	/* Contributions are computed per cluster */

	list_for_each_entry(s_phy, &cpu_topo->physical_head,
			    list_physical) {
		current_cluster = s_phy->physical_id;

		cluster_energy = 0.0;
		cluster_cap = 0.0;
		cluster_idl = 0.0;

		verbose_fprintf(stderr, 1, "\n\nCluster%c%37s | %13s | %7s | %7s | %12s | %12s | %12s |\n",
				'A' + current_cluster, "", "[us] Duration", "Power", "Energy", "E_cap", "E_idle", "E_wkup");

		/* All C-States on current cluster */

		for (j = 0; j < s_phy->cstates->cstate_max + 1; j++) {
			struct cpuidle_cstate *c = &s_phy->cstates->cstate[j];

			if (c->nrdata == 0) {
				verbose_fprintf(stderr, 2, 
						"      C%-2d       no hits for [%15s] |             0 |       0 |         |              |            0 |              |\n",
				 		j, c->name);
				continue;
			}

			cp = find_cstate_energy_info(current_cluster, c->name);
			if (!cp) {
				verbose_fprintf(stderr, 2, "      C%-2d no energy model for [%s] (%d hits, %f duration)\n",
				 		j, c->name, c->nrdata, c->duration);
				continue;
			}

			cluster_idl += c->duration * cp->cluster_idle_power;

			verbose_fprintf(stderr, 1, "      C%-2d +%7d hits for [%15s] | %13.0f | %7d | %7s | %12s | %12.0f | %12s |\n",
					j, c->nrdata, c->name,
					c->duration,
					cp->cluster_idle_power,
					"", "",
					cluster_idl,
					"");
		}

		/* Cluster P-state duration */
		for (j = 0; j < s_phy->pstates->max; j++) {
			struct cpufreq_pstate *p = &s_phy->pstates->pstate[j];

			if (p->freq == 0)
				continue;

			if (p->count == 0)
				continue;

			pp = find_pstate_energy_info(current_cluster, p->freq/1000);
			if (!pp) {
				verbose_fprintf(stderr, 2, "Cluster %c  frequency %u MHz no energy model for [%d] (%d hits, %f duration)\n",
					s_phy->physical_id + 'A', p->freq/1000,
					p->count, p->duration);
				continue;
			}

			cluster_cap += p->duration * pp->cluster_power;
			verbose_fprintf(stderr, 1, "          +%7d hits for [%11d MHz] | %13.0f | %7d | %7s | %12.0f | %12s | %12s |\n",
					p->count,
					pp->speed,
					p->duration,
					pp->cluster_power,
					"",
					cluster_cap,
					"", "");
		}

		/* All C-States and P-States for the CPUs on current cluster */
		list_for_each_entry(s_core, &s_phy->core_head, list_core) {

			list_for_each_entry(s_cpu, &s_core->cpu_head,
					    list_cpu) {

				/* All C-States of current CPU */

				for (i = 0; i < s_cpu->cstates->cstate_max + 1; i++) {
					struct cpuidle_cstate *c = &s_cpu->cstates->cstate[i];
					if (c->nrdata == 0) {
						verbose_fprintf(stderr, 2,
							"Cpu%d  C%-2d       no hits for [%15s] |             0 |       0 |         |              |            0 |              |\n",
						 	s_cpu->cpu_id, i, c->name);
						continue;
					}
					cp = find_cstate_energy_info(current_cluster, c->name);
					if (!cp) {
						verbose_fprintf(stderr, 2, "Cpu%d  C%-2d no energy model for [%s] (%d hits, %f duration)\n",
							s_cpu->cpu_id, i, c->name,
							c->nrdata, c->duration);
						continue;
					}
					cluster_idl += c->duration * cp->core_idle_power;

					verbose_fprintf(stderr, 1, "Cpu%d  C%-2d +%7d hits for [%15s] | %13.0f | %7d | %7s | %12s | %12.0f | %12s |\n",
							s_cpu->cpu_id, i, c->nrdata, c->name,
							c->duration,
							cp->core_idle_power,
							"", "",
							cluster_idl,
							"");
				}

				/* All P-States of current CPU */

				for (i = 0; i < s_cpu->pstates->max; i++) {
					struct cpufreq_pstate *p = &s_cpu->pstates->pstate[i];

					if (p->freq == 0)
						continue;

					if (p->count == 0) {
						verbose_fprintf(stderr, 2,
							"Cpu%d            no hits for [%11d MHz] |             0 |       0 |         |            0 |              |              |\n",
							s_cpu->cpu_id, p->freq/1000);
						continue;
					}
					pp = find_pstate_energy_info(current_cluster, p->freq/1000);
					if (!pp) {
						verbose_fprintf(stderr, 2, "Cpu%d  P%-2d no energy model for [%d] (%d hits, %f duration)\n",
							s_cpu->cpu_id, i, p->freq/1000,
							p->count, p->duration);
						continue;
					}

					cluster_cap += p->duration * pp->core_power;

					verbose_fprintf(stderr, 1, "Cpu%d      +%7d hits for [%11d MHz] | %13.0f | %7d | %7s | %12.0f | %12s | %12s |\n",
							s_cpu->cpu_id, p->count, p->freq/1000,
							p->duration, pp->core_power,
							"",
							cluster_cap,
							"", "");
				}
			}
		}

		verbose_fprintf(stderr, 1, "\n\nCluster%c%29s | %13s | %7s | %7s | %12s | %12s | %12s |\n",
				'A' + current_cluster, "", "[us] Duration", "Power", "Energy", "E_cap", "E_idle", "E_wkup");

		/* Convert all [us] components to [s] just here to avoid summing
		 * truncation errors due to small components */
		cluster_cap = US_TO_SEC(cluster_cap);
		cluster_idl = US_TO_SEC(cluster_idl);

		printf("Cluster%c Energy Caps  %14.0f (%e)\n",
				'A' + current_cluster, cluster_cap, cluster_cap);
		total_cap += cluster_cap;

		printf("Cluster%c Energy Idle  %14.0f (%e)\n",
				'A' + current_cluster, cluster_idl, cluster_idl);
		total_idl += cluster_idl;

		cluster_energy = cluster_cap + cluster_idl;
		total_energy += cluster_energy;

		printf("Cluster%c Energy Index %14.0f (%e)\n",
				'A' + current_cluster,
				cluster_energy, cluster_energy);
	}

	printf("\n   Total Energy Index %14.0f (%e)\n\n\n",
			total_energy, total_energy);
}
