MoreStatsAppState

Hello jMonkeys,

EDIT: replaced MoreStatsAppState.java source code with an ‘cleaner’ version (for changelog check bottom of this post). I apologize for any inconveniences.

as probably a lot of people do, while working on my project I decided it could be useful to have some more information on screen than are provided by the StatsAppState (yes i know DetailedProfilerState, both are awesome) but just some additional information like current memory usage, some GC and camera infos eg.

Since there might be other people that could potentially make a use of it i decided to share it with you.
Here is how one would use it:
copy & paste the 2 classes needed, adjust project declaration.
Since it extends StatsAppState, you can replace your

StatsAppState someName = new StatsAppState();

with

StatsAppState someName = new MoreStatsAppState();

there is also constructors to provide it with a Node and BitmapFont as with the StatAppState class.
The constructors expecting a boolean give the opportunity to additionally enable physicsSpace infos which only works when using the BulletAppState.
if in your code you got something like the following, it should also still work, so no need to change anything:

stateManager.getState(StatsAppState.class).toggleStats();

to those who extend SimpleApplication but dont specify a constructor or in their constructor got no super() call or got a super() call with no arguments would have to replace their constructor / super()-call by something like

 public Main() {
     super(new MoreStatsAppState(), new FlyCamAppState(), new AudioListenerState(), new DebugKeysAppState());
 }

if you use a super()-call like mentioned above, the SimpleApplications initialize()-Method picks it up ofc and enables toggling it on and off with f5 just as usual
if you dont extend simpleApplication you’re probably enough into jme that you can figure out how to use it yourself :smiley:

