1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-14 20:20:30 +00:00

Merge branch 'feature/new-metrics' into mc-1.16.x

This commit is contained in:
Jonathan Coates 2022-10-22 12:21:41 +01:00
commit 68da044ff2
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
43 changed files with 1107 additions and 760 deletions

View File

@ -16,7 +16,7 @@ import dan200.computercraft.core.apis.handles.EncodedWritableHandle;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.filesystem.FileSystemWrapper;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.core.metrics.Metrics;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@ -108,7 +108,7 @@ public class FSAPI implements ILuaAPI
@LuaFunction
public final String[] list( String path ) throws LuaException
{
environment.addTrackingChange( TrackingField.FS_OPS );
environment.observe( Metrics.FS_OPS );
try
{
return fileSystem.list( path );
@ -276,7 +276,7 @@ public class FSAPI implements ILuaAPI
{
try
{
environment.addTrackingChange( TrackingField.FS_OPS );
environment.observe( Metrics.FS_OPS );
fileSystem.makeDir( path );
}
catch( FileSystemException e )
@ -299,7 +299,7 @@ public class FSAPI implements ILuaAPI
{
try
{
environment.addTrackingChange( TrackingField.FS_OPS );
environment.observe( Metrics.FS_OPS );
fileSystem.move( path, dest );
}
catch( FileSystemException e )
@ -322,7 +322,7 @@ public class FSAPI implements ILuaAPI
{
try
{
environment.addTrackingChange( TrackingField.FS_OPS );
environment.observe( Metrics.FS_OPS );
fileSystem.copy( path, dest );
}
catch( FileSystemException e )
@ -345,7 +345,7 @@ public class FSAPI implements ILuaAPI
{
try
{
environment.addTrackingChange( TrackingField.FS_OPS );
environment.observe( Metrics.FS_OPS );
fileSystem.delete( path );
}
catch( FileSystemException e )
@ -411,7 +411,7 @@ public class FSAPI implements ILuaAPI
@LuaFunction
public final Object[] open( String path, String mode ) throws LuaException
{
environment.addTrackingChange( TrackingField.FS_OPS );
environment.observe( Metrics.FS_OPS );
try
{
switch( mode )
@ -532,7 +532,7 @@ public class FSAPI implements ILuaAPI
{
try
{
environment.addTrackingChange( TrackingField.FS_OPS );
environment.observe( Metrics.FS_OPS );
return fileSystem.find( path );
}
catch( FileSystemException e )

View File

@ -11,8 +11,8 @@ import dan200.computercraft.core.computer.ComputerEnvironment;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -75,10 +75,7 @@ public interface IAPIEnvironment
void cancelTimer( int id );
void addTrackingChange( @Nonnull TrackingField field, long change );
void observe( @Nonnull Metric.Event event, long change );
default void addTrackingChange( @Nonnull TrackingField field )
{
addTrackingChange( field, 1 );
}
void observe( @Nonnull Metric.Counter counter );
}

View File

@ -16,7 +16,7 @@ import dan200.computercraft.core.asm.LuaMethod;
import dan200.computercraft.core.asm.NamedMethod;
import dan200.computercraft.core.asm.PeripheralMethod;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.util.LuaUtil;
import javax.annotation.Nonnull;
@ -108,7 +108,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
if( method == null ) throw new LuaException( "No such method " + methodName );
environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
environment.observe( Metrics.PERIPHERAL_OPS );
return method.apply( peripheral, context, this, arguments );
}

View File

@ -12,7 +12,7 @@ import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.core.apis.http.Resource;
import dan200.computercraft.core.apis.http.ResourceGroup;
import dan200.computercraft.core.apis.http.options.Options;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.core.metrics.Metrics;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
@ -148,8 +148,8 @@ public class HttpRequest extends Resource<HttpRequest>
}
// Add request size to the tracker before opening the connection
environment.addTrackingChange( TrackingField.HTTP_REQUESTS, 1 );
environment.addTrackingChange( TrackingField.HTTP_UPLOAD, requestBody );
environment.observe( Metrics.HTTP_REQUESTS );
environment.observe( Metrics.HTTP_UPLOAD, requestBody );
HttpRequestHandler handler = currentRequest = new HttpRequestHandler( this, uri, method, options );
connectFuture = new Bootstrap()

View File

@ -13,7 +13,7 @@ import dan200.computercraft.core.apis.handles.HandleGeneric;
import dan200.computercraft.core.apis.http.HTTPRequestException;
import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.core.apis.http.options.Options;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.core.metrics.Metrics;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.channel.ChannelHandlerContext;
@ -202,7 +202,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
}
// Fire off a stats event
request.environment().addTrackingChange( TrackingField.HTTP_DOWNLOAD, getHeaderSize( responseHeaders ) + bytes.length );
request.environment().observe( Metrics.HTTP_DOWNLOAD, getHeaderSize( responseHeaders ) + bytes.length );
// Prepare to queue an event
ArrayByteChannel contents = new ArrayByteChannel( bytes );

View File

@ -8,7 +8,7 @@ package dan200.computercraft.core.apis.http.websocket;
import com.google.common.base.Objects;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.apis.http.options.Options;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.util.StringUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
@ -89,7 +89,7 @@ public class WebsocketHandle implements Closeable
throw new LuaException( "Message is too large" );
}
websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() );
websocket.environment().observe( Metrics.WEBSOCKET_OUTGOING, text.length() );
Channel channel = this.channel;
if( channel != null )

View File

@ -7,7 +7,7 @@ package dan200.computercraft.core.apis.http.websocket;
import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.core.apis.http.options.Options;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.core.metrics.Metrics;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse;
@ -66,14 +66,14 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
{
String data = ((TextWebSocketFrame) frame).text();
websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, data.length() );
websocket.environment().observe( Metrics.WEBSOCKET_INCOMING, data.length() );
websocket.environment().queueEvent( MESSAGE_EVENT, websocket.address(), data, false );
}
else if( frame instanceof BinaryWebSocketFrame )
{
byte[] converted = NetworkUtils.toBytes( frame.content() );
websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length );
websocket.environment().observe( Metrics.WEBSOCKET_INCOMING, converted.length );
websocket.environment().queueEvent( MESSAGE_EVENT, websocket.address(), converted, true );
}
else if( frame instanceof CloseWebSocketFrame )

View File

@ -36,7 +36,6 @@ public class Computer
private String label = null;
// Read-only fields about the computer
private final ComputerEnvironment computerEnvironment;
private final GlobalEnvironment globalEnvironment;
private final Terminal terminal;
private final ComputerExecutor executor;
@ -44,7 +43,7 @@ public class Computer
// Additional state about the computer and its environment.
private boolean blinking = false;
private final Environment internalEnvironment = new Environment( this );
private final Environment internalEnvironment;
private final AtomicBoolean externalOutputChanged = new AtomicBoolean();
private boolean startRequested;
@ -55,16 +54,11 @@ public class Computer
if( id < 0 ) throw new IllegalStateException( "Id has not been assigned" );
this.id = id;
this.globalEnvironment = globalEnvironment;
this.computerEnvironment = environment;
this.terminal = terminal;
executor = new ComputerExecutor( this );
serverExecutor = new MainThreadExecutor( this );
}
ComputerEnvironment getComputerEnvironment()
{
return computerEnvironment;
internalEnvironment = new Environment( this, environment );
executor = new ComputerExecutor( this, environment );
serverExecutor = new MainThreadExecutor( environment.getMetrics() );
}
GlobalEnvironment getGlobalEnvironment()

View File

