bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/scollector/collectors/dotnet_windows.go (about)

     1  package collectors
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"bosun.org/slog"
    10  
    11  	"bosun.org/cmd/scollector/conf"
    12  	"bosun.org/metadata"
    13  	"bosun.org/opentsdb"
    14  	"bosun.org/util"
    15  	"github.com/StackExchange/wmi"
    16  )
    17  
    18  var regexesDotNet = []*regexp.Regexp{}
    19  
    20  func init() {
    21  	AddProcessDotNetConfig = func(params conf.ProcessDotNet) error {
    22  		if params.Name == "" {
    23  			return fmt.Errorf("empty dotnet process name")
    24  		}
    25  		reg, err := regexp.Compile(params.Name)
    26  		if err != nil {
    27  			return err
    28  		}
    29  		regexesDotNet = append(regexesDotNet, reg)
    30  		return nil
    31  	}
    32  	WatchProcessesDotNet = func() {
    33  		if len(regexesDotNet) == 0 {
    34  			// If no process_dotnet settings configured in config file, use this set instead.
    35  			regexesDotNet = append(regexesDotNet, regexp.MustCompile("^w3wp"))
    36  		}
    37  
    38  		c := &IntervalCollector{
    39  			F: c_dotnet_loading,
    40  		}
    41  		c.init = wmiInit(c, func() interface{} {
    42  			return &[]Win32_PerfRawData_NETFramework_NETCLRLoading{}
    43  		}, "", &dotnetLoadingQuery)
    44  		collectors = append(collectors, c)
    45  
    46  		c = &IntervalCollector{
    47  			F: c_dotnet_memory,
    48  		}
    49  		c.init = wmiInit(c, func() interface{} {
    50  			return &[]Win32_PerfRawData_NETFramework_NETCLRMemory{}
    51  		}, `WHERE ProcessID <> 0`, &dotnetMemoryQuery)
    52  		collectors = append(collectors, c)
    53  
    54  		c = &IntervalCollector{
    55  			F: c_dotnet_sql,
    56  		}
    57  		c.init = wmiInit(c, func() interface{} {
    58  			return &[]Win32_PerfRawData_NETDataProviderforSqlServer_NETDataProviderforSqlServer{}
    59  		}, "", &dotnetSQLQuery)
    60  		collectors = append(collectors, c)
    61  	}
    62  }
    63  
    64  var (
    65  	dotnetLoadingQuery string
    66  	dotnetMemoryQuery  string
    67  	dotnetSQLQuery     string
    68  )
    69  
    70  func c_dotnet_loading() (opentsdb.MultiDataPoint, error) {
    71  	var dst []Win32_PerfRawData_NETFramework_NETCLRLoading
    72  	err := queryWmi(dotnetLoadingQuery, &dst)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	var md opentsdb.MultiDataPoint
    77  	for _, v := range dst {
    78  		if !util.NameMatches(v.Name, regexesDotNet) {
    79  			continue
    80  		}
    81  		id := "0"
    82  		raw_name := strings.Split(v.Name, "#")
    83  		name := raw_name[0]
    84  		if len(raw_name) == 2 {
    85  			id = raw_name[1]
    86  		}
    87  		// If you have a hash sign in your process name you don't deserve monitoring ;-)
    88  		if len(raw_name) > 2 {
    89  			continue
    90  		}
    91  		tags := opentsdb.TagSet{"name": name, "id": id}
    92  		Add(&md, "dotnet.current.appdomains", v.Currentappdomains, tags, metadata.Gauge, metadata.Count, descWinDotNetLoadingCurrentappdomains)
    93  		Add(&md, "dotnet.current.assemblies", v.CurrentAssemblies, tags, metadata.Gauge, metadata.Count, descWinDotNetLoadingCurrentAssemblies)
    94  		Add(&md, "dotnet.current.classes", v.CurrentClassesLoaded, tags, metadata.Gauge, metadata.Count, descWinDotNetLoadingCurrentClassesLoaded)
    95  		Add(&md, "dotnet.total.appdomains", v.TotalAppdomains, tags, metadata.Gauge, metadata.Count, descWinDotNetLoadingTotalAppdomains)
    96  		Add(&md, "dotnet.total.appdomains_unloaded", v.Totalappdomainsunloaded, tags, metadata.Gauge, metadata.Count, descWinDotNetLoadingTotalappdomainsunloaded)
    97  		Add(&md, "dotnet.total.assemblies", v.TotalAssemblies, tags, metadata.Gauge, metadata.Count, descWinDotNetLoadingTotalAssemblies)
    98  		Add(&md, "dotnet.total.classes", v.TotalClassesLoaded, tags, metadata.Gauge, metadata.Count, descWinDotNetLoadingTotalClassesLoaded)
    99  		Add(&md, "dotnet.total.load_failures", v.TotalNumberofLoadFailures, tags, metadata.Gauge, metadata.Count, descWinDotNetLoadingTotalNumberofLoadFailures)
   100  	}
   101  	return md, nil
   102  }
   103  
   104  const (
   105  	descWinDotNetLoadingCurrentappdomains         = "This counter displays the current number of AppDomains loaded in this application. AppDomains (application domains) provide a secure and versatile unit of processing that the CLR can use to provide isolation between applications running in the same process."
   106  	descWinDotNetLoadingCurrentAssemblies         = "This counter displays the current number of Assemblies loaded across all AppDomains in this application. If the Assembly is loaded as domain-neutral from multiple AppDomains then this counter is incremented once only. Assemblies can be loaded as domain-neutral when their code can be shared by all AppDomains or they can be loaded as domain-specific when their code is private to the AppDomain."
   107  	descWinDotNetLoadingCurrentClassesLoaded      = "This counter displays the current number of classes loaded in all Assemblies."
   108  	descWinDotNetLoadingTotalAppdomains           = "This counter displays the peak number of AppDomains loaded since the start of this application."
   109  	descWinDotNetLoadingTotalappdomainsunloaded   = "This counter displays the total number of AppDomains unloaded since the start of the application. If an AppDomain is loaded and unloaded multiple times this counter would count each of those unloads as separate."
   110  	descWinDotNetLoadingTotalAssemblies           = "This counter displays the total number of Assemblies loaded since the start of this application. If the Assembly is loaded as domain-neutral from multiple AppDomains then this counter is incremented once only."
   111  	descWinDotNetLoadingTotalClassesLoaded        = "This counter displays the cumulative number of classes loaded in all Assemblies since the start of this application."
   112  	descWinDotNetLoadingTotalNumberofLoadFailures = "This counter displays the peak number of classes that have failed to load since the start of the application. These load failures could be due to many reasons like inadequate security or illegal format."
   113  )
   114  
   115  type Win32_PerfRawData_NETFramework_NETCLRLoading struct {
   116  	Currentappdomains         uint32
   117  	CurrentAssemblies         uint32
   118  	CurrentClassesLoaded      uint32
   119  	Name                      string
   120  	TotalAppdomains           uint32
   121  	Totalappdomainsunloaded   uint32
   122  	TotalAssemblies           uint32
   123  	TotalClassesLoaded        uint32
   124  	TotalNumberofLoadFailures uint32
   125  }
   126  
   127  func c_dotnet_memory() (opentsdb.MultiDataPoint, error) {
   128  	var dst []Win32_PerfRawData_NETFramework_NETCLRMemory
   129  	err := queryWmi(dotnetMemoryQuery, &dst)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	var svc_dst []Win32_Service
   134  	var svc_q = wmi.CreateQuery(&svc_dst, `WHERE Started=true`)
   135  	err = queryWmi(svc_q, &svc_dst)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	var iis_dst []WorkerProcess
   140  	iis_q := wmi.CreateQuery(&iis_dst, "")
   141  	err = queryWmiNamespace(iis_q, &iis_dst, "root\\WebAdministration")
   142  	if err != nil {
   143  		iis_dst = nil
   144  	}
   145  	var md opentsdb.MultiDataPoint
   146  	for _, v := range dst {
   147  		var name string
   148  		service_match := false
   149  		iis_match := false
   150  		process_match := util.NameMatches(v.Name, regexesDotNet)
   151  		id := "0"
   152  		if process_match {
   153  			raw_name := strings.Split(v.Name, "#")
   154  			name = raw_name[0]
   155  			if len(raw_name) == 2 {
   156  				id = raw_name[1]
   157  			}
   158  			// If you have a hash sign in your process name you don't deserve monitoring ;-)
   159  			if len(raw_name) > 2 {
   160  				continue
   161  			}
   162  		}
   163  		// A Service match could "overwrite" a process match, but that is probably what we would want.
   164  		for _, svc := range svc_dst {
   165  			if util.NameMatches(svc.Name, regexesDotNet) {
   166  				// It is possible the pid has gone and been reused, but I think this unlikely
   167  				// and I'm not aware of an atomic join we could do anyways.
   168  				if svc.ProcessId != 0 && svc.ProcessId == v.ProcessID {
   169  					id = "0"
   170  					service_match = true
   171  					name = svc.Name
   172  					break
   173  				}
   174  			}
   175  		}
   176  		for _, a_pool := range iis_dst {
   177  			if a_pool.ProcessId == v.ProcessID {
   178  				id = "0"
   179  				iis_match = true
   180  				name = strings.Join([]string{"iis", a_pool.AppPoolName}, "_")
   181  				break
   182  			}
   183  		}
   184  		if !(service_match || process_match || iis_match) {
   185  			continue
   186  		}
   187  		tags := opentsdb.TagSet{"name": name, "id": id}
   188  		Add(&md, "dotnet.memory.finalization_survivors", v.FinalizationSurvivors, tags, metadata.Gauge, metadata.Count, descWinDotNetMemoryFinalizationSurvivors)
   189  		Add(&md, "dotnet.memory.gen0_promoted", v.Gen0PromotedBytesPerSec, tags, metadata.Counter, metadata.BytesPerSecond, descWinDotNetMemoryGen0PromotedBytesPerSec)
   190  		Add(&md, "dotnet.memory.gen0_promoted_finalized", v.PromotedFinalizationMemoryfromGen0, tags, metadata.Gauge, metadata.PerSecond, descWinDotNetMemoryPromotedFinalizationMemoryfromGen0)
   191  		Add(&md, "dotnet.memory.gen1_promoted", v.Gen1PromotedBytesPerSec, tags, metadata.Counter, metadata.BytesPerSecond, descWinDotNetMemoryGen1PromotedBytesPerSec)
   192  		Add(&md, "dotnet.memory.heap_allocations", v.AllocatedBytesPersec, tags, metadata.Counter, metadata.BytesPerSecond, descWinDotNetMemoryAllocatedBytesPersec)
   193  		Add(&md, "dotnet.memory.heap_size_gen0_max", v.Gen0heapsize, tags, metadata.Gauge, metadata.Bytes, descWinDotNetMemoryGen0heapsize)
   194  		Add(&md, "dotnet.memory.heap_size", v.Gen1heapsize, opentsdb.TagSet{"type": "gen1"}.Merge(tags), metadata.Gauge, metadata.Bytes, descWinDotNetMemoryGen1heapsize)
   195  		Add(&md, "dotnet.memory.heap_size", v.Gen2heapsize, opentsdb.TagSet{"type": "gen2"}.Merge(tags), metadata.Gauge, metadata.Bytes, descWinDotNetMemoryGen2heapsize)
   196  		Add(&md, "dotnet.memory.heap_size", v.LargeObjectHeapsize, opentsdb.TagSet{"type": "large_object"}.Merge(tags), metadata.Gauge, metadata.Bytes, descWinDotNetMemoryLargeObjectHeapsize)
   197  		Add(&md, "dotnet.memory.heap_size", v.NumberBytesinallHeaps, opentsdb.TagSet{"type": "total"}.Merge(tags), metadata.Gauge, metadata.Bytes, descWinDotNetMemoryNumberBytesinallHeaps)
   198  		Add(&md, "dotnet.memory.gc_handles", v.NumberGCHandles, tags, metadata.Gauge, metadata.Count, descWinDotNetMemoryNumberGCHandles)
   199  		Add(&md, "dotnet.memory.gc_collections", v.NumberGen0Collections, opentsdb.TagSet{"type": "gen0"}.Merge(tags), metadata.Counter, metadata.Count, descWinDotNetMemoryNumberGen0Collections)
   200  		Add(&md, "dotnet.memory.gc_collections", v.NumberGen1Collections, opentsdb.TagSet{"type": "gen1"}.Merge(tags), metadata.Counter, metadata.Count, descWinDotNetMemoryNumberGen1Collections)
   201  		Add(&md, "dotnet.memory.gc_collections", v.NumberGen2Collections, opentsdb.TagSet{"type": "gen2"}.Merge(tags), metadata.Counter, metadata.Count, descWinDotNetMemoryNumberGen2Collections)
   202  		Add(&md, "dotnet.memory.gc_collections", v.NumberInducedGC, opentsdb.TagSet{"type": "induced"}.Merge(tags), metadata.Counter, metadata.Count, descWinDotNetMemoryNumberInducedGC)
   203  		Add(&md, "dotnet.memory.pinned_objects", v.NumberofPinnedObjects, tags, metadata.Gauge, metadata.Count, descWinDotNetMemoryNumberofPinnedObjects)
   204  		Add(&md, "dotnet.memory.sink_blocks", v.NumberofSinkBlocksinuse, tags, metadata.Gauge, metadata.Count, descWinDotNetMemoryNumberofSinkBlocksinuse)
   205  		Add(&md, "dotnet.memory.virtual_committed", v.NumberTotalcommittedBytes, tags, metadata.Gauge, metadata.Bytes, descWinDotNetMemoryNumberTotalcommittedBytes)
   206  		Add(&md, "dotnet.memory.virtual_reserved", v.NumberTotalreservedBytes, tags, metadata.Gauge, metadata.Bytes, descWinDotNetMemoryNumberTotalreservedBytes)
   207  		if v.PercentTimeinGC_Base != 0 {
   208  			Add(&md, "dotnet.memory.gc_time", float64(v.PercentTimeinGC)/float64(v.PercentTimeinGC_Base)*100, tags, metadata.Gauge, metadata.Pct, descWinDotNetMemoryPercentTimeinGC)
   209  		}
   210  	}
   211  	return md, nil
   212  }
   213  
   214  const (
   215  	descWinDotNetMemoryAllocatedBytesPersec               = "This counter displays the rate of bytes per second allocated on the GC Heap. This counter is updated at the end of every GC; not at each allocation."
   216  	descWinDotNetMemoryFinalizationSurvivors              = "This counter displays the number of garbage collected objects that survive a collection because they are waiting to be finalized. If these objects hold references to other objects then those objects also survive but are not counted by this counter; This counter is not a cumulative counter; its updated at the end of every GC with count of the survivors during that particular GC only. This counter was designed to indicate the extra overhead that the application might incur because of finalization."
   217  	descWinDotNetMemoryGen0heapsize                       = "This counter displays the maximum bytes that can be allocated in generation 0 (Gen 0); its does not indicate the current number of bytes allocated in Gen 0. A Gen 0 GC is triggered when the allocations since the last GC exceed this size. The Gen 0 size is tuned by the Garbage Collector and can change during the execution of the application. At the end of a Gen 0 collection the size of the Gen 0 heap is infact 0 bytes; this counter displays the size (in bytes) of allocations that would trigger the next Gen 0 GC. This counter is updated at the end of a GC; its not updated on every allocation."
   218  	descWinDotNetMemoryGen0PromotedBytesPerSec            = "This counter displays the bytes per second that are promoted from generation 0 (youngest) to generation 1; objects that are promoted just because they are waiting to be finalized are not included in this counter. Memory is promoted when it survives a garbage collection. This counter was designed as an indicator of relatively long-lived objects being created per sec."
   219  	descWinDotNetMemoryGen1heapsize                       = "This counter displays the current number of bytes in generation 1 (Gen 1); this counter does not display the maximum size of Gen 1. Objects are not directly allocated in this generation; they are promoted from previous Gen 0 GCs. This counter is updated at the end of a GC; its not updated on every allocation."
   220  	descWinDotNetMemoryGen1PromotedBytesPerSec            = "This counter displays the bytes per second that are promoted from generation 1 to generation 2 (oldest); objects that are promoted just because they are waiting to be finalized are not included in this counter. Memory is promoted when it survives a garbage collection. Nothing is promoted from generation 2 since it is the oldest."
   221  	descWinDotNetMemoryGen2heapsize                       = "This counter displays the current number of bytes in generation 2 (Gen 2)."
   222  	descWinDotNetMemoryLargeObjectHeapsize                = "This counter displays the current size of the Large Object Heap in bytes. Objects greater than a threshold are treated as large objects by the Garbage Collector and are directly allocated in a special heap; they are not promoted through the generations. In CLR v1.1 and above this threshold is equal to 85000 bytes."
   223  	descWinDotNetMemoryNumberBytesinallHeaps              = "This counter is the sum of four other counters; Gen 0 Heap Size; Gen 1 Heap Size; Gen 2 Heap Size and the Large Object Heap Size. This counter indicates the current memory allocated in bytes on the GC Heaps."
   224  	descWinDotNetMemoryNumberGCHandles                    = "This counter displays the current number of GC Handles in use. GCHandles are handles to resources external to the CLR and the managed environment. Handles occupy small amounts of memory in the GCHeap but potentially expensive unmanaged resources."
   225  	descWinDotNetMemoryNumberGen0Collections              = "This counter displays the number of times the generation 0 objects (youngest; most recently allocated) are garbage collected (Gen 0 GC) since the start of the application. Gen 0 GC occurs when the available memory in generation 0 is not sufficient to satisfy an allocation request. This counter is incremented at the end of a Gen 0 GC. Higher generation GCs include all lower generation GCs. This counter is explicitly incremented when a higher generation (Gen 1 or Gen 2) GC occurs. _Global_ counter value is not accurate and should be ignored."
   226  	descWinDotNetMemoryNumberGen1Collections              = "This counter displays the number of times the generation 1 objects are garbage collected since the start of the application. The counter is incremented at the end of a Gen 1 GC. Higher generation GCs include all lower generation GCs. This counter is explicitly incremented when a higher generation (Gen 2) GC occurs. _Global_ counter value is not accurate and should be ignored."
   227  	descWinDotNetMemoryNumberGen2Collections              = "This counter displays the number of times the generation 2 objects (older) are garbage collected since the start of the application. The counter is incremented at the end of a Gen 2 GC (also called full GC). _Global_ counter value is not accurate and should be ignored."
   228  	descWinDotNetMemoryNumberInducedGC                    = "This counter displays the peak number of times a garbage collection was performed because of an explicit call to GC.Collect. Its a good practice to let the GC tune the frequency of its collections."
   229  	descWinDotNetMemoryNumberofPinnedObjects              = "This counter displays the number of pinned objects encountered in the last GC. This counter tracks the pinned objects only in the heaps that were garbage collected e.g. a Gen 0 GC would cause enumeration of pinned objects in the generation 0 heap only. A pinned object is one that the Garbage Collector cannot move in memory."
   230  	descWinDotNetMemoryNumberofSinkBlocksinuse            = "This counter displays the current number of sync blocks in use. Sync blocks are per-object data structures allocated for storing synchronization information. Sync blocks hold weak references to managed objects and need to be scanned by the Garbage Collector. Sync blocks are not limited to storing synchronization information and can also store COM interop metadata. This counter was designed to indicate performance problems with heavy use of synchronization primitives."
   231  	descWinDotNetMemoryNumberTotalcommittedBytes          = "This counter displays the amount of virtual memory (in bytes) currently committed by the Garbage Collector. Committed memory is the physical memory for which space has been reserved on the disk paging file."
   232  	descWinDotNetMemoryNumberTotalreservedBytes           = "This counter displays the amount of virtual memory (in bytes) currently reserved by the Garbage Collector. Reserved memory is the virtual memory space reserved for the application but no disk or main memory pages have been used."
   233  	descWinDotNetMemoryPercentTimeinGC                    = "Percent Time in GC is the percentage of elapsed time that was spent in performing a garbage collection (GC) since the last GC cycle. This counter is usually an indicator of the work done by the Garbage Collector on behalf of the application to collect and compact memory. This counter is updated only at the end of every GC and the counter value reflects the last observed value; its not an average."
   234  	descWinDotNetMemoryPromotedFinalizationMemoryfromGen0 = "This counter displays the bytes of memory that are promoted from generation 0 to generation 1 just because they are waiting to be finalized. This counter displays the value observed at the end of the last GC; its not a cumulative counter."
   235  )
   236  
   237  type Win32_PerfRawData_NETFramework_NETCLRMemory struct {
   238  	AllocatedBytesPersec               uint32
   239  	FinalizationSurvivors              uint32
   240  	Gen0heapsize                       uint32
   241  	Gen0PromotedBytesPerSec            uint32
   242  	Gen1heapsize                       uint32
   243  	Gen1PromotedBytesPerSec            uint32
   244  	Gen2heapsize                       uint32
   245  	LargeObjectHeapsize                uint32
   246  	Name                               string
   247  	NumberBytesinallHeaps              uint32
   248  	NumberGCHandles                    uint32
   249  	NumberGen0Collections              uint32
   250  	NumberGen1Collections              uint32
   251  	NumberGen2Collections              uint32
   252  	NumberInducedGC                    uint32
   253  	NumberofPinnedObjects              uint32
   254  	NumberofSinkBlocksinuse            uint32
   255  	NumberTotalcommittedBytes          uint32
   256  	NumberTotalreservedBytes           uint32
   257  	PercentTimeinGC                    uint32
   258  	PercentTimeinGC_Base               uint32
   259  	ProcessID                          uint32
   260  	PromotedFinalizationMemoryfromGen0 uint32
   261  }
   262  
   263  // The PID must be extracted from the Name field, and we get the name from the Pid
   264  var dotNetSQLPIDRegex = regexp.MustCompile(`.*\[(\d+)\]$`)
   265  
   266  func c_dotnet_sql() (opentsdb.MultiDataPoint, error) {
   267  	var dst []Win32_PerfRawData_NETDataProviderforSqlServer_NETDataProviderforSqlServer
   268  	err := queryWmi(dotnetSQLQuery, &dst)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	var svc_dst []Win32_Service
   273  	var svc_q = wmi.CreateQuery(&svc_dst, `WHERE Started=true`)
   274  	err = queryWmi(svc_q, &svc_dst)
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  	var iis_dst []WorkerProcess
   279  	iis_q := wmi.CreateQuery(&iis_dst, "")
   280  	err = queryWmiNamespace(iis_q, &iis_dst, "root\\WebAdministration")
   281  	if err != nil {
   282  		iis_dst = nil
   283  	}
   284  	var md opentsdb.MultiDataPoint
   285  
   286  	// We add the values of multiple pools that share a PID, this could cause some counter edge cases
   287  	// pidToRows is a map of pid to all the row indexes that share that IP
   288  	pidToRows := make(map[string][]int)
   289  	for i, v := range dst {
   290  		m := dotNetSQLPIDRegex.FindStringSubmatch(v.Name)
   291  		if len(m) != 2 {
   292  			slog.Errorf("unable to extract pid from dontnet SQL Provider for '%s'", v.Name)
   293  			continue
   294  		}
   295  		pidToRows[m[1]] = append(pidToRows[m[1]], i)
   296  	}
   297  	// skipIndex is used to skip entries in the metric loop that had their values added to an earlier entry
   298  	skipIndex := make(map[int]bool)
   299  	for _, rows := range pidToRows {
   300  		if len(rows) == 1 {
   301  			continue // Only one entry for this PID
   302  		}
   303  		// All fields that share a PID are summed to the first entry, other entries are marked
   304  		// for skipping in main metric loop
   305  		firstRow := &dst[rows[0]]
   306  		for _, idx := range rows[1:] {
   307  			skipIndex[idx] = true
   308  			nextRow := &dst[idx]
   309  			firstRow.HardDisconnectsPerSecond += nextRow.HardDisconnectsPerSecond
   310  			firstRow.NumberOfActiveConnectionPoolGroups += nextRow.NumberOfActiveConnectionPoolGroups
   311  			firstRow.NumberOfActiveConnectionPools += nextRow.NumberOfActiveConnectionPools
   312  			firstRow.NumberOfActiveConnections += nextRow.NumberOfActiveConnections
   313  			firstRow.NumberOfFreeConnections += nextRow.NumberOfFreeConnections
   314  			firstRow.NumberOfInactiveConnectionPoolGroups += nextRow.NumberOfInactiveConnectionPoolGroups
   315  			firstRow.NumberOfInactiveConnectionPools += nextRow.NumberOfInactiveConnectionPools
   316  			firstRow.NumberOfNonPooledConnections += nextRow.NumberOfNonPooledConnections
   317  			firstRow.NumberOfPooledConnections += nextRow.NumberOfPooledConnections
   318  			firstRow.NumberOfReclaimedConnections += nextRow.NumberOfReclaimedConnections
   319  			firstRow.NumberOfStasisConnections += nextRow.NumberOfStasisConnections
   320  			firstRow.SoftConnectsPerSecond += nextRow.SoftConnectsPerSecond
   321  			firstRow.SoftDisconnectsPerSecond += nextRow.SoftDisconnectsPerSecond
   322  		}
   323  	}
   324  
   325  	for i, v := range dst {
   326  		if skipIndex[i] {
   327  			// Skip entries that were summed into the first entry
   328  			continue
   329  		}
   330  		var name string
   331  		// Extract PID from the Name field, which is odd in this class
   332  		m := dotNetSQLPIDRegex.FindStringSubmatch(v.Name)
   333  		if len(m) != 2 {
   334  			// Error captured above
   335  			continue
   336  		}
   337  		pid, err := strconv.ParseUint(m[1], 10, 32)
   338  		if err != nil {
   339  			slog.Errorf("unable to parse extracted pid '%v' from '%v' into a unit32 in dotnet sql provider", m[1], v.Name)
   340  			continue
   341  		}
   342  		// We only look for Service and IIS matches in this collector
   343  		service_match := false
   344  		iis_match := false
   345  		// A Service match could "overwrite" a process match, but that is probably what we would want.
   346  		for _, svc := range svc_dst {
   347  			if util.NameMatches(svc.Name, regexesDotNet) {
   348  				// It is possible the pid has gone and been reused, but I think this unlikely
   349  				// and I'm not aware of an atomic join we could do anyways.
   350  				if svc.ProcessId != 0 && svc.ProcessId == uint32(pid) {
   351  					service_match = true
   352  					name = svc.Name
   353  					break
   354  				}
   355  			}
   356  		}
   357  		for _, a_pool := range iis_dst {
   358  			if a_pool.ProcessId == uint32(pid) {
   359  				iis_match = true
   360  				name = strings.Join([]string{"iis", a_pool.AppPoolName}, "_")
   361  				break
   362  			}
   363  		}
   364  		if !(service_match || iis_match) {
   365  			continue
   366  		}
   367  		tags := opentsdb.TagSet{"name": name, "id": "0"}
   368  		// Not a 100% on counter / gauge here, may see some wrong after collecting more data. PerSecond being a counter is expected
   369  		// however since this is a PerfRawData table
   370  		Add(&md, "dotnet.sql.hard_connects", v.HardConnectsPerSecond, tags, metadata.Counter, metadata.Connection, descWinDotNetSQLHardConnectsPerSecond)
   371  		Add(&md, "dotnet.sql.hard_disconnects", v.HardDisconnectsPerSecond, tags, metadata.Counter, metadata.Connection, descWinDotNetSQLHardDisconnectsPerSecond)
   372  
   373  		Add(&md, "dotnet.sql.soft_connects", v.SoftConnectsPerSecond, tags, metadata.Counter, metadata.Connection, descWinDotNetSQLSoftConnectsPerSecond)
   374  		Add(&md, "dotnet.sql.soft_disconnects", v.SoftDisconnectsPerSecond, tags, metadata.Counter, metadata.Connection, descWinDotNetSQLSoftDisconnectsPerSecond)
   375  
   376  		Add(&md, "dotnet.sql.active_conn_pool_groups", v.NumberOfActiveConnectionPoolGroups, tags, metadata.Gauge, metadata.Group, descWinDotNetSQLNumberOfActiveConnectionPoolGroups)
   377  		Add(&md, "dotnet.sql.inactive_conn_pool_groups", v.NumberOfInactiveConnectionPoolGroups, tags, metadata.Gauge, metadata.Group, descWinDotNetSQLNumberOfInactiveConnectionPoolGroups)
   378  
   379  		Add(&md, "dotnet.sql.active_conn_pools", v.NumberOfActiveConnectionPools, tags, metadata.Gauge, metadata.Pool, descWinDotNetSQLNumberOfActiveConnectionPools)
   380  		Add(&md, "dotnet.sql.inactive_conn_pools", v.NumberOfInactiveConnectionPools, tags, metadata.Gauge, metadata.Pool, descWinDotNetSQLNumberOfInactiveConnectionPools)
   381  
   382  		Add(&md, "dotnet.sql.active_connections", v.NumberOfActiveConnections, tags, metadata.Gauge, metadata.Connection, descWinDotNetSQLNumberOfActiveConnections)
   383  		Add(&md, "dotnet.sql.free_connections", v.NumberOfFreeConnections, tags, metadata.Gauge, metadata.Connection, descWinDotNetSQLNumberOfFreeConnections)
   384  		Add(&md, "dotnet.sql.non_pooled_connections", v.NumberOfNonPooledConnections, tags, metadata.Gauge, metadata.Connection, descWinDotNetSQLNumberOfNonPooledConnections)
   385  		Add(&md, "dotnet.sql.pooled_connections", v.NumberOfPooledConnections, tags, metadata.Gauge, metadata.Connection, descWinDotNetSQLNumberOfPooledConnections)
   386  		Add(&md, "dotnet.sql.reclaimed_connections", v.NumberOfReclaimedConnections, tags, metadata.Gauge, metadata.Connection, descWinDotNetSQLNumberOfReclaimedConnections)
   387  		Add(&md, "dotnet.sql.statis_connections", v.NumberOfStasisConnections, tags, metadata.Gauge, metadata.Connection, descWinDotNetSQLNumberOfStasisConnections)
   388  	}
   389  	return md, nil
   390  }
   391  
   392  const (
   393  	descWinDotNetSQLHardConnectsPerSecond                = "The number of actual connections per second that are being made to servers."
   394  	descWinDotNetSQLHardDisconnectsPerSecond             = "The number of actual disconnects per second that are being made to servers."
   395  	descWinDotNetSQLNumberOfActiveConnectionPoolGroups   = "The number of unique connection pool groups."
   396  	descWinDotNetSQLNumberOfActiveConnectionPools        = "The number of active connection pools."
   397  	descWinDotNetSQLNumberOfActiveConnections            = "The number of connections currently in-use."
   398  	descWinDotNetSQLNumberOfFreeConnections              = "The number of connections currently available for use."
   399  	descWinDotNetSQLNumberOfInactiveConnectionPoolGroups = "The number of unique pool groups waiting for pruning."
   400  	descWinDotNetSQLNumberOfInactiveConnectionPools      = "The number of inactive connection pools."
   401  	descWinDotNetSQLNumberOfNonPooledConnections         = "The number of connections that are not using connection pooling."
   402  	descWinDotNetSQLNumberOfPooledConnections            = "The number of connections that are managed by the connection pooler."
   403  	descWinDotNetSQLNumberOfReclaimedConnections         = "The number of connections reclaimed from GCed external connections."
   404  	descWinDotNetSQLNumberOfStasisConnections            = "The number of connections currently waiting to be made ready for use."
   405  	descWinDotNetSQLSoftConnectsPerSecond                = "The number of connections opened from the pool per second."
   406  	descWinDotNetSQLSoftDisconnectsPerSecond             = "The number of connections closed and returned to the pool per second."
   407  )
   408  
   409  // Win32_PerfRawData_NETDataProviderforSqlServer_NETDataProviderforSqlServer is actually a CIM_StatisticalInformation type
   410  type Win32_PerfRawData_NETDataProviderforSqlServer_NETDataProviderforSqlServer struct {
   411  	HardConnectsPerSecond                uint32
   412  	HardDisconnectsPerSecond             uint32
   413  	Name                                 string
   414  	NumberOfActiveConnectionPoolGroups   uint32
   415  	NumberOfActiveConnectionPools        uint32
   416  	NumberOfActiveConnections            uint32
   417  	NumberOfFreeConnections              uint32
   418  	NumberOfInactiveConnectionPoolGroups uint32
   419  	NumberOfInactiveConnectionPools      uint32
   420  	NumberOfNonPooledConnections         uint32
   421  	NumberOfPooledConnections            uint32
   422  	NumberOfReclaimedConnections         uint32
   423  	NumberOfStasisConnections            uint32
   424  	SoftConnectsPerSecond                uint32
   425  	SoftDisconnectsPerSecond             uint32
   426  }