Monitor website performance in IIS with Zabbix

Zabbix is a great tool to monitor your website performance on IIS using Performance Counters, PowerShell and WMI.
Published on Monday, 29 January 2024

Official Zabbix Logo

Monitor your website performance on IIS with Zabbix, Performance Counters, PowerShell and WMI, because it is important to keep an eye on website and webserver performance. For this, Zabbix is a great tool and knowing how to collect metrics using Performance Counters, PowerShell and WMI is readily in your toolbox.


I've written about Zabbix monitoring before: Use Zabbix to monitor your .NET CLR Garbage Collected heap, and Getting more out your Windows Performance Counters monitoring for web applications - for example. This time you're using almost the same techniques to monitor a website hosted in IIS. Sweet, right? For this you use Win32_PerfRawData_W3SVC_WebService class.

The Web Service object includes counters specific to the World Wide Web Publishing Service.

There are roughly 95 properties in the Win32_PerfRawData_W3SVC_WebService class. A lot are interesting, and a lot are only interesting if you're using them. For instance, I wouldn't want to know PutRequestsPersec and TotalPutRequests if I'm not using the HTTP PUT verb on my site or server. In one situation, these are my preferred items to monitor (metrics to collect):

  • Name
  • AnonymousUsersPersec
  • BytesReceivedPersec
  • BytesSentPersec
  • BytesTotalPersec
  • ConnectionAttemptsPersec
  • CurrentAnonymousUsers
  • CurrentConnections
  • DeleteRequestsPersec
  • FilesPersec
  • FilesReceivedPersec
  • FilesSentPersec
  • GetRequestsPersec
  • LockedErrorsPersec
  • LockRequestsPersec
  • LogonAttemptsPersec
  • MaximumConnections
  • NonAnonymousUsersPersec
  • NotFoundErrorsPersec
  • OptionsRequestsPersec
  • OtherRequestMethodsPersec
  • PostRequestsPersec
  • PropfindRequestsPersec
  • PutRequestsPersec
  • ServiceUptime
  • TotalAnonymousUsers
  • TotalBytesReceived
  • TotalBytesSent
  • TotalBytesTransferred
  • TotalDeleteRequests
  • TotalFilesReceived
  • TotalFilesSent
  • TotalFilesTransferred
  • TotalGetRequests
  • TotalLockedErrors
  • TotalLockRequests
  • TotalLogonAttempts
  • TotalMethodRequests
  • TotalMethodRequestsPersec
  • TotalNonAnonymousUsers
  • TotalNotFoundErrors
  • TotalOptionsRequests
  • TotalPostRequests
  • TotalPropfindRequests
  • TotalPutRequests
  • TotalUnlockRequests
  • UnlockRequestsPersec

Yes this is a lot, but you can always remove items if they don't prove valuable.

Psst, here is how to get the number of current connections in IIS using PowerShell:

Use PowerShell, Performance Counters and WMI to get the current number of active connections to IIS websites. Perfect for monitoring IIS webservers in Zabbix.

The following PowerShell script is what puts everything together and returns the info in a JSON. You run this through Zabbix Agent (2) as a UserParameter.

param (
  [Parameter(Position=0, Mandatory=$False)]
  [string] $action = "discovery"
)

# Get all website names
$allwebsites = (Get-CimInstance -EA SilentlyContinue -Query "select * from Win32_PerfRawData_W3SVC_WebService" -Namespace root\cimv2).Name

# Get all Win32_PerfRawData_W3SVC_WebService properties for all websites
$allwebsiteinfo = Get-CimInstance -EA SilentlyContinue -Query "select * from Win32_PerfRawData_W3SVC_WebService" -Namespace root\cimv2 | Select-Object Name,AnonymousUsersPersec,BytesReceivedPersec,BytesSentPersec,BytesTotalPersec,ConnectionAttemptsPersec,CurrentAnonymousUsers,CurrentConnections,DeleteRequestsPersec,FilesPersec,FilesReceivedPersec,FilesSentPersec,GetRequestsPersec,LockedErrorsPersec,LockRequestsPersec,LogonAttemptsPersec,MaximumConnections,NonAnonymousUsersPersec,NotFoundErrorsPersec,OptionsRequestsPersec,OtherRequestMethodsPersec,PostRequestsPersec,PropfindRequestsPersec,PutRequestsPersec,ServiceUptime,TotalAnonymousUsers,TotalBytesReceived,TotalBytesSent,TotalBytesTransferred,TotalDeleteRequests,TotalFilesReceived,TotalFilesSent,TotalFilesTransferred,TotalGetRequests,TotalLockedErrors,TotalLockRequests,TotalLogonAttempts,TotalMethodRequests,TotalMethodRequestsPersec,TotalNonAnonymousUsers,TotalNotFoundErrors,TotalOptionsRequests,TotalPostRequests,TotalPropfindRequests,TotalPutRequests,TotalUnlockRequests,UnlockRequestsPersec 