Overview over the information given:
-the first 3 lines is direct-, heap- and non-heap-memory. for the direct memory, there only is the currently used amount and the amount of directbuffers currently allocated given. for heap- and non-heap-memory the values are used-, committed- and max-memory in this order, shown in MB
-4th line is threads information: T (active Threads), D (Deamon threads), P (threads Peak) and S (total Started)
-5th, 6th and 7th lines are the number of GCs (that is total, minor and major) that happened since startup and their total durations
-8th and 9th line show minor and major GCs average frequency in seconds (like how often do they take place) and their average duration
-10th and 11th line show the camera direction and position (there is a "setPositionDivisor(Vector3f) method that can be used to scale the position in case for example you’re working on a blocky-game and your blocks are of size 3fx3fx3f, you can tell the MoreStatsAppState to divide the position by given size. That ofc only works if your blocky world is based on the origin. Rare usecase but won’t hurt)
-12th line tells you how many classes are currently loaded, have been unloaded again and were loaded in total
-13th line tell you for how long the JVM is running already and the percentage of time spent for GC
-14th line is only present if you call a constructor that expects a boolean and you provide it with true, its the physicsSpace information: R (RigidBody), G (GhostObject), C (Character) and V (Vehicle) currently in this physicsSpace
-15th line just shows current TPF

15th line automatically moves up (actually 1st to 13th line move down) by one line in case you didnt specify that you want physicsSpace information.

possible problems are:
-if you dont use toggleState() but manually setDisplayFps(boolean show) or setDisplayStatView(boolean show), you’ll have to add a call for setDisplayMoreStats(boolean show) and obviously cast it to MoreStatsAppState before.
-I HAVE ONLY TESTED IT ON MY MACHINE (win8 64 bit and so on), so there is no guarantee that it works for you :smiley:
-dependant on your GC specified in the JVM arguments, the percentage of time spent in GC might be off by a bit i guess
-sometimes the stats overflow the grey area
-based on System.nanoTime() measurement it takes about 0.8ms to receive the values needed
-it relies on another class “RuntimeInfos” that i post here too, so not too big of a problem

Here is a Screenshot first to show what it looks like:

Couldnt miss the chance to show off the current state of my project since im slowly starting to get a little proud of it :smiley: also the simple blue-cube-example would have been too boring. pyhsicsSpace information has been turned on in this example, but no physics objects are added since i did not yet have time to look into how i would want to implements physics unfortunately.

and here is the classes:
MoreStatsAppState.java:

package com.kabigames.api.appstates;

import com.jme3.app.Application;
import com.jme3.app.StatsAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.shape.Quad;
import com.kabigames.api.tools.RuntimeInfos;
import java.text.DecimalFormat;

/**
 *
 * @author Alexander Kasigkeit
 * @changelog: 1) fixed GC-time-percentage 2) added enum Orientation and related
 * constructors to decide where to place the additional infos 3) locally store
 * values that are used several times during update() method 4) formatted some
 * time-values to switch to unit second instead of millisecond when appropriate
 */
public class MoreStatsAppState extends StatsAppState {

    public enum Orientation {
        Vertically, Horizontally, TopLeft;
    }

    private static final DecimalFormat FREQ_TIME_FORMAT = new DecimalFormat("###,##0.0");
    private static final DecimalFormat TOTAL_TIME_FORMAT = new DecimalFormat("###,##0.00");
    private static final DecimalFormat POS_FORMAT = new DecimalFormat("###,##0.0");
    private static final DecimalFormat GC_TIMES_FORMAT = new DecimalFormat("##0.0000");
    private static final DecimalFormat CLASS_FORMAT = new DecimalFormat("###,###,##0");

    private static final int STATS_LINE_COUNT = 14;
    private static final int BULLET_LINE_COUNT = 1;

    private final int TOTAL_LINE_COUNT;
    private final boolean INCLUDE_BULLET;
    private final Vector3f POS_DIV = new Vector3f(1, 1, 1);
    private final Orientation ORIENTATION;

    private int frameHeight = 0;

    private Application app = null;

    private BitmapFont moreGuiFont = null;
    private BitmapText moreStatsText = null;
    private Geometry darkenMoreStats = null;

    private boolean showMoreStats = true;

    public MoreStatsAppState(boolean includeBullet, Orientation orientation, Node guiNode, BitmapFont guiFont) {
        super(guiNode, guiFont);
        INCLUDE_BULLET = includeBullet;
        ORIENTATION = orientation;
        TOTAL_LINE_COUNT = includeBullet ? STATS_LINE_COUNT + BULLET_LINE_COUNT : STATS_LINE_COUNT;
    }

    public MoreStatsAppState(Node guiNode, BitmapFont guiFont) {
        this(false, Orientation.Vertically, guiNode, guiFont);
    }

    public MoreStatsAppState(boolean includeBullet) {
        this(includeBullet, Orientation.Vertically, null, null);
    }

    public MoreStatsAppState(boolean includeBullet, Orientation orientation) {
        this(includeBullet, orientation, null, null);
    }

    public MoreStatsAppState(Orientation orientation) {
        this(false, orientation, null, null);
    }

    public MoreStatsAppState() {
        this(false, Orientation.Vertically, null, null);
    }

    public void setFrameHeight(int height) {
        frameHeight = height;
        if (ORIENTATION == Orientation.TopLeft) {
            if (moreStatsText != null) {
                moreStatsText.setLocalTranslation(0, frameHeight, 0);
                if (darkenMoreStats != null) {
                    darkenMoreStats.setLocalTranslation(0, frameHeight - moreStatsText.getLineHeight() * TOTAL_LINE_COUNT, -1);
                }
            }
        }
    }

    public void setPositionDivisor(Vector3f div) {
        POS_DIV.set(div);
    }

    public BitmapText getMoreStatsText() {
        return moreStatsText;
    }

    @Override
    public void setFont(BitmapFont guiFont) {
        super.setFont(guiFont);
    }

    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        this.app = app;
        super.initialize(stateManager, app);
        frameHeight = app.getContext().getSettings().getHeight();
        moreGuiFont = app.getAssetManager().loadFont("Interface/Fonts/Console.fnt");
        loadMoreStatsText();
        loadMoreDarken();
    }

    public void loadMoreStatsText() {
        if (moreStatsText == null) {
            moreStatsText = new BitmapText(moreGuiFont, false);
        }
        if (ORIENTATION == Orientation.Horizontally) {
            moreStatsText.setLocalTranslation(200, moreStatsText.getLineHeight() * TOTAL_LINE_COUNT, 0);
        } else if (ORIENTATION == Orientation.TopLeft) {
            moreStatsText.setLocalTranslation(0, frameHeight, 0);
        } else {
            moreStatsText.setLocalTranslation(0, fpsText.getLineHeight() + statsView.getHeight() + moreStatsText.getLineHeight() * TOTAL_LINE_COUNT, 0);
        }
        moreStatsText.setText("More Stats");
        moreStatsText.setCullHint(showMoreStats ? CullHint.Never : CullHint.Always);
        guiNode.attachChild(moreStatsText);
    }

    public void loadMoreDarken() {
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", new ColorRGBA(0, 0, 0, 0.5f));
        mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

        darkenMoreStats = new Geometry("MoreStatsDarken", new Quad(200, moreStatsText.getLineHeight() * TOTAL_LINE_COUNT));
        darkenMoreStats.setMaterial(mat);
        if (ORIENTATION == Orientation.Horizontally) {
            darkenMoreStats.setLocalTranslation(200, 0, -1);
        } else if (ORIENTATION == Orientation.TopLeft) {
            darkenMoreStats.setLocalTranslation(0, frameHeight - moreStatsText.getLineHeight() * TOTAL_LINE_COUNT, -1);
        } else {
            darkenMoreStats.setLocalTranslation(0, fpsText.getHeight() + statsView.getHeight(), -1);
        }
        darkenMoreStats.setCullHint(showMoreStats && isDarkenBehind() ? CullHint.Never : CullHint.Always);
        guiNode.attachChild(darkenMoreStats);
    }

    @Override
    public void toggleStats() {
        super.toggleStats();
        setDisplayMoreStats(!showMoreStats);
    }

    public void setDisplayMoreStats(boolean show) {
        showMoreStats = show;
        if (moreStatsText != null) {
            moreStatsText.setCullHint(show ? CullHint.Never : CullHint.Always);
            if (darkenMoreStats != null) {
                darkenMoreStats.setCullHint(showMoreStats && isDarkenBehind() ? CullHint.Never : CullHint.Always);
            }
        }
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        if (enabled) {
            moreStatsText.setCullHint(showMoreStats ? CullHint.Never : CullHint.Always);
            darkenMoreStats.setCullHint(showMoreStats && isDarkenBehind() ? CullHint.Never : CullHint.Always);
        } else {
            moreStatsText.setCullHint(CullHint.Always);
            moreStatsText.setCullHint(CullHint.Always);
        }
    }

    @Override
    public void update(float tpf) {
        super.update(tpf);
        if (!isEnabled()) {
            return;
        }
        if (showMoreStats) {
            //long start = System.nanoTime();
            float upTime = RuntimeInfos.getUpTime() / 1000.0f;
            StringBuilder sb = new StringBuilder(365);

            //direct memory infos
            sb.append("D-Mem: ");
            long dMem = RuntimeInfos.getDirectMemoryUsed();
            if (dMem < 1024) {
                sb.append(dMem);
                sb.append(" B, Count: ");
            } else if (dMem < (1024 * 1024)) {
                sb.append(RuntimeInfos.toKB(dMem));
                sb.append(" KB, Count: ");
            } else {
                sb.append(RuntimeInfos.toMB(dMem));
                sb.append(" MB, Count: ");
            }
            sb.append(RuntimeInfos.getDirectBufferCount());
            sb.append("\n");

            //heap memory infos
            sb.append("H-Mem: ");
            sb.append(RuntimeInfos.toMB(RuntimeInfos.getHeapMemoryUsed()));
            sb.append(" / ");
            sb.append(RuntimeInfos.toMB(RuntimeInfos.getHeapMemoryCommitted()));
            sb.append(" / ");
            sb.append(RuntimeInfos.toMB(RuntimeInfos.getHeapMemoryMax()));
            sb.append(" MB");
            sb.append("\n");

            //non-heap memory infos
            sb.append("N-Mem: ");
            sb.append(RuntimeInfos.toMB(RuntimeInfos.getNonHeapMemoryUsed()));
            sb.append(" / ");
            sb.append(RuntimeInfos.toMB(RuntimeInfos.getNonHeapMemoryCommitted()));
            sb.append(" / ");
            sb.append(RuntimeInfos.toMB(RuntimeInfos.getNonHeapMemoryMax()));
            sb.append(" MB");
            sb.append("\n");

            //thread infos
            sb.append("T: ");
            sb.append(RuntimeInfos.getActiveThreadCount());
            sb.append(", D: ");
            sb.append(RuntimeInfos.getDeamonThreadCount());
            sb.append(", P: ");
            sb.append(RuntimeInfos.getPeakThreadCount());
            sb.append(", S: ");
            sb.append(RuntimeInfos.getTotalStartedThreadsCount());
            sb.append("\n");

            //GC counts and times
            long totGCtimes = RuntimeInfos.getTotalGCTimes();
            long minGCcounts = RuntimeInfos.getMinorGCCounts();
            long minGCtimes = RuntimeInfos.getMinorGCTimes();
            long majGCcounts = RuntimeInfos.getMajorGCCounts();
            long majGCtimes = RuntimeInfos.getMajorGCTimes();
            sb.append("TotGCs: ");
            sb.append(RuntimeInfos.getTotalGCCounts());
            sb.append(", took: ");
            formatTime(sb, totGCtimes);
            sb.append("\n");
            sb.append("MinGCs: ");
            if (minGCcounts == 0) {
                sb.append("none");
            } else {
                sb.append(minGCcounts);
                sb.append(", took: ");
                formatTime(sb, minGCtimes);
            }
            sb.append("\n");
            sb.append("MajGCs: ");
            if (majGCcounts == 0) {
                sb.append("none");
            } else {
                sb.append(majGCcounts);
                sb.append(", took: ");
                formatTime(sb, majGCtimes);
            }
            sb.append("\n");

            //GC frequencies and avg durations
            sb.append("MinGC freq: ");
            if (minGCcounts == 0) {
                sb.append("none");
            } else {
                sb.append(FREQ_TIME_FORMAT.format((upTime) / minGCcounts));
                sb.append(" s, avg: ");
                sb.append(minGCtimes / minGCcounts);
                sb.append(" ms");
            }
            sb.append("\n");

            sb.append("MajGC freq: ");
            if (majGCcounts == 0) {
                sb.append("none");
            } else {
                formatFrequency(sb, upTime, majGCcounts);
                sb.append(", avg: ");
                //sb.append(FREQ_TIME_FORMAT.format((upTime) / majGCcounts));
                //sb.append(" s, avg: ");
                sb.append(majGCtimes / majGCcounts);
                sb.append(" ms");
            }
            sb.append("\n");

            //cam direction
            Vector3f dir = app.getCamera().getDirection();
            float absX = FastMath.abs(dir.getX());
            float absY = FastMath.abs(dir.getY());
            float absZ = FastMath.abs(dir.getZ());
            sb.append("Cam dir: ");
            if (absX >= absY && absX >= absZ) {
                if (dir.getX() > 0) {
                    sb.append("X +");
                } else {
                    sb.append("X -");
                }
            } else if (absY >= absX && absY >= absZ) {
                if (dir.getY() > 0) {
                    sb.append("Y +");
                } else {
                    sb.append("Y -");
                }
            } else {
                if (dir.getZ() > 0) {
                    sb.append("Z +");
                } else {
                    sb.append("Z -");
                }
            }
            sb.append("\n");

            //cam pos
            sb.append("Cam pos: ");
            sb.append(POS_FORMAT.format(app.getCamera().getLocation().x / POS_DIV.x));
            sb.append(", ");
            sb.append(POS_FORMAT.format(app.getCamera().getLocation().y / POS_DIV.y));
            sb.append(", ");
            sb.append(POS_FORMAT.format(app.getCamera().getLocation().z / POS_DIV.z));
            sb.append("\n");

            //classes
            sb.append("Classes: ");
            sb.append(CLASS_FORMAT.format(RuntimeInfos.getLoadedClassCount()));
            sb.append(" / ");
            sb.append(CLASS_FORMAT.format(RuntimeInfos.getTotalLoadedClassCount()));
            sb.append(" / ");
            sb.append(CLASS_FORMAT.format(RuntimeInfos.getUnloadedClassCount()));
            sb.append("\n");

            //uptime and time spent in GC
            int sec = (int) upTime;
            int min = sec / 60;
            sec = sec % 60;
            sb.append("UpTime: ");
            if (min > 0) {
                sb.append(min);
                if (sec > 9) {
                    sb.append(":");
                } else {
                    sb.append(":0");
                }
                sb.append(sec);
                sb.append(" min");
            } else {
                sb.append(sec);
                sb.append(" sec");
            }
            sb.append(", ");
            float perc = totGCtimes / (upTime * 1000f);
            sb.append(GC_TIMES_FORMAT.format(perc * 100));
            sb.append("% GC");
            sb.append("\n");

            //optional physicsspace info
            if (INCLUDE_BULLET) {
                BulletAppState bas = app.getStateManager().getState(BulletAppState.class);
                PhysicsSpace ps;
                if (bas != null && (ps = bas.getPhysicsSpace()) != null) {
                    sb.append("R: ");
                    sb.append(ps.getRigidBodyList().size());
                    sb.append(", G: ");
                    sb.append(ps.getGhostObjectList().size());
                    sb.append(", C: ");
                    sb.append(ps.getCharacterList().size());
                    sb.append(", V: ");
                    sb.append(ps.getVehicleList().size());
                } else {
                    sb.append("no PhysicsSpace found");
                }
                sb.append("\n");
            }

            //tpf
            sb.append("TPF: ");
            sb.append(tpf);
            sb.append("\n");

            moreStatsText.setText(sb.toString());
            //long dur = System.nanoTime() - start;
            //System.out.println("stat calculation took " + (dur / 1000000.0) + " ms");
            //if you add or remove information you can use this check 
            //to adjust initial size of StringBuilder to remove the need to resize it
            //System.out.println("text length " + moreStatsText.getText().length());
        }
    }

    private void formatFrequency(StringBuilder sb, float upTimeInSeconds, long count) {
        float freq = upTimeInSeconds / count;
        if (freq < 60f) {
            sb.append(FREQ_TIME_FORMAT.format(freq));
            sb.append(" s");
        } else {
            int min = (int) (freq / 60f);
            int sec = (int) (freq % 60f);
            sb.append(min);
            sb.append(":");
            if (sec < 10) {
                sb.append("0");
            }
            sb.append(sec);
            sb.append(" min");
        }
    }

    private void formatTime(StringBuilder sb, long dur) {
        if (dur < 1000L) {
            sb.append(dur);
            sb.append(" ms");
        } else {
            sb.append(TOTAL_TIME_FORMAT.format(dur / 1000.0f));
            sb.append(" s");
        }
    }

    @Override
    public void cleanup() {
        super.cleanup();
        guiNode.detachChild(moreStatsText);
        guiNode.detachChild(darkenMoreStats);
    }
}

