Performance counters in C#

Door GrimaceODespair op maandag 14 oktober 2013 02:14 - Reacties (6)
CategorieŽn: C#, Windows, Views: 4.235

Windows performance counters zijn een (misschien vooral door mezelf) erg misbegrepen topic en goede, begrijpbare codevoorbeelden zijn moeilijk te vinden. Daarom deze kleine utility class, waarmee het mogelijk is op een intuÔtieve manier enkele standaardmetingen te verrichten.

De class kan na creatie als volgt aangeroepen worden:

code:
1
2
3
4
using (perfCounters.Monitor())
{
  // do some task
}



Van de code binnen de using wordt dan automatisch bijgehouden hoe vaak ze wordt aangeroepen, hoe vaak dat gebeurt per seconde en hoe lang de code gemiddeld duurt.


In de Prestatiemeter ziet dat er dan zo uit:

Performance Counters

En tenslotte de class zelf. Ik claim geen flexibiliteit of uitbreidbaarheid, enkel hersenloos copy-pastewerk voor eenvoudige metingen:


code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class PerformanceCounters
  {
    public string Category;
    public PerformanceCounter NumberOfOperations { get; private set; }
    public PerformanceCounter OperationsPerSecond { get; private set; }
    public PerformanceCounter AveragePerOperation { get; private set; }
    public PerformanceCounter AveragePerOperationBase { get; private set; }

    public PerformanceCounters(string category = "PerfCountTester", bool create = false)
    {
      Category = category;
      Initialize(create);
    }

    public void Initialize(bool create)
    {
      var counters = new CounterCreationDataCollection(
        new[]
          {
            new CounterCreationData("# operations executed", "Total number of operations executed",
                                    PerformanceCounterType.NumberOfItems32),
            new CounterCreationData("# operations / sec", "Number of operations executed per second",
                                    PerformanceCounterType.RateOfCountsPerSecond32),
            new CounterCreationData("average time per operation", "Average duration per operation execution",
                                    PerformanceCounterType.AverageTimer32),
            new CounterCreationData("average time per operation base", "Average duration per operation execution base",
                                    PerformanceCounterType.AverageBase),
          });

      if (create && PerformanceCounterCategory.Exists(Category) == false)
      {
        PerformanceCounterCategory.Create(Category, "Performance Counter Tester in C#",
                                          PerformanceCounterCategoryType.MultiInstance, counters);
      }

      var processName = Process.GetCurrentProcess().ProcessName;

      NumberOfOperations = new PerformanceCounter(Category, counters[0].CounterName, processName, false);
      OperationsPerSecond = new PerformanceCounter(Category, counters[1].CounterName, processName, false);
      AveragePerOperation = new PerformanceCounter(Category, counters[2].CounterName, processName, false);
      AveragePerOperationBase = new PerformanceCounter(Category, counters[3].CounterName, processName, false);
    }

    public IDisposable Monitor()
    {
      return new Timer(this);
    }

    private class Timer : IDisposable
    {
      private readonly PerformanceCounters _counters;
      private readonly Stopwatch _stopwatch = new Stopwatch();

      public Timer(PerformanceCounters counters)
      {
        _counters = counters;
        _stopwatch.Start();
      }

      public void Dispose()
      {
        _stopwatch.Stop();

        var value = _stopwatch.Elapsed.Ticks * Stopwatch.Frequency / TimeSpan.TicksPerSecond;
        _counters.AveragePerOperation.IncrementBy(value);
        _counters.AveragePerOperationBase.Increment();
        _counters.NumberOfOperations.Increment();
        _counters.OperationsPerSecond.Increment();
      }
    }
  }

Volgende: In-band bulk import met SqlServer 12-'13 In-band bulk import met SqlServer
Volgende: KopiŽren met checksums onder Windows 10-'13 KopiŽren met checksums onder Windows

Reacties


