1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-26 07:03:22 +00:00

Various improvements to the tracking system

- Trackers are created per-user, meaning multiple people can run
   /computercraft track at once.
 - Allow sorting the tracking information by arbitrary fields.
 - Add support for tracking arbitrary fields (though none are currently
   implemented).
This commit is contained in:
SquidDev 2018-04-16 21:06:16 +01:00
parent 6cf32f1f74
commit bfeafe163f
9 changed files with 383 additions and 137 deletions

View File

@ -11,6 +11,7 @@
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 @@ interface IPeripheralChangeListener
String getLabel();
void setLabel( String label );
void addTrackingChange( TrackingField field, long change );
}

View File

@ -20,6 +20,8 @@
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 void setLabel( String label )
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 )

View File

@ -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 @@ private void execute( BlockingQueue<ITask> queue ) throws InterruptedException
{
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 )

View File

@ -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 <em>will</em> 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> 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> timings = new ArrayList<>();
private static final Map<Computer, Timings> 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<Timings> 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 );
}
}

View File

@ -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> computer;
private final int computerId;
private long tasks;
private long totalTime;
private long maxTime;
private final TObjectLongHashMap<TrackingField> 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 ) );
}
}

View File

@ -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<UUID, TrackingContext> 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 );
}
}
}

View File

@ -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 <em>will</em> 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<ComputerTracker> timings = new ArrayList<>();
private final Map<Computer, ComputerTracker> 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<ComputerTracker> getTimings()
{
ArrayList<ComputerTracker> 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 );
}
}
}

View File

@ -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<String, TrackingField> 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<String> format;
public String id()
{
return id;
}
public String displayName()
{
return displayName;
}
private TrackingField( String id, String displayName, LongFunction<String> 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<String> format )
{
TrackingField field = new TrackingField( id, displayName, format );
fields.put( id, field );
return field;
}
public static Map<String, TrackingField> fields()
{
return Collections.unmodifiableMap( fields );
}
}

View File

@ -4,9 +4,13 @@
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 @@
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 List<String> getCompletion( @Nonnull CommandContext context, @Nonnull Lis
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments )
{
ComputerTimeTracker.start();
getTimingContext( context ).start();
String stopCommand = "/" + context.parent().getFullPath() + " stop";
context.getSender().sendMessage( list(
@ -330,23 +336,54 @@ public void execute( @Nonnull CommandContext context, @Nonnull List<String> argu
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> 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<String> 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<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
{
if( arguments.size() == 1 )
{
String match = arguments.get( 0 );
List<String> 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 @@ private static ITextComponent linkPosition( CommandContext context, ServerComput
}
}
private static void displayTimings( CommandContext context ) throws CommandException
private static TrackingContext getTimingContext( CommandContext context )
{
List<ComputerTimeTracker.Timings> 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<ComputerTracker> timings, TrackingField field ) throws CommandException
{
if( timings.isEmpty() ) throw new CommandException( "No timings available" );
Map<Computer, ServerComputer> lookup = new HashMap<>();
int maxId = 0, maxInstance = 0;
@ -411,7 +457,17 @@ private static void displayTimings( CommandContext context ) throws CommandExcep
ICommandSender sender = context.getSender();
boolean isPlayer = sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
for( ComputerTimeTracker.Timings entry : timings )
timings.sort( Comparator.<ComputerTracker, Long>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 @@ private static void displayTimings( CommandContext context ) throws CommandExcep
) );
}
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() );