and the needed RuntimeInfos.java:

package com.kabigames.api.utils;

import com.jme3.util.MemoryUtils;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadMXBean;
import java.util.List;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

/**
 *
 * @author Alexander Kasigkeit
 */
public class RuntimeInfos {

    protected static final MBeanServer M_BEANS = ManagementFactory.getPlatformMBeanServer();
    protected static final MemoryMXBean MEM_BEAN = ManagementFactory.getMemoryMXBean();
    protected static final List<GarbageCollectorMXBean> GC_BEANS = ManagementFactory.getGarbageCollectorMXBeans();
    protected static final ThreadMXBean THREAD_BEAN = ManagementFactory.getThreadMXBean();
    protected static final RuntimeMXBean RUNTIME_BEAN = ManagementFactory.getRuntimeMXBean();
    protected static final ClassLoadingMXBean CLASSLOADING_BEAN = ManagementFactory.getClassLoadingMXBean();
    protected static final OperatingSystemMXBean OS_BEAN = ManagementFactory.getOperatingSystemMXBean();

    private static ObjectName operatingSystem;

    static {
        try {
            operatingSystem = new ObjectName("java.lang", "type", "OperatingSystem");
        } catch (MalformedObjectNameException ex) {
        }
    }

    public static long getTotalPhysicalMemory() {
        try {
            Long value = (Long) M_BEANS.getAttribute(operatingSystem, "TotalPhysicalMemorySize");
            return value == null ? -1 : value;
        } catch (JMException ex) {
            return -1;
        }
    }

    public static int getAvailableProcessor() {
        return OS_BEAN.getAvailableProcessors();
    }

    public static double getSystemLoadAverage() {
        return OS_BEAN.getSystemLoadAverage();
    }

    public static long getUpTime() {
        return RUNTIME_BEAN.getUptime();
    }

    public static long getStartTime() {
        return RUNTIME_BEAN.getStartTime();
    }

    public static int getLoadedClassCount() {
        return CLASSLOADING_BEAN.getLoadedClassCount();
    }

    public static long getUnloadedClassCount() {
        return CLASSLOADING_BEAN.getUnloadedClassCount();
    }

    public static long getTotalLoadedClassCount() {
        return CLASSLOADING_BEAN.getTotalLoadedClassCount();
    }

    public static long getTotalGCTimes() {
        long time = 0L;
        for (GarbageCollectorMXBean gcBean : GC_BEANS) {
            time += gcBean.getCollectionTime();
        }
        return time;
    }

    public static long getTotalGCCounts() {
        long cnt = 0L;
        for (GarbageCollectorMXBean gcBean : GC_BEANS) {
            cnt += gcBean.getCollectionCount();
        }
        return cnt;
    }

    public static long getMinorGCTimes() {
        return GC_BEANS.get(0).getCollectionTime();
    }

    public static long getMinorGCCounts() {
        return GC_BEANS.get(0).getCollectionCount();
    }

    public static long getMajorGCTimes() {
        return GC_BEANS.get(1).getCollectionTime();
    }

    public static long getMajorGCCounts() {
        return GC_BEANS.get(1).getCollectionCount();
    }

    public static long getDirectMemoryUsed() {
        return MemoryUtils.getDirectMemoryUsage();
    }

    public static long getDirectBufferCount() {
        return MemoryUtils.getDirectMemoryCount();
    }

    public static long getHeapMemoryUsed() {
        return MEM_BEAN.getHeapMemoryUsage().getUsed();
    }

    public static long getHeapMemoryCommitted() {
        return MEM_BEAN.getHeapMemoryUsage().getCommitted();
    }

    public static long getHeapMemoryInit() {
        return MEM_BEAN.getHeapMemoryUsage().getInit();
    }

    public static long getHeapMemoryMax() {
        return MEM_BEAN.getHeapMemoryUsage().getMax();
    }

    public static long getNonHeapMemoryUsed() {
        return MEM_BEAN.getNonHeapMemoryUsage().getUsed();
    }

    public static long getNonHeapMemoryCommitted() {
        return MEM_BEAN.getNonHeapMemoryUsage().getCommitted();
    }