@ -7,6 +7,7 @@ package dan200.computercraft.core.computer;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.metrics.MetricsObserver;
import javax.annotation.Nullable;
@ -26,6 +27,14 @@ public interface ComputerEnvironment
*/
double getTimeOfDay();
/**
* Get the {@link MetricsObserver} for this computer. This should be constant for the duration of this
* {@link ComputerEnvironment}.
*
* @return This computer's {@link MetricsObserver}.
*/
MetricsObserver getMetrics();
/**
* Construct the mount for this computer's user-writable data.
*

View File

@ -15,9 +15,11 @@ import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.lua.MachineEnvironment;
import dan200.computercraft.core.lua.MachineResult;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.IoUtil;
@ -57,6 +59,8 @@ final class ComputerExecutor
private static final int QUEUE_LIMIT = 256;
private final Computer computer;
private final ComputerEnvironment computerEnvironment;
private final MetricsObserver metrics;
private final List<ILuaAPI> apis = new ArrayList<>();
final TimeoutState timeout = new TimeoutState();
@ -157,12 +161,14 @@ final class ComputerExecutor
*/
final AtomicReference<Thread> executingThread = new AtomicReference<>();
ComputerExecutor( Computer computer )
ComputerExecutor( Computer computer, ComputerEnvironment computerEnvironment )
{
// Ensure the computer thread is running as required.
ComputerThread.start();
this.computer = computer;
this.computerEnvironment = computerEnvironment;
metrics = computerEnvironment.getMetrics();
Environment environment = computer.getEnvironment();
@ -345,7 +351,7 @@ final class ComputerExecutor
private IWritableMount getRootMount()
{
if( rootMount == null ) rootMount = computer.getComputerEnvironment().createRootMount();
if( rootMount == null ) rootMount = computerEnvironment.createRootMount();
return rootMount;
}
@ -395,7 +401,9 @@ final class ComputerExecutor
}
// Create the lua machine
ILuaMachine machine = luaFactory.create( computer, timeout );
ILuaMachine machine = luaFactory.create( new MachineEnvironment(
new LuaContext( computer ), metrics, timeout, computer.getGlobalEnvironment().getHostString()
) );
// Add the APIs. We unwrap them (yes, this is horrible) to get access to the underlying object.
for( ILuaAPI api : apis ) machine.addAPI( api instanceof ApiWrapper ? ((ApiWrapper) api).getDelegate() : api );
@ -522,7 +530,7 @@ final class ComputerExecutor
timeout.stopTimer();
}
Tracking.addTaskTiming( getComputer(), timeout.nanoCurrent() );
metrics.observe( Metrics.COMPUTER_TASKS, timeout.nanoCurrent() );
if( interruptedEvent ) return true;

View File

@ -10,9 +10,9 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@ -42,6 +42,8 @@ import java.util.Iterator;
public final class Environment implements IAPIEnvironment
{
private final Computer computer;
private final ComputerEnvironment environment;
private final MetricsObserver metrics;
private boolean internalOutputChanged = false;
private final int[] internalOutput = new int[ComputerSide.COUNT];
@ -60,9 +62,11 @@ public final class Environment implements IAPIEnvironment
private final Int2ObjectMap<Timer> timers = new Int2ObjectOpenHashMap<>();
private int nextTimerToken = 0;
Environment( Computer computer )
Environment( Computer computer, ComputerEnvironment environment )
{
this.computer = computer;
this.environment = environment;
metrics = environment.getMetrics();
}
@Override
@ -75,7 +79,7 @@ public final class Environment implements IAPIEnvironment
@Override
public ComputerEnvironment getComputerEnvironment()
{
return computer.getComputerEnvironment();
return environment;
}
@Nonnull
@ -367,9 +371,15 @@ public final class Environment implements IAPIEnvironment
}
@Override
public void addTrackingChange( @Nonnull TrackingField field, long change )
public void observe( @Nonnull Metric.Event event, long change )
{
Tracking.addValue( computer, field, change );
metrics.observe( event, change );
}
@Override
public void observe( @Nonnull Metric.Counter counter )
{
metrics.observe( counter );
}
private static class Timer

View File

@ -3,14 +3,12 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.MainThread;
import javax.annotation.Nonnull;

View File

@ -7,7 +7,8 @@ package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.shared.turtle.core.TurtleBrain;
import net.minecraft.tileentity.TileEntity;
@ -56,7 +57,7 @@ final class MainThreadExecutor implements IWorkMonitor
*/
private static final int MAX_TASKS = 5000;
private final Computer computer;
private final MetricsObserver metrics;
/**
* A lock used for any changes to {@link #tasks}, or {@link #onQueue}. This will be
@ -109,9 +110,9 @@ final class MainThreadExecutor implements IWorkMonitor
long virtualTime;
MainThreadExecutor( Computer computer )
MainThreadExecutor( MetricsObserver metrics )
{
this.computer = computer;
this.metrics = metrics;
}
/**
@ -194,7 +195,7 @@ final class MainThreadExecutor implements IWorkMonitor
private void consumeTime( long time )
{
Tracking.addServerTiming( computer, time );
metrics.observe( Metrics.SERVER_TASKS, time );
// Reset the budget if moving onto a new tick. We know this is safe, as this will only have happened if
// #tickCooling() isn't called, and so we didn't overrun the previous tick.

View File

@ -12,10 +12,9 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaFunction;
import dan200.computercraft.core.asm.LuaMethod;
import dan200.computercraft.core.asm.ObjectSource;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.TimeoutState;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.shared.util.ThreadUtils;
import org.squiddev.cobalt.*;
import org.squiddev.cobalt.compiler.CompileException;
@ -52,7 +51,6 @@ public class CobaltLuaMachine implements ILuaMachine
private static final LuaMethod FUNCTION_METHOD = ( target, context, args ) -> ((ILuaFunction) target).call( args );
private final Computer computer;
private final TimeoutState timeout;
private final TimeoutDebugHandler debug;
private final ILuaContext context;
@ -63,19 +61,19 @@ public class CobaltLuaMachine implements ILuaMachine
private LuaThread mainRoutine = null;
private String eventFilter = null;
public CobaltLuaMachine( Computer computer, TimeoutState timeout )
public CobaltLuaMachine( MachineEnvironment environment )
{
this.computer = computer;
this.timeout = timeout;
context = new LuaContext( computer );
timeout = environment.timeout;
context = environment.context;
debug = new TimeoutDebugHandler();
// Create an environment to run in
MetricsObserver metrics = environment.metrics;
LuaState state = this.state = LuaState.builder()
.resourceManipulator( new VoidResourceManipulator() )
.debug( debug )
.coroutineExecutor( command -> {
Tracking.addValue( this.computer, TrackingField.COROUTINES_CREATED, 1 );
metrics.observe( Metrics.COROUTINES_CREATED );
COROUTINES.execute( () -> {
try
{
@ -83,7 +81,7 @@ public class CobaltLuaMachine implements ILuaMachine
}
finally
{
Tracking.addValue( this.computer, TrackingField.COROUTINES_DISPOSED, 1 );
metrics.observe( Metrics.COROUTINES_DISPOSED );
}
} );
} )
@ -110,7 +108,7 @@ public class CobaltLuaMachine implements ILuaMachine
// Add version globals
globals.rawset( "_VERSION", valueOf( "Lua 5.1" ) );
globals.rawset( "_HOST", valueOf( computer.getAPIEnvironment().getGlobalEnvironment().getHostString() ) );
globals.rawset( "_HOST", valueOf( environment.hostString ) );
globals.rawset( "_CC_DEFAULT_SETTINGS", valueOf( ComputerCraft.defaultComputerSettings ) );
if( ComputerCraft.disableLua51Features )
{

View File

@ -7,8 +7,6 @@ package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.IDynamicLuaObject;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.TimeoutState;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -77,6 +75,6 @@ public interface ILuaMachine
interface Factory
{
ILuaMachine create( Computer computer, TimeoutState timeout );
ILuaMachine create( MachineEnvironment environment );
}
}

View File

@ -0,0 +1,49 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.core.computer.TimeoutState;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver;
/**
* Arguments used to construct a {@link ILuaMachine}.
*
* @see ILuaMachine.Factory
*/
public class MachineEnvironment
{
/**
* The Lua context to execute main-thread tasks with.
*/
public final ILuaContext context;
/**
* A sink to submit metrics to. You do not need to submit task timings here, it should only be for additional
* metrics such as {@link Metrics#COROUTINES_CREATED}
*/
public final MetricsObserver metrics;
/**
* The current timeout state. This should be used by the machine to interrupt its execution.
*/
public final TimeoutState timeout;
/**
* A {@linkplain GlobalEnvironment#getHostString() host string} to identify the current environment.
*/
public final String hostString;
public MachineEnvironment( ILuaContext context, MetricsObserver metrics, TimeoutState timeout, String hostString )
{
this.context = context;
this.metrics = metrics;
this.timeout = timeout;
this.hostString = hostString;
}
}

View File

