/**
 * Copyright (c) 2004-2005, Regents of the University of California
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the University of California, Los Angeles nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package avrora.monitors;

import avrora.arch.AbstractInstr;
import avrora.core.Program;
import avrora.sim.Simulator;
import avrora.sim.State;
import cck.stat.StatUtil;
import cck.text.*;
import cck.util.Option;
import java.util.*;

/**
 * The <code>ProfileMonitor</code> class represents a monitor that can collect profiling information such as
 * counts and branchcounts about the program as it executes.
 *
 * @author Ben L. Titzer
 */
public class ProfileMonitor extends MonitorFactory {

    public final Option.Bool CYCLES = newOption("record-cycles", true,
            "This option controls whether this monitor will record " +
            "the cycles consumed by each instruction or basic block. ");
    public final Option.Long PERIOD = newOption("period", 0,
            "This option specifies whether the profiling will be exact or periodic. When " +
            "this option is set to non-zero, then a sample of the program counter is taken at " +
            "the specified period in clock cycles, rather than through probes at each instruction.");
    public final Option.Bool CLASSES = newOption("instr-classes", false,
            "This option selects whether the profiling monitor will generate a report of the " +
            "types of instructions that were executed most frequently by the program.");

    /**
     * The <code>Monitor</code> inner class contains the probes and formatting code that
     * can report the profile for the program after it has finished executing.
     */
    public class Mon implements Monitor {
        public final Simulator simulator;
        public final Program program;

        public final long[] icount;
        public final long[] itime;

        Mon(Simulator s) {
            simulator = s;
            program = s.getProgram();

            // allocate a global array for the count of each instruction
            icount = new long[program.program_end];
            // allocate a global array for the cycles of each instruction
            itime = new long[program.program_end];

            long period = PERIOD.get();
            if ( period > 0 ) {
                // insert the periodic probe
                s.insertEvent(new PeriodicProfile(period), period);
            } else if ( CYCLES.get() ) {
                // insert the count and cycles probe
                s.insertProbe(new CCProbe());
            } else {
                // insert just the count probe
                s.insertProbe(new CProbe());
            }
        }

        /**
         * The <code>PeriodicProfile</code> class can be used as a simulator event to periodically
         * sample the program counter value. This can be used to get an approximation of
         * the execution profile.
         */
        public class PeriodicProfile implements Simulator.Event {
            private final long period;

            PeriodicProfile(long p) {
                period = p;
            }

            public void fire() {
                icount[simulator.getState().getPC()]++;
                simulator.insertEvent(this, period);
            }
        }

        /**
         * The <code>CCProbe</code> class implements a probe that keeps track of the
         * execution count of each instruction as well as the number of cycles that
         * it has consumed.
         */
        public class CCProbe implements Simulator.Probe {
            protected long timeBegan;

            public void fireBefore(State state, int pc) {
                icount[pc]++;
                timeBegan = state.getCycles();
            }

            public void fireAfter(State state, int pc) {
                itime[pc] += state.getCycles() - timeBegan;
            }
        }

        /**
         * The <code>CProbe</code> class implements a simple probe that keeps a count
         * of how many times each instruction in the program has been executed.
         */
        public class CProbe extends Simulator.Probe.Empty {

            public void fireBefore(State state, int pc) {
                icount[pc]++;
            }
        }

        public void report() {

            computeTotals();
            reportProfile();

            if ( CLASSES.get() ) {
                reportInstrProfile();
            }
            Terminal.nextln();
        }

        long totalcount;
        long totalcycles;

