SquirrelJME/SquirrelJME

View on GitHub
emulators/emulator-base/src/main/java/cc/squirreljme/emulator/profiler/__NodeTable__.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.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class contains the parsed node table which defines the structure for
 * each entry within the frame tree. A node represents a single frame.
 *
 * @since 2018/11/11
 */
final class __NodeTable__
{
    /** Linear set of frame nodes. */
    private final List<ProfiledFrame> _linear = 
        new ArrayList<>();
    
    /** Offsets for each node. */
    private final Map<ProfiledFrame, __Position__> _offsets =
        new HashMap<>();
    
    /** Current position. */
    private final __Position__ _at =
        new __Position__();
    
    /** Overflowed narrow point? */
    private boolean _overflowed;
    
    /**
     * Parses the frames and loads into a node table.
     *
     * @param __fs The input frames to map.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/11
     */
    public final void parse(Iterable<ProfiledFrame> __fs)
        throws NullPointerException
    {
        if (__fs == null)
            throw new NullPointerException("NARG");
            
        List<ProfiledFrame> linear = this._linear;
        Map<ProfiledFrame, __Position__> offsets = this._offsets;
        
        // Overflow check for wider offsets
        boolean overflowed = false;
        
        // Determine positions for all nodes
        __Position__ at = this._at;
        for (ProfiledFrame f : __fs)
        {
            // Too deep?
            if (f._depth >= ProfiledFrame.MAX_STACK_DEPTH)
                continue;
            
            // Will need to know how many sub-frames there are
            Collection<ProfiledFrame> sub = f._frames.values();
            
            // Track position of this frame
            linear.add(f);
            offsets.put(f, at.increment(sub.size()));
            
            // Handle sub-frames for this frame
            this.parse(sub);
            
            // If these many bytes were written we overflow now
            if (at._narrow > 16777215)
                overflowed = true;
        }
        
        // Did we overflow?
        if (overflowed)
            this._overflowed = true;
    }
    
    /**
     * Parses the frames and loads into a node table.
     *
     * @param __t The thread to parse.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/11
     */
    public final void parseThread(ProfiledThread __t)
        throws NullPointerException
    {
        if (__t == null)
            throw new NullPointerException("NARG");
        
        Iterable<ProfiledFrame> frames = __t._frames.values();
        
        // Create virtual frame since the root actually can have multiple
        // methods forking from it and the profiler format can only handle
        // a single root
        ProfiledFrame vframe = new ProfiledFrame(FrameLocation.ENTRY_POINT,
            0);
        
        // Only ever called once
        vframe._numCalls = 1;
        
        // Initialize frame times with thread times
        vframe._totalTime = __t._totalTime;
        vframe._totalCpuTime = __t._cpuTime;
        
        // Share the same time as the entire trace
        vframe._selfTime = __t._totalTime;
        vframe._selfCpuTime = __t._cpuTime;
        
        // Store the thread sub-frames into this virtual thread
        Map<FrameLocation, ProfiledFrame> vsubf = vframe._frames;
        for (ProfiledFrame f : frames)
            vsubf.put(f.location, f);
        
        // Parse this special frame, which will then parse its sub-frames
        // accordingly
        this.parse(Arrays.<ProfiledFrame>asList(vframe));
    }        
    