# Master function that returns a hashtable containing metrics per website
function get-WebsiteInfo($website) {
  Try {
    $data = ($allwebsiteinfo | Where-Object Name -eq "${website}")
    $hashtable = @{
      Name = $data.Name
      AnonymousUsersPersec = $data.AnonymousUsersPersec
      BytesReceivedPersec = $data.BytesReceivedPersec
      BytesSentPersec = $data.BytesSentPersec
      BytesTotalPersec = $data.BytesTotalPersec
      ConnectionAttemptsPersec = $data.ConnectionAttemptsPersec
      CurrentAnonymousUsers = $data.CurrentAnonymousUsers
      CurrentConnections = $data.CurrentConnections
      DeleteRequestsPersec = $data.DeleteRequestsPersec
      FilesPersec = $data.FilesPersec
      FilesReceivedPersec = $data.FilesReceivedPersec
      FilesSentPersec = $data.FilesSentPersec
      GetRequestsPersec = $data.GetRequestsPersec
      LockedErrorsPersec = $data.LockedErrorsPersec
      LockRequestsPersec = $data.LockRequestsPersec
      LogonAttemptsPersec = $data.LogonAttemptsPersec
      MaximumConnections = $data.MaximumConnections
      NonAnonymousUsersPersec = $data.NonAnonymousUsersPersec
      NotFoundErrorsPersec = $data.NotFoundErrorsPersec
      OptionsRequestsPersec = $data.OptionsRequestsPersec
      OtherRequestMethodsPersec = $data.OtherRequestMethodsPersec
      PostRequestsPersec = $data.PostRequestsPersec
      PropfindRequestsPersec = $data.PropfindRequestsPersec
      PutRequestsPersec = $data.PutRequestsPersec
      ServiceUptime = $data.ServiceUptime
      TotalAnonymousUsers = $data.TotalAnonymousUsers
      TotalBytesReceived = $data.TotalBytesReceived
      TotalBytesSent = $data.TotalBytesSent
      TotalBytesTransferred =$data.TotalBytesTransferred
      TotalDeleteRequests = $data.TotalDeleteRequests
      TotalFilesReceived = $data.TotalFilesReceived
      TotalFilesSent = $data.TotalFilesSent
      TotalFilesTransferred = $data.TotalFilesTransferred
      TotalGetRequests = $data.TotalGetRequests
      TotalLockedErrors = $data.TotalLockedErrors
      TotalLockRequests = $data.TotalLockRequests
      TotalLogonAttempts = $data.TotalLogonAttempts
      TotalMethodRequests = $data.TotalMethodRequests
      TotalMethodRequestsPersec = $data.TotalMethodRequestsPersec
      TotalNonAnonymousUsers = $data.TotalNonAnonymousUsers
      TotalNotFoundErrors = $data.TotalNotFoundErrors
      TotalOptionsRequests = $data.TotalOptionsRequests
      TotalPostRequests = $data.TotalPostRequests
      TotalPropfindRequests = $data.TotalPropfindRequests
      TotalPutRequests = $data.TotalPutRequests
      TotalUnlockRequests = $data.TotalUnlockRequests
      UnlockRequestsPersec = $data.UnlockRequestsPersec
    }

    @($hashtable.keys) | % {
      if (-not $hashtable[$_]) {
        $hashtable.Remove($_)
      }
    }
    return $hashtable
  }
  Catch {
  }
}

switch ($action) {
  "discovery" {
    @{  
      "data" = $allwebsites | foreach { @{
        "{#WEBSITE}" = $_
      }}
    } | ConvertTo-Json
  }
  "getwebsiteinfo" {
    # Loop over all website names, store the function result in an array
    $WebsiteInfo = @{}
    $allwebsites | foreach {
      $WebsiteInfo[$_] = Get-WebsiteInfo $_
    }
    # Convert the array to JSON
    $WebsiteInfo | ConvertTo-Json

  }
  default {
      "Script error"
 }
}