        private void reportProfile() {
            int imax = icount.length;

            TermUtil.printSeparator("Profiling results for node "+simulator.getID());
            Terminal.printGreen("       Address     Count  Run     Cycles     Cumulative");
            Terminal.nextln();
            TermUtil.printThinSeparator(Terminal.MAXLINE);

            // report the profile for each instruction in the program
            for (int cntr = 0; cntr < imax; cntr = program.getNextPC(cntr)) {
                int start = cntr;
                int runlength = 1;
                long curcount = icount[cntr];
                long cumulcycles = itime[cntr];

                // collapse long runs of equivalent counts (e.g. basic blocks)
                int nextpc;
                for (; cntr < imax - 2; cntr = nextpc) {
                    nextpc = program.getNextPC(cntr);
                    if (nextpc >= icount.length || icount[nextpc] != curcount) break;
                    runlength++;
                    cumulcycles += itime[nextpc];
                }

                // format the results appropriately (columnar)
                String cnt = StringUtil.rightJustify(curcount, 8);
                float pcnt = computePercent(runlength*curcount, cumulcycles);
                String percent = "";
                String addr;
                if (runlength > 1) {
                    // if there is a run, adjust the count and address strings appropriately
                    addr = StringUtil.addrToString(start) + '-' + StringUtil.addrToString(cntr);
                    percent = " x" + runlength;
                } else {
                    addr = "       " + StringUtil.addrToString(start);
                }

                percent = StringUtil.leftJustify(percent, 7);

                // compute the percentage of total execution time
                if (curcount != 0) {
                    percent += StringUtil.rightJustify(cumulcycles, 8);
                    percent += " = " + StringUtil.rightJustify(StringUtil.toFixedFloat(pcnt, 4),8) + " %";
                }

                TermUtil.reportQuantity(' ' + addr, cnt, percent);
            }
        }

        private void computeTotals() {
            // compute the total cycle count
            totalcycles = StatUtil.sum(itime);
            totalcount = StatUtil.sum(icount);
        }

        private float computePercent(long count, long cycles) {
            if ( CYCLES.get() )
                return 100.0f * cycles / totalcycles;
            else
                return 100.0f * count / totalcount;
        }

        class InstrProfileEntry implements Comparable<InstrProfileEntry> {
            String name;
            long count;
            long cycles;

            public int compareTo(InstrProfileEntry other) {
                if ( this.cycles > 0 ) {
                    if ( other.cycles > this.cycles ) return 1;
                    if ( other.cycles < this.cycles ) return -1;
                } else {
                    if ( other.count > this.count ) return 1;
                    if ( other.count < this.count ) return -1;
                }

                return 0;
            }
        }

        private void reportInstrProfile() {
            List<InstrProfileEntry> profile = computeInstrProfile();

            TermUtil.printSeparator(Terminal.MAXLINE, "Profiling Results by Instruction Type");
            Terminal.printGreen(" Instruction      Count    Cycles   Percent");
            Terminal.nextln();
            TermUtil.printThinSeparator(Terminal.MAXLINE);

            for (InstrProfileEntry ipe : profile) {
                float pcnt = computePercent(ipe.count, ipe.cycles);
                String p = StringUtil.toFixedFloat(pcnt, 4) + " %";
                Terminal.printGreen("   "+StringUtil.rightJustify(ipe.name, 9));
                Terminal.print(": ");
                Terminal.printBrightCyan(StringUtil.rightJustify(ipe.count, 9));
                Terminal.print("  "+StringUtil.rightJustify(ipe.cycles, 8));
                Terminal.print("  "+StringUtil.rightJustify(p, 10));
                Terminal.nextln();
            }
        }

        private List<InstrProfileEntry> computeInstrProfile() {
            HashMap<String,InstrProfileEntry> cmap = new HashMap<String,InstrProfileEntry>();

            for ( int cntr = 0; cntr < icount.length; cntr++ ) {
                if ( icount[cntr] == 0 ) continue;
                AbstractInstr i = program.readInstr(cntr);
                if ( i == null ) continue;
                String variant = i.getName();
                InstrProfileEntry entry = (InstrProfileEntry)cmap.get(variant);
                if  ( entry == null ) {
                    entry = new InstrProfileEntry();
                    entry.name = variant;
                    cmap.put(variant, entry);
                }
                entry.count += icount[cntr];
                entry.cycles += itime[cntr];
            }

            Enumeration<InstrProfileEntry> e = Collections.enumeration(cmap.values());
            List<InstrProfileEntry> l = Collections.list(e);
            Collections.sort(l);
            return l;
        }

    }

    public ProfileMonitor() {
        super("The \"profile\" monitor profiles the execution history " +
                "of every instruction in the program and generates a textual report " +
                "of the execution frequency for all instructions.");
    }

    public Monitor newMonitor(Simulator s) {
        return new Mon(s);
    }
}
