One of the biggest gaps in performance counters is the lack of information about current memory usage. One of the requirements for the certification of applications on Windows Phone 7 is limitation to 90 MB memory usage by an application for phones with less memory than 256 MB. At the moment there is no phone on the market that meet this condition. Theoretically, we should not worry. However, according to the information from Microsoft the next generation operating system Windows Phone Tango should already allow for the production of low-budget phones with less memory.

From this moment Microsoft will probably check the memory usage by applications on mobile phones. I propose to make it faster, and be now ready for the arrival of new equipment. Currently available phones with Windows Phone 7.5 assign to application about 200 MB to 350 MB of memory. After exceeding this value, the program will be closed by the operating system with the Out of memory exception.

In a situation, when your application is behaving in a stable manner, and you know, that user by his actions will not be able to increase the memory usage, we are in a good position. We should only check whether application behaves stably during development. For this purpose you can use Memory profiler, which is available from the Visual Studio. An example of its actions shows following picture:

Memory usage graph

This is not a convenient solution. Information about the memory consumption is achieved only after the end of the program. A much better solution would be to obtain this information currently. To this end I propose to add to the information about the memory used. Such information can be displayed near the counter system.

Measuring memory usage - Idea

For this I propose to dispel include three indicators:

  • Current – Information about current memory consumption,
  • Available – Information on the amount of memory available for the program,
  • Peak – The maximum amount of memory consumed by the application.

Each of these values ​​will be expressed in kb. In addition, the indicators Current and Peak should change color depending on the used memory. The limiting value should be 90 MB – memory limit for applications, which occurs in the case of devices with less RAM than 256 MB. This level is the reference for two values ​​of the warning:

  • first level – 67,5 MB – Indicators change color to yellow,
  • second level – 81 MB – Indicators turn red.

The levels are chosen, so they could be used as warning signs. For devices with low memory Microsoft does not recommend crossing 60 MB memory consumption.

From Mango Microsoft provides a much simplified way to retrieve information about the memory usage, as well as the other device parameters. You can use class DeviceStatus.

/// <summary>
/// Gets the current memory usage, in bytes.
/// </summary>
/// <value>Current memory usage</value>
private static long CurrentMemoryUsage
{
  get { return DeviceStatus.ApplicationCurrentMemoryUsage; }
}

/// <summary>
/// Gets the peak memory usage, in bytes.
/// </summary>
/// <value>Peak memory usage</value>
private static long PeakMemoryUsage
{
  get { return DeviceStatus.ApplicationPeakMemoryUsage; }
}

/// <summary>
/// Gets the application memory usage limit in bytes.
/// </summary>
private static long ApplicationMemoryUsageLimit
{
  get { return DeviceStatus.ApplicationMemoryUsageLimit; }
}

The global implementation includes the following class:

/// <summary>
/// Class that is responsible for memory measurements and display indicators
/// </summary>
public static class MemoryDiagnoster
{
  /// <summary>
  /// Popup control
  /// </summary>
  private static Popup popup;

  /// <summary>
  /// Text block with current memory usage value
  /// </summary>
  private static TextBlock currentMemoryBlock;

  /// <summary>
  /// Text block with peak memory usage value
  /// </summary>
  private static TextBlock peakMemoryBlock;

  /// <summary>
  /// Dispatcher timer
  /// </summary>
  private static DispatcherTimer dispatcherTimer;

  /// <summary>
  /// Decides if garbage collector should be run before each memory reading
  /// </summary>
  private static bool forceGarbageColector;

  /// <summary>
  /// Definition of 90MB memory limit for application on devices with less than 256 RAM
  /// </summary>
  private const long MAX_LIMIT_OF_MEMORY = 90 * 1024 * 1024;

  // Colors definitions
  private static readonly SolidColorBrush redBrush = new SolidColorBrush(Colors.Red);
  private static readonly SolidColorBrush yellowBrush = new SolidColorBrush(Colors.Yellow);
  private static readonly SolidColorBrush greenBrush = new SolidColorBrush(Colors.Green);

  /// <summary>
  /// Definition of last colour used by current memory indicator
  /// </summary>
  private static SolidColorBrush lastCurrentBrush;

  /// <summary>
  /// Definition of last colour used by peak memory indicator
  /// </summary>
  private static SolidColorBrush lastPeakBrush;

  /// <summary>
  /// Gets the current memory usage, in bytes.
  /// </summary>
  /// <value>Current memory usage</value>
  private static long CurrentMemoryUsage
  {
    get { return DeviceStatus.ApplicationCurrentMemoryUsage; }
  }

  /// <summary>
  /// Gets the peak memory usage, in bytes.
  /// </summary>
  /// <value>Peak memory usage</value>
  private static long PeakMemoryUsage
  {
    get { return DeviceStatus.ApplicationPeakMemoryUsage; }
  }

  /// <summary>
  /// Gets the application memory usage limit in bytes.
  /// </summary>
  private static long ApplicationMemoryUsageLimit
  {
    get { return DeviceStatus.ApplicationMemoryUsageLimit; }
  }

  /// <summary>
  /// Starts the memory monitoring process
  /// </summary>
  /// <param name="timespan">The timespan between following memory measurement.</param>
  /// <param name="forceGarbageColector">
  /// if set to <c>true</c> garbage collector is force to run before memory measurement.
  ///</param>
  public static void StartMemoryMonitoring(TimeSpan timespan, bool forceGarbageColector = false)
  {
    if (dispatcherTimer != null)
    {
      throw new InvalidOperationException("Process is already running.");
    }

    lastCurrentBrush = greenBrush;
    lastPeakBrush = greenBrush;
    MemoryDiagnoster.forceGarbageColector = forceGarbageColector;
    ShowControl();
    StartTimer(timespan);
  }

