server | v3 switch to v4  

Photon Dashboard Customization

In the Photon Dashboard tutorial, we have discussed how to install and display Photon's built in performance counters. Now we are going to see how we can easily create our own, custom counters and adjust the layout of the Photon Dashboard to our needs.

Let's assume you have a custom application that is extending the Lite application, and want to add another counter - for example, imagine that you have a custom operation "SendMessage" and that you want to know how many messages are sent per second.

Contents

Implementing Custom Performance Counters

At first, your application needs to track the desired values. We are going to use the in-memory counters from ExitGames.Diagnostics.Counter for this purpose.

Make sure that your application has a reference to the ExitGamesLibs.dll from the /lib folder:

Photon Server: Photon Dashboard Customizing Reference

Back To Top

Counter Declaration

The next step is to create a counter definition class. Add "using"-directives for ExitGames.Diagnostics.Counter and ExitGames.Diagnostics.Monitoring and declare your custom counter like shown below. Add a "PublishCounter" attribute with a unique name; this makes sure that the counter value will be published by the the counter publisher (see below).


using ExitGames.Diagnostics.Counter;
using ExitGames.Diagnostics.Monitoring;

public static class MyCustomCounter
{
  [PublishCounter(MessagesPerSecond")]
  public static readonly CountsPerSecondCounter MessagesPerSecond = new CountsPerSecondCounter();
}

See the appendix for a list of available counter types.

Back To Top

Counter Usage

In the appropriate place in your code, increment or decrement the counter. For example, to count the number of sent messages per second, add the following code to the method that handles your "SendMessage" operation:


protected virtual void HandleSendMessageOperation(LitePeer peer, OperationRequest operationRequest)
{
  // [... operation logic ...]

  // increment performance counter:
  MyCustomCounter.MessagesPerSecond.Increment();
}

That's all - you have done everything that is required to collect your performance data now .

Back To Top

Counter Publisher Initialisation

The next step is to publish the collected performance data to the Photon Dashboard.

For this, you need to initialize a CounterPublisher during the start-up of our application. The Setup method of your application is a good place for this.

This example shows how to initialize the counter publisher and add your custom counter definition class. You can choose an arbitrary name for the second parameter - it's used as the category in which the counter will be displayed by the dashboard later.


protected override void Setup()
{
  // [... setup logic  ...]

  // initialize counter publisher and add your custom counter class(es):
  if (PhotonSettings.Default.CounterPublisher.Enabled)
  {
    // counters for the photon dashboard

    CounterPublisher.DefaultInstance.AddStaticCounterClass(typeof(MyCustomCounter), "MyApplication");
    CounterPublisher.DefaultInstance.Start();
  }
}

Back To Top

Counter Publisher Configuration

The last step is to add the appropriate configuration for the counter publisher. Configure it to send data to the IP / Port where your Photon Dashboard is running. Remember, that multicasting is only working in the same subnet; to publish performance data between different networks, use the concrete IP and make sure that the port is opened up in the firewall.


  <ConfigSections>

    <section name="Photon" type="Photon.SocketServer.Diagnostics.Configuration.PhotonSettings, Photon.SocketServer"/>
  </configSections>

  <Photon>
    <CounterPublisher
      enabled="True"

      endpoint="255.255.255.255:40001"
      protocol="udp"
      sendInterface=""
      updateInterval="1"
      publishInterval="10"/> 
  </Photon>

Back To Top

Understanding The Photon Dashboard

In the previous steps we have made sure that our application tracks and publishes the required performance data. Now we need to make sure that the Photon Dashboard, which receives the published data, can display the counter values.

To extend the Photon Dashboard with your custom counters, it's helpful to understand roughly how the dashboard is working internally. The following schema should give you a good idea.

Photon Server: Photon Dashboard Customizing Schema

A counter receiver is listening on the specified UDP port for the counter data published by Photon. It stores the received values in RRD ("round robin database") files. These are files of a fixed size (ie., after they are initially created, they are not growing - no matter how long the dashboard is running or how much data it receives) and store values for a fixed period of a time - after that, the oldest entries are overwritten.

At certain intervals, the dashboard renders image files from the RRD files. A configuration file called Graphs.xml specifies how these images should look like. We'll discuss the configuration options further in the next section.

The Photon Dashboard contains an internal HTTP server, so you can easily access and browse the performance counter graphs on a web site, which can also be customized to your needs.

Back To Top

Extending The Photon Dashboard

Counter Receiver

See the Photon Dashboard tutorial to learn how to configure the Counter Receiver, e.g. the ports / IPs it is listening to.

Back To Top

Graph Renderer

For each graph that we want to create from our custom counters, we need to add an item to the Graphs.xml config file. You can find that file in /bin_tools/dashboard.

The items in the Graphs.xml file look like this:


<add sender="*" fileName="MyApplication\MessagesPerSecond" title="Messages sent / sec" timeSpan="00:10:00">

 <Items>
  <add fileName="MyApplication.MessagesPerSecond.rrd" dataSource="GAUGE" cf="AVERAGE" type="Area" color="Blue"/>

 </Items>
</add>

In most cases, you only need to adjust the image filename, the datasource (RRD) file name, and the title.

However, here is an explanation of all available attributes, in case you want to create some more advanced graphs:

  • sender - To display counters from all source servers in a single graph, set to sender="*". If you want to display only counters from specific servers, add the hostname, e.g.: sender="ServerA"
  • filename - The image file for your counter application will be created inside the "Images" folder. You can specify a subfolder like "MyApplication\MessagesPerSecond.jpg", if you want your image to be shown in an own category. Folder- and filenames are arbitrary.
  • title - an arbitrary title that is shown in your graph.
  • timeSpan - The graph shows counter data for the specified timespan, e.g. for the last 10 minutes. The shorter the time span is, the more fine-grained the displayed values are.
  • lowerLimit - Only values that exceed the specified value are displayed. The y-axis will start at the lowerLimit.
  • upperLimit - Only values that are below the specified value are displayed. The Y-axis will stop at the upperLimit. E.g., to show a percentage scale, you can limit the X-axis to 0-100 by setting lowerLimit="0" and upperLimit="100"

Each graph can show values from one or many data source items. These are the available attributes for the elements in the "Items" section:

  • filename - The name of the RRD data source file where the received counter values are stored. The data sources reside in the "PerfData" folder and are named as "AppName.CounterName.rrd", where the AppName is the name that was specified during the counter publisher initialization (here: "MyApplication") and the CounterName is the name that was specified in the "PublishCounter" attribute (here: "MessagesPerSecond").
  • dataSource - Choose the format of the datasource. performance counter values are stored in two different formats: "GAUGE" (default) and "COUNTER". The "GAUGE" type data source stores the actual performance counter value. the "COUNTER" type data source stores the rate of change in a specific time frame, assuming the value never decreases. (A traffic counter on a network interface card would be a good example). Typically, you should use dataSource="GAUGE" here.
  • cf - The consolidation function. Supported values are "AVERAGE", "MINIMUM", "MAXIMUM", "LAST".
  • type - The graph type. Supported values are "Area", "Line1", "Line2", "Line3" and "Stack".
  • color - Display the data from this data source in the defined color.

For a better understanding how graphs are created from RRD data sources, see https://oss.oetiker.ch/rrdtool/tut/rrd-beginners.en.html.

Back To Top

HTTP Server

Information on the configuration options for the HTTP server - e.g., on which port and IP it should run - can be found in the Photon Dashboard tutorial.

If you want to customize the look of the dashboard web UI, you can make changes to the html file or the .css stylesheets in /deploy/bin_tools/dashboard/web.

Back To Top

Appendix: List Of Counters From ExitGames.Diagnostics.Counter

This section discusses the available types of in-memory counters from the ExitGames.Diagnostics.Counter namespace in greater detail.

All counters implement the ICounter interface:


namespace ExitGames.Diagnostics.Counter
{
    public interface ICounter
    {
        string Name { get; }

        // Gets the type of the counter.

        CounterType CounterType { get; }

        // Gets the next value. 
        float GetNextValue();

        // Increments the counter by one and returns the new value.
        long Increment();

        // Increments the counter by a given value and returns the new value.
        long IncrementBy(long value);

        // Decrements the counter by one and returns the new value.
        long Decrement();
    }
}

As you see, there are mainly methods to increment / decrement the counter value.

GetNextValue is the method to return the actual "value" of the counter. This can be a calculated value - for example, when you call GetNextValue on an AverageCounter, the counter calculates the average of all increments / decrements since the previous call to GetNextValue and returns that value. GetNextValue resets internal values - you should never call it in your code manually, otherwise the published data will be incorrect. The CounterPublisher makes calls to GetNextValue at regular intervals.

Back To Top

NumericCounter

This is a very straight-forward counter. It holds a single value; GetNextValue returns that single value and does not reset the counter.

Example: At the start of a 10-second-interval, the NumericCounter has a value of 5. It is incremented 2 times, with values of 1 and 6, and decremented once with a value of 3, resulting in a value of 5+1+6-3 = 9 for this interval.

Typical use-case: The number of users that are currently logged on to your system.

Simple code example:


public void AddToCache(ArrayList items) 
{
   foreach (var item in items) 
   {
     Cache.Insert(item);
   }

   MyCacheItemCounter.IncrementBy(items.Count);
}

public void RemoveFromCache(ArrayList items) 
{
   foreach (var item in items) 
   {
     Cache.Remove(item);
   }

   MyCacheItemCounter.IncrementBy( -items.Count);
}

// When the CounterPublisher calls MyCacheItemCounter.GetNextValue(), it always returns the current number of items in cache.

Back To Top

AverageCounter

The AverageCounter tracks how often it was called since the last reset, and can be incremented by any value. It returns the average amount per call in the current interval (= total amount / # of calls).

Example: In a 10-second-interval, the AverageCounter is incremented 4 times, with values of 1,2,4,5. This is 12 in total, resulting in an average of 12/4 = 3 for this interval.

Typical use-case: The average execution time of a certain operation, or the average text length per sent message.

Simple code example:


public void WinGame(Player player, int coins) 
{
   player.Account.Add(coins);

   AverageCoinsWonCounter.IncrementBy(coins);
}

public void LoseGame(Player player, int coins) 
{
   player.Account.Remove(coins);

   AverageCoinsWonCounter.IncrementBy( -coins);
}

// When the CounterPublisher calls AverageCoinsWonCounter.GetNextValue(), it returns the average amount a player has won in the interval since the previous call to GetNextValue(). 
// This amount might as well be negative. The internal counter value is reset afterwards - i.e., if no Game was won or lost between two calls to GetNextValue(), the average is 0.

Back To Top

CountsPerSecondCounter

The CountsPerSecondCounter holds a single value, which can be incremented / decremented by any value. It returns the average amount per second in the last interval. You only need to increment / decrement the counter, it converts these calls in "per second" values automatically.

Example: In a 10-second-interval, the CountsPerSecondCounter is incremented 3 times, with values of 10, 20, and 70. This is 100 in total, resulting in a value of 10 per second for this interval.

Typical use-case: The number of sent bytes per second on a network interface, or the number of times a specific action is taken per second.

Simple code example:


public void RaiseEvent() 
{
   // do something to raise an event

   EventsPerSecondCounter.Increment(); 
}

// RaiseEvent can be called at any time - multiple times per second, or only once every few seconds, and requires no fixed interval.
// When the CounterPublisher calls EventsPerSecondCounter.GetNextValue(), the counter checks how many seconds have passed since the the last call to GetNextValue(). 
// It calculates it's return value as  "CurrentValue  / SecondsPassed".  The internal value is reset when GetNextValue() is called.

Back To Top

PerformanceCounterReader

The purpose of the PerformanceCounterReader is to read values from underlying Windows performance counters. It is a read-only counter and can not be incremented / decremented. This is failsafe, i.e. no errors are thrown if the performance counter does not exist or access is not allowed.

Back To Top

WindowsPerformanceCounter

The WindowsPerformanceCounter is a wrapper class for Windows performance counters; it provides read / write access to the underlying counter. The performance counter need to exist and the reading / writing application needs to have the appropriate rights, otherwise an exception is raised.

For more information on Windows performance counters, the MSDN is a good starting point: https://msdn.microsoft.com/en-us/library/system.diagnostics.performancecounter.aspx

To Document Top