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:
commit
68da044ff2
@ -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 )
|
||||
|
@ -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 );
|
||||
}
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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 );
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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 )
|
||||
{
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
141
src/main/java/dan200/computercraft/core/metrics/Metric.java
Normal file
141
src/main/java/dan200/computercraft/core/metrics/Metric.java
Normal 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 ) );
|
||||
}
|
||||
}
|
41
src/main/java/dan200/computercraft/core/metrics/Metrics.java
Normal file
41
src/main/java/dan200/computercraft/core/metrics/Metrics.java
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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 ) );
|
||||
}
|
||||
}
|
@ -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 )
|
||||
{
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -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 ) );
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
|
@ -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() );
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 ) ) );
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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?>)
|
||||
|
@ -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 )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -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 )
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user