    public static long getNonHeapMemoryInit() {
        return MEM_BEAN.getNonHeapMemoryUsage().getInit();
    }

    public static long getNonHeapMemoryMax() {
        return MEM_BEAN.getNonHeapMemoryUsage().getMax();
    }

    public static long getPendingFinalizationCount() {
        return MEM_BEAN.getObjectPendingFinalizationCount();
    }

    public static long getActiveThreadCount() {
        return THREAD_BEAN.getThreadCount();
    }

    public static long getPeakThreadCount() {
        return THREAD_BEAN.getPeakThreadCount();
    }

    public static long getDeamonThreadCount() {
        return THREAD_BEAN.getDaemonThreadCount();
    }

    public static long getTotalStartedThreadsCount() {
        return THREAD_BEAN.getTotalStartedThreadCount();
    }

    public static long toKB(long bytes) {
        return bytes / 1024;
    }

    public static long toMB(long bytes) {
        return bytes / (1024 * 1024);
    }

    public static long toGB(long bytes) {
        return bytes / (1024 * 1024 * 1024);
    }

    public static long toTB(long bytes) {
        return bytes / (1024 * 1024 * 1024 * 1024);
    }
}

in MoreStatsAppState you will also have to adjust the import for RuntimeInfos dependant on where you put it.
not all information from RuntimeInfos is used. For example if you got sort of a launcher that would start up your actual game, you can use RuntimeInfos.getTotalPhysicalMemory() to get the total amount of physical memory available and dynamically adjust your JVM arguments when starting your actual game.
I have to underline again though, i only tested it on my machine.
In case some license information is needed, just do with the classes what you feel like, but if your pc explodes im sorry i wont replace it :smiley:

In case you got any advices how to improve it or which stats could also be useful id be happy if you shared you thoughts with me!

Greetings from the shire,
Samwise

ChangeLog:

  1. multiplied GC-time-percentage by 100 to actually show off percentage. Was an old leftover from before i actually decided to make it extend StatsAppState and format some infos to make it cleaner and more usable for other people.
  2. added enum Orientation and related constructors to decide if you want to place the MoreStatsText to the right or to the top of the StatsView or in the top-left corner of the frame. Default still is Orientation.Vertically (that is above StatsView). When you use Orientation.TopLeft and the resolution changes during runtime you can call moreStatsAppState.setFrameHeight(xyz) to place it back in the topleft corner (thanks to @Pesegato for pointing out overlap problems with the BasicProfilerState)
  3. locally store values that are used several times during update() method to reduce number of cpu cycles used.
  4. formatted some time-values to switch to unit second instead of millisecond when appropriate.

I hope i didnt mess anything up now :smiley:

10 Likes

Hey, there is a bit of game on your stats, and it looks very nice btw.

1 Like

that comment gave me a good double-grin, thanks a lot :smiley:

To me, it looks like an upgrade over the current one. Hope to see it PR merged into jme
EDIT: Only downside is that it overlaps the basicprofiler state. I solved by removing bullet (which I don’t use)

Hey pesegato,
Thanks for your reply!
Id like to investigate a little more into it but im having trouble to reproduce the overlap. Do you by any chance scale the statsview by calling something like

statemanager.getState(StatsAppState.class).getStatsView().scale(1.2f);

Or similar? Only that way i could reproduce the overlap and im not yet sure how to pickup such change and adjust the positions appropriately.
Greetings!

EDIT: sry i got that wrong, its about the BasicProfilerState, ill look into it!
EDIT2: quick but “dirty” solution would be to replace

moreStatsText.setLocalTranslation(0, fpsText.getLineHeight() + statsView.getHeight() + moreStatsText.getLineHeight() * TOTAL_LINE_COUNT, 0);

with

moreStatsText.setLocalTranslation(200, moreStatsText.getLineHeight() * TOTAL_LINE_COUNT, 0);

in loadMoreStatsText(), and also replace

darkenMoreStats.setLocalTranslation(0, fpsText.getHeight() + statsView.getHeight(), -1);

with

darkenMoreStats.setLocalTranslation(200, 0, -1);

in loadMoreDarken().
That basically places the moreStatsText to the right of the fps / statsview, instead of on top of it :smiley: