- 51 - option provides a full dump of most network-related structures cumulative readings since the
machine was started. By filtering this, taking differences, and plotting various data, you get a good idea of the network traffic background and the extra load imposed by your application.
Using netstat with this application shows that connection, resolution of server object, and the three remote method invocations require four TCP sockets and 39 packets of data frames to be
transferred. These include a socket pair opened from the client to the registry to determine the server location, and then a second socket pair between the client and the server. The frames include
several handshake packets required as part of the RMI protocol, and other overhead that RMI imposes. The socket pair between the registry and server are not recorded, because the pair lives
longer than the interval that measures differences recorded by netstat. However, some of the frames are probably communication packets between the registry and the server.
Another useful piece of equipment is a network sniffer. This is a hardware device you plug into the network line that views and can save all network traffic that is passed along that wire. If you
absolutely must know every detail of what is happening on the wire, you may need one of these.
More detailed information on network utilities and tools can be found in system-specific performance tuning books see
Chapter 14 , for more about system-specific tools and tuning tips.
2.6.1 Replacing Sockets
Occasionally, you need to be able to see what is happening to your sockets and to know what information is passing through them and the sizes of the packets being transferred. It is usually best
to install your own trace points into the application for all communication external to the application; the extra overheads are generally small compared to network or any IO overheads
and can usually be ignored. The application can be deployed with these tracers in place but configured so as not to trace until required.
However, the sockets are often used by third-party classes, and you cannot directly wrap the reads and writes. You could use a packet sniffer that is plugged into the network, but this can prove
troublesome when used for application-specific purposes and can be expensive. A more useful possibility I have employed is to wrap the socket IO with my own classes. You can almost do this
generically using the
SocketImplFactory
, but if you install your own
SocketImplFactory
, there is no protocol to allow you to access the default socket implementation, so another way must be
used. You could add a
SocketImplFactory
class into
java.net
, which then gives you access to the default
PlainSocketImpl
class, but this is no more generic than the previous possibility, as it too cannot normally be delivered with an application. My preferred solution, which is also not
deliverable, is to wrap the sockets by replacing the
java.net.Socket
class with my own implementation. This is simpler than the previous alternatives and can be quite powerful. Only two
methods from the core classes need changing, namely those that provide access to the input stream and output stream. You need to create your own input stream and output stream wrapper classes to
provide logging. The two methods in
Socket
are
getInputStream
and
getOutputStream
, and the new versions of these look as follows:
public InputStream getInputStream throws IOException { return new tuning.socket.SockInStreamLoggerthis, impl.getInputStream ;
} public OutputStream getOutputStream throws IOException {
return new tuning.socket.SockOutStreamLoggerthis, impl.getOutputStream ; }
- 52 - The required stream classes are listed shortly. Rather than using generic classes, I tend to customize
the logging on a per-application basis. I even tend to vary the logging implementation for different tests, slowly cutting out more superfluous communications data and headers, so that I can focus on
a small amount of detail. Usually I focus on the number of transfers, the amount of data transferred, and the application-specific type of data being transferred. For a distributed RMI type
communication, I want to know the method calls and argument types, and occasionally some of the arguments: the data is serialized and so can be accessed using the
Serializable
framework. As with the customized
Object
class in the Section 2.4
section, you need to ensure that your customized
Socket
class comes first in your boot classpath, before the JDK
Socket
version. The RMI example from the previous section results in the following trace when run with customized
socket tracing. The trace is from the client only. I have replaced lines of data with my own interpretation in bold of the data sent or read:
Message of size 7 written by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092]
client-registry handshake Message of size 16 read by Socket
Socket[addr=jack127.0.0.1,port=1099,localport=1092] client-registry handshake
Message of size 15 written by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092]
client-registry handshake: client identification Message of size 53 written by Socket
Socket[addr=jack127.0.0.1,port=1099,localport=1092] client-registry query: asking for the location of the Server Object
Message of size 210 read by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092]
client-registry query: reply giving details of the Server Object Message of size 7 written by Socket
Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server handshake
Message of size 16 read by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093]
client-server handshake Message of size 15 written by Socket
Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server handshake: client identification
Message of size 342 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093]
client-server handshake: security handshake Message of size 283 read by Socket
Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server handshake: security handshake
Message of size 1 written by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092]
Message of size 1 read by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092]
Message of size 15 written by Socket Socket[addr=jack127.0.0.1,port=1099,localport=1092]
client-registry handoff Message of size 1 written by Socket
Socket[addr=localhost127.0.0.1,port=1087,localport=1093] Message of size 1 read by Socket
Socket[addr=localhost127.0.0.1,port=1087,localport=1093] Message of size 42 written by Socket
Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server rmi: set boolean request
Message of size 22 read by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093]
client-server rmi: set boolean reply
- 53 -
Message of size 1 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093]
Message of size 1 read by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093]
Message of size 120 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093]
client-server rmi: set Object request Message of size 22 read by Socket
Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server rmi: set Object reply
Message of size 45 written by Socket Socket[addr=localhost127.0.0.1,port=1087,localport=1093]
client-server rmi: set int request Message of size 22 read by Socket
Socket[addr=localhost127.0.0.1,port=1087,localport=1093] client-server rmi: set int reply
Here is one possible implementation for the stream classes required by the altered
Socket
class:
package tuning.socket; import java.io.InputStream;
import java.io.OutputStream; import java.io.IOException;
import java.net.Socket; public class SockStreamLogger
{ public static boolean LOG_SIZE = false;
public static boolean LOG_MESSAGE = false; public static void readSocket so, int sz, byte[] buf, int off {
logfalse, so, sz, buf, off; } public static void writtenSocket so, int sz, byte[] buf, int off {
logtrue, so, sz, buf, off; } public static void logboolean isWritten, Socket so,
int sz, byte[] buf, int off {
if LOG_SIZE {
System.err.printMessage of size ; System.err.printsz;
System.err.printisWritten ? written : read; System.err.print by Socket ;
System.err.printlnso; }
if LOG_MESSAGE System.err.printlnnew Stringbuf, off, sz;
} }
public class SockInStreamLogger extends InputStream {
Socket s; InputStream in;
byte[] one_byte = new byte[1]; public SockInStreamLoggerSocket so, InputStream i{in = i; s = so;}
public int available throws IOException {return in.available ;} public void close throws IOException {in.close ;}
public void markint readlimit {in.markreadlimit;} public boolean markSupported {return in.markSupported ;}
public int read throws IOException { int ret = in.read ;
one_byte[0] = byte ret;
- 54 -
SockStreamLogger.reads, 1, one_byte, 0; return ret;
} public int readbyte b[] throws IOException {
int sz = in.readb; SockStreamLogger.reads, sz, b, 0;
return sz; }
public int readbyte b[], int off, int len throws IOException { int sz = in.readb, off, len;
SockStreamLogger.reads, sz, b, off; return sz;
} public void reset throws IOException {in.reset ;}
public long skiplong n throws IOException {return in.skipn;} }
public class SockOutStreamLogger extends OutputStream {
Socket s; OutputStream out;
byte[] one_byte = new byte[1]; public SockOutStreamLoggerSocket so, OutputStream o{out = o; s = so;}
public void writeint b throws IOException { out.writeb;
one_byte[0] = byte b; SockStreamLogger.writtens, 1, one_byte, 0;
} public void writebyte b[] throws IOException {
out.writeb; SockStreamLogger.writtens, b.length, b, 0;
} public void writebyte b[], int off, int len throws IOException {
out.writeb, off, len; SockStreamLogger.writtens, len, b, off;
} public void flush throws IOException {out.flush ;}
public void close throws IOException {out.close ;} }
2.7 Performance Checklist