@ -0,0 +1,141 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.metrics;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.LongFunction;
/**
* A metric is some event which is emitted by a computer and observed by a {@link MetricsObserver}.
* <p>
* It comes in two forms: a simple {@link Metric.Counter} counts how many times an event has occurred (for instance,
* how many HTTP requests have there been) while a {@link Metric.Event} has a discrete value for each event (for
* instance, the number of bytes downloaded in this HTTP request). The values tied to a {@link Metric.Event}s can be
* accumulated, and thus used to derive averages, rates and be sorted into histogram buckets.
*/
public abstract class Metric
{
private static final Map<String, Metric> allMetrics = new ConcurrentHashMap<>();
private static final AtomicInteger nextId = new AtomicInteger();
private final int id;
private final String name;
private final String unit;
private final LongFunction<String> format;
private Metric( String name, String unit, LongFunction<String> format )
{
if( allMetrics.containsKey( name ) ) throw new IllegalStateException( "Duplicate key " + name );
id = nextId.getAndIncrement();
this.name = name;
this.unit = unit;
this.format = format;
allMetrics.put( name, this );
}
/**
* The unique ID for this metric.
* <p>
* Each metric is assigned a consecutive metric ID starting from 0. This allows you to store metrics in an array,
* rather than a map.
*
* @return The metric's ID.
*/
public int id()
{
return id;
}
/**
* The unique name for this metric.
*
* @return The metric's name.
*/
public String name()
{
return name;
}
/**
* The unit used for this metric. This should be a lowercase "identifier-like" such as {@literal ms}. If no unit is
* relevant, it can be empty.
*
* @return The metric's unit.
*/
public String unit()
{
return unit;
}
/**
* Format a value according to the metric's formatting rules. Implementations may choose to append units to the
* returned value where relevant.
*
* @param value The value to format.
* @return The formatted value.
*/
public String format( long value )
{
return format.apply( value );
}
@Override
public String toString()
{
return getClass().getName() + ":" + name();
}
/**
* Get a map of all metrics.
*
* @return A map of all metrics.
*/
public static Map<String, Metric> metrics()
{
return Collections.unmodifiableMap( allMetrics );
}
public static final class Counter extends Metric
{
public Counter( String id )
{
super( id, "", Metric::formatDefault );
}
}
public static final class Event extends Metric
{
public Event( String id, String unit, LongFunction<String> format )
{
super( id, unit, format );
}
}
public static String formatTime( long value )
{
return String.format( "%.1fms", value * 1e-6 );
}
public static String formatDefault( long value )
{
return String.format( "%d", value );
}
private static final int KILOBYTE_SIZE = 1024;
private static final String SI_PREFIXES = "KMGT";
public static String formatBytes( long bytes )
{
if( bytes < KILOBYTE_SIZE ) return String.format( "%d B", bytes );
int exp = (int) (Math.log( (double) bytes ) / Math.log( KILOBYTE_SIZE ));
if( exp > SI_PREFIXES.length() ) exp = SI_PREFIXES.length();
return String.format( "%.1f %siB", bytes / Math.pow( KILOBYTE_SIZE, exp ), SI_PREFIXES.charAt( exp - 1 ) );
}
}

View File

@ -0,0 +1,41 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.metrics;
/**
* Built-in metrics that CC produces.
*/
public final class Metrics
{
private Metrics()
{
}
public static final Metric.Event COMPUTER_TASKS = new Metric.Event( "computer_tasks", "ms", Metric::formatTime );
public static final Metric.Event SERVER_TASKS = new Metric.Event( "server_tasks", "ms", Metric::formatTime );
public static final Metric.Counter PERIPHERAL_OPS = new Metric.Counter( "peripheral" );
public static final Metric.Counter FS_OPS = new Metric.Counter( "fs" );
public static final Metric.Counter HTTP_REQUESTS = new Metric.Counter( "http_requests" );
public static final Metric.Event HTTP_UPLOAD = new Metric.Event( "http_upload", "bytes", Metric::formatBytes );
public static final Metric.Event HTTP_DOWNLOAD = new Metric.Event( "http_download", "bytes", Metric::formatBytes );
public static final Metric.Event WEBSOCKET_INCOMING = new Metric.Event( "websocket_incoming", "bytes", Metric::formatBytes );
public static final Metric.Event WEBSOCKET_OUTGOING = new Metric.Event( "websocket_outgoing", "bytes", Metric::formatBytes );
public static final Metric.Counter COROUTINES_CREATED = new Metric.Counter( "coroutines_created" );
public static final Metric.Counter COROUTINES_DISPOSED = new Metric.Counter( "coroutines_dead" );
public static final Metric.Counter TURTLE_OPS = new Metric.Counter( "turtle_ops" );
/**
* Ensures metrics are registered.
*/
public static void init()
{
}
}

View File

@ -0,0 +1,36 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.metrics;
import dan200.computercraft.core.computer.ComputerEnvironment;
/**
* A metrics observer is used to report metrics for a single computer.
* <p>
* Various components (such as the computer scheduler or http API) will report statistics about their behaviour to the
* observer. The observer may choose to consume these metrics, aggregating them and presenting them to the user in some
* manner.
*
* @see ComputerEnvironment#getMetrics()
* @see Metrics Built-in metrics which will be reported.
*/
public interface MetricsObserver
{
/**
* Increment a counter by 1.
*
* @param counter The counter to observe.
*/
void observe( Metric.Counter counter );
/**
* Observe a single instance of an event.
*
* @param event The event to observe.
* @param value The value corresponding to this event.
*/
void observe( Metric.Event event, long value );
}

View File

@ -1,156 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.tracking;
import com.google.common.base.CaseFormat;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.computer.Computer;
import net.minecraft.util.text.LanguageMap;
import javax.annotation.Nonnull;
import javax.management.*;
import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.LongSupplier;
public final class ComputerMBean implements DynamicMBean, Tracker
{
private static final Set<TrackingField> SKIP = new HashSet<>( Arrays.asList(
TrackingField.TASKS, TrackingField.TOTAL_TIME, TrackingField.AVERAGE_TIME, TrackingField.MAX_TIME,
TrackingField.SERVER_COUNT, TrackingField.SERVER_TIME
) );
private static ComputerMBean instance;
private final Map<String, LongSupplier> attributes = new HashMap<>();
private final Map<TrackingField, Counter> values = new HashMap<>();
private final MBeanInfo info;
private ComputerMBean()
{
List<MBeanAttributeInfo> attributes = new ArrayList<>();
for( Map.Entry<String, TrackingField> field : TrackingField.fields().entrySet() )
{
if( SKIP.contains( field.getValue() ) ) continue;
String name = CaseFormat.LOWER_UNDERSCORE.to( CaseFormat.LOWER_CAMEL, field.getKey() );
add( name, field.getValue(), attributes, null );
}
add( "task", TrackingField.TOTAL_TIME, attributes, TrackingField.TASKS );
add( "serverTask", TrackingField.SERVER_TIME, attributes, TrackingField.SERVER_COUNT );
this.info = new MBeanInfo(
ComputerMBean.class.getSimpleName(),
"metrics about all computers on the server",
attributes.toArray( new MBeanAttributeInfo[0] ), null, null, null
);
}
public static void register()
{
try
{
ManagementFactory.getPlatformMBeanServer().registerMBean( instance = new ComputerMBean(), new ObjectName( "dan200.computercraft:type=Computers" ) );
}
catch( InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException |
MalformedObjectNameException e )
{
ComputerCraft.log.warn( "Failed to register JMX bean", e );
}
}
public static void registerTracker()
{
if( instance != null ) Tracking.add( instance );
}
@Override
public Object getAttribute( String attribute ) throws AttributeNotFoundException, MBeanException, ReflectionException
{
LongSupplier value = attributes.get( attribute );
if( value == null ) throw new AttributeNotFoundException();
return value.getAsLong();
}
@Override
public void setAttribute( Attribute attribute ) throws InvalidAttributeValueException
{
throw new InvalidAttributeValueException( "Cannot set attribute" );
}
@Override
public AttributeList getAttributes( String[] attributes )
{
return null;
}
@Override
public AttributeList setAttributes( AttributeList attributes )
{
return new AttributeList();
}
@Override
public Object invoke( String actionName, Object[] params, String[] signature ) throws MBeanException, ReflectionException
{
return null;
}
@Override
@Nonnull
public MBeanInfo getMBeanInfo()
{
return info;
}
@Override
public void addTaskTiming( Computer computer, long time )
{
addValue( computer, TrackingField.TOTAL_TIME, time );
}
@Override
public void addServerTiming( Computer computer, long time )
{
addValue( computer, TrackingField.SERVER_TIME, time );
}
@Override
public void addValue( Computer computer, TrackingField field, long change )
{
Counter counter = values.get( field );
counter.value.addAndGet( change );
counter.count.incrementAndGet();
}
private MBeanAttributeInfo addAttribute( String name, String description, LongSupplier value )
{
attributes.put( name, value );
return new MBeanAttributeInfo( name, "long", description, true, false, false );
}
private void add( String name, TrackingField field, List<MBeanAttributeInfo> attributes, TrackingField count )
{
Counter counter = new Counter();
values.put( field, counter );
String prettyName = LanguageMap.getInstance().getOrDefault( field.translationKey() );
attributes.add( addAttribute( name, prettyName, counter.value::longValue ) );
if( count != null )
{
String countName = LanguageMap.getInstance().getOrDefault( count.translationKey() );
attributes.add( addAttribute( name + "Count", countName, counter.count::longValue ) );
}
}
private static class Counter
{
AtomicLong value = new AtomicLong();
AtomicLong count = new AtomicLong();
}
}

View File

@ -1,122 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.tracking;
import dan200.computercraft.core.computer.Computer;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
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 long serverCount;
private long serverTime;
private final Object2LongOpenHashMap<TrackingField> fields;
public ComputerTracker( Computer computer )
{
this.computer = new WeakReference<>( computer );
computerId = computer.getID();
fields = new Object2LongOpenHashMap<>();
}
ComputerTracker( ComputerTracker timings )
{
computer = timings.computer;
computerId = timings.computerId;
tasks = timings.tasks;
totalTime = timings.totalTime;
maxTime = timings.maxTime;
serverCount = timings.serverCount;
serverTime = timings.serverTime;
fields = new Object2LongOpenHashMap<>( 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 addTaskTiming( long time )
{
tasks++;
totalTime += time;
if( time > maxTime ) maxTime = time;
}
void addMainTiming( long time )
{
serverCount++;
serverTime += time;
}
void addValue( TrackingField field, long change )
{
synchronized( fields )
{
fields.addTo( field, 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 tasks == 0 ? 0 : totalTime / tasks;
if( field == TrackingField.SERVER_COUNT ) return serverCount;
if( field == TrackingField.SERVER_TIME ) return serverTime;
synchronized( fields )
{
return fields.getLong( field );
}
}
public String getFormatted( TrackingField field )
{
return field.format( get( field ) );
}
}

View File

@ -1,47 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.tracking;
import dan200.computercraft.core.computer.Computer;
public interface Tracker
{
/**
* Report how long a task executed on the computer thread took.
*
* Computer thread tasks include events or a computer being turned on/off.
*
* @param computer The computer processing this task
* @param time The time taken for this task.
*/
default void addTaskTiming( Computer computer, long time )
{
}
/**
* Report how long a task executed on the server thread took.
*
* Server tasks include actions performed by peripherals.
*
* @param computer The computer processing this task
* @param time The time taken for this task.
*/
default void addServerTiming( Computer computer, long time )
{
}
/**
* Increment an arbitrary field by some value. Implementations may track how often this is called
* as well as the change, to compute some level of "average".
*
* @param computer The computer to increment
* @param field The field to increment.
* @param change The amount to increment said field by.
*/
default void addValue( Computer computer, TrackingField field, long change )
{
}
}

View File

@ -1,87 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.tracking;
import dan200.computercraft.core.computer.Computer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
public final class Tracking
{
static final AtomicInteger tracking = new AtomicInteger( 0 );
private static final Object lock = new Object();
private static final HashMap<UUID, TrackingContext> contexts = new HashMap<>();
private static final List<Tracker> trackers = new ArrayList<>();
private Tracking() {}
public static TrackingContext getContext( UUID uuid )
{
synchronized( lock )
{
TrackingContext context = contexts.get( uuid );
if( context == null ) contexts.put( uuid, context = new TrackingContext() );
return context;
}
}
public static void add( Tracker tracker )
{
synchronized( lock )
{
trackers.add( tracker );
tracking.incrementAndGet();
}
}
public static void addTaskTiming( Computer computer, long time )
{
if( tracking.get() == 0 ) return;
synchronized( contexts )
{
for( TrackingContext context : contexts.values() ) context.addTaskTiming( computer, time );
for( Tracker tracker : trackers ) tracker.addTaskTiming( computer, time );
}
}
public static void addServerTiming( Computer computer, long time )
{
if( tracking.get() == 0 ) return;
synchronized( contexts )
{
for( TrackingContext context : contexts.values() ) context.addServerTiming( computer, time );
for( Tracker tracker : trackers ) tracker.addServerTiming( computer, time );
}
}
public static void addValue( Computer computer, TrackingField field, long change )
{
if( tracking.get() == 0 ) return;
synchronized( lock )
{
for( TrackingContext context : contexts.values() ) context.addValue( computer, field, change );
for( Tracker tracker : trackers ) tracker.addValue( computer, field, change );
}
}
public static void reset()
{
synchronized( lock )
{
contexts.clear();
trackers.clear();
tracking.set( 0 );
}
}
}

View File

@ -1,116 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
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 implements Tracker
{
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> getImmutableTimings()
{
ArrayList<ComputerTracker> timings = new ArrayList<>( this.timings.size() );
for( ComputerTracker timing : this.timings ) timings.add( new ComputerTracker( timing ) );
return timings;
}
public synchronized List<ComputerTracker> getTimings()
{
return new ArrayList<>( timings );
}
@Override
public void addTaskTiming( 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.addTaskTiming( time );
}
}
@Override
public void addServerTiming( 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.addMainTiming( time );
}
}
@Override
public 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

@ -1,96 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.tracking;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.LongFunction;
public final class TrackingField
{
private static final Map<String, TrackingField> fields = new HashMap<>();
public static final TrackingField TASKS = TrackingField.of( "tasks", x -> String.format( "%4d", x ) );
public static final TrackingField TOTAL_TIME = TrackingField.of( "total", x -> String.format( "%7.1fms", x / 1e6 ) );
public static final TrackingField AVERAGE_TIME = TrackingField.of( "average", x -> String.format( "%4.1fms", x / 1e6 ) );
public static final TrackingField MAX_TIME = TrackingField.of( "max", x -> String.format( "%5.1fms", x / 1e6 ) );
public static final TrackingField SERVER_COUNT = TrackingField.of( "server_count", x -> String.format( "%4d", x ) );
public static final TrackingField SERVER_TIME = TrackingField.of( "server_time", x -> String.format( "%7.1fms", x / 1e6 ) );
public static final TrackingField PERIPHERAL_OPS = TrackingField.of( "peripheral", TrackingField::formatDefault );
public static final TrackingField FS_OPS = TrackingField.of( "fs", TrackingField::formatDefault );
public static final TrackingField TURTLE_OPS = TrackingField.of( "turtle", TrackingField::formatDefault );
public static final TrackingField HTTP_REQUESTS = TrackingField.of( "http", TrackingField::formatDefault );
public static final TrackingField HTTP_UPLOAD = TrackingField.of( "http_upload", TrackingField::formatBytes );
public static final TrackingField HTTP_DOWNLOAD = TrackingField.of( "http_download", TrackingField::formatBytes );
public static final TrackingField WEBSOCKET_INCOMING = TrackingField.of( "websocket_incoming", TrackingField::formatBytes );
public static final TrackingField WEBSOCKET_OUTGOING = TrackingField.of( "websocket_outgoing", TrackingField::formatBytes );
public static final TrackingField COROUTINES_CREATED = TrackingField.of( "coroutines_created", x -> String.format( "%4d", x ) );
public static final TrackingField COROUTINES_DISPOSED = TrackingField.of( "coroutines_dead", x -> String.format( "%4d", x ) );
private final String id;
private final String translationKey;
private final LongFunction<String> format;
public String id()
{
return id;
}
public String translationKey()
{
return translationKey;
}
private TrackingField( String id, LongFunction<String> format )
{
this.id = id;
translationKey = "tracking_field.computercraft." + id + ".name";
this.format = format;
}
public String format( long value )
{
return format.apply( value );
}
public static TrackingField of( String id, LongFunction<String> format )
{
TrackingField field = new TrackingField( id, format );
fields.put( id, field );
return field;
}
public static Map<String, TrackingField> fields()
{
return Collections.unmodifiableMap( fields );
}
private static String formatDefault( long value )
{
return String.format( "%6d", value );
}
/**
* So technically a kibibyte, but let's not argue here.
*/
private static final int KILOBYTE_SIZE = 1024;
private static final String SI_PREFIXES = "KMGT";
private static String formatBytes( long bytes )
{
if( bytes < 1024 ) return String.format( "%10d B", bytes );
int exp = (int) (Math.log( bytes ) / Math.log( KILOBYTE_SIZE ));
if( exp > SI_PREFIXES.length() ) exp = SI_PREFIXES.length();
return String.format( "%10.1f %siB", bytes / Math.pow( KILOBYTE_SIZE, exp ), SI_PREFIXES.charAt( exp - 1 ) );
}
}

View File

@ -9,10 +9,9 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.filesystem.ResourceMount;
import dan200.computercraft.core.tracking.ComputerMBean;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.metrics.ComputerMBean;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import net.minecraft.entity.EntityType;
import net.minecraft.loot.ConstantRange;
@ -74,7 +73,7 @@ public final class CommonHooks
resetState();
ServerContext.create( server );
ComputerMBean.registerTracker();
ComputerMBean.start( server );
}
@SubscribeEvent
@ -88,7 +87,6 @@ public final class CommonHooks
ServerContext.close();
MainThread.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
NetworkUtils.reset();
}

