ZABBIX FEATURE REQUESTS
  1. ZABBIX FEATURE REQUESTS
  2. ZBXNEXT-2247

Add discovery of Performance Counter instances on Windows Agent

    Details

    • Type: New Feature Request New Feature Request
    • Status: Closed
    • Priority: Minor Minor
    • Resolution: Duplicate
    • Affects Version/s: 2.2.1
    • Fix Version/s: None
    • Component/s: Agent (G)

      Description

      I'd like to be able to discover Performance Counter instance natively via the Windows Agent. This way Discovery Rules can be created for the automated monitoring of Multi-Instance Performance Counters such as Process, PhysicalDisk, LogicalDisk, Paging File, etc.

      I initially used a PowerShell script to discover Perf Counter instances but realized most of the work is already done in the Agent source and is a much preferred method.

      The following code has worked nicely for me (Agent sources v2.2.1)

      <moved to comments below>

        Issue Links

          Activity

          Hide
          Ryan Armstrong added a comment -

          To use the discovery rule, use key 'perf_counter.discovery[<Category>]'. This will return '

          {#INSTANCE}' names which you then use in Discovery Rule Item Prototypes like 'perf_counter[<Category>({#INSTANCE}

          )\CounterName]'

          Apologies for using the incorrect MarkDown in the initial request. I can't seem to edit it. Moderators, please feel free to edit at your leisure.

          Show
          Ryan Armstrong added a comment - To use the discovery rule, use key 'perf_counter.discovery [<Category>] '. This will return ' {#INSTANCE}' names which you then use in Discovery Rule Item Prototypes like 'perf_counter[<Category>({#INSTANCE} )\CounterName]' Apologies for using the incorrect MarkDown in the initial request. I can't seem to edit it. Moderators, please feel free to edit at your leisure.
          Hide
          Ryan Armstrong added a comment -

          Here's the code in a readable fashion:

          include/sysinfo.h[244]
          int	PERF_COUNTER_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result); 
          
          src/libs/zbxsysinfo/win32/win32.c[58]
          	{"perf_counter.discovery",	CF_HAVEPARAMS,	PERF_COUNTER_DISCOVERY,	 "Processor"}, 
          
          src/libs/zbxsysinfo/win32/pdhmon.c[25]
          #include "zbxjson.h" 
          
          src/libs/zbxsysinfo/win32/pdhmon.c[149]
          int PERF_COUNTER_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result)
          {
          	const char			*__function_name = "PERF_COUNTER_DISCOVERY";
          	char				counterPath[PDH_MAX_COUNTER_PATH], *tmp;
          	LPWSTR				wcounterPath;
          
          	int					ret = SYSINFO_RET_FAIL;
          	struct zbx_json		j;
          	
          	PDH_STATUS			status;
          	LPTSTR				mszCounterList = NULL;	
          	DWORD				pcchCounterListLength = 0;
          	LPTSTR				mszInstanceList = NULL;
          	DWORD				pcchInstanceListLength = 0;
          	LPWSTR				nameBuffer = NULL;
          	char				*name = NULL;
          	
          	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
          	
          	// Get requested counter path
          	if (1 != request->nparam)
          		goto clean;
          
          	tmp = get_rparam(request, 0);
          	if (NULL == tmp || '\0' == *tmp)
          		goto clean;
          		
          	// Validate counter path
          	strscpy(counterPath, tmp);
          	wcounterPath = zbx_utf8_to_unicode(counterPath);
          	
          	// Get required buffer sizes
          	status = PdhEnumObjectItems(
          		NULL,						// _In_     LPCTSTR szDataSource
          		NULL,						// _In_     LPCTSTR szMachineName
          		wcounterPath,				// _In_     LPCTSTR szObjectName
          		NULL,						// _Out_    LPTSTR mszCounterList
          		&pcchCounterListLength,		// _Inout_  LPDWORD pcchCounterListLength
          		NULL,						// _Out_    LPTSTR mszInstanceList
          		&pcchInstanceListLength,	// _Inout_  LPDWORD pcchInstanceListLength
          		PERF_DETAIL_WIZARD,			// _In_     DWORD dwDetailLevel
          		0							// DWORD dwFlags
          	);
          	
          	if(status != PDH_MORE_DATA) {
          		zabbix_log(LOG_LEVEL_ERR, "cannot get required buffer size for counter path '%s': %s",
          		counterPath, strerror_from_module(status, L"PDH.DLL"));
          		goto clean;
          	}
          	
          	if(0 == pcchInstanceListLength) {
          		zabbix_log(LOG_LEVEL_ERR, "counter object '%s' does not support multiple instances.", counterPath);
          		goto clean;
          	}
          	
          	// Initialize buffers
          	mszCounterList = (LPTSTR) zbx_malloc(mszCounterList, sizeof(TCHAR) * pcchCounterListLength);
          	mszInstanceList = (LPTSTR) zbx_malloc(mszInstanceList, sizeof(TCHAR) * pcchInstanceListLength);
          	
          	// Get counter and instance names
          	status = PdhEnumObjectItems(
          		NULL,						// _In_     LPCTSTR szDataSource
          		NULL,						// _In_     LPCTSTR szMachineName
          		wcounterPath,				// _In_     LPCTSTR szObjectName
          		mszCounterList,				// _Out_    LPTSTR mszCounterList
          		&pcchCounterListLength,		// _Inout_  LPDWORD pcchCounterListLength
          		mszInstanceList,			// _Out_    LPTSTR mszInstanceList
          		&pcchInstanceListLength,	// _Inout_  LPDWORD pcchInstanceListLength
          		PERF_DETAIL_WIZARD,			// _In_     DWORD dwDetailLevel
          		0							// DWORD dwFlags
          	);
          	
          	if(status != ERROR_SUCCESS) {
          		zabbix_log(LOG_LEVEL_ERR, "cannot get instance and counter names for '%s': %s",
          		counterPath, strerror_from_module(status, L"PDH.DLL"));
          		goto clean;
          	}
          	
          	// Create JSON Output
          	zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN);
          	zbx_json_addarray(&j, ZBX_PROTO_TAG_DATA);
          	
          	for(nameBuffer = mszInstanceList; *nameBuffer != 0; nameBuffer += wcslen(nameBuffer) + 1) {
          		name = zbx_unicode_to_utf8(nameBuffer);
          	
          		zbx_json_addobject(&j, NULL);
          		zbx_json_addstring(&j, "{#INSTANCE}", name, ZBX_JSON_TYPE_STRING);
          		zbx_json_close(&j);
          		
          		zbx_free(name);
          	}
          		
          	zbx_json_close(&j);
          
          	// Result
          	SET_STR_RESULT(result, strdup(j.buffer));
          	ret = SYSINFO_RET_OK;
          	
          	// Clean up
          	zbx_json_free(&j);
          	zbx_free(wcounterPath);
          	zbx_free(mszCounterList);
          	zbx_free(mszInstanceList);
          	
          clean:
          	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
          	
          	return ret;	
          }
          
          Show
          Ryan Armstrong added a comment - Here's the code in a readable fashion: include/sysinfo.h [244] int PERF_COUNTER_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result); src/libs/zbxsysinfo/win32/win32.c [58] { "perf_counter.discovery" , CF_HAVEPARAMS, PERF_COUNTER_DISCOVERY, "Processor" }, src/libs/zbxsysinfo/win32/pdhmon.c [25] #include "zbxjson.h" src/libs/zbxsysinfo/win32/pdhmon.c [149] int PERF_COUNTER_DISCOVERY(AGENT_REQUEST *request, AGENT_RESULT *result) { const char *__function_name = "PERF_COUNTER_DISCOVERY" ; char counterPath[PDH_MAX_COUNTER_PATH], *tmp; LPWSTR wcounterPath; int ret = SYSINFO_RET_FAIL; struct zbx_json j; PDH_STATUS status; LPTSTR mszCounterList = NULL; DWORD pcchCounterListLength = 0; LPTSTR mszInstanceList = NULL; DWORD pcchInstanceListLength = 0; LPWSTR nameBuffer = NULL; char *name = NULL; zabbix_log(LOG_LEVEL_DEBUG, "In %s()" , __function_name); // Get requested counter path if (1 != request->nparam) goto clean; tmp = get_rparam(request, 0); if (NULL == tmp || '\0' == *tmp) goto clean; // Validate counter path strscpy(counterPath, tmp); wcounterPath = zbx_utf8_to_unicode(counterPath); // Get required buffer sizes status = PdhEnumObjectItems( NULL, // _In_ LPCTSTR szDataSource NULL, // _In_ LPCTSTR szMachineName wcounterPath, // _In_ LPCTSTR szObjectName NULL, // _Out_ LPTSTR mszCounterList &pcchCounterListLength, // _Inout_ LPDWORD pcchCounterListLength NULL, // _Out_ LPTSTR mszInstanceList &pcchInstanceListLength, // _Inout_ LPDWORD pcchInstanceListLength PERF_DETAIL_WIZARD, // _In_ DWORD dwDetailLevel 0 // DWORD dwFlags ); if (status != PDH_MORE_DATA) { zabbix_log(LOG_LEVEL_ERR, "cannot get required buffer size for counter path '%s': %s" , counterPath, strerror_from_module(status, L "PDH.DLL" )); goto clean; } if (0 == pcchInstanceListLength) { zabbix_log(LOG_LEVEL_ERR, "counter object '%s' does not support multiple instances." , counterPath); goto clean; } // Initialize buffers mszCounterList = (LPTSTR) zbx_malloc(mszCounterList, sizeof(TCHAR) * pcchCounterListLength); mszInstanceList = (LPTSTR) zbx_malloc(mszInstanceList, sizeof(TCHAR) * pcchInstanceListLength); // Get counter and instance names status = PdhEnumObjectItems( NULL, // _In_ LPCTSTR szDataSource NULL, // _In_ LPCTSTR szMachineName wcounterPath, // _In_ LPCTSTR szObjectName mszCounterList, // _Out_ LPTSTR mszCounterList &pcchCounterListLength, // _Inout_ LPDWORD pcchCounterListLength mszInstanceList, // _Out_ LPTSTR mszInstanceList &pcchInstanceListLength, // _Inout_ LPDWORD pcchInstanceListLength PERF_DETAIL_WIZARD, // _In_ DWORD dwDetailLevel 0 // DWORD dwFlags ); if (status != ERROR_SUCCESS) { zabbix_log(LOG_LEVEL_ERR, "cannot get instance and counter names for '%s': %s" , counterPath, strerror_from_module(status, L "PDH.DLL" )); goto clean; } // Create JSON Output zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN); zbx_json_addarray(&j, ZBX_PROTO_TAG_DATA); for (nameBuffer = mszInstanceList; *nameBuffer != 0; nameBuffer += wcslen(nameBuffer) + 1) { name = zbx_unicode_to_utf8(nameBuffer); zbx_json_addobject(&j, NULL); zbx_json_addstring(&j, "{#INSTANCE}" , name, ZBX_JSON_TYPE_STRING); zbx_json_close(&j); zbx_free(name); } zbx_json_close(&j); // Result SET_STR_RESULT(result, strdup(j.buffer)); ret = SYSINFO_RET_OK; // Clean up zbx_json_free(&j); zbx_free(wcounterPath); zbx_free(mszCounterList); zbx_free(mszInstanceList); clean: zabbix_log(LOG_LEVEL_DEBUG, "End of %s()" , __function_name); return ret; }
          Hide
          Oleksiy Zagorskyi added a comment -

          Description truncated as requested by reporter.

          In any case it would be better to create a patch and attach it here.

          Show
          Oleksiy Zagorskyi added a comment - Description truncated as requested by reporter. In any case it would be better to create a patch and attach it here.
          Hide
          Oleksiy Zagorskyi added a comment -

          Closed as duplicate of ZBXNEXT-1839

          Show
          Oleksiy Zagorskyi added a comment - Closed as duplicate of ZBXNEXT-1839
          Hide
          jrouvier added a comment -

          Maybe some help for that, i didn't find anything online.
          A UserParameter to retreive all instances of a perf counter (could be certainly optimized )

          UserParameter=perf_counter.discovery[*],powershell -C "$coma='';write-output '{\"data\":[';typeperf -qx \"$1\" | where {$.StartsWith(\"$1\")} | select $ | %

          { $_ -replace '.*\(([^\)]+)\).*', '$$1' }

          | sort | get-unique | % { ($coma + '{\"

          {#INSTANCE}\":\"' + $_ + '\"}'); $coma=','; }; write-output \"]}\""

          Usage in discovery : perf_counter.discovery[\Process]
          And in item prototype : perf_counter[\Process({#INSTANCE}

          )\ID Process]

          Show
          jrouvier added a comment - Maybe some help for that, i didn't find anything online. A UserParameter to retreive all instances of a perf counter (could be certainly optimized ) UserParameter=perf_counter.discovery [*] ,powershell -C "$coma='';write-output '{\"data\":[';typeperf -qx \"$1\" | where {$ .StartsWith(\"$1\")} | select $ | % { $_ -replace '.*\(([^\)]+)\).*', '$$1' } | sort | get-unique | % { ($coma + '{\" {#INSTANCE}\":\"' + $_ + '\"}'); $coma=','; }; write-output \"]}\"" Usage in discovery : perf_counter.discovery [\Process] And in item prototype : perf_counter[\Process({#INSTANCE} )\ID Process]
          Hide
          Paolo Bozzini added a comment -

          Hi jrouvier,

          if i set your script in unserparameter i get a lot of syntax error, i need to LLD of all site in my windows' hosting infrastrucuture.

          Have you any updated powershell script?

          Thank you

          Show
          Paolo Bozzini added a comment - Hi jrouvier, if i set your script in unserparameter i get a lot of syntax error, i need to LLD of all site in my windows' hosting infrastrucuture. Have you any updated powershell script? Thank you
          Hide
          jrouvier added a comment - - edited

          Looks like a formatting problem, here is what I use every where:

          UserParameter=perf_counter.discovery[*],powershell -C "$coma='';write-output '{\"data\":['; (Get-Counter -Counter \"$1\").CounterSamples | ?{$_.InstanceName -match \"$2\"} | %{ try{ ($coma + '{\"{#$3INSTANCE}\":\"' + ($_.Path -replace '.*\(([^^\)]+)\).*','$$1') + '\",\"{#$3PID}\":' + $_.CookedValue + '}'); $coma=','; }catch{} } ; write-output \"]}\""
          

          Example of use:

          zabbix_get -s 10.0.0.1 -k 'perf_counter.discovery[\Process(*)\ID Process,myprocessname,VARPREFIX]'
          

          which return

          {"data":[
          {"{#VARPREFIXINSTANCE}":"myprocessname","{#VARPREFIXPID}":4936}
          ,{"{#VARPREFIXINSTANCE}":"myprocessname","{#VARPREFIXPID}":10485}
          ]}
          

          Just put ".*" in myprocessname if you want all instances

          Show
          jrouvier added a comment - - edited Looks like a formatting problem, here is what I use every where: UserParameter=perf_counter.discovery[*],powershell -C "$coma='';write-output '{\" data\ ":['; (Get-Counter -Counter \" $1\ ").CounterSamples | ?{$_.InstanceName -match \" $2\ "} | %{ try { ($coma + '{\" {#$3INSTANCE}\ ":\" ' + ($_.Path -replace '.*\(([^^\)]+)\).*','$$1') + '\ ",\" {#$3PID}\ ":' + $_.CookedValue + '}'); $coma=','; } catch {} } ; write-output \" ]}\"" Example of use: zabbix_get -s 10.0.0.1 -k 'perf_counter.discovery[\ Process (*)\ID Process ,myprocessname,VARPREFIX]' which return { "data" :[ { "{#VARPREFIXINSTANCE}" : "myprocessname" , "{#VARPREFIXPID}" :4936} ,{ "{#VARPREFIXINSTANCE}" : "myprocessname" , "{#VARPREFIXPID}" :10485} ]} Just put ".*" in myprocessname if you want all instances
          Hide
          Paolo Bozzini added a comment -

          Hi,

          if i use

          zabbix_get -s 1.1.1.1 -k 'perf_counter.discovery["\Web Service (.*)\Total Bytes Transferred"]'

          i get the following error:

          Get-Counter : The specified object was not found on the computer.

          but if i navigate to perfmon i can see the value.

          What's wrong?

          Thanks for the support

          Show
          Paolo Bozzini added a comment - Hi, if i use zabbix_get -s 1.1.1.1 -k 'perf_counter.discovery ["\Web Service (.*)\Total Bytes Transferred"] ' i get the following error: Get-Counter : The specified object was not found on the computer. but if i navigate to perfmon i can see the value. What's wrong? Thanks for the support
          Hide
          jrouvier added a comment -

          try:
          perf_counter.discovery[\Web Service(*)\Total Bytes Transferred,.*,WS]

          Show
          jrouvier added a comment - try: perf_counter.discovery [\Web Service(*)\Total Bytes Transferred,.*,WS]
          Hide
          Paolo Bozzini added a comment -

          Yess!!

          Now i must create all the item prototype and graphic.

          May i ask other support in case?

          Show
          Paolo Bozzini added a comment - Yess!! Now i must create all the item prototype and graphic. May i ask other support in case?
          Hide
          Paolo Bozzini added a comment -

          Is it possible only have the list of site without any other parameter?

          Your query give me only the information about that value for all web site but this operation can be done configuring the item prototype..

          i would like a query as:

          per_counter.discovery[\Web Service(*),.*,WS]

          Thanks

          Show
          Paolo Bozzini added a comment - Is it possible only have the list of site without any other parameter? Your query give me only the information about that value for all web site but this operation can be done configuring the item prototype.. i would like a query as: per_counter.discovery [\Web Service(*),.*,WS] Thanks

            People

            • Assignee:
              Unassigned
              Reporter:
              Ryan Armstrong
            • Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: