Object Pooling, Part 8 - Monitoring Pool Performance

This is the eighth article in my mini series about object pooling. Be sure to read part 1, part 2, part 3, part 4, part 5, part 6 and part 7 first. So, although the number of downloads of the sample program hasn’t been exactly great, I’m finally continuing the series. As I said in a previous article, I’d like to factor out the resizing behaviour – but before I do that, I want to integrate functionality that’ll let me evaluate the pool’s performance with regard to resizing, so I can assess the different resizing approaches reliably. Fortunately performance monitoring is something that needs to be integrated into the pool implementation anyway and Windows has all the tools on board. So I only need to configure a few performance counters in Windows, make sure that these counters are updated with current numbers and the Windows performance monitoring frontend is available to the user to show it all in a nice graph. (I’m sure some of you have never heard of the performance monitor application – it’s available on the Administrative tools menu and it’s called, not surprisingly, Performance.) Here’s the download for the current version, including these changes: ObjectPooling-2.zip

And these are the changes - at the end of the Pool class:

private string performanceCategoryName;
/// <summary>
/// Gets or sets a value indicating the name of the performance counter category.
/// </summary>
public string PerformanceCategoryName {
  get {
    return performanceCategoryName;
  }
  set {
    if (performanceCategoryName != value) {
      performanceCategoryName = value;
      InvalidateCounters( );
    }
  }
}

private string performanceCounterNamePrefix;
/// <summary>
/// Gets or sets a value indicating a prefix that is used for all the performance
/// counters of this pool instance.
/// </summary>
public string PerformanceCounterNamePrefix {
  get {
    return performanceCounterNamePrefix;
  }
  set {
    if (performanceCounterNamePrefix != value) {
      performanceCounterNamePrefix = value;
      InvalidateCounters( );
    }
  }
}

/// <summary>
/// Set the properties PerformanceCategoryName and PerformanceCounterNamePrefix to the values
/// passed in to the categoryName and counterNamePrefix parameters, then calls InitializeCounters().
/// </summary>
public void InitializeCounters(string categoryName, string counterNamePrefix) {
  performanceCategoryName = categoryName;
  performanceCounterNamePrefix = counterNamePrefix;
  InitializeCounters( );
}

private const string STR_Poolsize = "Pool size";
private const string STR_PoolsizeHelp = "The current number of poolable objects.";
private const string STR_ObjectsInUse = "Objects in use";
private const string STR_ObjectsInUseHelp =
  "The number of objects that are currently allocated to a pool user.";

PerformanceCounter poolSizeCounter;
PerformanceCounter inUseCounter;

/// <summary>
/// Initializes the performance counters according to the settings of the PerformanceCategoryName
/// and PerformanceCounterNamePrefix properties.
/// </summary>
public void InitializeCounters( ) {
  if (PerformanceCounterCategory.Exists(performanceCategoryName))
    PerformanceCounterCategory.Delete(performanceCategoryName);

  CounterCreationDataCollection counters = new CounterCreationDataCollection( );

  counters.Add(new CounterCreationData(performanceCounterNamePrefix + STR_Poolsize,
    STR_PoolsizeHelp, PerformanceCounterType.NumberOfItems32));
  counters.Add(new CounterCreationData(performanceCounterNamePrefix + STR_ObjectsInUse,
    STR_ObjectsInUseHelp, PerformanceCounterType.NumberOfItems32));

  PerformanceCounterCategory.Create(performanceCategoryName, "",
    PerformanceCounterCategoryType.SingleInstance, counters);

  poolSizeCounter = new PerformanceCounter(performanceCategoryName,
    performanceCounterNamePrefix + STR_Poolsize, false);
  inUseCounter = new PerformanceCounter(performanceCategoryName,
    performanceCounterNamePrefix + STR_ObjectsInUse, false);
}

void InvalidateCounter(ref PerformanceCounter counter) {
  if (counter != null) {
    counter.Dispose( );
    counter = null;
  }
}

void InvalidateCounters( ) {
  InvalidateCounter(ref poolSizeCounter);
  InvalidateCounter(ref inUseCounter);
}

In the ExtendPoolBy method:

// ...
lock (poolLock) {
  count = Math.Min(maxPoolSize - pool.Count, count);

  for (int i = 0; i < count; i++) {
    Slot slot = delayObjectCreation ? new Slot(objectFactory) :
      new Slot(objectFactory.CreateObject( ));
    pool.Add(slot);
  }

  if (poolSizeCounter != null)
    poolSizeCounter.RawValue = pool.Count;
}
// ...

In the ShrinkPoolBy method:

// ...
if (poolSizeCounter != null)
  poolSizeCounter.RawValue = pool.Count;
// ...

… and similar code for the other counter in the GetObject and ReleaseObject methods.

Sorry, this blog does not support comments.

I used various blog hosting services since this blog was established in 2005, but unfortunately they turned out to be unreliable in the long term and comment threads were lost in unavoidable transitions. At this time I don't want to enable third-party services for comments since it has become obvious in recent years that these providers invariably monetize information about their visitors and users.

Please use the links in the page footer to get in touch with me. I'm available for conversations on Keybase, Matrix, Mastodon or Twitter, as well as via email.