  /// <summary>
  /// Stops the timer and hides the counter
  /// </summary>
  public static void StartMemoryMonitoring()
  {
    HideControl();
    StopTimer();
  }

  /// <summary>
  /// Shows the control with results of memory measurements.
  /// </summary>
  private static void ShowControl()
  {
    popup = new Popup();
    double fontSize = (double)Application.Current.Resources["PhoneFontSizeSmall"] - 2;
    Brush foreground = (Brush)Application.Current.Resources["PhoneForegroundBrush"];
    StackPanel stackPanel = new StackPanel
      {
        Orientation = Orientation.Horizontal,
        Background = (Brush)Application.Current.Resources["PhoneSemitransparentBrush"]
      };
    currentMemoryBlock = new TextBlock
      {
        Text = "---",
        FontSize = fontSize,
        Foreground = lastCurrentBrush
      };
    peakMemoryBlock = new TextBlock
      {
        Text = "---",
        FontSize = fontSize,
        Foreground = lastPeakBrush
      };
    stackPanel.Children.Add(new TextBlock
                              {
                                Text = "Current: ",
                                FontSize = fontSize,
                                Foreground = foreground
                              });
    stackPanel.Children.Add(currentMemoryBlock);
    stackPanel.Children.Add(new TextBlock
                              {
                                Text = " / " + string.Format("{0:N0}",
                                ApplicationMemoryUsageLimit / 1024) ,
                                FontSize = fontSize,
                                Foreground = foreground
                              });
    stackPanel.Children.Add(new TextBlock
                              {
                                Text = " kb Peak: ",
                                FontSize = fontSize,
                                Foreground = foreground
                              });
    stackPanel.Children.Add(peakMemoryBlock);
    stackPanel.Children.Add(new TextBlock
                              {
                                Text = " kb",
                                FontSize = fontSize,
                                Foreground = foreground
                              });
    stackPanel.RenderTransform = new CompositeTransform
                                   {
                                     Rotation = 90,
                                     TranslateX = 480,
                                     TranslateY = 405,
                                     CenterX = 0,
                                     CenterY = 0
                                   };
    popup.Child = stackPanel;
    popup.IsOpen = true;
  }

  /// <summary>
  /// Hides the control.
  /// </summary>
  private static void HideControl()
  {
    popup.IsOpen = false;
    popup = null;
  }

  /// <summary>
  /// Starts the timer.
  /// </summary>
  /// <param name="timespan">The timespan.</param>
  private static void StartTimer(TimeSpan timespan)
  {
    dispatcherTimer = new DispatcherTimer {Interval = timespan};
    dispatcherTimer.Tick += DispatcherTimerTick;
    dispatcherTimer.Start();
  }

  /// <summary>
  /// Stops the timer.
  /// </summary>
  private static void StopTimer()
  {
    dispatcherTimer.Stop();
    dispatcherTimer = null;
  }

  /// <summary>
  /// Updates memory usage indicators on every tick.
  /// </summary>
  /// <param name="sender">The sender.</param>
  /// <param name="e">
  /// The <see cref="System.EventArgs"/> instance containing the event data.
  /// </param>
  private static void DispatcherTimerTick(object sender, EventArgs e)
  {
    if (forceGarbageColector)
    {
      GC.Collect();
    }

    long currentMemory = CurrentMemoryUsage;
    currentMemoryBlock.Text = string.Format("{0:N0}", currentMemory / 1024);
    SolidColorBrush currentMemoryBrush = GetBrushForUsage(currentMemory);
    if (currentMemoryBrush != lastCurrentBrush)
    {
      currentMemoryBlock.Foreground = currentMemoryBrush;
      lastCurrentBrush = currentMemoryBrush;
    }

    long peakMemory = PeakMemoryUsage;
    peakMemoryBlock.Text = string.Format("{0:N0}", peakMemory / 1024);
    SolidColorBrush peakMemoryBrush = GetBrushForUsage(peakMemory);
    if (peakMemoryBrush != lastPeakBrush)
    {
      peakMemoryBlock.Foreground = peakMemoryBrush;
      lastPeakBrush = peakMemoryBrush;
    }
  }

  /// <summary>
  /// Gets the proper brush colour which depends of memory usage.
  /// </summary>
  /// <param name="memoryUsage">The memory usage.</param>
  /// <returns>Colour for memory usage</returns>
  private static SolidColorBrush GetBrushForUsage(long memoryUsage)
  {
    double percent = memoryUsage / (double)MAX_LIMIT_OF_MEMORY;
    if (percent <= 0.75)
    {
      return greenBrush;
    }

    if (percent <= 0.90)
    {
      return yellowBrush;
    }

    return redBrush;
  }
}&#91;/code&#93;
<p>To run the indicators you need to add one line of code:</p>
[code lang="csharp"]MemoryDiagnoster.StartMemoryMonitoring(TimeSpan.FromMilliseconds(500), [use Garbage Collector]);

After this line of code, the information about the memory usage should be shown on your phone. You can change the time, that passes between the measurements of memory and start the process to force Garbage Collector during each measurement.

The result of the proposed solution is shown below. Memory gauges were used in the program Kursy Walut.

Measuring memory usage - Kursy Walut