Sample Application 5

What it Does

Sample application 5 demonstrates the setup and use of the ApplicationMetrics project, which was included with version 1.3.0.0 of MethodInvocationRemoting. The sample application is based around a GUI application that approximates the value of Pi (the same as in sample application 4). All of the main MethodInvocationRemoting classes used are passed metric logger objects on construction, and the metric logger objects output detailed instrumentation information on the activity and performance of MethodInvocationRemoting.

Code Analysis

Object Setup

During setup of the application, multiple classes implementing the IMetricLogger interface are instantiated. The extract from the C# side code below shows the setup of 4 metric loggers logging to a file, the console, a Microsoft Access database, and Windows performance counters...

// Setup metric logger and MethodInvocationRemoting objects MetricLoggerDistributor distributor = new MetricLoggerDistributor(); ConsoleMetricLogger consoleMetricLogger = new ConsoleMetricLogger(5000, true); using (FileApplicationLogger logger = new FileApplicationLogger(LogLevel.Information, '|', " ", @"C:\Temp\C#Sender.log")) using (SizeLimitedBufferProcessor fileMetricLoggerBufferProcessor = new SizeLimitedBufferProcessor(50)) using (FileMetricLogger fileMetricLogger = new FileMetricLogger('|', @"C:\Temp\C#Metrics.log", fileMetricLoggerBufferProcessor, true)) using (SizeLimitedBufferProcessor accessMetricLoggerBufferProcessor = new SizeLimitedBufferProcessor(50)) using (MicrosoftAccessMetricLogger accessMetricLogger = new MicrosoftAccessMetricLogger(@"C:\Temp\MetricLogger.mdb", "SampleApplication5-C#", accessMetricLoggerBufferProcessor, true)) using (PerformanceCounterMetricLogger perfmonMetricLogger = new PerformanceCounterMetricLogger("SampleApplication5Metrics", "Metrics produced by MethodInvocationRemoting sample application 5", 1000, true))

The FileMetricLogger and MicrosoftAccessMetricLogger are constructed using the SizeLimitedBufferProcessor class to handle queue/buffer processing, and their size limit set to 50 metric events (i.e. the contents of the queues will be written to the file and Access database whenever the total metric events logged reaches 50). An instance of the PerformanceCounterMetricLogger class is created using the deprecated constructor, which defaults to use the LoopingWorkerThreadBufferProcessor class for queue/buffer processing. The interval to write to the Windows performance counters is set to 1000 milliseconds. The ConsoleMetricLogger is also instantiated using the deprecated constructor, and is configured to write metrics to the console every 5 seconds.

Additionally, for all metric loggers the 'intervalMetricChecking' parameter is set to true, so an exception will be thrown if the correct order of logging interval metrics is not followed (e.g. if the End() method is called for a metric without a corresponding Begin()). The MicrosoftAccessMetricLogger must reference the sample Microsoft Access database included in the \Resources folder of MethodInvocationRemoting. Either ensure this file exists in the 'C:\Temp\' folder or adjust the path to the correct location.

A class MetricLoggerDistributor is included with the project, which implements the IMetricLogger interface, and as the name suggests, distributes calls to IMetricLogger methods to multiple objects implementing IMetricLogger. When the MethodInvocationRemoting sender, receiver, and serializer objects are constructed, an instance of the MetricLoggerDistributor class is set on them...

