/*
 * hyptop - Show hypervisor performance data on System z
 *
 * Hyptop z/VM data gatherer that operates on debugfs
 *
 * Copyright IBM Corp. 2010, 2017
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#include "dg_debugfs.h"
#include "helper.h"
#include "hyptop.h"
#include "sd.h"

#define VM_CPU_TYPE	"UN"
#define VM_CPU_ID	"ALL"
#define NAME_LEN	8
#define DEBUGFS_FILE	"diag_2fc"
#define VM_CPU_ID_OPERATING	"0"
#define VM_CPU_ID_STOPPED	"1"

static u64 l_update_time_us;
static long l_2fc_buf_size;
static int l_use_debugfs_vmd0c;
static char l_guest_name[64];

/*
 * Diag 2fc data structure definition
 */
struct l_diag2fc_data {
	u32	version;
	u32	flags;
	u64	used_cpu;
	u64	el_time;
	u64	mem_min_kb;
	u64	mem_max_kb;
	u64	mem_share_kb;
	u64	mem_used_kb;
	u32	pcpus;
	u32	lcpus;
	u32	vcpus;
	u32	ocpus;
	u32	cpu_max;
	u32	cpu_shares;
	u32	cpu_use_samp;
	u32	cpu_delay_samp;
	u32	page_wait_samp;
	u32	idle_samp;
	u32	other_samp;
	u32	total_samp;
	char	guest_name[NAME_LEN];
};

/*
 * Header for debugfs file "diag_2fc"
 */
struct l_debugfs_d2fc_hdr {
	u64	len;
	u16	version;
	char	tod_ext[16];
	u64	count;
	char	reserved[30];
} __attribute__ ((packed));

struct l_debugfs_d2fc {
	struct l_debugfs_d2fc_hdr	h;
	char			diag2fc_buf[];
} __attribute__ ((packed));

 /*
 * Get local guest name
 */
static void l_guest_name_init(void)
{
	int level, found = 0;
	char line[1024];
	FILE *fh;

	fh = fopen("/proc/sysinfo", "r");
	if (!fh)
		ERR_EXIT_ERRNO("Could not open '/proc/sysinfo'");
	while (fgets(line, sizeof(line), fh)) {
		if (sscanf(line, "VM%02d Name: %s", &level, l_guest_name) == 2)
			found = 1;
	}
	if (!found)
		ERR_EXIT("Could find guest name in '/proc/sysinfo'");
	fclose(fh);
}

/*
 * Get existing or create new CPU
 */
static struct sd_cpu *l_cpu_alloc(struct sd_sys *guest, const char *id, int cnt)
{
	struct sd_cpu *cpu = sd_cpu_get(guest, id);

	return cpu ? cpu : sd_cpu_new(guest, id, SD_CPU_TYPE_STR_UN, cnt);
}

/*
 * Get number of operating CPUs
 */
static int l_ocpus(struct l_diag2fc_data *data)
{
	/*
	 * For guests with ABSOLUTE or limit SHARE, ocpus and cpu_max is zero.
	 * In this case we return vcpus.
	 */
	return (data->cpu_max == 0) ? data->vcpus : data->ocpus;
}

/*
 * Fill operating CPUs with data
 */
static void l_cpu_oper_fill(struct sd_sys *guest, struct l_diag2fc_data *data)
{
	struct sd_cpu *cpu;

	cpu = l_cpu_alloc(guest, VM_CPU_ID_OPERATING, l_ocpus(data));
	sd_cpu_state_set(cpu, SD_CPU_STATE_OPERATING);
	sd_cpu_cpu_time_us_set(cpu, data->used_cpu);
	sd_cpu_online_time_us_set(cpu, data->el_time);
	sd_cpu_cnt(cpu) = l_ocpus(data);
	sd_cpu_commit(cpu);
}

/*
 * Fill stopped CPUs with data
 */
static void l_cpu_stop_fill(struct sd_sys *guest, struct l_diag2fc_data *data)
{
	int cnt = data->vcpus - l_ocpus(data);
	struct sd_cpu *cpu;

	cpu = l_cpu_alloc(guest, VM_CPU_ID_STOPPED, cnt);
	sd_cpu_state_set(cpu, SD_CPU_STATE_STOPPED);
	sd_cpu_cpu_time_us_set(cpu, 0);
	sd_cpu_online_time_us_set(cpu, 0);
	sd_cpu_cnt(cpu) = cnt;
	sd_cpu_commit(cpu);
}

/*
 * Fill CPUs will data
 */
static void l_cpu_fill(struct sd_sys *guest, struct l_diag2fc_data *data)
{
	if (l_ocpus(data) > 0)
		l_cpu_oper_fill(guest, data);
	if (data->vcpus - l_ocpus(data) > 0)
		l_cpu_stop_fill(guest, data);
}

/*
 * Fill "guest" with data
 */
static void l_sd_sys_fill(struct sd_sys *guest, struct l_diag2fc_data *data)
{
	if (l_use_debugfs_vmd0c && (strcmp(guest->id, l_guest_name) == 0))
		dg_debugfs_vmd0c_sys_cpu_fill(guest, data->el_time,
					      data->vcpus);
	else
		l_cpu_fill(guest, data);

	sd_sys_weight_cur_set(guest, data->cpu_shares);
	sd_sys_weight_max_set(guest, data->cpu_max);

	sd_sys_mem_min_kib_set(guest, data->mem_min_kb);
	sd_sys_mem_max_kib_set(guest, data->mem_max_kb);
	sd_sys_mem_use_kib_set(guest, data->mem_used_kb);

	sd_sys_update_time_us_set(guest, l_update_time_us);
	sd_sys_commit(guest);
}

/*
 * Read debugfs file
 */