When ran, you can expect the following JSON output to use in your Zabbix template:

 "example.com":  {
    "TotalNotFoundErrors":  23,
    "MaximumConnections":  12,
    "LogonAttemptsPersec":  221,
    "TotalNonAnonymousUsers":  120,
    "TotalMethodRequests":  222,
    "TotalFilesTransferred":  27,
    "BytesReceivedPersec":  196478,
    "ServiceUptime":  2993992,
    "TotalLogonAttempts":  221,
    "BytesTotalPersec":  1825488,
    "PostRequestsPersec":  4,
    "TotalGetRequests":  212,
    "FilesPersec":  27,
    "BytesSentPersec":  1629010,
    "TotalPostRequests":  4,
    "NonAnonymousUsersPersec":  120,
    "NotFoundErrorsPersec":  23,
    "Name":  "example.com",
    "TotalBytesTransferred":  1825488,
    "TotalMethodRequestsPersec":  222,
    "TotalFilesSent":  27,
    "AnonymousUsersPersec":  94,
    "FilesSentPersec":  27,
    "GetRequestsPersec":  212,
    "TotalBytesSent":  1629010,
    "TotalBytesReceived":  196478,
    "TotalAnonymousUsers":  94,
    "ConnectionAttemptsPersec":  219
  },

  "example.org":  {
    "TotalNotFoundErrors":  25,
    "MaximumConnections":  8,
    "OtherRequestMethodsPersec":  1,
    "LogonAttemptsPersec":  375,
    "TotalNonAnonymousUsers":  58,
    "TotalMethodRequests":  378,
    "TotalFilesTransferred":  33,
    "BytesReceivedPersec":  133698,
    "ServiceUptime":  2993992,
    "TotalLogonAttempts":  375,
    "BytesTotalPersec":  1339416,
    "PostRequestsPersec":  28,
    "TotalGetRequests":  339,
    "FilesPersec":  33,
    "BytesSentPersec":  1205718,
    "TotalPostRequests":  28,
    "NonAnonymousUsersPersec":  58,
    "NotFoundErrorsPersec":  25,
    "Name":  "example.org",
    "TotalBytesTransferred":  1339416,
    "TotalMethodRequestsPersec":  378,
    "TotalFilesSent":  33,
    "AnonymousUsersPersec":  192,
    "FilesSentPersec":  33,
    "GetRequestsPersec":  339,
    "TotalBytesSent":  1205718,
    "TotalBytesReceived":  133698,
    "TotalAnonymousUsers":  192,
    "ConnectionAttemptsPersec":  358
  }

Save the above code into a file (Get-WebsiteMetrics.ps1 for example) and configure it in your Zabbix configuration as a UserParameter, along with a parent fetcher:

UserParameter=Fetch,powershell -NoProfile -ExecutionPolicy Bypass -File C:\path\to\Get-WebsiteMetrics.ps1 discovery
UserParameter=w3wp.metric,powershell -NoProfile -ExecutionPolicy Bypass -File C:\path\to\Get-WebsiteMetrics.ps1 getwebsiteinfo

This way you can create an autodiscover template with dependent items:

<items>
  <item>
    <name>NewFetcher</name>
    <key>Fetch</key>
    <history>0</history>
    <trends>0</trends>
    <value_type>TEXT</value_type>
  </item>

<!-- ... -->

</items>
<discovery_rules>
  <discovery_rule>
    <name>Website Insights</name>
    <key>w3wp.discovery</key>
    <delay>1m</delay>
    <item_prototypes>
      <item_prototype>
        <name>Website: {#WEBSITE} AnonymousUsersPersec</name>
        <type>DEPENDENT</type>
        <key>w3wp.metric.AnonymousUsersPersec[{#WEBSITE}]</key>
        <delay>0</delay>
        <history>7d</history>
        <description>The rate users are making anonymous connections to the Web service.</description>
        <application_prototypes>
          <application_prototype>
            <name>website: {#WEBSITE}</name>
          </application_prototype>
        </application_prototypes>
        <preprocessing>
          <step>
            <type>JSONPATH</type>
            <params>$.AnonymousUsersPersec</params>
          </step>
          <step>
            <type>CHANGE_PER_SECOND</type>
            <params/>
          </step>
        </preprocessing>
        <master_item>
          <key>newfetch[{#WEBSITE}]</key>
        </master_item>
      </item_prototype>

      <!-- ... -->

  </discovery_rule>
</discovery_rules>

If you liked this post then be sure to check out my other Zabbix posts @ Sysadmins of the North: