SquirrelJME/SquirrelJME

View on GitHub
emulators/emulator-base/src/main/java/cc/squirreljme/emulator/profiler/ProfiledThread.java

Summary

Maintainability
A
0 mins
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package cc.squirreljme.emulator.profiler;

import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;

/**
 * This stores information along with the thread information along with the
 * call stack.
 *
 * This class is not thread safe and it is assumed to be called from the same
 * thread each time.
 *
 * @since 2018/11/10
 */
public final class ProfiledThread
{
    /** The name of this thread. */
    protected final String name;
    
    /** The root frames for this thread. */
    final Map<FrameLocation, ProfiledFrame> _frames =
        new LinkedHashMap<>();
    
    /** The stack of currently active frames. */
    private final Deque<ProfiledFrame> _stack =
        new LinkedList<>();
    
    /** A reference to the current thread for time tracking. */
    
    /** Grand invocation total. */
    long _invtotal;
    
    /** Total time. */
    long _totalTime;
    
    /** CPU time. */
    long _cpuTime;
    
    /**
     * Initializes the thread information.
     *
     * @param __n The name of the thread.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/10
     */
    public ProfiledThread(String __n)
        throws NullPointerException
    {
        if (__n == null)
            throw new NullPointerException("NARG");
        
        this.name = __n;
    }
    
    /**
     * Enters the given frame, the enter time is the system time.
     *
     * @param __cl The name of the class.
     * @param __mn The name of the method.
     * @param __md The type of the method.
     * @throws NullPointerException On null arguments.
     * @since 2019/06/30
     */
    public ProfiledFrame enterFrame(String __cl, String __mn, String __md)
        throws NullPointerException
    {
        return this.enterFrame(__cl, __mn, __md, System.nanoTime());
    }
    
    /**
     * Enters the given frame.
     *
     * @param __cl The name of the class.
     * @param __mn The name of the method.
     * @param __md The type of the method.
     * @param __ns The The starting time in nanoseconds.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/10
     */
    public ProfiledFrame enterFrame(String __cl, String __mn, String __md,
        long __ns)
        throws NullPointerException
    {
        if (__cl == null || __mn == null || __md == null)
            throw new NullPointerException("NARG");
        
        // Used as map key
        FrameLocation loc = new FrameLocation(__cl, __mn, __md);
        
        // We need to know the top-most frame because
        Deque<ProfiledFrame> stack = this._stack;
        ProfiledFrame top = stack.peek();
        
        // If the frame was not already recorded in the map then add a new one
        // but we always keep existing frames since their times add up and
        // such. Also remember that there is depth to this, like we cannot
        // just keep adding to the root and such!
        Map<FrameLocation, ProfiledFrame> frames = (top == null ?
            this._frames : top._frames);
        ProfiledFrame rv = frames.get(loc);
        if (rv == null)
            frames.put(loc,
                (rv = new ProfiledFrame(loc, stack.size() + 1)));
        
        // Tell the top-most frame that we are in an invoke, so this removes
        // self time accordingly
        if (top != null)
            top.invokeStart(__ns);
        
        // Push the frame to the stack since it is active
        this._stack.push(rv);
        
        // Indicate that this frame has been entered
        rv.enteredFrame(__ns);
        
        // Using this frame
        return rv;
    }
    
    /**
     * Exits all frames as needed, the current time is used.
     *
     * @since 2019/06/30
     */
    public void exitAll()
    {
        this.exitAll(System.nanoTime());
    }
    
    /**
     * Exits all frames as needed.
     *
     * @param __ns The nanoseconds when exit all has happened.
     * @since 2018/11/11
     */
    public void exitAll(long __ns)
    {
        // Empty the frame stack
        Deque<ProfiledFrame> stack = this._stack;
        while (!stack.isEmpty())
            this.exitFrame(__ns);
    }
    
    /**
     * Exits the frame which at the top of the stack, the current time is
     * used.
     *
     * @return The exited frame.
     * @throws IllegalStateException If there is no frame to exit.
     * @since 2018/11/10
     */
    public ProfiledFrame exitFrame()
        throws IllegalStateException
    {
        return this.exitFrame(System.nanoTime());
    }
    
    /**
     * Exits the frame which at the top of the stack.
     *
     * @param __ns The nanoseconds when the frame exited.
     * @return The exited frame.
     * @throws IllegalStateException If there is no frame to exit.
     * @since 2018/11/10
     */
    public ProfiledFrame exitFrame(long __ns)
        throws IllegalStateException
    {
        /* {@squirreljme.error AH07 No frame is in the stack to exit in.} */
        Deque<ProfiledFrame> stack = this._stack;
        ProfiledFrame rv = stack.pop();
        if (rv == null)
            throw new IllegalStateException("AH07");
        
        // Tell that popped frame we left
        // returns: [0:total, 1:self, 2:self CPU, 3:sleepTime]
        long[] times = rv.exitedFrame(__ns);
        
        // The total thread time is the cumulative of the self times
        this._totalTime += Math.max(0, times[1]);
        this._cpuTime += Math.max(0, times[2]);
        
        // Every frame that is set gets the cumulative time of the self time
        // of the frame that just exited, so this way all parent frames will
        // have times greater than or equal to the sub-frames
        for (ProfiledFrame frame : stack)
        {
            frame._totalTime += Math.max(0, times[1]);
            frame._totalCpuTime += Math.max(0, times[2]);
        }
        
        // If we had a frame underneath, say the invocation has ended
        ProfiledFrame newTop = stack.peek();
        if (newTop != null)
        {
            // End the invocation
            newTop.invokeEnd(__ns);
        }
        
        // If all threads are out, count the times
        else
        {
            // Invocation total goes up after each method ends
            this._invtotal++;
        }
        
        // Return the frame which was popped
        return rv;
    }
}