    /**
     * Writes the node table to the given stream.
     *
     * @param __os The stream to write to.
     * @param __mids Node IDs.
     * @throws IOException On write errors.
     * @throws NullPointerException On null arguments.
     * @since 2018/11/11
     */
    public final void writeTo(OutputStream __os,
        Map<FrameLocation, Integer> __mids)
        throws IOException, NullPointerException
    {
        if (__os == null || __mids == null)
            throw new NullPointerException("NARG");
        
        DataOutputStream dos = new DataOutputStream(__os);
        
        // If there were a large number of entries written then sub-node
        // offsets use more bytes
        List<ProfiledFrame> linear = this._linear;
        boolean wide = this._overflowed;
        
        // Just go through every frame and write it using a simple linear
        // index
        Map<ProfiledFrame, __Position__> offsets = this._offsets;
        for (ProfiledFrame f : linear)
        {
            // Too deep or missing location ID?
            FrameLocation location = f.location;
            Integer flid = __mids.get(f.location);
            if (f._depth >= ProfiledFrame.MAX_STACK_DEPTH || flid == null)
                continue;
            
            // The frame location ID, this is data stored in a previous table
            dos.writeShort(flid);
            
            // Number of calls, force to be 1 since this should always be
            // the case
            dos.writeInt(Math.max(1, f._numCalls));
            
            // Total time and self time (not sleeping/blocking)
            __NodeTable__.__writeLong40(dos, f._totalTime);
            __NodeTable__.__writeLong40(dos, f._selfTime);
            
            // Total time and self time (with sleeping/blocking)
            __NodeTable__.__writeLong40(dos, f._totalCpuTime);
            __NodeTable__.__writeLong40(dos, f._selfCpuTime);
            
            // Write offsets to the sub-frame nodes
            Collection<ProfiledFrame> sub = f._frames.values();
            
            // Determine and write the actual number of sub-frames used
            int actsubs = 0;
            for (ProfiledFrame sf : sub)
                if (sf._depth >= ProfiledFrame.MAX_STACK_DEPTH)
                    continue;
                else
                    actsubs++;
            dos.writeShort(actsubs);
            
            // Write sub-frame table
            for (ProfiledFrame sf : sub)
            {
                // Too deep
                if (sf._depth >= ProfiledFrame.MAX_STACK_DEPTH)
                    continue;
                
                __Position__ p = offsets.get(sf);
                
                // Nodes will either be wide or narrow
                if (wide)
                    dos.writeInt(p._wide);
                else
                    __NodeTable__.__writeInt24(dos, p._narrow);
            }
        }
    }
    
    /**
     * Writes a 24-bit int.
     *
     * @param __dos The output stream.
     * @param __v The value to write.
     * @throws IOException On write errors.
     * @since 2018/11/11
     */
    static final void __writeInt24(DataOutputStream __dos, int __v)
        throws IOException
    {
        // Only unsigned
        if (__v < 0)
            __v = 0;
        
        // This is limited to 24 bits, so cap it so there
        else if (__v > 16777215)
            __v = 16777215;
        
        // Write values
        __dos.writeByte((byte)(__v >>> 16));
        __dos.writeByte((byte)(__v >>> 8));
        __dos.writeByte((byte)(__v));
    }
    
    /**
     * Writes a 40 bit long.
     *
     * @param __dos The output stream.
     * @param __v The value to write.
     * @throws IOException On write errors.
     * @since 2018/11/11
     */
    static final void __writeLong40(DataOutputStream __dos, long __v)
        throws IOException
    {
        // Only unsigned
        if (__v < 0)
            __v = 0;
        
        // This is limited to 40 bits, so cap it so there
        else if (__v > 1099511627775L)
            __v = 1099511627775L;
        
        // Write values
        __dos.writeByte((byte)(__v >>> 32));
        __dos.writeByte((byte)(__v >>> 24));
        __dos.writeByte((byte)(__v >>> 16));
        __dos.writeByte((byte)(__v >>> 8));
        __dos.writeByte((byte)(__v));
    }
    
    /**
     * Position of a node within the node table.
     *
     * @since 2018/11/11
     */
    private static final class __Position__
    {
        /** Narrow position. */
        int _narrow;
        
        /** Wide position. */
        int _wide;
        
        /**
         * Initializes default position.
         *
         * @since 2018/11/11
         */
        __Position__()
        {
        }
        
        /**
         * Initializes for the given positions.
         *
         * @param __n The narrow position.
         * @param __w The wide position.
         * @since 2018/11/11
         */
        __Position__(int __n, int __w)
        {
            this._narrow = __n;
            this._wide = __w;
        }
        
        /**
         * Increments this position by the given number of sub-node units
         * and returns the old one.
         *
         * @param __num The number of sub-node references to increment by.
         * @return The old position in a new object.
         * @throws IllegalArgumentException If the number of nodes is negative.
         * @since 2018/11/11
         */
        public __Position__ increment(int __num)
            throws IllegalArgumentException
        {
            /* {@squirreljme.error AH08 Cannot write negative number of
            nodes.} */
            if (__num < 0)
                throw new IllegalArgumentException("AH08");
            
            // Store the old position first
            int narrow = this._narrow,
                wide = this._wide;
            __Position__ rv = new __Position__(narrow, wide);
            
            // Increment with new offsets
            this._narrow = narrow + 28 + (__num * 3);
            this._wide = wide + 28 + (__num * 4);
            
            return rv;
        }
    }
}