/* $Id: global.c,v 1.26 2003/03/09 22:04:29 mikpe Exp $
 * global.c
 *
 * usage: ./global [sampling_interval_usec [sleep_interval_sec]]
 *
 * This test program illustrates how a process may use the
 * Linux x86 Performance-Monitoring Counters interface to
 * do system-wide performance monitoring.
 *
 * Copyright (C) 2000-2003  Mikael Pettersson
 */
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "libperfctr.h"

static struct gperfctr *gperfctr;
static struct perfctr_info info;
static unsigned int nrcpus;
static unsigned char cpu_logical_map[8*sizeof(long)];
static struct gperfctr_control *control;
static struct gperfctr_state *state;
static struct gperfctr_state *prev_state;
static unsigned int sample_num;
static int counting_mips;	/* for CPUs that cannot FLOPS */
static unsigned long sampling_interval = 1000000; /* XXX: reduce for >4GHz CPUs */
static unsigned int sleep_interval = 5;

static jmp_buf main_buf;

static void onint(int sig)	/* ^C handler */
{
    longjmp(main_buf, 1);
}

static void setup_cpu_logical_map_and_nrcpus(unsigned long cpus)
{
    unsigned int i, nr;
    unsigned long mask;

    i = 0;
    for(nr = 0, mask = 1; cpus != 0; nr += 1, mask <<= 1) {
	if( cpus & mask ) {
	    cpus &= ~mask;
	    cpu_logical_map[i] = nr;
	    ++i;
	}
    }
    nrcpus = i;
}

static void do_init(void)
{
    struct sigaction act;
    size_t nbytes;
    unsigned int i;

    gperfctr = gperfctr_open();
    if( !gperfctr ) {
	perror("gperfctr_open");
	exit(1);
    }
    if( gperfctr_info(gperfctr, &info) < 0 ) {
	perror("gperfctr_info");
	exit(1);
    }
    printf("\nPerfCtr Info:\n");
    perfctr_info_print(&info);

    memset(&act, 0, sizeof act);
    act.sa_handler = onint;
    if( sigaction(SIGINT, &act, NULL) < 0 ) {
	perror("unable to catch SIGINT");
	exit(1);
    }

    /* use all non-forbidden CPUs */

    setup_cpu_logical_map_and_nrcpus(info.cpus & ~info.cpus_forbidden);

    /* now alloc control and state memory based on nrcpus */

    nbytes = offsetof(struct gperfctr_control, cpu_control[0])
	+ nrcpus * sizeof(control->cpu_control[0]);
    control = malloc(nbytes);
    if( !control ) {
	perror("malloc");
	exit(1);
    }
    memset(control, 0, nbytes);

    nbytes = offsetof(struct gperfctr_state, cpu_state[0])
	+ nrcpus * sizeof(state->cpu_state[0]);
    state = malloc(nbytes);
    prev_state = malloc(nbytes);
    if( !state || !prev_state ) {
	perror("malloc");
	exit(1);
    }
    memset(state, 0, nbytes);
    memset(prev_state, 0, nbytes);

    /* format state to indicate which CPUs we want to sample */

    for(i = 0; i < nrcpus; ++i)
	state->cpu_state[i].cpu = cpu_logical_map[i];
    state->nrcpus = nrcpus;
}

static int do_read(unsigned int sleep_interval)
{
    unsigned int i, cpu, ctr;

    if( gperfctr_read(gperfctr, state) < 0 ) {
	perror("gperfctr_read");
	return -1;
    }
    printf("\nSample #%u\n", ++sample_num);
    for(i = 0; i < state->nrcpus; ++i) {
	cpu = state->cpu_state[i].cpu;
	printf("\nCPU %d:\n", cpu);
	if( state->cpu_state[i].cpu_control.tsc_on )
	    printf("\ttsc\t%lld\n", state->cpu_state[i].sum.tsc);
	for(ctr = 0; ctr < state->cpu_state[i].cpu_control.nractrs; ++ctr)
	    printf("\tpmc[%d]\t%lld\n",
		   ctr, state->cpu_state[i].sum.pmc[ctr]);
	if( ctr >= 1 ) {	/* compute and display MFLOP/s or MIP/s */
	    unsigned long long tsc = state->cpu_state[i].sum.tsc;
	    unsigned long long prev_tsc = prev_state->cpu_state[i].sum.tsc;
	    unsigned long long ticks = tsc - prev_tsc;
	    unsigned long long pmc0 = state->cpu_state[i].sum.pmc[0];
	    unsigned long long prev_pmc0 = prev_state->cpu_state[i].sum.pmc[0];
	    unsigned long long ops = pmc0 - prev_pmc0;
	    double seconds = state->cpu_state[i].cpu_control.tsc_on
		? ((double)ticks / (double)info.cpu_khz) / 1000.0
		: (double)sleep_interval; /* don't div-by-0 on WinChip ... */
	    printf("\tSince previous sample:\n");
	    printf("\tSECONDS\t%.15g\n", seconds);
	    printf("\t%s\t%llu\n", counting_mips ? "INSNS" : "FLOPS", ops);
	    printf("\t%s/s\t%.15g\n",
		   counting_mips ? "MIP" : "MFLOP",
		   ((double)ops / seconds) / 1e6);
	    prev_state->cpu_state[i].sum.tsc = tsc;
	    prev_state->cpu_state[i].sum.pmc[0] = pmc0;
	}
    }
    return 0;
}