using (TcpRemoteSender tcpSender = new TcpRemoteSender(System.Net.IPAddress.Loopback, 55000, 10, 1000, 30000, 25, logger, distributor)) using (TcpRemoteReceiver tcpReceiver = new TcpRemoteReceiver(55001, 10, 1000, 25, logger, distributor)) { RemoteReceiverDecompressor decompressorReceiver = new RemoteReceiverDecompressor(tcpReceiver, logger, distributor); MethodInvocationSerializer serializer = new MethodInvocationSerializer(new SerializerOperationMap(), logger, distributor); MethodInvocationRemoteSender methodInvocationRemoteSender = new MethodInvocationRemoteSender(serializer, tcpSender, decompressorReceiver, logger, distributor);

...then further on in the code, the 4 metric loggers are added to the distributor, so that each metric event generated by the MethodInvocationRemoting objects is sent to all four metric loggers...

distributor.AddLogger(consoleMetricLogger); distributor.AddLogger(fileMetricLogger); distributor.AddLogger(accessMetricLogger); distributor.AddLogger(perfmonMetricLogger);

Registering Metrics and Aggregates

For the 2 metric loggers which implement interface IMetricAggregateLogger (ConsoleMetricLogger and PerformanceCounterMetricLogger), the metric aggregates which they should calculate must be defined. This step is performed by private method DefineMetricAggregates(). The code for the method appears below...

private static void DefineMetricAggregates(IMetricAggregateLogger aggregateLogger) { aggregateLogger.DefineMetricAggregate(new SerializedMethodInvocationSize(0), new RemoteMethodSent(), "BytesSentPerRemoteMethodSent", "The average number of bytes sent per remote method sent"); aggregateLogger.DefineMetricAggregate(new ReceivedMessageSize(0), new RemoteMethodSent(), "BytesReceivedPerRemoteMethodSent", "The average number of bytes received per remote method sent"); aggregateLogger.DefineMetricAggregate(new MessageSendTime(), new RemoteMethodSent(), "MessageSendTimePerRemoteMethodSent", "The average message send time per remote method sent"); aggregateLogger.DefineMetricAggregate(new MessageReceiveTime(), new RemoteMethodSent(), "MessageReceiveTimePerRemoteMethodSent", "The average message receive time per remote method sent"); aggregateLogger.DefineMetricAggregate(new RemoteMethodSendTime(), new RemoteMethodSent(), "AverageRemoteMethodSendTime", "The average time taken to send and invoke a remote method"); aggregateLogger.DefineMetricAggregate(new SerializedMethodInvocationSize(0), TimeUnit.Second, "BytesSentPerSecond", "The average number of bytes sent per second"); aggregateLogger.DefineMetricAggregate(new ReceivedMessageSize(0), TimeUnit.Second, "BytesReceivedPerSecond", "The average number of bytes received per second"); aggregateLogger.DefineMetricAggregate(new RemoteMethodSent(), TimeUnit.Minute, "RemoteMethodsSentPerMinute", "The average number of remote methods sent per minute"); aggregateLogger.DefineMetricAggregate(new ReturnValueDeserializeTime(), "TimeSpentDeserializingReturnValue", "The fraction of total runtime spent deserializing return values"); }

The following table explains each of the metric aggregates defined in both the C# and Java sides...

Name Numerator Denominator Description Notes
C#
BytesSentPerRemoteMethodSent SerializedMethodInvocationSize RemoteMethodSent The average number of bytes sent per remote method sent. Technically this records the number of characters sent, and the actual number of bytes depends on the character encoding used. However for standard ASCII character the number of characters sent and the number of bytes sent should be equivalent. Additionally the number of characters is recorded by the MethodInvocationSerializer class, and hence does not include any header or delimiter bytes added to transport the message (e.g. as is done by the TcpRemoteSender class).
BytesReceivedPerRemoteMethodSent ReceivedMessageSize RemoteMethodSent The average number of bytes received per remote method sent. See notes for 'BytesSentPerRemoteMethodSent' aggregate.
MessageSendTimePer
RemoteMethodSent
MessageSendTime RemoteMethodSent The average time taken to send data per remote method sent in milliseconds.
MessageReceiveTimePer
RemoteMethodSent
MessageReceiveTime RemoteMethodSent The average time taken to receive data per remote method sent in milliseconds.
AverageRemoteMethodSendTime RemoteMethodSendTime RemoteMethodSent The average time taken to send a remote method in milliseconds.
BytesSentPerSecond SerializedMethodInvocationSize TimeUnit.Second The average number of bytes sent per second. See notes for 'BytesSentPerRemoteMethodSent' aggregate.
BytesReceivedPerSecond ReceivedMessageSize TimeUnit.Second The average number of bytes received per second. See notes for 'BytesSentPerRemoteMethodSent' aggregate.
RemoteMethodsSentPerMinute RemoteMethodSent TimeUnit.Minute The average number of remote methods sent per minute.
TimeSpentDeserializingReturnValue ReturnValueDeserializeTime (total runtime of metric logger) The percentage of total runtime (i.e. since the metric logger Start() method was called) that consumed deserializing method invocation return values. For remote invocations of method GenerateScenarios() with a large number of scenarios, the return value will contain a large array, and hence the time taken to deserialize this return value could be significant.
Java
BytesSentPerRemoteMethodReceived CompressedStringSize RemoteMethodReceived The average number of bytes sent per remote method received. Technically this records the number of characters sent, and the actual number of bytes depends on the character encoding used. However for standard ASCII character the number of characters sent and the number of bytes sent should be equivalent. Additionally the number of characters is recorded by the RemoteSenderCompressor class, and hence does not include any header or delimiter bytes added to transport the message (e.g. as is done by the TcpRemoteSender class).
BytesReceivedPer
RemoteMethodReceived
ReceivedMessageSize RemoteMethodReceived The average number of bytes received per remote method received. See notes for 'BytesSentPer
RemoteMethodReceived' aggregate.
MessageSendTimePer
RemoteMethodReceived
MessageSendTime RemoteMethodReceived The average time taken to send data per remote method received in milliseconds.
MessageReceiveTimePer
RemoteMethodReceived
MessageReceiveTime RemoteMethodReceived The average time taken to receive data per remote method received in milliseconds.
AverageRemoteMethodReceiveTime RemoteMethodReceiveTime RemoteMethodReceived The average time taken to receive a remote method in milliseconds.
BytesSentPerSecond CompressedStringSize TimeUnit.SECONDS The average number of bytes sent per second. See notes for 'BytesSentPer
RemoteMethodReceived' aggregate.
BytesReceivedPerSecond ReceivedMessageSize TimeUnit.SECONDS The average number of bytes received per second. See notes for 'BytesSentPer
RemoteMethodReceived' aggregate.
RemoteMethodsReceivedPerMinute RemoteMethodReceived() TimeUnit.MINUTES The average number of remote methods received per minute.
TimeSpentCompressingReturnValues StringCompressTime (total runtime of metric logger) The percentage of total runtime (i.e. since the metric logger Start() method was called) that consumed compressing method invocation return values. For remote invocations of method GenerateScenarios() with a large number of scenarios, the return value will contain a large array, and hence the time taken to compress this return value could be significant.
CompressionRatio CompressedStringSize SerializedReturnValueSize The total size of all compressed return values divided by the total size of all uncompressed return values. Hence this effectively shows the compression ratio achieved by the RemoteSender
Compressor class.

The FileMetricLogger and ConsoleMetricLogger will record any base metrics which are logged (through IMetricLogger methods) without requiring any pre-definition or registration of the metrics. The MicrosoftAccessMetricLogger also does not require any registration (although the metrics must be defined in the relevant lookup tables in the Access database). Because of the need to create performance counters in Windows in advance however, the PerformanceCounterMetricLogger requires that any logged metrics are first registered using the RegisterMetric() method. This is performed in the following code...

perfmonMetricLogger.RegisterMetric(new RemoteMethodSendTime()); perfmonMetricLogger.RegisterMetric(new RemoteMethodSent()); perfmonMetricLogger.RegisterMetric(new MethodInvocationSerializeTime()); perfmonMetricLogger.RegisterMetric(new MethodInvocationSerialized()); perfmonMetricLogger.RegisterMetric(new SerializedMethodInvocationSize(0)); perfmonMetricLogger.RegisterMetric(new ReturnValueDeserializeTime()); perfmonMetricLogger.RegisterMetric(new ReturnValueDeserialized()); perfmonMetricLogger.RegisterMetric(new StringDecompressTime()); perfmonMetricLogger.RegisterMetric(new RemoteReceiverDecompressorReadBufferCreated()); perfmonMetricLogger.RegisterMetric(new StringDecompressed()); perfmonMetricLogger.RegisterMetric(new TcpRemoteReceiverReconnected()); perfmonMetricLogger.RegisterMetric(new MessageReceiveTime()); perfmonMetricLogger.RegisterMetric(new MessageReceived()); perfmonMetricLogger.RegisterMetric(new ReceivedMessageSize(0)); perfmonMetricLogger.RegisterMetric(new TcpRemoteReceiverDuplicateSequenceNumber()); perfmonMetricLogger.RegisterMetric(new MessageSendTime()); perfmonMetricLogger.RegisterMetric(new MessageSent()); perfmonMetricLogger.RegisterMetric(new TcpRemoteSenderReconnected());

Additionally, for the PerformanceCounterMetricLogger class, the CreatePerformanceCounters() method is called to create the relevant Windows performance counters and performance counter category in the operating system. Note that the Windows account running the sample application requires appropriate administrative privileges to perform this step...

// Create performance monitor counters in the operating system perfmonMetricLogger.CreatePerformanceCounters();

Starting and Connecting

Any class deriving from MetricLoggerBuffer must have its worker thread started and subsequently stopped. In earlier versions of ApplicationMetrics, this was performed by calling the Start() and Stop() on the metric logger class. From ApplicationMetrics version 1.4.0.0, these methods have been moved to the IBufferProcessingStrategy interface. However the Start() method in classes deriving from MetricAggregateLogger performs additional required initialization steps specific to that class. Generally, the following rules should be observed...

  • For classes deriving from MetricLoggerBuffer (i.e. FileMetricLogger and MicrosoftAccessMetricLogger), Start() should be called on the IBufferProcessingStrategy class.
  • For classes deriving from MetricAggregateLogger (i.e. ConsoleMetricLogger and PerformanceCounterMetricLogger), Start() should be called on the class itself.
  • Stop() should be called on the IBufferProcessingStrategy class (which also offers an additional overload of the method not available on the metric logger class).

In addition, on the MicrosoftAccessMetricLogger object, the Connect() and Disconnect() methods must be called to connect to and disconnect from the database. These steps are performed in the following code extract...

consoleMetricLogger.Start(); fileMetricLoggerBufferProcessor.Start(); accessMetricLoggerBufferProcessor.Start(); accessMetricLogger.Connect(); perfmonMetricLogger.Start(); // Connect to the MethodInvocationRemoteReceiver tcpSender.Connect(); tcpReceiver.Connect(); // Setup the layers of the application MVP model MainView mainView = new MainView(); Model model = new Model(methodInvocationRemoteSender); Presenter presenter = new Presenter(mainView, model); mainView.Presenter = presenter; // Start the application Application.Run(mainView); // Disconnect from the MethodInvocationRemoteReceiver tcpReceiver.Disconnect(); tcpSender.Disconnect(); // Stop the metric loggers consoleMetricLogger.Stop(); fileMetricLoggerBufferProcessor.Stop(); accessMetricLoggerBufferProcessor.Stop(); accessMetricLogger.Disconnect(); perfmonMetricLogger.Stop();

Setup

Similar to standard logging through the ApplicationLogging project, the metric logging can be enabled or disabled in MethodInvocationRemoting at compilation. In C# the conditional compilation symbol 'METRICS_ON' should be set in the MethodInvocationRemoting project build options to enable metric logging. In Java, as with ApplicationLogging, the metrics logging can be enabled by building with Ant, and setting the relevant options in the AntBuild.xml config file as shown below...

<!-- Comment this section to disable metrics --> <property name="BeginMetrics.token" value="*/"/> <property name="EndMetrics.token" value="/*"/> <!-- Uncomment this section to disable metrics <property name="BeginMetrics.token" value=""/> <property name="EndMetrics.token" value=""/> -->

The Java code can be started with a command similar to the following (ensure the version of methodinvocationremoting in the 'AntBuild' folder with metric logging enabled is referenced)...

java -cp "[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\SampleApplication5\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\MethodInvocationRemoting\AntBuild\bin";"[path containing MethodInvocationRemoting]\MethodInvocationRemoting\Java\Referenced Libraries\commons-codec-1.9.jar" Program

The following should be displayed on the console...

Press [ENTER] to stop the application.

The Java side of the application can be stopped at any time by pressing 'Enter' at the console. The C# code can be started by executing the SampleApplication5.exe file, which will display the following GUI...

Application 5 Window

Using the application is exactly the same as for SampleApplication4. A number of scenarios (up to 2000) should be entered into the relevant field, and the 'Calculate' button clicked. A remote method call is sent to Java where a number of scenarios are generated. The scenarios are returned to C#, where an approximate value is calculated and displayed in the GUI.

Viewing Metrics

The ConsoleMetricLogger objects on both the C# and Java sides write all current metrics and metric aggregates to the console every 5 seconds. The output will be similar to below in C#...

--------------------------------------------------- -- Application metrics as of 2014-09-12 00:29:11 -- --------------------------------------------------- MethodInvocationSerialized: 4 MessageSent: 4 MessageReceived: 4 RemoteReceiverDecompressorReadBufferCreated: 123 StringDecompressed: 4 ReturnValueDeserialized: 4 RemoteMethodSent: 4 SerializedMethodInvocationSize: 1062 ReceivedMessageSize: 23720 MethodInvocationSerializeTime: 0 MessageSendTime: 280 MessageReceiveTime: 32 StringDecompressTime: 0 ReturnValueDeserializeTime: 0 RemoteMethodSendTime: 751 RemoteMethodsSentPerMinute: 4 BytesSentPerRemoteMethodSent: 265.5 BytesReceivedPerRemoteMethodSent: 5930 BytesSentPerSecond: 35.4 BytesReceivedPerSecond: 790.666666666667 MessageSendTimePerRemoteMethodSent: 70 MessageReceiveTimePerRemoteMethodSent: 8 AverageRemoteMethodSendTime: 187.75 TimeSpentDeserializingReturnValue: 0

...and this in Java...

--------------------------------------------------- -- Application metrics as of 2014-09-12 00:29:25 -- --------------------------------------------------- RemoteMethodReceived: 4 MessageSent: 4 MethodInvocationDeserialized: 4 MessageReceived: 4 StringCompressed: 4 ReturnValueSerialized: 4 CompressedStringSize: 23720 ReceivedMessageSize: 1062 SerializedReturnValueSize: 123380 MessageReceiveTime: 0 ReturnValueSerializeTime: 407 StringCompressTime: 94 MessageSendTime: 484 MethodInvocationDeserializeTime: 31 RemoteMethodReceiveTime: 1016 BytesSentPerRemoteMethodReceived: 5930.0 BytesReceivedPerRemoteMethodReceived: 265.5 BytesSentPerSecond: 527.1111111111111 BytesReceivedPerSecond: 23.6 CompressionRatio: 0.19225158048306046 MessageSendTimePerRemoteMethodReceived: 121.0 MessageReceiveTimePerRemoteMethodReceived: 0.0 AverageRemoteMethodReceiveTime: 254.0 TimeSpentCompressingReturnValues: 0.0020617200008773277

At the same time, each individual metric event will be written to files by the FileMetricLogger classes on both sides. The output will be similar to the following...

2014-09-12 00:32:31.265 | MethodInvocationSerialized 2014-09-12 00:32:31.406 | MessageSent 2014-09-12 00:32:31.406 | MessageReceived 2014-09-12 00:32:31.406 | RemoteReceiverDecompressorReadBufferCreated 2014-09-12 00:32:31.406 | RemoteReceiverDecompressorReadBufferCreated 2014-09-12 00:32:31.406 | RemoteReceiverDecompressorReadBufferCreated 2014-09-12 00:32:31.406 | RemoteReceiverDecompressorReadBufferCreated 2014-09-12 00:32:31.406 | StringDecompressed 2014-09-12 00:32:31.437 | ReturnValueDeserialized 2014-09-12 00:32:31.437 | RemoteMethodSent 2014-09-12 00:32:31.265 | SerializedMethodInvocationSize | 265 2014-09-12 00:32:31.406 | ReceivedMessageSize | 952 2014-09-12 00:32:31.265 | MethodInvocationSerializeTime | 0 2014-09-12 00:32:31.281 | MessageSendTime | 125 2014-09-12 00:32:31.406 | MessageReceiveTime | 0 2014-09-12 00:32:31.406 | StringDecompressTime | 0 2014-09-12 00:32:31.421 | ReturnValueDeserializeTime | 16 2014-09-12 00:32:31.265 | RemoteMethodSendTime | 172

Similarly each individual metric will be written to the Microsoft Access database. A number of queries are defined in the database which present the metrics in a readable format. The below screen shot shows a sample of the query 'VwAllMetricInstances', which shows a consolidated view of all logged metrics from both C# and Java...

Microsoft Access Metrics

In the Windows Performance Monitor, the counters to display must be selected through the 'Add Counters' window. The counters defined for the sample application appear in the category 'SampleApplication5Metrics'...

Windows Performance Monitor - Adding Counters

In the below example, three counters are added... AverageRemoteMethodSendTime, BytesSentPerRemoteMethodSent, and BytesSentPerSecond...

Windows Performance Monitor - Adding Counters

The 'Line' view in Performance Monitor shows a graph view of the metrics. In the below case the vertical scale of the graph has been adjusted to a value of 400. Note that the standard counter for BytesSentPerRemoteMethodSent shows a continual average (represented by the blue line). The instantaneous counter however shows only peaks when bytes are sent (represented by the yellow line)...

Windows Performance Monitor - Line View

The 'Report' view can also be used to display all counters in a list...

Windows Performance Monitor - Report View

Remember that the Performance Monitor must be started after the sample application is started (i.e. after the CreatePerformanceCounters() method is called on the PerformanceCounterMetricLogger object) or the counters may not be available to add, or may not display correct values. Also if using aggregates with a time unit denominator, the polling interval of the Performance Monitor must be set to greater than the denominator time unit, for the counters to correctly display.