static void l_read_debugfs(struct l_debugfs_d2fc_hdr **hdr,
			   struct l_diag2fc_data **data)
{
	long real_buf_size;
	ssize_t rc;
	void *buf;
	int fh;

	do {
		fh = dg_debugfs_open(DEBUGFS_FILE);
		*hdr = buf = ht_alloc(l_2fc_buf_size);
		rc = read(fh, buf, l_2fc_buf_size);
		if (rc == -1)
			ERR_EXIT_ERRNO("Reading hypervisor data failed");
		close(fh);
		real_buf_size = (*hdr)->len + sizeof(struct l_debugfs_d2fc_hdr);
		if (rc == real_buf_size)
			break;
		l_2fc_buf_size = real_buf_size;
		ht_free(buf);
	} while (1);
	*data = buf + sizeof(struct l_debugfs_d2fc_hdr);
}

/*
 * Fill System Data
 */
static void l_sd_sys_root_fill(struct sd_sys *sys)
{
	struct l_diag2fc_data *d2fc_data;
	struct l_debugfs_d2fc_hdr *hdr;
	struct sd_cpu *cpu;
	unsigned int i;

	do {
		l_read_debugfs(&hdr, &d2fc_data);
		if (l_update_time_us != ht_ext_tod_2_us(&hdr->tod_ext)) {
			l_update_time_us = ht_ext_tod_2_us(&hdr->tod_ext);
			break;
		}
		/*
		 * Got old snapshot from kernel. Wait some time until
		 * new snapshot is available.
		 */
		ht_free(hdr);
		usleep(DBFS_WAIT_TIME_US);
	} while (1);

	cpu = sd_cpu_get(sys, VM_CPU_ID);
	if (!cpu)
		cpu = sd_cpu_new(sys, VM_CPU_ID, SD_CPU_TYPE_STR_UN,
				 d2fc_data[0].lcpus);
	sd_cpu_state_set(cpu, SD_CPU_STATE_UNKNOWN);
	sd_cpu_cnt(cpu) = d2fc_data[0].lcpus;
	sd_cpu_commit(cpu);

	for (i = 0; i < hdr->count; i++) {
		struct l_diag2fc_data *data = &d2fc_data[i];
		char guest_name[NAME_LEN + 1];
		struct sd_sys *guest;

		guest_name[NAME_LEN] = 0;
		ht_ebcdic_to_ascii(data->guest_name, guest_name, NAME_LEN);
		ht_strstrip(guest_name);

		guest = sd_sys_get(sys, guest_name);
		if (!guest)
			guest = sd_sys_new(sys, guest_name);
		l_sd_sys_fill(guest, data);
	}
	ht_free(hdr);
	sd_sys_commit(sys);
}

/*
 * Update system data
 */
static void l_sd_update(void)
{
	struct sd_sys *root = sd_sys_root_get();

	sd_sys_update_start(root);
	l_sd_sys_root_fill(root);
	sd_sys_update_end(root, l_update_time_us);
}

/*
 * Supported system items
 */
static struct sd_sys_item *l_sys_item_vec[] = {
	&sd_sys_item_cpu_cnt,
	&sd_sys_item_cpu_oper_cnt,
	&sd_sys_item_cpu_diff,
	&sd_sys_item_mgm_diff,
	&sd_sys_item_cpu,
	&sd_sys_item_mgm,
	&sd_sys_item_online,
	&sd_sys_item_mem_use,
	&sd_sys_item_mem_max,
	&sd_sys_item_weight_cur,
	&sd_sys_item_weight_max,
	NULL,
};

/*
 * Default system items
 */
static struct sd_sys_item *l_sys_item_enable_vec[] = {
	&sd_sys_item_cpu_cnt,
	&sd_sys_item_cpu_diff,
	&sd_sys_item_cpu,
	&sd_sys_item_online,
	&sd_sys_item_mem_max,
	&sd_sys_item_mem_use,
	&sd_sys_item_weight_cur,
	NULL,
};

/*
 * Supported CPU items
 */
static struct sd_cpu_item *l_cpu_item_vec[] = {
	&sd_cpu_item_cpu_diff,
	&sd_cpu_item_mgm_diff,
	&sd_cpu_item_cpu,
	&sd_cpu_item_mgm,
	&sd_cpu_item_online,
	NULL,
};

/*
 * Default CPU items
 */
static struct sd_cpu_item *l_cpu_item_enable_vec[] = {
	&sd_cpu_item_cpu_diff,
	NULL,
};

/*
 * Supported CPU types
 */
static struct sd_cpu_type *l_cpu_type_vec[] = {
	&sd_cpu_type_un,
	NULL,
};

/*
 * Define data gatherer structure
 */
static struct sd_dg dg_debugfs_vm_dg = {
	.update_sys		= l_sd_update,
	.cpu_type_vec		= l_cpu_type_vec,
	.sys_item_vec		= l_sys_item_vec,
	.sys_item_enable_vec	= l_sys_item_enable_vec,
	.cpu_item_vec		= l_cpu_item_vec,
	.cpu_item_enable_vec	= l_cpu_item_enable_vec,
};

/*
 * Initialize z/VM debugfs data gatherer
 */
int dg_debugfs_vm_init(void)
{
	int fh;

	fh = dg_debugfs_vmd0c_init();
	if (fh == 0)
		l_use_debugfs_vmd0c = 1;
	fh = dg_debugfs_open(DEBUGFS_FILE);
	if (fh < 0)
		return fh;
	else
		close(fh);
	l_2fc_buf_size = sizeof(struct l_debugfs_d2fc_hdr);
	l_guest_name_init();
	sd_dg_register(&dg_debugfs_vm_dg, 0);
	return 0;
}
