diff --git a/src/main/java/dan200/computercraft/core/apis/http/HTTPRequest.java b/src/main/java/dan200/computercraft/core/apis/http/HTTPRequest.java index a5f205110..93b0b96a5 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/HTTPRequest.java +++ b/src/main/java/dan200/computercraft/core/apis/http/HTTPRequest.java @@ -15,6 +15,7 @@ import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.handles.BinaryInputHandle; import dan200.computercraft.core.apis.handles.EncodedInputHandle; +import dan200.computercraft.core.tracking.TrackingField; import javax.annotation.Nonnull; import java.io.*; @@ -101,7 +102,7 @@ public class HTTPRequest implements Runnable { // Queue the failure event if not. String error = e.getMessage(); - m_environment.queueEvent( "http_failure", new Object[] { m_urlString, error == null ? "Could not connect" : error, null } ); + m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, error == null ? "Could not connect" : error, null } ); return; } @@ -134,6 +135,11 @@ public class HTTPRequest implements Runnable } } + // Add request size and count to the tracker before opening the connection + m_environment.addTrackingChange( TrackingField.HTTP_REQUESTS ); + m_environment.addTrackingChange( TrackingField.HTTP_UPLOAD, + getHeaderSize( connection.getRequestProperties() ) + (m_postText == null ? 0 : m_postText.length()) ); + // Send POST text if( m_postText != null ) { @@ -178,6 +184,9 @@ public class HTTPRequest implements Runnable headers.put( header.getKey(), joiner.join( header.getValue() ) ); } + m_environment.addTrackingChange( TrackingField.HTTP_DOWNLOAD, + getHeaderSize( connection.getHeaderFields() ) + result.length ); + InputStream contents = new ByteArrayInputStream( result ); ILuaObject stream = wrapStream( m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, connection.getContentEncoding() ), @@ -189,17 +198,17 @@ public class HTTPRequest implements Runnable // Queue the appropriate event. if( responseSuccess ) { - m_environment.queueEvent( "http_success", new Object[] { m_urlString, stream } ); + m_environment.queueEvent( "http_success", new Object[]{ m_urlString, stream } ); } else { - m_environment.queueEvent( "http_failure", new Object[] { m_urlString, "Could not connect", stream } ); + m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, "Could not connect", stream } ); } } catch( IOException e ) { // There was an error - m_environment.queueEvent( "http_failure", new Object[] { m_urlString, "Could not connect", null } ); + m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, "Could not connect", null } ); } } @@ -209,8 +218,8 @@ public class HTTPRequest implements Runnable final int methodOffset = oldMethods.length; final String[] newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 ); - newMethods[ methodOffset + 0 ] = "getResponseCode"; - newMethods[ methodOffset + 1 ] = "getResponseHeaders"; + newMethods[methodOffset + 0] = "getResponseCode"; + newMethods[methodOffset + 1] = "getResponseHeaders"; return new ILuaObject() { @@ -233,12 +242,12 @@ public class HTTPRequest implements Runnable case 0: { // getResponseCode - return new Object[] { responseCode }; + return new Object[]{ responseCode }; } case 1: { // getResponseHeaders - return new Object[] { responseHeaders }; + return new Object[]{ responseHeaders }; } default: { @@ -248,4 +257,15 @@ public class HTTPRequest implements Runnable } }; } + + private static long getHeaderSize( Map> headers ) + { + long size = 0; + for( Map.Entry> header : headers.entrySet() ) + { + size += header.getKey() == null ? 0 : header.getKey().length(); + for( String value : header.getValue() ) size += value == null ? 0 : value.length() + 1; + } + return size; + } } diff --git a/src/main/java/dan200/computercraft/core/apis/http/WebsocketConnection.java b/src/main/java/dan200/computercraft/core/apis/http/WebsocketConnection.java index ddfc12110..5efad05b9 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/WebsocketConnection.java +++ b/src/main/java/dan200/computercraft/core/apis/http/WebsocketConnection.java @@ -12,6 +12,7 @@ import dan200.computercraft.api.lua.ILuaObject; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.core.apis.HTTPAPI; import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.tracking.TrackingField; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; @@ -71,7 +72,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler imp private void onClosed() { close( true ); - computer.queueEvent( CLOSE_EVENT, new Object[] { url } ); + computer.queueEvent( CLOSE_EVENT, new Object[]{ url } ); } @Override @@ -102,7 +103,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler imp if( !handshaker.isHandshakeComplete() ) { handshaker.finishHandshake( ch, (FullHttpResponse) msg ); - computer.queueEvent( SUCCESS_EVENT, new Object[] { url, this } ); + computer.queueEvent( SUCCESS_EVENT, new Object[]{ url, this } ); return; } @@ -115,14 +116,19 @@ public class WebsocketConnection extends SimpleChannelInboundHandler imp WebSocketFrame frame = (WebSocketFrame) msg; if( frame instanceof TextWebSocketFrame ) { - computer.queueEvent( MESSAGE_EVENT, new Object[] { url, ((TextWebSocketFrame) frame).text() } ); + String data = ((TextWebSocketFrame) frame).text(); + + computer.addTrackingChange( TrackingField.WEBSOCKET_INCOMING, data.length() ); + computer.queueEvent( MESSAGE_EVENT, new Object[]{ url, data } ); } else if( frame instanceof BinaryWebSocketFrame ) { ByteBuf data = frame.content(); - byte[] converted = new byte[ data.readableBytes() ]; + byte[] converted = new byte[data.readableBytes()]; data.readBytes( converted ); - computer.queueEvent( MESSAGE_EVENT, new Object[] { url, data } ); + + computer.addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length ); + computer.queueEvent( MESSAGE_EVENT, new Object[]{ url, converted } ); } else if( frame instanceof CloseWebSocketFrame ) { @@ -135,7 +141,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler imp public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause ) { ctx.close(); - computer.queueEvent( FAILURE_EVENT, new Object[] { + computer.queueEvent( FAILURE_EVENT, new Object[]{ url, cause instanceof WebSocketHandshakeException ? cause.getMessage() : "Could not connect" } ); @@ -145,7 +151,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler imp @Override public String[] getMethodNames() { - return new String[] { "receive", "send", "close" }; + return new String[]{ "receive", "send", "close" }; } @Nullable @@ -159,15 +165,16 @@ public class WebsocketConnection extends SimpleChannelInboundHandler imp { checkOpen(); Object[] event = context.pullEvent( MESSAGE_EVENT ); - if( event.length >= 3 && Objects.equal( event[ 1 ], url ) ) + if( event.length >= 3 && Objects.equal( event[1], url ) ) { - return new Object[] { event[ 2 ] }; + return new Object[]{ event[2] }; } } case 1: { checkOpen(); - String text = arguments.length > 0 && arguments[ 0 ] != null ? arguments[ 0 ].toString() : ""; + String text = arguments.length > 0 && arguments[0] != null ? arguments[0].toString() : ""; + computer.addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() ); channel.writeAndFlush( new TextWebSocketFrame( text ) ); return null; } diff --git a/src/main/java/dan200/computercraft/core/tracking/TrackingField.java b/src/main/java/dan200/computercraft/core/tracking/TrackingField.java index d6d98b1c2..71f1c5196 100644 --- a/src/main/java/dan200/computercraft/core/tracking/TrackingField.java +++ b/src/main/java/dan200/computercraft/core/tracking/TrackingField.java @@ -17,9 +17,16 @@ public class TrackingField public static final TrackingField SERVER_COUNT = TrackingField.of( "server_count", "Server task count", x -> String.format( "%4d", x ) ); public static final TrackingField SERVER_TIME = TrackingField.of( "server_time", "Server task time", x -> String.format( "%7.1fms", x / 1e6 ) ); - public static final TrackingField PERIPHERAL_OPS = TrackingField.of( "peripheral", "Peripheral calls", x -> String.format( "%6d", x ) ); - public static final TrackingField FS_OPS = TrackingField.of( "fs", "Filesystem operations", x -> String.format( "%6d", x ) ); - public static final TrackingField TURTLE_OPS = TrackingField.of( "turtle", "Turtle operations", x -> String.format( "%6d", x ) ); + public static final TrackingField PERIPHERAL_OPS = TrackingField.of( "peripheral", "Peripheral calls", TrackingField::formatDefault ); + public static final TrackingField FS_OPS = TrackingField.of( "fs", "Filesystem operations", TrackingField::formatDefault ); + public static final TrackingField TURTLE_OPS = TrackingField.of( "turtle", "Turtle operations", TrackingField::formatDefault ); + + public static final TrackingField HTTP_REQUESTS = TrackingField.of( "http", "HTTP requests", TrackingField::formatDefault ); + public static final TrackingField HTTP_UPLOAD = TrackingField.of( "http_upload", "HTTP upload", TrackingField::formatBytes ); + public static final TrackingField HTTP_DOWNLOAD = TrackingField.of( "http_download", "HTTT download", TrackingField::formatBytes ); + + public static final TrackingField WEBSOCKET_INCOMING = TrackingField.of( "websocket_incoming", "Websocket incoming", TrackingField::formatBytes ); + public static final TrackingField WEBSOCKET_OUTGOING = TrackingField.of( "websocket_outgoing", "Websocket outgoing", TrackingField::formatBytes ); private final String id; private final String displayName; @@ -58,4 +65,24 @@ public class TrackingField { 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 ) ); + } }