Door Tweakers user RobIII, dinsdag 15 oktober 2013 02:27

Ik wil toch graag iedereen die dit leest even adviseren MSDN er op na te slaan danwel dit of dit artikel (e.v.a.) door te nemen. Er komt nogal wat kijken bij 't (correct) gebruik van perfcounters wat in dit artikel allemaal niet genoemd wordt. Verder vind ik 't gebruik van IDisposable erg twijfelachtig hier; je wil toch niet per "operation" een nieuwe instance hebben en die daarna weer disposen enkel om een Increment() voor elkaar te krijgen? Ook geloof ik niet dat metingen heel erg betrouwbaar zullen zijn als je per meting een nieuwe stopwatch instantieert i.t.t. een die "doorloopt" voor langere duur dan een "operatie". IDisposable wordt hier enkel gebruikt zodat je 't in een using... statement kunt gooien, maar er wordt helemaal niets gedisposed.

[Reactie gewijzigd op dinsdag 15 oktober 2013 02:36]


Door Tweakers user GrimaceODespair, dinsdag 15 oktober 2013 05:58

Volgens mij is het lijstje artikels dat je geeft precies de volgorde waarin ik ze heb geraadpleegd (alleen was ik met Google begonnen) :) Ik ga akkoord dat een gedegen begrip van performance counters wellicht leidt tot beter gebruik, maar de toepassing die ik voor ogen had was niet de monitoring van een kerncentrale, maar eerder een grove, quick and niet tť dirty oplossing voor enkele simpele metingen van repetitieve taken.

Het ging in mijn geval ook over taken die makkelijk een seconde duren. Misschien moet dat er ergens bij in een disclaimer. Het heeft inderdaad weinig zin operaties te timen die evenlang duren als het aanmaken en vernietigen van een paar objecten.

Over het gebruik van IDisposable als guard kunnen we nog wel een paar uur bakkeleien. Ik wou hier nog een opmerking over C++ maken en hoe het daar uit den boze is om scoping met destructors te doen, maar ik vraag me plots af hoe verschillend dat eigenlijk is van wat ik doe (behalve dat ik meer controle heb over de scope). edit: ik zie nu pas dat de poster van SO jusit vanuit dat standpunt vertrok :D

En toegegeven, het gebruik van Timers zou ook efficiŽnter kunnen, bijvoorbeeld via een ThreadLocal.

Verder is het niet zo dat ik voor de sake of simplicity graag anti-patronen introduceer, integendeel. Ik probeer net het evenwicht te vinden tussen heldere code en correcte oplossingen. Het helpt dat daarbij dat de code via een blog meteen aan "public scrutiny" onderworpen is, dus in ieder geval bedankt voor je commentaar ;)

[Reactie gewijzigd op dinsdag 15 oktober 2013 06:04]


Door Tweakers user Schnoop, dinsdag 15 oktober 2013 09:44

Het kan aan mij liggen maar wat is precies het voordeel van deze eigen implementatie tenopzichte van de standaard performance profiling tools van Vsual Studio?

Door Tweakers user Jan Jansen, dinsdag 15 oktober 2013 12:03

Kun je dit ook gebruiken om b.v. een addin / plugin te maken waarbij je bepaalde sql performance counters kunt tonen in een erp paket of website?

Door Tweakers user RobIII, dinsdag 15 oktober 2013 13:53

Nee, daarvoor moet je performance counters uitlezen i.p.v. ze aanmaken / ernaar schrijven ;)

Door Tweakers user GrimaceODespair, dinsdag 15 oktober 2013 21:08

Schnoop schreef op dinsdag 15 oktober 2013 @ 09:44:
Het kan aan mij liggen maar wat is precies het voordeel van deze eigen implementatie tenopzichte van de standaard performance profiling tools van Vsual Studio?
Ik implementeer geen tool, maar genereer de data die je met de tool kunt analyseren.

Reageren is niet meer mogelijk