static void print_control(const struct perfctr_cpu_control *control)
{
    printf("\nControl used:\n");
    perfctr_cpu_control_print(control);
}

static void setup_control(struct perfctr_cpu_control *control)
{
    unsigned int tsc_on = 1;
    unsigned int nractrs = 1;
    unsigned int pmc_map0 = 0;
    unsigned int evntsel0 = 0;

    memset(control, 0, sizeof *control);

    /* Attempt to set up control to count clocks via the TSC
       and FLOPS via PMC0. */
    switch( info.cpu_type ) {
      case PERFCTR_X86_GENERIC:
	nractrs = 0;		/* no PMCs available */
	break;
      case PERFCTR_X86_INTEL_P5:
      case PERFCTR_X86_INTEL_P5MMX:
      case PERFCTR_X86_CYRIX_MII:
	/* event 0x22 (FLOPS), any CPL */
	evntsel0 = 0x22 | (3 << 6);
	break;
      case PERFCTR_X86_INTEL_P6:
      case PERFCTR_X86_INTEL_PII:
      case PERFCTR_X86_INTEL_PIII:
	/* note: FLOPS is only available in PERFCTR0 */
	/* event 0xC1 (FLOPS), any CPL, Enable */
	evntsel0 = 0xC1 | (3 << 16) | (1 << 22);
	break;
      case PERFCTR_X86_AMD_K7:
	/* K7 apparently can't count FLOPS. */
	counting_mips = 1;
	/* event 0xC0 (RETIRED_INSTRUCTIONS), any CPL, Enable */
	evntsel0 = 0xC0 | (3 << 16) | (1 << 22);
	break;
      case PERFCTR_X86_AMD_K8:
	/* RETIRED_FPU_INSTRS, Unit Mask "x87 instrs", any CPL, Enable */
	evntsel0 = 0xCB | (0x01 << 8) | (3 << 16) | (1 << 22);
	break;
      case PERFCTR_X86_WINCHIP_C6:
	counting_mips = 1;	/* can't count FLOPS */
	tsc_on = 0;		/* no working TSC available */
	evntsel0 = 0x02;	/* X86_INSTRUCTIONS */
	break;
      case PERFCTR_X86_WINCHIP_2:
	counting_mips = 1;	/* can't count FLOPS */
	tsc_on = 0;		/* no working TSC available */
	evntsel0 = 0x16;	/* INSTRUCTIONS_EXECUTED */
	break;
      case PERFCTR_X86_VIA_C3:
	counting_mips = 1;	/* can't count FLOPS */
	pmc_map0 = 1;		/* redirect PMC0 to PERFCTR1 */
	evntsel0 = 0xC0;	/* INSTRUCTIONS_EXECUTED */
	break;
      case PERFCTR_X86_INTEL_P4:
      case PERFCTR_X86_INTEL_P4M2:
	nractrs = 2;
	/* set up PMC(1) to produce tagged x87_FP_uop:s */
	control->pmc_map[1] = 0x8 | (1 << 31);
	control->evntsel[1] = (0x3 << 16) | (1 << 13) | (1 << 12);
	control->p4.escr[1] = (4 << 25) | (1 << 24) | (1 << 5) | (1 << 4) | (1 << 2);
	/* set up PMC(0) to count execution_event(X87_FP_retired) */
	pmc_map0 = 0xC | (1 << 31);
	evntsel0 = (0x3 << 16) | (5 << 13) | (1 << 12);
	control->p4.escr[0] = (0xC << 25) | (1 << 9) | (1 << 2);
	break;
      default:
	fprintf(stderr, "cpu_type %u (%s) not supported\n",
		info.cpu_type, perfctr_info_cpu_name(&info));
	exit(1);
    }
    control->tsc_on = tsc_on;
    control->nractrs = nractrs;
    control->pmc_map[0] = pmc_map0;
    control->evntsel[0] = evntsel0;

    print_control(control);
}

static void do_enable(unsigned long sampling_interval)
{
    struct perfctr_cpu_control cpu_control;
    unsigned int i;

    setup_control(&cpu_control);

    for(i = 0; i < nrcpus; ++i) {
	control->cpu_control[i].cpu = cpu_logical_map[i];
	control->cpu_control[i].cpu_control = cpu_control;
    }
    control->nrcpus = nrcpus;
    control->interval_usec = sampling_interval;

    if( gperfctr_control(gperfctr, control) < 0 ) {
	perror("gperfctr_control");
	exit(1);
    }
}

int main(int argc, const char **argv)
{
    if( argc >= 2 ) {
	sampling_interval = strtoul(argv[1], NULL, 0);
	if( argc >= 3 )
	    sleep_interval = strtoul(argv[2], NULL, 0);
    }

    do_init();
    do_enable(sampling_interval);

    printf("\nSampling interval:\t%lu usec\n", sampling_interval);
    printf("Sleep interval:\t\t%u sec\n", sleep_interval);

    if( setjmp(main_buf) == 0 ) {
	do {
	    sleep(sleep_interval);
	} while( do_read(sleep_interval) == 0 );
    }
    printf("shutting down..\n");
    gperfctr_stop(gperfctr);
    return 0;
}
