diff --git a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java
index 541e34ba8..5c716c4d7 100644
--- a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java
+++ b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java
@@ -11,6 +11,7 @@ import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
+import dan200.computercraft.core.tracking.TrackingField;
public interface IAPIEnvironment
{
@@ -42,4 +43,6 @@ public interface IAPIEnvironment
String getLabel();
void setLabel( String label );
+
+ void addTrackingChange( TrackingField field, long change );
}
diff --git a/src/main/java/dan200/computercraft/core/computer/Computer.java b/src/main/java/dan200/computercraft/core/computer/Computer.java
index d62ee2be0..f6bfa70e8 100644
--- a/src/main/java/dan200/computercraft/core/computer/Computer.java
+++ b/src/main/java/dan200/computercraft/core/computer/Computer.java
@@ -20,6 +20,8 @@ import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.terminal.Terminal;
+import dan200.computercraft.core.tracking.Tracking;
+import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -167,6 +169,12 @@ public class Computer
m_computer.setLabel( label );
}
+ @Override
+ public void addTrackingChange( TrackingField field, long change )
+ {
+ Tracking.addValue( m_computer, field, change );
+ }
+
public void onPeripheralChanged( int side, IPeripheral peripheral )
{
synchronized( m_computer.m_peripherals )
diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java
index fa071396e..d97166ce5 100644
--- a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java
+++ b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java
@@ -7,6 +7,7 @@
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
+import dan200.computercraft.core.tracking.Tracking;
import java.util.HashSet;
import java.util.Set;
@@ -233,7 +234,7 @@ public class ComputerThread
{
long stop = System.nanoTime();
Computer computer = task.getOwner();
- if( computer != null ) ComputerTimeTracker.addTiming( computer, stop - start );
+ if( computer != null ) Tracking.addTiming( computer, stop - start );
// Re-add it back onto the queue or remove it
synchronized( s_taskLock )
diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerTimeTracker.java b/src/main/java/dan200/computercraft/core/computer/ComputerTimeTracker.java
deleted file mode 100644
index 0ee9f3aa5..000000000
--- a/src/main/java/dan200/computercraft/core/computer/ComputerTimeTracker.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package dan200.computercraft.core.computer;
-
-import com.google.common.collect.MapMaker;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Tracks timing information about computers, including how long they ran for
- * and the number of events they handled.
- *
- * Note that this will track computers which have been deleted (hence
- * the presence of {@link #timingLookup} and {@link #timings}
- */
-public class ComputerTimeTracker
-{
- public static class Timings
- {
- private final WeakReference computer;
- private final int computerId;
-
- private int tasks;
-
- private long totalTime;
- private long maxTime;
-
- public Timings( @Nonnull Computer computer )
- {
- this.computer = new WeakReference<>( computer );
- this.computerId = computer.getID();
- }
-
- @Nullable
- public Computer getComputer()
- {
- return computer.get();
- }
-
- public int getComputerId()
- {
- return computerId;
- }
-
- public int getTasks()
- {
- return tasks;
- }
-
- public long getTotalTime()
- {
- return totalTime;
- }
-
- public long getMaxTime()
- {
- return maxTime;
- }
-
- public double getAverage()
- {
- return totalTime / (double) tasks;
- }
-
- void update( long time )
- {
- tasks++;
- totalTime += time;
- if( time > maxTime ) maxTime = time;
- }
- }
-
- private static boolean tracking;
- private static final List timings = new ArrayList<>();
- private static final Map timingLookup = new MapMaker().weakKeys().makeMap();
-
- public synchronized static void start()
- {
- tracking = true;
-
- timings.clear();
- timingLookup.clear();
- }
-
- public synchronized static boolean stop()
- {
- if( !tracking ) return false;
-
- tracking = false;
- timingLookup.clear();
- return true;
- }
-
- public static synchronized List getTimings()
- {
- return new ArrayList<>( timings );
- }
-
- public static synchronized void addTiming( Computer computer, long time )
- {
- if( !tracking ) return;
-
- Timings timings = ComputerTimeTracker.timingLookup.get( computer );
- if( timings == null )
- {
- timings = new Timings( computer );
- timingLookup.put( computer, timings );
- ComputerTimeTracker.timings.add( timings );
- }
-
- timings.update( time );
- }
-}
diff --git a/src/main/java/dan200/computercraft/core/tracking/ComputerTracker.java b/src/main/java/dan200/computercraft/core/tracking/ComputerTracker.java
new file mode 100644
index 000000000..64b936a3d
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/tracking/ComputerTracker.java
@@ -0,0 +1,96 @@
+package dan200.computercraft.core.tracking;
+
+import dan200.computercraft.core.computer.Computer;
+import gnu.trove.map.hash.TObjectLongHashMap;
+
+import javax.annotation.Nullable;
+import java.lang.ref.WeakReference;
+
+public class ComputerTracker
+{
+ private final WeakReference computer;
+ private final int computerId;
+
+ private long tasks;
+ private long totalTime;
+ private long maxTime;
+
+ private final TObjectLongHashMap fields;
+
+ public ComputerTracker( Computer computer )
+ {
+ this.computer = new WeakReference<>( computer );
+ this.computerId = computer.getID();
+ this.fields = new TObjectLongHashMap<>();
+ }
+
+ ComputerTracker( ComputerTracker timings )
+ {
+ this.computer = timings.computer;
+ this.computerId = timings.computerId;
+
+ this.tasks = timings.tasks;
+ this.totalTime = timings.totalTime;
+ this.maxTime = timings.maxTime;
+ this.fields = new TObjectLongHashMap<>( timings.fields );
+ }
+
+
+ @Nullable
+ public Computer getComputer()
+ {
+ return computer.get();
+ }
+
+ public int getComputerId()
+ {
+ return computerId;
+ }
+
+ public long getTasks()
+ {
+ return tasks;
+ }
+
+ public long getTotalTime()
+ {
+ return totalTime;
+ }
+
+ public long getMaxTime()
+ {
+ return maxTime;
+ }
+
+ public long getAverage()
+ {
+ return totalTime / tasks;
+ }
+
+ void addTiming( long time )
+ {
+ tasks++;
+ totalTime += time;
+ if( time > maxTime ) maxTime = time;
+ }
+
+ void addValue( TrackingField field, long change )
+ {
+ fields.adjustOrPutValue( field, change, change );
+ }
+
+ public long get( TrackingField field )
+ {
+ if( field == TrackingField.TASKS ) return tasks;
+ if( field == TrackingField.MAX_TIME ) return maxTime;
+ if( field == TrackingField.TOTAL_TIME ) return totalTime;
+ if( field == TrackingField.AVERAGE_TIME ) return totalTime / tasks;
+
+ return fields.get( field );
+ }
+
+ public String getFormatted( TrackingField field )
+ {
+ return field.format( get( field ) );
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/tracking/Tracking.java b/src/main/java/dan200/computercraft/core/tracking/Tracking.java
new file mode 100644
index 000000000..219d247fc
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/tracking/Tracking.java
@@ -0,0 +1,52 @@
+package dan200.computercraft.core.tracking;
+
+import dan200.computercraft.core.computer.Computer;
+
+import java.util.HashMap;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class Tracking
+{
+ static final AtomicInteger tracking = new AtomicInteger( 0 );
+ private static final HashMap contexts = new HashMap<>();
+
+ public static TrackingContext getContext( UUID uuid )
+ {
+ synchronized( contexts )
+ {
+ TrackingContext context = contexts.get( uuid );
+ if( context == null ) contexts.put( uuid, context = new TrackingContext() );
+ return context;
+ }
+ }
+
+ public static void addTiming( Computer computer, long time )
+ {
+ if( tracking.get() == 0 ) return;
+
+ synchronized( contexts )
+ {
+ for( TrackingContext context : contexts.values() ) context.addTiming( computer, time );
+ }
+ }
+
+ public static void addValue( Computer computer, TrackingField field, long change )
+ {
+ if( tracking.get() == 0 ) return;
+
+ synchronized( contexts )
+ {
+ for( TrackingContext context : contexts.values() ) context.addValue( computer, field, change );
+ }
+ }
+
+ public static void reset()
+ {
+ synchronized( contexts )
+ {
+ contexts.clear();
+ tracking.set( 0 );
+ }
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/tracking/TrackingContext.java b/src/main/java/dan200/computercraft/core/tracking/TrackingContext.java
new file mode 100644
index 000000000..cc99ed144
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/tracking/TrackingContext.java
@@ -0,0 +1,85 @@
+package dan200.computercraft.core.tracking;
+
+import com.google.common.collect.MapMaker;
+import dan200.computercraft.core.computer.Computer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tracks timing information about computers, including how long they ran for
+ * and the number of events they handled.
+ *
+ * Note that this will track computers which have been deleted (hence
+ * the presence of {@link #timingLookup} and {@link #timings}
+ */
+public class TrackingContext
+{
+ private boolean tracking = false;
+
+ private final List timings = new ArrayList<>();
+ private final Map timingLookup = new MapMaker().weakKeys().makeMap();
+
+ public synchronized void start()
+ {
+ if( !tracking ) Tracking.tracking.incrementAndGet();
+ tracking = true;
+
+ timings.clear();
+ timingLookup.clear();
+ }
+
+ public synchronized boolean stop()
+ {
+ if( !tracking ) return false;
+
+ Tracking.tracking.decrementAndGet();
+ tracking = false;
+ timingLookup.clear();
+ return true;
+ }
+
+ public synchronized List getTimings()
+ {
+ ArrayList timings = new ArrayList<>( this.timings.size() );
+ for( ComputerTracker timing : this.timings ) timings.add( new ComputerTracker( timing ) );
+ return timings;
+ }
+
+ public void addTiming( Computer computer, long time )
+ {
+ if( !tracking ) return;
+
+ synchronized( this )
+ {
+ ComputerTracker computerTimings = timingLookup.get( computer );
+ if( computerTimings == null )
+ {
+ computerTimings = new ComputerTracker( computer );
+ timingLookup.put( computer, computerTimings );
+ timings.add( computerTimings );
+ }
+
+ computerTimings.addTiming( time );
+ }
+ }
+
+ public synchronized void addValue( Computer computer, TrackingField field, long change )
+ {
+ if( !tracking ) return;
+
+ synchronized( this )
+ {
+ ComputerTracker computerTimings = timingLookup.get( computer );
+ if( computerTimings == null )
+ {
+ computerTimings = new ComputerTracker( computer );
+ timingLookup.put( computer, computerTimings );
+ timings.add( computerTimings );
+ }
+
+ computerTimings.addValue( field, change );
+ }
+ }
+}
diff --git a/src/main/java/dan200/computercraft/core/tracking/TrackingField.java b/src/main/java/dan200/computercraft/core/tracking/TrackingField.java
new file mode 100644
index 000000000..392fb42fb
--- /dev/null
+++ b/src/main/java/dan200/computercraft/core/tracking/TrackingField.java
@@ -0,0 +1,54 @@
+package dan200.computercraft.core.tracking;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.LongFunction;
+
+public class TrackingField
+{
+ private static final Map fields = new HashMap<>();
+
+ public static final TrackingField TASKS = TrackingField.of( "tasks", "Tasks", x -> String.format( "%4d", x ) );
+ public static final TrackingField TOTAL_TIME = TrackingField.of( "total", "Total time", x -> String.format( "%7.1fms", x / 1e6 ) );
+ public static final TrackingField AVERAGE_TIME = TrackingField.of( "average", "Average time", x -> String.format( "%4.1fms", x / 1e6 ) );
+ public static final TrackingField MAX_TIME = TrackingField.of( "max", "Max time", x -> String.format( "%5.1fms", x / 1e6 ) );
+
+ private final String id;
+ private final String displayName;
+ private final LongFunction format;
+
+ public String id()
+ {
+ return id;
+ }
+
+ public String displayName()
+ {
+ return displayName;
+ }
+
+ private TrackingField( String id, String displayName, LongFunction format )
+ {
+ this.id = id;
+ this.displayName = displayName;
+ this.format = format;
+ }
+
+ public String format( long value )
+ {
+ return format.apply( value );
+ }
+
+ public static TrackingField of( String id, String displayName, LongFunction format )
+ {
+ TrackingField field = new TrackingField( id, displayName, format );
+ fields.put( id, field );
+ return field;
+ }
+
+ public static Map fields()
+ {
+ return Collections.unmodifiableMap( fields );
+ }
+}
diff --git a/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java b/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java
index 8036185bd..df602b534 100644
--- a/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java
+++ b/src/main/java/dan200/computercraft/shared/command/CommandComputerCraft.java
@@ -4,9 +4,13 @@ import com.google.common.collect.Sets;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
-import dan200.computercraft.core.computer.ComputerTimeTracker;
+import dan200.computercraft.core.tracking.ComputerTracker;
+import dan200.computercraft.core.tracking.Tracking;
+import dan200.computercraft.core.tracking.TrackingContext;
+import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.command.framework.*;
import dan200.computercraft.shared.computer.core.ServerComputer;
+import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.Entity;
@@ -25,6 +29,8 @@ import static dan200.computercraft.shared.command.framework.ChatHelpers.*;
public final class CommandComputerCraft extends CommandDelegate
{
+ public static final UUID SYSTEM_UUID = new UUID( 0, 0 );
+
public CommandComputerCraft()
{
super( create() );
@@ -311,7 +317,7 @@ public final class CommandComputerCraft extends CommandDelegate
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List arguments )
{
- ComputerTimeTracker.start();
+ getTimingContext( context ).start();
String stopCommand = "/" + context.parent().getFullPath() + " stop";
context.getSender().sendMessage( list(
@@ -330,23 +336,54 @@ public final class CommandComputerCraft extends CommandDelegate
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List arguments ) throws CommandException
{
- if( !ComputerTimeTracker.stop() ) throw new CommandException( "Tracking not enabled" );
- displayTimings( context );
+ TrackingContext timings = getTimingContext( context );
+ if( !timings.stop() ) throw new CommandException( "Tracking not enabled" );
+ displayTimings( context, timings.getTimings(), TrackingField.AVERAGE_TIME );
}
} );
track.register( new SubCommandBase(
- "dump", "Dump the latest track results", UserLevel.OWNER_OP,
+ "dump", "[kind]", "Dump the latest track results", UserLevel.OWNER_OP,
"Dump the latest results of computer tracking."
)
{
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List arguments ) throws CommandException
{
- displayTimings( context );
+ TrackingField field = TrackingField.AVERAGE_TIME;
+ if( arguments.size() >= 1 )
+ {
+ field = TrackingField.fields().get( arguments.get( 0 ) );
+ if( field == null ) throw new CommandException( "Unknown field '" + arguments.get( 0 ) + "'" );
+ }
+
+ displayTimings( context, getTimingContext( context ).getTimings(), field );
+ }
+
+ @Nonnull
+ @Override
+ public List getCompletion( @Nonnull CommandContext context, @Nonnull List arguments )
+ {
+ if( arguments.size() == 1 )
+ {
+ String match = arguments.get( 0 );
+
+ List out = new ArrayList<>();
+ for( String key : TrackingField.fields().keySet() )
+ {
+ if( CommandBase.doesStringStartWith( match, key ) ) out.add( key );
+ }
+
+ out.sort( Comparator.naturalOrder() );
+ return out;
+ }
+ else
+ {
+ return super.getCompletion( context, arguments );
+ }
}
} );
-
+
root.register( new SubCommandBase(
"reload", "Reload the ComputerCraft config file", UserLevel.OWNER_OP,
"Reload the ComputerCraft config file"
@@ -390,13 +427,22 @@ public final class CommandComputerCraft extends CommandDelegate
}
}
- private static void displayTimings( CommandContext context ) throws CommandException
+ private static TrackingContext getTimingContext( CommandContext context )
{
- List timings = ComputerTimeTracker.getTimings();
- if( timings.isEmpty() ) throw new CommandException( "No timings available" );
+ Entity entity = context.getSender().getCommandSenderEntity();
+ if( entity instanceof EntityPlayerMP )
+ {
+ return Tracking.getContext( entity.getUniqueID() );
+ }
+ else
+ {
+ return Tracking.getContext( SYSTEM_UUID );
+ }
+ }
- timings.sort( Comparator.comparing( ComputerTimeTracker.Timings::getAverage ).reversed() );
- TextTable table = new TextTable( "Computer", "Tasks", "Total", "Average", "Maximum" );
+ private static void displayTimings( CommandContext context, List timings, TrackingField field ) throws CommandException
+ {
+ if( timings.isEmpty() ) throw new CommandException( "No timings available" );
Map lookup = new HashMap<>();
int maxId = 0, maxInstance = 0;
@@ -411,7 +457,17 @@ public final class CommandComputerCraft extends CommandDelegate
ICommandSender sender = context.getSender();
boolean isPlayer = sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
- for( ComputerTimeTracker.Timings entry : timings )
+ timings.sort( Comparator.comparing( x -> x.get( field ) ).reversed() );
+
+ boolean defaultLayout = field == TrackingField.TASKS || field == TrackingField.TOTAL_TIME
+ || field == TrackingField.AVERAGE_TIME || field == TrackingField.MAX_TIME;
+
+
+ TextTable table = defaultLayout
+ ? new TextTable( "Computer", "Tasks", "Total", "Average", "Maximum" )
+ : new TextTable( "Computer", field.displayName() );
+
+ for( ComputerTracker entry : timings )
{
Computer computer = entry.getComputer();
ServerComputer serverComputer = computer == null ? null : lookup.get( computer );
@@ -437,13 +493,20 @@ public final class CommandComputerCraft extends CommandDelegate
) );
}
- table.addRow(
- computerComponent,
- formatted( "%4d", entry.getTasks() ),
- text( String.format( "%7.1f", entry.getTotalTime() / 1e6 ) + "ms" ),
- text( String.format( "%4.1f", entry.getAverage() / 1e6 ) + "ms" ),
- text( String.format( "%5.1f", entry.getMaxTime() / 1e6 ) + "ms" )
- );
+ if( defaultLayout )
+ {
+ table.addRow(
+ computerComponent,
+ text( entry.getFormatted( TrackingField.TASKS ) ),
+ text( entry.getFormatted( TrackingField.TOTAL_TIME ) ),
+ text( entry.getFormatted( TrackingField.AVERAGE_TIME ) ),
+ text( entry.getFormatted( TrackingField.MAX_TIME ) )
+ );
+ }
+ else
+ {
+ table.addRow( computerComponent, text( entry.getFormatted( field ) ) );
+ }
}
table.displayTo( context.getSender() );