View File

@ -9,17 +9,17 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerSide;
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.core.metrics.Metrics;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.computer.metrics.basic.Aggregate;
import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
import dan200.computercraft.shared.computer.metrics.basic.BasicComputerMetricsObserver;
import dan200.computercraft.shared.computer.metrics.basic.ComputerMetrics;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import net.minecraft.command.CommandSource;
import net.minecraft.entity.Entity;
@ -46,7 +46,7 @@ import static dan200.computercraft.shared.command.Exceptions.*;
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.getComputerArgument;
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.oneComputer;
import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.*;
import static dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType.trackingField;
import static dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType.metric;
import static dan200.computercraft.shared.command.builder.CommandBuilder.args;
import static dan200.computercraft.shared.command.builder.CommandBuilder.command;
import static dan200.computercraft.shared.command.builder.HelpingArgumentBuilder.choice;
@ -248,7 +248,7 @@ public final class CommandComputerCraft
.then( command( "start" )
.requires( UserLevel.OWNER_OP )
.executes( context -> {
getTimingContext( context.getSource() ).start();
getMetricsInstance( context.getSource() ).start();
String stopCommand = "/computercraft track stop";
context.getSource().sendSuccess( translate( "commands.computercraft.track.start.stop",
@ -259,17 +259,17 @@ public final class CommandComputerCraft
.then( command( "stop" )
.requires( UserLevel.OWNER_OP )
.executes( context -> {
TrackingContext timings = getTimingContext( context.getSource() );
BasicComputerMetricsObserver timings = getMetricsInstance( context.getSource() );
if( !timings.stop() ) throw NOT_TRACKING_EXCEPTION.create();
displayTimings( context.getSource(), timings.getImmutableTimings(), TrackingField.AVERAGE_TIME, DEFAULT_FIELDS );
displayTimings( context.getSource(), timings.getSnapshot(), new AggregatedMetric( Metrics.COMPUTER_TASKS, Aggregate.AVG ), DEFAULT_FIELDS );
return 1;
} ) )
.then( command( "dump" )
.requires( UserLevel.OWNER_OP )
.argManyValue( "fields", trackingField(), DEFAULT_FIELDS )
.argManyValue( "fields", metric(), DEFAULT_FIELDS )
.executes( ( context, fields ) -> {
TrackingField sort;
AggregatedMetric sort;
if( fields.size() == 1 && DEFAULT_FIELDS.contains( fields.get( 0 ) ) )
{
sort = fields.get( 0 );
@ -362,50 +362,48 @@ public final class CommandComputerCraft
}
@Nonnull
private static TrackingContext getTimingContext( CommandSource source )
private static BasicComputerMetricsObserver getMetricsInstance( CommandSource source )
{
Entity entity = source.getEntity();
return entity instanceof PlayerEntity ? Tracking.getContext( entity.getUUID() ) : Tracking.getContext( SYSTEM_UUID );
return ServerContext.get( source.getServer() ).metrics().getMetricsInstance( entity instanceof PlayerEntity ? entity.getUUID() : SYSTEM_UUID );
}
private static final List<TrackingField> DEFAULT_FIELDS = Arrays.asList( TrackingField.TASKS, TrackingField.TOTAL_TIME, TrackingField.AVERAGE_TIME, TrackingField.MAX_TIME );
private static final List<AggregatedMetric> DEFAULT_FIELDS = Arrays.asList(
new AggregatedMetric( Metrics.COMPUTER_TASKS, Aggregate.COUNT ),
new AggregatedMetric( Metrics.COMPUTER_TASKS, Aggregate.NONE ),
new AggregatedMetric( Metrics.COMPUTER_TASKS, Aggregate.AVG ),
new AggregatedMetric( Metrics.COMPUTER_TASKS, Aggregate.MAX )
);
private static int displayTimings( CommandSource source, TrackingField sortField, List<TrackingField> fields ) throws CommandSyntaxException
private static int displayTimings( CommandSource source, AggregatedMetric sortField, List<AggregatedMetric> fields ) throws CommandSyntaxException
{
return displayTimings( source, getTimingContext( source ).getTimings(), sortField, fields );
return displayTimings( source, getMetricsInstance( source ).getTimings(), sortField, fields );
}
private static int displayTimings( CommandSource source, @Nonnull List<ComputerTracker> timings, @Nonnull TrackingField sortField, @Nonnull List<TrackingField> fields ) throws CommandSyntaxException
private static int displayTimings( CommandSource source, List<ComputerMetrics> timings, AggregatedMetric sortField, List<AggregatedMetric> fields ) throws CommandSyntaxException
{
if( timings.isEmpty() ) throw NO_TIMINGS_EXCEPTION.create();
Map<Computer, ServerComputer> lookup = new HashMap<>();
int maxId = 0, maxInstance = 0;
for( ServerComputer server : ServerContext.get( source.getServer() ).registry().getComputers() )
{
lookup.put( server.getComputer(), server );
if( server.getInstanceID() > maxInstance ) maxInstance = server.getInstanceID();
if( server.getID() > maxId ) maxId = server.getID();
}
timings.sort( Comparator.<ComputerTracker, Long>comparing( x -> x.get( sortField ) ).reversed() );
timings.sort( Comparator.<ComputerMetrics, Long>comparing( x -> x.get( sortField.metric(), sortField.aggregate() ) ).reversed() );
ITextComponent[] headers = new ITextComponent[1 + fields.size()];
headers[0] = translate( "commands.computercraft.track.dump.computer" );
for( int i = 0; i < fields.size(); i++ ) headers[i + 1] = translate( fields.get( i ).translationKey() );
for( int i = 0; i < fields.size(); i++ ) headers[i + 1] = fields.get( i ).displayName();
TableBuilder table = new TableBuilder( TRACK_ID, headers );
for( ComputerTracker entry : timings )
for( ComputerMetrics entry : timings )
{
Computer computer = entry.getComputer();
ServerComputer serverComputer = computer == null ? null : lookup.get( computer );
ServerComputer serverComputer = entry.computer();
ITextComponent computerComponent = linkComputer( source, serverComputer, entry.getComputerId() );
ITextComponent computerComponent = linkComputer( source, serverComputer, entry.computerId() );
ITextComponent[] row = new ITextComponent[1 + fields.size()];
row[0] = computerComponent;
for( int i = 0; i < fields.size(); i++ ) row[i + 1] = text( entry.getFormatted( fields.get( i ) ) );
for( int i = 0; i < fields.size(); i++ )
{
AggregatedMetric metric = fields.get( i );
row[i + 1] = text( entry.getFormatted( metric.metric(), metric.aggregate() ) );
}
table.row( row );
}

View File

@ -32,7 +32,7 @@ public final class ArgumentSerializers
public static void register()
{
register( new ResourceLocation( ComputerCraft.MOD_ID, "tracking_field" ), TrackingFieldArgumentType.trackingField() );
register( new ResourceLocation( ComputerCraft.MOD_ID, "tracking_field" ), TrackingFieldArgumentType.metric() );
register( new ResourceLocation( ComputerCraft.MOD_ID, "computer" ), ComputerArgumentType.oneComputer() );
register( new ResourceLocation( ComputerCraft.MOD_ID, "computers" ), ComputersArgumentType.class, new ComputersArgumentType.Serializer() );
registerUnsafe( new ResourceLocation( ComputerCraft.MOD_ID, "repeat" ), RepeatArgumentType.class, new RepeatArgumentType.Serializer() );

View File

@ -5,21 +5,24 @@
*/
package dan200.computercraft.shared.command.arguments;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.command.Exceptions;
import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
import static dan200.computercraft.shared.command.text.ChatHelpers.translate;
import java.util.stream.Collectors;
public final class TrackingFieldArgumentType extends ChoiceArgumentType<TrackingField>
public final class TrackingFieldArgumentType extends ChoiceArgumentType<AggregatedMetric>
{
private static final TrackingFieldArgumentType INSTANCE = new TrackingFieldArgumentType();
private TrackingFieldArgumentType()
{
super( TrackingField.fields().values(), TrackingField::id, x -> translate( x.translationKey() ), Exceptions.TRACKING_FIELD_ARG_NONE );
super(
AggregatedMetric.aggregatedMetrics().collect( Collectors.toList() ),
AggregatedMetric::name, AggregatedMetric::displayName, Exceptions.TRACKING_FIELD_ARG_NONE
);
}
public static TrackingFieldArgumentType trackingField()
public static TrackingFieldArgumentType metric()
{
return INSTANCE;
}

View File

@ -14,6 +14,7 @@ import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerEnvironment;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.network.NetworkHandler;
@ -37,6 +38,7 @@ public class ServerComputer implements InputHandler, ComputerEnvironment
private BlockPos position;
private final ComputerFamily family;
private final MetricsObserver metrics;
private final Computer computer;
private final Terminal terminal;
@ -53,6 +55,7 @@ public class ServerComputer implements InputHandler, ComputerEnvironment
ServerContext context = ServerContext.get( world.getServer() );
instanceID = context.registry().getUnusedInstanceID();
terminal = new Terminal( terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged );
metrics = context.metrics().createMetricObserver( this );
computer = new Computer( context.environment(), this, terminal, computerID );
computer.setLabel( label );
@ -262,8 +265,6 @@ public class ServerComputer implements InputHandler, ComputerEnvironment
computer.setLabel( label );
}
// IComputerEnvironment implementation
@Override
public double getTimeOfDay()
{
@ -276,6 +277,12 @@ public class ServerComputer implements InputHandler, ComputerEnvironment
return (int) ((world.getDayTime() + 6000) / 24000) + 1;
}
@Override
public MetricsObserver getMetrics()
{
return metrics;
}
@Override
public IWritableMount createRootMount()
{

View File

@ -11,6 +11,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.core.computer.GlobalEnvironment;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
import dan200.computercraft.shared.util.IDAssigner;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.World;
@ -42,6 +43,7 @@ public final class ServerContext
private final MinecraftServer server;
private final ServerComputerRegistry registry = new ServerComputerRegistry();
private final GlobalMetrics metrics = new GlobalMetrics();
private final GlobalEnvironment environment;
private final IDAssigner idAssigner;
private final Path storageDir;
@ -145,6 +147,16 @@ public final class ServerContext
return storageDir;
}
/**
* Get the current global metrics store.
*
* @return The current metrics store.
*/
public GlobalMetrics metrics()
{
return metrics;
}
private static final class Environment implements GlobalEnvironment
{
private final MinecraftServer server;

View File

@ -0,0 +1,167 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.metrics;
import com.google.common.base.CaseFormat;
import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.metrics.basic.Aggregate;
import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.management.*;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.LongSupplier;
/**
* An MBean which exposes aggregate statistics about all computers on the server.
*/
public final class ComputerMBean implements DynamicMBean, ComputerMetricsObserver
{
private static final Logger LOGGER = LoggerFactory.getLogger( ComputerMBean.class );
private static @Nullable ComputerMBean instance;
private final Map<String, LongSupplier> attributes = new HashMap<>();
private final Int2ObjectMap<Counter> values = new Int2ObjectOpenHashMap<>();
private final MBeanInfo info;
private ComputerMBean()
{
Metrics.init();
List<MBeanAttributeInfo> attributes = new ArrayList<>();
for( Map.Entry<String, Metric> field : Metric.metrics().entrySet() )
{
String name = CaseFormat.LOWER_UNDERSCORE.to( CaseFormat.LOWER_CAMEL, field.getKey() );
add( name, field.getValue(), attributes );
}
info = new MBeanInfo( ComputerMBean.class.getSimpleName(), "metrics about all computers on the server", attributes.toArray( new MBeanAttributeInfo[0] ), null, null, null );
}
public static void register()
{
if( instance != null ) return;
try
{
ManagementFactory.getPlatformMBeanServer().registerMBean( instance = new ComputerMBean(), new ObjectName( "dan200.computercraft:type=Computers" ) );
}
catch( InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException |
MalformedObjectNameException e )
{
LOGGER.warn( "Failed to register JMX bean", e );
}
}
public static void start( MinecraftServer server )
{
if( instance != null ) ServerContext.get( server ).metrics().addObserver( instance );
}
@Override
public Object getAttribute( String attribute ) throws AttributeNotFoundException
{
LongSupplier value = attributes.get( attribute );
if( value == null ) throw new AttributeNotFoundException();
return value.getAsLong();
}
@Override
public void setAttribute( Attribute attribute ) throws InvalidAttributeValueException
{
throw new InvalidAttributeValueException( "Cannot set attribute" );
}
@Override
public AttributeList getAttributes( String[] names )
{
AttributeList result = new AttributeList( names.length );
for( String name : names )
{
LongSupplier value = attributes.get( name );
if( value != null ) result.add( new Attribute( name, value.getAsLong() ) );
}
return result;
}
@Override
public AttributeList setAttributes( AttributeList attributes )
{
return new AttributeList();
}
@Nullable
@Override
public Object invoke( String actionName, Object[] params, String[] signature )
{
return null;
}
@Override
public MBeanInfo getMBeanInfo()
{
return info;
}
private void observe( Metric field, long change )
{
Counter counter = values.get( field.id() );
counter.value.addAndGet( change );
counter.count.incrementAndGet();
}
@Override
public void observe( ServerComputer computer, Metric.Counter counter )
{
observe( counter, 1 );
}
@Override
public void observe( ServerComputer computer, Metric.Event event, long value )
{
observe( event, value );
}
private MBeanAttributeInfo addAttribute( String name, String description, LongSupplier value )
{
attributes.put( name, value );
return new MBeanAttributeInfo( name, "long", description, true, false, false );
}
private void add( String name, Metric field, List<MBeanAttributeInfo> attributes )
{
Counter counter = new Counter();
values.put( field.id(), counter );
String prettyName = new AggregatedMetric( field, Aggregate.NONE ).displayName().getString();
attributes.add( addAttribute( name, prettyName, counter.value::longValue ) );
if( field instanceof Metric.Event )
{
String countName = new AggregatedMetric( field, Aggregate.COUNT ).displayName().getString();
attributes.add( addAttribute( name + "Count", countName, counter.count::longValue ) );
}
}
private static class Counter
{
final AtomicLong value = new AtomicLong();
final AtomicLong count = new AtomicLong();
}
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.metrics;
import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.shared.computer.core.ServerComputer;
/**
* A global version of {@link MetricsObserver}, which monitors multiple computers.
*/
public interface ComputerMetricsObserver
{
/**
* Increment a computer's counter by 1.
*
* @param computer The computer which incremented its counter.
* @param counter The counter to observe.
* @see MetricsObserver#observe(Metric.Counter)
*/
void observe( ServerComputer computer, Metric.Counter counter );
/**
* Observe a single instance of an event.
*
* @param computer The computer which incremented its counter.
* @param event The event to observe.
* @param value The value corresponding to this event.
* @see MetricsObserver#observe(Metric.Event, long)
*/
void observe( ServerComputer computer, Metric.Event event, long value );
}

View File

@ -0,0 +1,121 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.metrics;
import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.metrics.basic.BasicComputerMetricsObserver;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
/**
* The global metrics system.
*
* @see ServerContext#metrics() To obtain an instance of this system.
*/
public final class GlobalMetrics
{
volatile boolean enabled = false;
final Object lock = new Object();
final List<ComputerMetricsObserver> trackers = new ArrayList<>();
private final HashMap<UUID, BasicComputerMetricsObserver> instances = new HashMap<>();
/**
* Get a metrics observer for a specific player. This will not be active until
* {@link BasicComputerMetricsObserver#start()} is called.
*
* @param uuid The player's UUID.
* @return The metrics instance for this player.
*/
public BasicComputerMetricsObserver getMetricsInstance( UUID uuid )
{
synchronized( lock )
{
BasicComputerMetricsObserver context = instances.get( uuid );
if( context == null ) instances.put( uuid, context = new BasicComputerMetricsObserver( this ) );
return context;
}
}
/**
* Add a new global metrics observer. This will receive metrics data for all computers.
*
* @param tracker The observer to add.
*/
public void addObserver( ComputerMetricsObserver tracker )
{
synchronized( lock )
{
if( trackers.contains( tracker ) ) return;
trackers.add( tracker );
enabled = true;
}
}
/**
* Remove a previously-registered global metrics observer.
*
* @param tracker The observer to add.
*/
public void removeObserver( ComputerMetricsObserver tracker )
{
synchronized( lock )
{
trackers.remove( tracker );
enabled = !trackers.isEmpty();
}
}
/**
* Create an observer for a computer. This will delegate to all registered {@link ComputerMetricsObserver}s.
*
* @param computer The computer to create the observer for.
* @return The instantiated observer.
*/
public MetricsObserver createMetricObserver( ServerComputer computer )
{
return new DispatchObserver( computer );
}
private final class DispatchObserver implements MetricsObserver
{
private final ServerComputer computer;
private DispatchObserver( ServerComputer computer )
{
this.computer = computer;
}
@Override
public void observe( Metric.Counter counter )
{
if( !enabled ) return;
synchronized( lock )
{
// TODO: The lock here is nasty and aggressive. However, in my benchmarks I've found it has about
// equivalent performance to a CoW list and atomics. Would be good to drill into this, as locks do not
// scale well.
for( ComputerMetricsObserver observer : trackers ) observer.observe( computer, counter );
}
}
@Override
public void observe( Metric.Event event, long value )
{
if( !enabled ) return;
synchronized( lock )
{
for( ComputerMetricsObserver observer : trackers ) observer.observe( computer, event, value );
}
}
}
}

View File

@ -0,0 +1,33 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.metrics.basic;
import dan200.computercraft.core.metrics.Metric;
/**
* An aggregate over a {@link Metric}.
* <p>
* Only {@link Metric.Event events} support non-{@link Aggregate#NONE} aggregates.
*/
public enum Aggregate
{
NONE( "none" ),
COUNT( "count" ),
AVG( "avg" ),
MAX( "max" );
private final String id;
Aggregate( String id )
{
this.id = id;
}
public String id()
{
return id;
}
}

View File

@ -0,0 +1,62 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.metrics.basic;
import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.metrics.Metrics;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* An aggregate of a specific metric.
*/
public class AggregatedMetric
{
private static final String TRANSLATION_PREFIX = "tracking_field.computercraft.";
private final Metric metric;
private final Aggregate aggregate;
public AggregatedMetric( Metric metric, Aggregate aggregate )
{
this.metric = metric;
this.aggregate = aggregate;
}
public Metric metric()
{
return metric;
}
public Aggregate aggregate()
{
return aggregate;
}
public static Stream<AggregatedMetric> aggregatedMetrics()
{
Metrics.init();
return Metric.metrics().values().stream()
.flatMap( m -> m instanceof Metric.Counter
? Stream.of( new AggregatedMetric( m, Aggregate.NONE ) )
: Arrays.stream( Aggregate.values() ).map( a -> new AggregatedMetric( m, a ) )
);
}
public String name()
{
return aggregate() == Aggregate.NONE ? metric.name() : metric().name() + "_" + aggregate().id();
}
public ITextComponent displayName()
{
TranslationTextComponent name = new TranslationTextComponent( TRANSLATION_PREFIX + metric().name() + ".name" );
return aggregate() == Aggregate.NONE ? name : new TranslationTextComponent( TRANSLATION_PREFIX + aggregate().id(), name );
}
}

View File

@ -0,0 +1,89 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.metrics.basic;
import com.google.common.collect.MapMaker;
import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.metrics.ComputerMetricsObserver;
import dan200.computercraft.shared.computer.metrics.GlobalMetrics;
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.
* <p>
* Note that this will retain timings for computers which have been deleted.
*/
public class BasicComputerMetricsObserver implements ComputerMetricsObserver
{
private final GlobalMetrics owner;
private boolean tracking = false;
private final List<ComputerMetrics> timings = new ArrayList<>();
private final Map<ServerComputer, ComputerMetrics> timingLookup = new MapMaker().weakKeys().makeMap();
public BasicComputerMetricsObserver( GlobalMetrics owner )
{
this.owner = owner;
}
public synchronized void start()
{
if( !tracking ) owner.addObserver( this );
tracking = true;
timings.clear();
timingLookup.clear();
}
public synchronized boolean stop()
{
if( !tracking ) return false;
owner.removeObserver( this );
tracking = false;
timingLookup.clear();
return true;
}
public synchronized List<ComputerMetrics> getSnapshot()
{
ArrayList<ComputerMetrics> timings = new ArrayList<>( this.timings.size() );
for( ComputerMetrics timing : this.timings ) timings.add( new ComputerMetrics( timing ) );
return timings;
}
public synchronized List<ComputerMetrics> getTimings()
{
return new ArrayList<>( timings );
}
private ComputerMetrics getMetrics( ServerComputer computer )
{
ComputerMetrics existing = timingLookup.get( computer );
if( existing != null ) return existing;
ComputerMetrics metrics = new ComputerMetrics( computer );
timingLookup.put( computer, metrics );
timings.add( metrics );
return metrics;
}
@Override
public synchronized void observe( ServerComputer computer, Metric.Counter counter )
{
getMetrics( computer ).observe( counter );
}
@Override
public synchronized void observe( ServerComputer computer, Metric.Event event, long value )
{
getMetrics( computer ).observe( event, value );
}
}

View File

@ -0,0 +1,129 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.computer.metrics.basic;
import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.shared.computer.core.ServerComputer;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.Arrays;
/**
* Metrics for an individual computer.
*/
public final class ComputerMetrics
{
private static final int DEFAULT_LEN = 16;
private final WeakReference<ServerComputer> computer;
private final int computerId;
private long[] counts;
private long[] totals;
private long[] max;
ComputerMetrics( ServerComputer computer )
{
this.computer = new WeakReference<>( computer );
computerId = computer.getID();
counts = new long[DEFAULT_LEN];
totals = new long[DEFAULT_LEN];
max = new long[DEFAULT_LEN];
}
ComputerMetrics( ComputerMetrics other )
{
computer = other.computer;
computerId = other.computerId;
counts = Arrays.copyOf( other.counts, other.counts.length );
totals = Arrays.copyOf( other.totals, other.totals.length );
max = Arrays.copyOf( other.max, other.max.length );
}
@Nullable
public ServerComputer computer()
{
return computer.get();
}
public int computerId()
{
return computerId;
}
private static long get( long[] values, Metric metric )
{
return metric.id() >= values.length ? 0 : values[metric.id()];
}
private long avg( long total, long count )
{
return count == 0 ? 0 : total / count;
}
public long get( Metric metric, Aggregate aggregate )
{
if( metric instanceof Metric.Counter ) return get( counts, metric );
if( metric instanceof Metric.Event )
{
switch( aggregate )
{
case NONE:
return get( totals, metric );
case COUNT:
return get( counts, metric );
case AVG:
return avg( get( totals, metric ), get( counts, metric ) );
case MAX:
return get( max, metric );
default:
throw new IllegalArgumentException();
}
}
throw new IllegalArgumentException( "Unknown metric " + metric.name() );
}
public String getFormatted( Metric field, Aggregate aggregate )
{
long value = get( field, aggregate );
switch( aggregate )
{
case COUNT:
return Metric.formatDefault( value );
case AVG:
case MAX:
case NONE:
return field.format( value );
default:
throw new IllegalArgumentException();
}
}
private void ensureCapacity( Metric metric )
{
if( metric.id() < counts.length ) return;
int newCapacity = Math.max( metric.id(), counts.length * 2 );
counts = Arrays.copyOf( counts, newCapacity );
totals = Arrays.copyOf( totals, newCapacity );
max = Arrays.copyOf( max, newCapacity );
}
void observe( Metric.Counter counter )
{
ensureCapacity( counter );
counts[counter.id()]++;
}
void observe( Metric.Event event, long value )
{
ensureCapacity( event );
counts[event.id()]++;
totals[event.id()] += value;
if( value > max[event.id()] ) max[event.id()] = value;
}
}

View File

@ -14,7 +14,7 @@ import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.event.TurtleActionEvent;
import dan200.computercraft.api.turtle.event.TurtleInspectItemEvent;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.peripheral.generic.data.ItemData;
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
import dan200.computercraft.shared.turtle.core.*;
@ -93,7 +93,7 @@ public class TurtleAPI implements ILuaAPI
private MethodResult trackCommand( ITurtleCommand command )
{
environment.addTrackingChange( TrackingField.TURTLE_OPS );
environment.observe( Metrics.TURTLE_OPS );
return turtle.executeCommand( command );
}
@ -191,7 +191,7 @@ public class TurtleAPI implements ILuaAPI
@LuaFunction
public final MethodResult dig( Optional<TurtleSide> side )
{
environment.addTrackingChange( TrackingField.TURTLE_OPS );
environment.observe( Metrics.TURTLE_OPS );
return trackCommand( TurtleToolCommand.dig( InteractDirection.FORWARD, side.orElse( null ) ) );
}
@ -207,7 +207,7 @@ public class TurtleAPI implements ILuaAPI
@LuaFunction
public final MethodResult digUp( Optional<TurtleSide> side )
{
environment.addTrackingChange( TrackingField.TURTLE_OPS );
environment.observe( Metrics.TURTLE_OPS );
return trackCommand( TurtleToolCommand.dig( InteractDirection.UP, side.orElse( null ) ) );
}
@ -223,7 +223,7 @@ public class TurtleAPI implements ILuaAPI
@LuaFunction
public final MethodResult digDown( Optional<TurtleSide> side )
{
environment.addTrackingChange( TrackingField.TURTLE_OPS );
environment.observe( Metrics.TURTLE_OPS );
return trackCommand( TurtleToolCommand.dig( InteractDirection.DOWN, side.orElse( null ) ) );
}

View File

@ -92,12 +92,8 @@
"argument.computercraft.computer.many_matching": "Multiple computers matching '%s' (instances %s)",
"argument.computercraft.tracking_field.no_field": "Unknown field '%s'",
"argument.computercraft.argument_expected": "Argument expected",
"tracking_field.computercraft.tasks.name": "Tasks",
"tracking_field.computercraft.total.name": "Total time",
"tracking_field.computercraft.average.name": "Average time",
"tracking_field.computercraft.max.name": "Max time",
"tracking_field.computercraft.server_count.name": "Server task count",
"tracking_field.computercraft.server_time.name": "Server task time",
"tracking_field.computercraft.computer_tasks.name": "Tasks",
"tracking_field.computercraft.server_tasks.name": "Server tasks",
"tracking_field.computercraft.peripheral.name": "Peripheral calls",
"tracking_field.computercraft.fs.name": "Filesystem operations",
"tracking_field.computercraft.turtle.name": "Turtle operations",
@ -108,6 +104,9 @@
"tracking_field.computercraft.websocket_outgoing.name": "Websocket outgoing",
"tracking_field.computercraft.coroutines_created.name": "Coroutines created",
"tracking_field.computercraft.coroutines_dead.name": "Coroutines disposed",
"tracking_field.computercraft.max": "%s (max)",
"tracking_field.computercraft.avg": "%s (avg)",
"tracking_field.computercraft.count": "%s (count)",
"gui.computercraft.tooltip.copy": "Copy to clipboard",
"gui.computercraft.tooltip.computer_id": "Computer ID: %s",
"gui.computercraft.tooltip.disk_id": "Disk ID: %s",

View File

@ -10,8 +10,8 @@ import dan200.computercraft.core.computer.ComputerEnvironment
import dan200.computercraft.core.computer.ComputerSide
import dan200.computercraft.core.computer.GlobalEnvironment
import dan200.computercraft.core.filesystem.FileSystem
import dan200.computercraft.core.metrics.Metric
import dan200.computercraft.core.terminal.Terminal
import dan200.computercraft.core.tracking.TrackingField
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
@ -43,7 +43,8 @@ abstract class NullApiEnvironment : IAPIEnvironment {
override fun setLabel(label: String?) {}
override fun startTimer(ticks: Long): Int = 0
override fun cancelTimer(id: Int) {}
override fun addTrackingChange(field: TrackingField, change: Long) {}
override fun observe(field: Metric.Counter) {}
override fun observe(field: Metric.Event, change: Long) {}
}
class EventResult(val name: String, val args: Array<Any?>)

View File

@ -11,6 +11,8 @@ import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount;
import dan200.computercraft.core.filesystem.MemoryMount;
import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.metrics.MetricsObserver;
import javax.annotation.Nonnull;
import java.io.File;
@ -24,7 +26,7 @@ import java.net.URL;
/**
* A very basic environment.
*/
public class BasicEnvironment implements ComputerEnvironment, GlobalEnvironment
public class BasicEnvironment implements ComputerEnvironment, GlobalEnvironment, MetricsObserver
{
private final IWritableMount mount;
@ -56,6 +58,12 @@ public class BasicEnvironment implements ComputerEnvironment, GlobalEnvironment
return 0;
}
@Override
public MetricsObserver getMetrics()
{
return this;
}
@Nonnull
@Override
public String getHostString()
@ -144,4 +152,14 @@ public class BasicEnvironment implements ComputerEnvironment, GlobalEnvironment
return new File( url.getPath() );
}
}
@Override
public void observe( Metric.Counter counter )
{
}
@Override
public void observe( Metric.Event event, long value )
{
}
}

View File

@ -25,7 +25,7 @@ import java.util.concurrent.locks.ReentrantLock;
/**
* Creates "fake" computers, which just run user-defined tasks rather than Lua code.
*
* <p>
* Note, this will clobber some parts of the global state. It's recommended you use this inside an {@link IsolatedRunner}.
*/
public class FakeComputerManager
@ -43,7 +43,7 @@ public class FakeComputerManager
static
{
ComputerExecutor.luaFactory = ( computer, timeout ) -> new DummyLuaMachine( timeout, machines.get( computer ) );
ComputerExecutor.luaFactory = args -> new DummyLuaMachine( args.timeout );
}
/**
@ -57,7 +57,9 @@ public class FakeComputerManager
{
BasicEnvironment environment = new BasicEnvironment();
Computer computer = new Computer( environment, environment, new Terminal( 51, 19, true ), 0 );
machines.put( computer, new ConcurrentLinkedQueue<>() );
ConcurrentLinkedQueue<Task> tasks = new ConcurrentLinkedQueue<>();
computer.addApi( new QueuePassingAPI( tasks ) );
machines.put( computer, tasks );
return computer;
}
@ -158,20 +160,36 @@ public class FakeComputerManager
throw (T) e;
}
private static final class QueuePassingAPI implements ILuaAPI
{
final Queue<Task> tasks;
private QueuePassingAPI( Queue<Task> tasks )
{
this.tasks = tasks;
}
@Override
public String[] getNames()
{
return new String[0];
}
}
private static class DummyLuaMachine implements ILuaMachine
{
private final TimeoutState state;
private final Queue<Task> handleEvent;
private @javax.annotation.Nullable Queue<Task> tasks;
DummyLuaMachine( TimeoutState state, Queue<Task> handleEvent )
DummyLuaMachine( TimeoutState state )
{
this.state = state;
this.handleEvent = handleEvent;
}
@Override
public void addAPI( @Nonnull ILuaAPI api )
{
if( api instanceof QueuePassingAPI ) tasks = ((QueuePassingAPI) api).tasks;
}
@Override
@ -185,7 +203,8 @@ public class FakeComputerManager
{
try
{
return handleEvent.remove().run( state );
if( tasks == null ) throw new IllegalStateException( "Not received tasks yet" );
return tasks.remove().run( state );
}
catch( Throwable e )
{