From a7476491942a55a2c6a76e0ec39b4db848832a9e Mon Sep 17 00:00:00 2001
From: "th.l" <thl-cmk@outlook.com>
Date: Thu, 18 Apr 2024 19:21:24 +0200
Subject: [PATCH] update project

---
 README.md                                     |   1 +
 agent_based/cisco_asyncos_dns.py              |  90 -------------
 agent_based/cisco_asyncos_queue.py            | 105 ---------------
 cisco_asyncos.mkp                             | Bin 10670 -> 0 bytes
 {doc => img}/.gitkeep                         |   0
 {doc => img}/sample_fan.png                   | Bin
 {doc => img}/sample_license.png               | Bin
 {doc => img}/sample_power.png                 | Bin
 {doc => img}/sample_raid.png                  | Bin
 {doc => img}/sample_temp.png                  | Bin
 {doc => img}/sample_updates.png               | Bin
 {doc => img}/sample_workqueue.png             | Bin
 mkp/cisco_asyncos-0.4.1-20240418.mkp          | Bin 0 -> 11759 bytes
 .../agent_based}/cisco_asyncos_bandwidth.py   |  73 +++++------
 .../agent_based}/cisco_asyncos_cache.py       |  72 +++++++----
 .../agent_based}/cisco_asyncos_conn.py        |  72 ++++++-----
 .../agent_based}/cisco_asyncos_cpu.py         |  28 +++-
 source/agent_based/cisco_asyncos_dns.py       | 112 ++++++++++++++++
 .../agent_based}/cisco_asyncos_fan.py         |  43 ++++---
 .../cisco_asyncos_feature_keys.py             |  68 ++++++----
 .../agent_based}/cisco_asyncos_mem.py         |  33 +++--
 .../agent_based}/cisco_asyncos_messageage.py  |  44 ++++---
 .../agent_based}/cisco_asyncos_power.py       |  30 +++--
 source/agent_based/cisco_asyncos_queue.py     | 121 ++++++++++++++++++
 .../agent_based}/cisco_asyncos_raid.py        |  43 ++++---
 .../agent_based}/cisco_asyncos_resources.py   |  46 ++++---
 .../agent_based}/cisco_asyncos_temp.py        |  41 +++---
 .../agent_based}/cisco_asyncos_updates.py     |  96 ++++++++------
 source/gui/metrics/cisco_asyncos.py           | 100 +++++++++++++++
 source/gui/wato/cisco_asyncos_dns.py          |  51 ++++++++
 .../gui}/wato/cisco_asyncos_feature_keys.py   |   8 +-
 .../gui}/wato/cisco_asyncos_license.py        |   8 +-
 .../gui}/wato/cisco_asyncos_queue.py          |   7 +-
 .../gui}/wato/cisco_asyncos_updates.py        |   8 +-
 {packages => source/packages}/cisco_asyncos   |  12 +-
 35 files changed, 826 insertions(+), 486 deletions(-)
 delete mode 100644 agent_based/cisco_asyncos_dns.py
 delete mode 100644 agent_based/cisco_asyncos_queue.py
 delete mode 100644 cisco_asyncos.mkp
 rename {doc => img}/.gitkeep (100%)
 rename {doc => img}/sample_fan.png (100%)
 rename {doc => img}/sample_license.png (100%)
 rename {doc => img}/sample_power.png (100%)
 rename {doc => img}/sample_raid.png (100%)
 rename {doc => img}/sample_temp.png (100%)
 rename {doc => img}/sample_updates.png (100%)
 rename {doc => img}/sample_workqueue.png (100%)
 create mode 100644 mkp/cisco_asyncos-0.4.1-20240418.mkp
 rename {agent_based => source/agent_based}/cisco_asyncos_bandwidth.py (55%)
 rename {agent_based => source/agent_based}/cisco_asyncos_cache.py (53%)
 rename {agent_based => source/agent_based}/cisco_asyncos_conn.py (50%)
 rename {agent_based => source/agent_based}/cisco_asyncos_cpu.py (73%)
 create mode 100644 source/agent_based/cisco_asyncos_dns.py
 rename {agent_based => source/agent_based}/cisco_asyncos_fan.py (82%)
 rename {agent_based => source/agent_based}/cisco_asyncos_feature_keys.py (84%)
 rename {agent_based => source/agent_based}/cisco_asyncos_mem.py (75%)
 rename {agent_based => source/agent_based}/cisco_asyncos_messageage.py (72%)
 rename {agent_based => source/agent_based}/cisco_asyncos_power.py (88%)
 create mode 100644 source/agent_based/cisco_asyncos_queue.py
 rename {agent_based => source/agent_based}/cisco_asyncos_raid.py (81%)
 rename {agent_based => source/agent_based}/cisco_asyncos_resources.py (70%)
 rename {agent_based => source/agent_based}/cisco_asyncos_temp.py (75%)
 rename {agent_based => source/agent_based}/cisco_asyncos_updates.py (82%)
 create mode 100644 source/gui/metrics/cisco_asyncos.py
 create mode 100644 source/gui/wato/cisco_asyncos_dns.py
 rename {gui => source/gui}/wato/cisco_asyncos_feature_keys.py (87%)
 rename {gui => source/gui}/wato/cisco_asyncos_license.py (87%)
 rename {gui => source/gui}/wato/cisco_asyncos_queue.py (82%)
 rename {gui => source/gui}/wato/cisco_asyncos_updates.py (86%)
 rename {packages => source/packages}/cisco_asyncos (89%)

diff --git a/README.md b/README.md
index a34402a..4fa2cab 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[PACKAGE]: ../../raw/master/mkp/cisco_asyncos-0.4.1-20240418.mkp "cisco_asyncos-0.4.1-20240418.mkp"
 # Cisco AsyncOS (IronPort)
 
 **Note: this package is for CheckMK version 2.x. For other versions see the corresponding branch.**
diff --git a/agent_based/cisco_asyncos_dns.py b/agent_based/cisco_asyncos_dns.py
deleted file mode 100644
index 534f79a..0000000
--- a/agent_based/cisco_asyncos_dns.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# License: GNU General Public License v2
-#
-# Author: thl-cmk[at]outlook[dot]com
-# URL   : https://thl-cmk.hopto.org
-# Date  : 2020-03-08
-#
-# monitors status Cisco IronPort Appliances (ESA) DNS requests
-# Tested with: C380, C370
-#
-# 2021-03-24: rewrite for CMK 2.0
-#
-# .1.3.6.1.4.1.15497.1.1.1.15.0 = Gauge32: 0
-# .1.3.6.1.4.1.15497.1.1.1.16.0 = Gauge32: 0
-#
-# ASYNCOS-MAIL-MIB::outstandingDNSRequests.0 = Gauge32: 0
-# ASYNCOS-MAIL-MIB::pendingDNSRequests.0 = Gauge32: 0
-#
-#
-from typing import Mapping, List, NamedTuple, Optional
-
-from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
-    CheckResult,
-    StringTable,
-)
-
-from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
-    Result,
-    Metric,
-    SNMPTree,
-    contains,
-    State,
-)
-
-
-class CiscoAsyncosDns(NamedTuple):
-    outstandingDNSRequests: int
-    pendingDNSRequests: int
-
-
-# [['0', '0']]
-def parse_cisco_asyncos_dns(string_table: StringTable) -> Optional[CiscoAsyncosDns]:
-    try:
-        return CiscoAsyncosDns(
-            outstandingDNSRequests=int(string_table[0][0]),
-            pendingDNSRequests=int(string_table[0][1]),
-        )
-    except IndexError:
-        return
-
-
-def discovery_cisco_asyncos_dns(section: CiscoAsyncosDns) -> DiscoveryResult:
-    yield Service()
-
-
-def check_cisco_asyncos_dns(params, section: CiscoAsyncosDns) -> CheckResult:
-    yield Result(state=State.OK,
-                 summary='%s outstanding, %s pending' % (
-                     section.outstandingDNSRequests, section.pendingDNSRequests))
-
-    yield Metric(name='outstandingDNSRequests', value=section.outstandingDNSRequests)
-    yield Metric(name='pendingDNSRequests', value=section.pendingDNSRequests)
-
-
-register.snmp_section(
-    name='cisco_asyncos_dns',
-    parse_function=parse_cisco_asyncos_dns,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
-            oids=[
-                '15',  # outstandingDNSRequests
-                '16',  # pendingDNSRequests
-            ]
-        ),
-    detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
-)
-
-register.check_plugin(
-    name='cisco_asyncos_dns',
-    service_name='DNS requests',
-    discovery_function=discovery_cisco_asyncos_dns,
-    check_function=check_cisco_asyncos_dns,
-    check_default_parameters={},
-)
diff --git a/agent_based/cisco_asyncos_queue.py b/agent_based/cisco_asyncos_queue.py
deleted file mode 100644
index b4a7988..0000000
--- a/agent_based/cisco_asyncos_queue.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# License: GNU General Public License v2
-#
-# Author: thl-cmk[at]outlook[dot]com
-# URL   : https://thl-cmk.hopto.org
-# Date  : 2020-02-10
-#
-#  added wato, fixed inventory function, changed snmp scan function, changed item to 'work queue'
-#
-# 2021-03-24: rewrite for CMK 2.0
-#
-
-from typing import Mapping, List, NamedTuple, Optional
-
-from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
-    CheckResult,
-    StringTable,
-)
-
-from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
-    Result,
-    Metric,
-    SNMPTree,
-    contains,
-    State,
-)
-
-
-class CiscoAsyncosQueue(NamedTuple):
-    state: State
-    status_readable: str
-    workQueueMessages: int
-
-
-def get_status_readable(st: str) -> str:
-    states = {
-        '1': 'enough',
-        '2': 'near full',
-        '3': 'full',
-    }
-    return states.get(st, st)
-
-
-def get_cmk_state(st: str) -> State:
-    states = {
-        '1': State.OK,
-        '2': State.CRIT,
-        '3': State.WARN
-    }
-    return states.get(st, State.CRIT)
-
-
-# [['1', '1']]
-def parse_cisco_asyncos_messageage(string_table: StringTable) -> Optional[CiscoAsyncosQueue]:
-    try:
-        return CiscoAsyncosQueue(
-            state=get_cmk_state(string_table[0][0]),
-            status_readable=get_status_readable(string_table[0][0]),
-            workQueueMessages=int(string_table[0][1])
-        )
-    except IndexError:
-        return
-
-
-def discovery_cisco_asyncos_queue(section: CiscoAsyncosQueue) -> DiscoveryResult:
-    yield Service()
-
-
-def check_cisco_asyncos_queue(params, section: CiscoAsyncosQueue) -> CheckResult:
-    yield from check_levels(
-        value=section.workQueueMessages,
-        label='Messages in work queue',
-        levels_upper=params.get('upper', None),
-        metric_name='work_queue_size',
-    )
-    yield Result(state=section.state, notice='Queue space: %s' % section.status_readable)
-
-
-register.snmp_section(
-    name='cisco_asyncos_queue',
-    parse_function=parse_cisco_asyncos_messageage,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
-            oids=[
-                '5',  # queueAvailabilityStatus
-                '11'  # workQueueMessages
-            ]
-        ),
-    detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
-)
-
-register.check_plugin(
-    name='cisco_asyncos_queue',
-    service_name='Queue',
-    discovery_function=discovery_cisco_asyncos_queue,
-    check_function=check_cisco_asyncos_queue,
-    check_default_parameters={'levels': (50, 100)},
-    check_ruleset_name='cisco_asyncos_queue',
-)
diff --git a/cisco_asyncos.mkp b/cisco_asyncos.mkp
deleted file mode 100644
index 60bcf605212b7552bb0db41df2d304ff042462d5..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 10670
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
VS_l5$Xexpr>bJR_L8U-L{XZKJ^2GoE

diff --git a/doc/.gitkeep b/img/.gitkeep
similarity index 100%
rename from doc/.gitkeep
rename to img/.gitkeep
diff --git a/doc/sample_fan.png b/img/sample_fan.png
similarity index 100%
rename from doc/sample_fan.png
rename to img/sample_fan.png
diff --git a/doc/sample_license.png b/img/sample_license.png
similarity index 100%
rename from doc/sample_license.png
rename to img/sample_license.png
diff --git a/doc/sample_power.png b/img/sample_power.png
similarity index 100%
rename from doc/sample_power.png
rename to img/sample_power.png
diff --git a/doc/sample_raid.png b/img/sample_raid.png
similarity index 100%
rename from doc/sample_raid.png
rename to img/sample_raid.png
diff --git a/doc/sample_temp.png b/img/sample_temp.png
similarity index 100%
rename from doc/sample_temp.png
rename to img/sample_temp.png
diff --git a/doc/sample_updates.png b/img/sample_updates.png
similarity index 100%
rename from doc/sample_updates.png
rename to img/sample_updates.png
diff --git a/doc/sample_workqueue.png b/img/sample_workqueue.png
similarity index 100%
rename from doc/sample_workqueue.png
rename to img/sample_workqueue.png
diff --git a/mkp/cisco_asyncos-0.4.1-20240418.mkp b/mkp/cisco_asyncos-0.4.1-20240418.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..a794d4f34a5506ada4d5d0ed69d3d03c83785c39
GIT binary patch
literal 11759
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
EA2lh0Gynhq

literal 0
HcmV?d00001

diff --git a/agent_based/cisco_asyncos_bandwidth.py b/source/agent_based/cisco_asyncos_bandwidth.py
similarity index 55%
rename from agent_based/cisco_asyncos_bandwidth.py
rename to source/agent_based/cisco_asyncos_bandwidth.py
index 4d73994..eb6a701 100644
--- a/agent_based/cisco_asyncos_bandwidth.py
+++ b/source/agent_based/cisco_asyncos_bandwidth.py
@@ -7,27 +7,27 @@
 # URL   : https://thl-cmk.hopto.org
 # Date  : 2021-03-24
 #
-# only use full for Cisco WSA appliances
+# only useful for Cisco WSA appliances
 #
 # 2023-03-06: fixed: IndexError in parse function
-#
+# 2024-04-18: fixed bandwidth values to bytes/s (kb/s), see MIB
 
-from typing import Mapping, List, NamedTuple, Optional
+from typing import NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
+    HostLabel,
+    SNMPTree,
     Service,
     check_levels,
-    Result,
-    SNMPTree,
     contains,
-    State,
+    register,
     render,
 )
 
@@ -38,13 +38,22 @@ class CiscoAsyncosBandwidth(NamedTuple):
     bandwidth_day: int
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 # [['0', '0', '247']]
-def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> Optional[CiscoAsyncosBandwidth]:
+def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> CiscoAsyncosBandwidth | None:
     try:
         return CiscoAsyncosBandwidth(
-            bandwidth_now=int(string_table[0][0]),
-            bandwidth_hour=int(string_table[0][1]),
-            bandwidth_day = int(string_table[0][2]),
+            bandwidth_now=int(string_table[0][0]) * 1000 / 8,  # kbits/s -> bytes/s
+            bandwidth_hour=int(string_table[0][1]) * 1000 / 8,
+            bandwidth_day=int(string_table[0][2]) * 1000 / 8,
         )
     except IndexError:
         return
@@ -56,44 +65,36 @@ def discovery_cisco_asyncos_bandwidth(section: CiscoAsyncosBandwidth) -> Discove
 
 def check_cisco_asyncos_bandwidth(params, section: CiscoAsyncosBandwidth) -> CheckResult:
     yield from check_levels(
-        section.bandwidth_now * 8,
+        value=section.bandwidth_now,
         label='Current bandwidth',
-        levels_upper=params.get('upper', None),
-        levels_lower=params.get('lower', None),
-        metric_name='bandwith_now',
+        # levels_upper=params.get('upper', None),  # not implemented
+        # levels_lower=params.get('lower', None),  # not implemented
+        metric_name='bandwidth_now',
         render_func=render.networkbandwidth,
-        # unit='bits/s'
     )
     yield from check_levels(
-        section.bandwidth_hour * 8,
-        label='last hour',
-        # levels_upper=params.get('upper', None),
-        # levels_lower=params.get('lower', None),
-        # metric_name='bandwith_now',
+        value=section.bandwidth_hour,
+        label='Last hour',
         render_func=render.networkbandwidth
     )
     yield from check_levels(
-        section.bandwidth_day * 8,
-        label='last day',
-        #levels_upper=params.get('upper', None),
-        #levels_lower=params.get('lower', None),
-        #metric_name='bandwith_now',
+        value=section.bandwidth_day,
+        label='Last day',
         render_func=render.networkbandwidth
     )
 
-    # yield Result(state=State.OK, summary='last hour: %s, last day: %s' % (section.bandwidth_hour, section.bandwidth_day))
-
 
 register.snmp_section(
     name='cisco_asyncos_bandwidth',
     parse_function=parse_cisco_asyncos_bandwidth,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.2.3.7.4',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentBandWTotPerf
-            oids=[
-                '1',  # cacheBwidthTotalNow
-                '3',  # cacheBwidthTotal1hrMean
-                '5',  # cacheBwidthTotal1dayMean
-            ]
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.2.3.7.4',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentBandWTotPerf
+        oids=[
+            '1',  # cacheBwidthTotalNow (kb/s -> 1000 bits/s)
+            '3',  # cacheBwidthTotal1hrMean (kb/s)
+            '5',  # cacheBwidthTotal1dayMean (kb/s)
+        ]
     ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
diff --git a/agent_based/cisco_asyncos_cache.py b/source/agent_based/cisco_asyncos_cache.py
similarity index 53%
rename from agent_based/cisco_asyncos_cache.py
rename to source/agent_based/cisco_asyncos_cache.py
index bf53571..54dac16 100644
--- a/agent_based/cisco_asyncos_cache.py
+++ b/source/agent_based/cisco_asyncos_cache.py
@@ -7,26 +7,27 @@
 # URL   : https://thl-cmk.hopto.org
 # Date  : 2021-03-24
 #
-# only use full for Cisco WSA appliances
+# only useful for Cisco WSA appliances
 #
 
-from typing import Mapping, List, NamedTuple, Optional
+from typing import NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
-    Result,
+    HostLabel,
     Metric,
+    Result,
     SNMPTree,
-    contains,
+    Service,
     State,
+    contains,
+    register,
 )
 
 
@@ -38,15 +39,24 @@ class CiscoAsyncosCache(NamedTuple):
     cache_total_resptime: int
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 # [['0', '0', '0', '0', '0']]
-def parse_cisco_asyncos_cache(string_table: StringTable) -> Optional[CiscoAsyncosCache]:
+def parse_cisco_asyncos_cache(string_table: StringTable) -> CiscoAsyncosCache | None:
     try:
         return CiscoAsyncosCache(
-           cache_hits_now=int(string_table[0][0]),
-           cache_misses_now=int(string_table[0][1]),
-           cache_hit_resptime_now=int(string_table[0][2]),
-           cache_miss_resptime_now=int(string_table[0][3]),
-           cache_total_resptime=int(string_table[0][4]),
+            cache_hits_now=int(string_table[0][0]),
+            cache_misses_now=int(string_table[0][1]),
+            cache_hit_resptime_now=int(string_table[0][2]),
+            cache_miss_resptime_now=int(string_table[0][3]),
+            cache_total_resptime=int(string_table[0][4]),
         )
     except IndexError:
         return
@@ -57,10 +67,14 @@ def discovery_cisco_asyncos_cache(section: CiscoAsyncosCache) -> DiscoveryResult
 
 
 def check_cisco_asyncos_cache(params, section: CiscoAsyncosCache) -> CheckResult:
-    yield Result(state=State.OK,
-                 summary='Cache Stats current %s Hits (%sms response time), %s Misses (%sms response time) and %sms total response time' % (
-                 section.cache_hits_now, section.cache_hit_resptime_now, section.cache_misses_now,
-                 section.cache_miss_resptime_now, section.cache_total_resptime))
+    yield Result(
+        state=State.OK,
+        summary=f'Cache Stats current {section.cache_hits_now} Hits '
+                f'({section.cache_hit_resptime_now}ms response time), '
+                f'{section.cache_misses_now} Misses '
+                f'({section.cache_miss_resptime_now}ms response time) and '
+                f'{section.cache_total_resptime}ms total response time'
+    )
 
     yield Metric(name='cache_hits_now', value=section.cache_hits_now)
     yield Metric(name='cache_misses_now', value=section.cache_misses_now)
@@ -68,19 +82,21 @@ def check_cisco_asyncos_cache(params, section: CiscoAsyncosCache) -> CheckResult
     yield Metric(name='cache_miss_resptime_now', value=section.cache_miss_resptime_now)
     yield Metric(name='cache_total_resptime', value=section.cache_total_resptime)
 
+
 register.snmp_section(
     name='cisco_asyncos_cache',
     parse_function=parse_cisco_asyncos_cache,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.2.3.7',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentPerf
-            oids=[
-                '5.1',  # cacheHitsNow
-                '6.1',  # cacheMissesNow
-                '7.1',  # cacheHitRespTimeNow
-                '8.1',  # cacheMissRespTimeNow
-                '9.1',  # cacheTotalRespTimeNow
-            ]
-        ),
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.2.3.7',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyRecentPerf
+        oids=[
+            '5.1',  # cacheHitsNow
+            '6.1',  # cacheMissesNow
+            '7.1',  # cacheHitRespTimeNow
+            '8.1',  # cacheMissRespTimeNow
+            '9.1',  # cacheTotalRespTimeNow
+        ]
+    ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
 
diff --git a/agent_based/cisco_asyncos_conn.py b/source/agent_based/cisco_asyncos_conn.py
similarity index 50%
rename from agent_based/cisco_asyncos_conn.py
rename to source/agent_based/cisco_asyncos_conn.py
index f5436cd..c7e5986 100644
--- a/agent_based/cisco_asyncos_conn.py
+++ b/source/agent_based/cisco_asyncos_conn.py
@@ -9,42 +9,52 @@
 #
 # only use full for Cisco WSA appliances
 #
-from typing import Mapping, List, NamedTuple, Optional
+from typing import NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
-    Result,
+    HostLabel,
     Metric,
+    Result,
     SNMPTree,
-    contains,
+    Service,
     State,
+    contains,
+    register,
 )
 
 
 class CiscoAsyncosConn(NamedTuple):
-    cacheClientIdleConns: int
-    cacheClientTotalConns: int
-    cacheClientMaxConns: int
+    cache_client_idle_conns: int
+    cache_client_total_conns: int
+    cache_client_max_conns: int
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
 
 
 # [['0', '5', '66277']]
-def parse_cisco_asyncos_conn(string_table: StringTable) -> Optional[CiscoAsyncosConn]:
+def parse_cisco_asyncos_conn(string_table: StringTable) -> CiscoAsyncosConn | None:
     try:
         return CiscoAsyncosConn(
-            cacheClientIdleConns=int(string_table[0][0]),
-            cacheClientTotalConns=int(string_table[0][1]),
-            cacheClientMaxConns=int(string_table[0][2]),
+            cache_client_idle_conns=int(string_table[0][0]),
+            cache_client_total_conns=int(string_table[0][1]),
+            cache_client_max_conns=int(string_table[0][2]),
         )
     except IndexError:
-        return  
+        return
 
 
 def discovery_cisco_asyncos_conn(section: CiscoAsyncosConn) -> DiscoveryResult:
@@ -52,26 +62,30 @@ def discovery_cisco_asyncos_conn(section: CiscoAsyncosConn) -> DiscoveryResult:
 
 
 def check_cisco_asyncos_conn(params, section: CiscoAsyncosConn) -> CheckResult:
-    yield Result(state=State.OK,
-                 summary='%s Active Connections - %s Idle Connections - Maximum Connections was %s' % (
-                     section.cacheClientTotalConns, section.cacheClientIdleConns, section.cacheClientMaxConns))
+    yield Result(
+        state=State.OK,
+        summary=f'{section.cache_client_total_conns} Active Connections - '
+                f'{section.cache_client_idle_conns} Idle Connections - Maximum Connections was '
+                f'{section.cache_client_max_conns}'
+    )
 
-    yield Metric(name='cacheClientIdleConns', value=section.cacheClientIdleConns)
-    yield Metric(name='cacheClientTotalConns', value=section.cacheClientTotalConns)
-    yield Metric(name='cacheClientMaxConns', value=section.cacheClientMaxConns)
+    yield Metric(name='cacheClientIdleConns', value=section.cache_client_idle_conns)
+    yield Metric(name='cacheClientTotalConns', value=section.cache_client_total_conns)
+    yield Metric(name='cacheClientMaxConns', value=section.cache_client_max_conns)
 
 
 register.snmp_section(
     name='cisco_asyncos_conn',
     parse_function=parse_cisco_asyncos_conn,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.2.3.2',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyClientSidePerf
-            oids=[
-                '7',  # cacheClientIdleConns
-                '8',  # cacheClientTotalConns
-                '9',  # cacheClientMaxConns
-            ]
-        ),
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.2.3.2',  # ASYNCOSWEBSECURITYAPPLIANCE-MIB::proxyClientSidePerf
+        oids=[
+            '7',  # cacheClientIdleConns
+            '8',  # cacheClientTotalConns
+            '9',  # cacheClientMaxConns
+        ]
+    ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
 
diff --git a/agent_based/cisco_asyncos_cpu.py b/source/agent_based/cisco_asyncos_cpu.py
similarity index 73%
rename from agent_based/cisco_asyncos_cpu.py
rename to source/agent_based/cisco_asyncos_cpu.py
index 5123500..bb00f79 100644
--- a/agent_based/cisco_asyncos_cpu.py
+++ b/source/agent_based/cisco_asyncos_cpu.py
@@ -7,28 +7,41 @@
 # URL   : https://thl-cmk.hopto.org
 # Date  : 2021-03-21
 
-from typing import Mapping, Dict, List, Tuple, NamedTuple
+# 2024-04-18: added host label cmk/os_family:AsyncOS
+
+from typing import List
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
+    HostLabel,
+    SNMPTree,
     Service,
     check_levels,
-    SNMPTree,
     contains,
+    register,
 )
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 #
 # sample string_table
 # [[['41']]]
 #
-def parse_cisco_asyncos_cpu(string_table: List[StringTable]) -> int:
+def parse_cisco_asyncos_cpu(string_table: List[StringTable]) -> int | None:
     try:
         return int(string_table[0][0][0])
     except KeyError:
@@ -55,11 +68,12 @@ def check_cisco_asyncos_cpu(params, section: int) -> CheckResult:
 register.snmp_section(
     name='cisco_asyncos_cpu',
     parse_function=parse_cisco_asyncos_cpu,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1',
+            base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB::asyncOSMailObjects
             oids=[
-                '2'  # ASYNCOS-MAIL-MIB::perCentCPUUtilization
+                '2'  # perCentCPUUtilization
             ]
         ),
     ],
diff --git a/source/agent_based/cisco_asyncos_dns.py b/source/agent_based/cisco_asyncos_dns.py
new file mode 100644
index 0000000..dc25ff9
--- /dev/null
+++ b/source/agent_based/cisco_asyncos_dns.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# License: GNU General Public License v2
+#
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2020-03-08
+#
+# monitors status Cisco IronPort Appliances (ESA) DNS requests
+# Tested with: C380, C370
+#
+# 2021-03-24: rewrite for CMK 2.0
+# 2024-04-18: changed to check_levels
+#             added upper levels
+#             added metrics definition
+
+#
+# .1.3.6.1.4.1.15497.1.1.1.15.0 = Gauge32: 0
+# .1.3.6.1.4.1.15497.1.1.1.16.0 = Gauge32: 0
+#
+# ASYNCOS-MAIL-MIB::outstandingDNSRequests.0 = Gauge32: 0
+# ASYNCOS-MAIL-MIB::pendingDNSRequests.0 = Gauge32: 0
+#
+#
+from typing import NamedTuple
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
+    StringTable,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    HostLabel,
+    SNMPTree,
+    Service,
+    check_levels,
+    contains,
+    register,
+)
+
+
+class CiscoAsyncosDns(NamedTuple):
+    outstanding_dns_requests: int
+    pending_dns_requests: int
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
+# [['0', '0']]
+def parse_cisco_asyncos_dns(string_table: StringTable) -> CiscoAsyncosDns | None:
+    try:
+        return CiscoAsyncosDns(
+            outstanding_dns_requests=int(string_table[0][0]),
+            pending_dns_requests=int(string_table[0][1]),
+        )
+    except IndexError:
+        return
+
+
+def discovery_cisco_asyncos_dns(section: CiscoAsyncosDns) -> DiscoveryResult:
+    yield Service()
+
+
+def check_cisco_asyncos_dns(params, section: CiscoAsyncosDns) -> CheckResult:
+    yield from check_levels(
+        value=section.outstanding_dns_requests,
+        label='Outstanding requests',
+        metric_name='outstandingDNSRequests',
+        render_func=lambda v: f'{v}',
+        levels_upper=params.get('levels_outstanding')
+    )
+    yield from check_levels(
+        value=section.pending_dns_requests,
+        label='Pending requests',
+        metric_name='pendingDNSRequests',
+        render_func=lambda v: f'{v}',
+        levels_upper=params.get('levels_pending')
+    )
+
+
+register.snmp_section(
+    name='cisco_asyncos_dns',
+    parse_function=parse_cisco_asyncos_dns,
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
+        oids=[
+            '15',  # outstandingDNSRequests
+            '16',  # pendingDNSRequests
+        ]
+    ),
+    detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
+)
+
+register.check_plugin(
+    name='cisco_asyncos_dns',
+    service_name='DNS requests',
+    discovery_function=discovery_cisco_asyncos_dns,
+    check_function=check_cisco_asyncos_dns,
+    check_default_parameters={},
+    check_ruleset_name='cisco_asyncos_dns'
+)
diff --git a/agent_based/cisco_asyncos_fan.py b/source/agent_based/cisco_asyncos_fan.py
similarity index 82%
rename from agent_based/cisco_asyncos_fan.py
rename to source/agent_based/cisco_asyncos_fan.py
index 13e6618..5e69950 100644
--- a/agent_based/cisco_asyncos_fan.py
+++ b/source/agent_based/cisco_asyncos_fan.py
@@ -44,25 +44,36 @@
 from typing import Dict, List
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
+    HostLabel,
+    SNMPTree,
     Service,
     check_levels,
-    State,
-    SNMPTree,
     contains,
+    register,
 )
 
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 #
 # [[['6400', 'FAN 1'], ['6300', 'FAN 2'], ['6400', 'FAN 3'], ['6400', 'FAN 4'], ['6400', 'FAN 5'], ['6800', 'FAN 6']]]
 #
 
-def parse_cisco_asyncos_fan(string_table: List[StringTable]) -> dict:
+def parse_cisco_asyncos_fan(string_table: List[StringTable]) -> Dict:
     sensors = {}
     for sensor in string_table[0]:
         try:
@@ -83,23 +94,23 @@ def discovery_cisco_asyncos_fan(section: Dict) -> DiscoveryResult:
 def check_cisco_asyncos_fan(item, params, section) -> CheckResult:
     try:
         value = section[item]
-
-        yield from check_levels(
-            value,
-            label='Speed',
-            levels_lower=params.get('lower', None),
-            levels_upper=params.get('upper', None),
-            metric_name='fan' if params.get('output_metrics') else None,
-            render_func=lambda v: '%s RPM' % str(v),
-        )
-
     except KeyError:
-        pass
+        return
+
+    yield from check_levels(
+        value=value,
+        label='Speed',
+        levels_lower=params.get('lower', None),
+        levels_upper=params.get('upper', None),
+        metric_name='fan' if params.get('output_metrics') else None,
+        render_func=lambda v: f'{str(v)} RPM',
+    )
 
 
 register.snmp_section(
     name='cisco_asyncos_fan',
     parse_function=parse_cisco_asyncos_fan,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1.10.1',  # ASYNCOS-MAIL-MIB::fanEntry
diff --git a/agent_based/cisco_asyncos_feature_keys.py b/source/agent_based/cisco_asyncos_feature_keys.py
similarity index 84%
rename from agent_based/cisco_asyncos_feature_keys.py
rename to source/agent_based/cisco_asyncos_feature_keys.py
index 4d75a73..37b078b 100644
--- a/agent_based/cisco_asyncos_feature_keys.py
+++ b/source/agent_based/cisco_asyncos_feature_keys.py
@@ -111,36 +111,45 @@ import time
 from typing import Mapping, List, NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
+    HostLabel,
     Result,
     SNMPTree,
-    contains,
+    Service,
     State,
-    render,
+    contains,
+    register,
 )
 
 
-def get_isperpetual(isperpetual):
+def get_is_perpetual(is_perpetual):
     s_status = {
         '1': True,
         '2': False
     }
-    return s_status.get(isperpetual, False)
+    return s_status.get(is_perpetual, False)
 
 
 class CiscoAsyncosLicense(NamedTuple):
     perpetual: bool
-    secondsuntilexpire: int
-    daysuntilexpire: int
-    expiredate: str
+    seconds_until_expire: int
+    days_until_expire: int
+    expire_date: str
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
 
 
 # [[
@@ -158,12 +167,12 @@ class CiscoAsyncosLicense(NamedTuple):
 #  ]]
 def parse_cisco_asyncos_feature_keys(string_table: List[StringTable]) -> Mapping[str, CiscoAsyncosLicense]:
     licenses = {}
-    for license, isperpetual, secondsuntilexpire in string_table[0]:
+    for license, is_perpetual, seconds_until_expire in string_table[0]:
         licenses.update({license: CiscoAsyncosLicense(
-            perpetual=get_isperpetual(isperpetual),
-            secondsuntilexpire=int(secondsuntilexpire),
-            daysuntilexpire=int(secondsuntilexpire) // 3600 // 24,
-            expiredate=time.strftime("%d %b %Y", time.localtime(time.time() + int(secondsuntilexpire)))
+            perpetual=get_is_perpetual(is_perpetual),
+            seconds_until_expire=int(seconds_until_expire),
+            days_until_expire=int(seconds_until_expire) // 3600 // 24,
+            expire_date=time.strftime("%d %b %Y", time.localtime(time.time() + int(seconds_until_expire)))
         )})
     return licenses
 
@@ -193,30 +202,37 @@ def check_cisco_asyncos_feature_keys(params, section: Mapping[str, CiscoAsyncosL
     ignore_count = 0
     for license in section.keys():
         if section[license].perpetual:
-            yield Result(state=State.OK, notice='%s is perpetual (will not expire)' % license)
+            yield Result(state=State.OK, notice=f'{license} is perpetual (will not expire)')
         elif license in features_ignore:
-            yield Result(state=State.OK, notice='%s will expire at %s (%s days). Feature ignored!' % (
-            license, section[license].expiredate, section[license].daysuntilexpire))
+            yield Result(
+                state=State.OK,
+                notice=f'{license} will expire at {section[license].expire_date} '
+                       f'({section[license].days_until_expire} days). Feature ignored!'
+            )
             ignore_count += 1
-        elif section[license].secondsuntilexpire == 0:
-            yield Result(state=State.CRIT, notice='%s has expired or is not licensed' % license)
+        elif section[license].seconds_until_expire == 0:
+            yield Result(state=State.CRIT, notice=f'{license} has expired or is not licensed')
         else:
-            if section[license].daysuntilexpire < crit:
+            if section[license].days_until_expire < crit:
                 state = State.CRIT
-            elif section[license].daysuntilexpire < warn:
+            elif section[license].days_until_expire < warn:
                 state = State.WARN
             else:
                 state = State.OK
 
-            yield Result(state=state, notice='%s will expire at %s (%s days)' % (
-            license, section[license].expiredate, section[license].daysuntilexpire))
+            yield Result(
+                state=state,
+                notice=f'{license} will expire at {section[license].expire_date} '
+                       f'({section[license].days_until_expire} days)'
+            )
 
-    yield Result(state=State.OK, summary='%d/%d Features found/ignored' % (len(section.keys()), ignore_count))
+    yield Result(state=State.OK, summary=f'{len(section.keys())}/{ignore_count} Features found/ignored')
 
 
 register.snmp_section(
     name='cisco_asyncos_feature_keys',
     parse_function=parse_cisco_asyncos_feature_keys,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1.12.1',  # ASYNCOS-MAIL-MIB::keyExpirationEntry
diff --git a/agent_based/cisco_asyncos_mem.py b/source/agent_based/cisco_asyncos_mem.py
similarity index 75%
rename from agent_based/cisco_asyncos_mem.py
rename to source/agent_based/cisco_asyncos_mem.py
index f50d747..237e57f 100644
--- a/agent_based/cisco_asyncos_mem.py
+++ b/source/agent_based/cisco_asyncos_mem.py
@@ -7,6 +7,8 @@
 # URL   : https://thl-cmk.hopto.org
 # Date  : 2021-03-21
 #
+# 2024-04-18: fixed params for levels_upper (ThX to alexander[dot]laengle[at]lgt[dot]com)
+#
 # .1.3.6.1.4.1.15497.1.1.1.1.0 = INTEGER: 0
 # .1.3.6.1.4.1.15497.1.1.1.7.0 = INTEGER: 1
 #
@@ -17,31 +19,38 @@
 # [[u'0', u'1']]
 #
 
-from typing import Mapping, Dict, List, Tuple, NamedTuple
+from typing import List
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
+    HostLabel,
+    SNMPTree,
     Service,
     check_levels,
-    SNMPTree,
     contains,
+    register,
 )
 
 
-# factory_settings['cisco_asyncos_mem_default_levels'] = {
-#         'levels':(80.0, 90.0),
-#     }
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
 
 #
 # [[['48']]
 #
-def parse_cisco_asyncos_mem(string_table: List[StringTable]) -> int:
+def parse_cisco_asyncos_mem(string_table: List[StringTable]) -> int | None:
     try:
         return int(string_table[0][0][0])
     except KeyError:
@@ -53,21 +62,19 @@ def discovery_cisco_asyncos_mem(section: int) -> DiscoveryResult:
 
 
 def check_cisco_asyncos_mem(params, section: int) -> CheckResult:
-    def render_percent(value) -> str:
-        return '%s%%' % value
-
     yield from check_levels(
         section,
         label='Memory utilization',
-        levels_upper=params.get('util', None),
+        levels_upper=params.get('levels', None),
         metric_name='util',
-        render_func=render_percent,
+        render_func=lambda v: f'{v}%',
     )
 
 
 register.snmp_section(
     name='cisco_asyncos_mem',
     parse_function=parse_cisco_asyncos_mem,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1',
diff --git a/agent_based/cisco_asyncos_messageage.py b/source/agent_based/cisco_asyncos_messageage.py
similarity index 72%
rename from agent_based/cisco_asyncos_messageage.py
rename to source/agent_based/cisco_asyncos_messageage.py
index 91383d7..9b6b7a8 100644
--- a/agent_based/cisco_asyncos_messageage.py
+++ b/source/agent_based/cisco_asyncos_messageage.py
@@ -17,35 +17,44 @@
 # ASYNCOS-MAIL-MIB::oldestMessageAge.0 = Gauge32: 259706
 #
 
-from typing import Mapping, List, NamedTuple, Optional
+from typing import NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
+    HostLabel,
+    SNMPTree,
     Service,
     check_levels,
-    Result,
-    Metric,
-    SNMPTree,
     contains,
-    State,
+    register,
     render,
 )
 
 
 class CiscoAsyncosMessageage(NamedTuple):
-    messageage: int
+    message: int
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
 
 # [['259297']]
-def parse_cisco_asyncos_messageage(string_table: StringTable) -> Optional[CiscoAsyncosMessageage]:
+def parse_cisco_asyncos_messageage(string_table: StringTable) -> CiscoAsyncosMessageage | None:
     try:
         return CiscoAsyncosMessageage(
-            messageage=int(string_table[0][0]),
+            message=int(string_table[0][0]),
         )
     except IndexError:
         return
@@ -57,7 +66,7 @@ def discovery_cisco_asyncos_messageage(section: CiscoAsyncosMessageage) -> Disco
 
 def check_cisco_asyncos_messageage(params, section: CiscoAsyncosMessageage) -> CheckResult:
     yield from check_levels(
-        value=section.messageage,
+        value=section.message,
         label='Oldest messages',
         metric_name='message_age',
         levels_upper=params.get('upper', None),
@@ -68,12 +77,13 @@ def check_cisco_asyncos_messageage(params, section: CiscoAsyncosMessageage) -> C
 register.snmp_section(
     name='cisco_asyncos_messageage',
     parse_function=parse_cisco_asyncos_messageage,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
-            oids=[
-                '14',  # oldestMessageAge
-            ]
-        ),
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
+        oids=[
+            '14',  # oldestMessageAge
+        ]
+    ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
 
diff --git a/agent_based/cisco_asyncos_power.py b/source/agent_based/cisco_asyncos_power.py
similarity index 88%
rename from agent_based/cisco_asyncos_power.py
rename to source/agent_based/cisco_asyncos_power.py
index 2ea635e..ee043d1 100644
--- a/agent_based/cisco_asyncos_power.py
+++ b/source/agent_based/cisco_asyncos_power.py
@@ -35,28 +35,39 @@
 from typing import Dict, List, NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
+    HostLabel,
     Result,
-    State,
     SNMPTree,
+    Service,
+    State,
     contains,
+    register,
 )
 
 
 class CiscoAsyncosPowersupply(NamedTuple):
     redundancy: State
-    state: State
     redundancy_readable: str
+    state: State
     state_readable: str
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 #
 # [[['2', '1', 'PS 1'], ['2', '1', 'PS 2']]]
 #
@@ -89,10 +100,10 @@ def parse_cisco_asyncos_power(string_table: List[StringTable]) -> dict:
             redundancy, redundancy_readable = get_ps_redundancy(redundancy)
 
             sensors.update({item: CiscoAsyncosPowersupply(
-                state=state,
-                state_readable=state_readable,
                 redundancy=redundancy,
                 redundancy_readable=redundancy_readable,
+                state=state,
+                state_readable=state_readable,
             )})
         except (ValueError, IndexError):
             pass
@@ -115,8 +126,8 @@ def check_cisco_asyncos_power(item, section) -> CheckResult:
     try:
         sensor = section[item]
 
-        yield Result(state=sensor.state, summary='Status: %s' % sensor.state_readable)
-        yield Result(state=sensor.redundancy, summary='Redundancy: %s' % sensor.redundancy_readable)
+        yield Result(state=sensor.state, summary=f'Status: {sensor.state_readable}')
+        yield Result(state=sensor.redundancy, summary=f'Redundancy: {sensor.redundancy_readable}')
 
     except KeyError:
         pass
@@ -125,6 +136,7 @@ def check_cisco_asyncos_power(item, section) -> CheckResult:
 register.snmp_section(
     name='cisco_asyncos_power',
     parse_function=parse_cisco_asyncos_power,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1.8.1',  # ASYNCOS-MAIL-MIB::powerSupplyEntry
diff --git a/source/agent_based/cisco_asyncos_queue.py b/source/agent_based/cisco_asyncos_queue.py
new file mode 100644
index 0000000..9792927
--- /dev/null
+++ b/source/agent_based/cisco_asyncos_queue.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# License: GNU General Public License v2
+#
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2020-02-10
+#
+#  added wato, fixed inventory function, changed snmp scan function, changed item to 'work queue'
+#
+# 2021-03-24: rewrite for CMK 2.0
+# 2024-04-18: fixed wrong queue states (ThX to alexander[dot]laengle[at]lgt[dot]com)
+#             fixed levels for queue full not working (ThX to alexander[dot]laengle[at]lgt[dot]com)
+#             changed state for not defined state from CRIT to UNKNOWN
+#             a bit of clean up
+
+from typing import NamedTuple
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
+    StringTable,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    HostLabel,
+    Result,
+    SNMPTree,
+    Service,
+    State,
+    check_levels,
+    contains,
+    register,
+)
+
+
+class CiscoAsyncosQueue(NamedTuple):
+    state: State
+    state_readable: str
+    work_queue_messages: int | None
+
+
+def get_state_readable(st: str) -> str:
+    states = {
+        '1': 'enough',
+        '2': 'near full',
+        '3': 'full',
+    }
+    return states.get(st, st)
+
+
+def get_cmk_state(st: str) -> State:
+    states = {
+        '1': State.OK,
+        '2': State.WARN,
+        '3': State.CRIT
+    }
+    return states.get(st, State.UNKNOWN)
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
+# [['1', '3451']]
+def parse_cisco_asyncos_queue(string_table: StringTable) -> CiscoAsyncosQueue | None:
+    try:
+        queue_availability_status, work_queue_messages = string_table[0]
+    except IndexError:
+        return
+
+    return CiscoAsyncosQueue(
+        state=get_cmk_state(queue_availability_status),
+        state_readable=get_state_readable(queue_availability_status),
+        work_queue_messages=int(work_queue_messages) if work_queue_messages.isdigit() else None
+    )
+
+
+def discovery_cisco_asyncos_queue(section: CiscoAsyncosQueue) -> DiscoveryResult:
+    yield Service()
+
+
+def check_cisco_asyncos_queue(params, section: CiscoAsyncosQueue) -> CheckResult:
+    yield from check_levels(
+        value=section.work_queue_messages,
+        label='Messages in work queue',
+        levels_upper=params.get('levels', None),
+        metric_name='work_queue_size',
+    )
+    yield Result(state=section.state, notice=f'Queue space: {section.state_readable}')
+
+
+register.snmp_section(
+    name='cisco_asyncos_queue',
+    parse_function=parse_cisco_asyncos_queue,
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB::asyncOSMailObjects
+        oids=[
+            '5',  # queueAvailabilityStatus
+            '11'  # workQueueMessages
+        ]
+    ),
+    detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
+)
+
+register.check_plugin(
+    name='cisco_asyncos_queue',
+    service_name='Queue',
+    discovery_function=discovery_cisco_asyncos_queue,
+    check_function=check_cisco_asyncos_queue,
+    check_default_parameters={'levels': (50, 100)},
+    check_ruleset_name='cisco_asyncos_queue',
+)
diff --git a/agent_based/cisco_asyncos_raid.py b/source/agent_based/cisco_asyncos_raid.py
similarity index 81%
rename from agent_based/cisco_asyncos_raid.py
rename to source/agent_based/cisco_asyncos_raid.py
index be917e5..70d48a0 100644
--- a/agent_based/cisco_asyncos_raid.py
+++ b/source/agent_based/cisco_asyncos_raid.py
@@ -36,25 +36,27 @@
 from typing import Mapping, List, NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
+    HostLabel,
     Result,
     SNMPTree,
-    contains,
+    Service,
     State,
+    contains,
+    register,
 )
 
 
 class CiscoAsyncosRaid(NamedTuple):
     state: State
-    status_readable: str
-    lasterror: str
+    state_readable: str
+    last_error: str
 
 
 def get_cmk_state(st: str) -> State:
@@ -66,7 +68,7 @@ def get_cmk_state(st: str) -> State:
     return states.get(st, State.CRIT)
 
 
-def get_status_readable(st: str) -> str:
+def get_state_readable(st: str) -> str:
     states = {
         '1': 'healthy',
         '2': 'failure',
@@ -75,6 +77,15 @@ def get_status_readable(st: str) -> str:
     return states.get(st, st)
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 # [[
 #   ['1', 'Drive 0', 'No Errors'],
 #   ['2', 'Drive 1', 'UNKNOWN'],
@@ -85,11 +96,11 @@ def get_status_readable(st: str) -> str:
 #  ]]
 def parse_cisco_asyncos_raid(string_table: List[StringTable]) -> Mapping[str, CiscoAsyncosRaid]:
     arrays = {}
-    for raidStatus, raidID, raidLastError in string_table[0]:
-        arrays.update({raidID: CiscoAsyncosRaid(
-            state=get_cmk_state(raidStatus),
-            status_readable=get_status_readable(raidStatus),
-            lasterror=raidLastError
+    for raid_status, raid_id, raid_last_error in string_table[0]:
+        arrays.update({raid_id: CiscoAsyncosRaid(
+            state=get_cmk_state(raid_status),
+            state_readable=get_state_readable(raid_status),
+            last_error=raid_last_error
         )})
     return arrays
 
@@ -109,9 +120,10 @@ def discovery_cisco_asyncos_raid(section: Mapping[str, CiscoAsyncosRaid]) -> Dis
 
 def check_cisco_asyncos_raid(item, section: Mapping[str, CiscoAsyncosRaid]) -> CheckResult:
     try:
-        yield Result(state=section[item].state,
-                     summary='RAID status: %s, last error: %s' % (section[item].state, section[item].lasterror),
-                     )
+        yield Result(
+            state=section[item].state,
+            summary=f'RAID status: {section[item].state}, last error: {section[item].last_error}',
+        )
     except KeyError:
         pass
 
@@ -119,6 +131,7 @@ def check_cisco_asyncos_raid(item, section: Mapping[str, CiscoAsyncosRaid]) -> C
 register.snmp_section(
     name='cisco_asyncos_raid',
     parse_function=parse_cisco_asyncos_raid,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1.18.1',  # ASYNCOS-MAIL-MIB::raidEntry
diff --git a/agent_based/cisco_asyncos_resources.py b/source/agent_based/cisco_asyncos_resources.py
similarity index 70%
rename from agent_based/cisco_asyncos_resources.py
rename to source/agent_based/cisco_asyncos_resources.py
index c6041a2..a771e9b 100644
--- a/agent_based/cisco_asyncos_resources.py
+++ b/source/agent_based/cisco_asyncos_resources.py
@@ -17,27 +17,27 @@
 # ASYNCOS-MAIL-MIB::resourceConservationReason.0 = INTEGER: noResourceConservation(1)
 #
 #
-from typing import Mapping, List, NamedTuple, Optional
+from typing import NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
+    HostLabel,
     Result,
     SNMPTree,
-    contains,
+    Service,
     State,
-    render,
+    contains,
+    register,
 )
 
 
-def get_status_readable(st: str) -> str:
+def get_state_readable(st: str) -> str:
     states = {
         '1': 'none',
         '2': 'memory shortage',
@@ -59,14 +59,23 @@ def get_cmk_state(st: str) -> State:
 
 class CiscoAsyncosResources(NamedTuple):
     state: State
-    status_readable: str
+    state_readable: str
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
 
 
-def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> Optional[CiscoAsyncosResources]:
+def parse_cisco_asyncos_bandwidth(string_table: StringTable) -> CiscoAsyncosResources | None:
     try:
         return CiscoAsyncosResources(
             state=get_cmk_state(string_table[0][0]),
-            status_readable=get_status_readable(string_table[0][0])
+            state_readable=get_state_readable(string_table[0][0])
         )
     except IndexError:
         return
@@ -77,18 +86,19 @@ def discovery_cisco_asyncos_resources(section: CiscoAsyncosResources) -> Discove
 
 
 def check_cisco_asyncos_resources(section: CiscoAsyncosResources) -> CheckResult:
-    yield Result(state=section.state, notice='Resource conservation %s' % section.status_readable)
+    yield Result(state=section.state, notice=f'Resource conservation {section.state_readable}')
 
 
 register.snmp_section(
     name='cisco_asyncos_resources',
     parse_function=parse_cisco_asyncos_bandwidth,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
-            oids=[
-                '6',  # resourceConservationReason
-            ]
-        ),
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.1.1',  # ASYNCOS-MAIL-MIB
+        oids=[
+            '6',  # resourceConservationReason
+        ]
+    ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
 
diff --git a/agent_based/cisco_asyncos_temp.py b/source/agent_based/cisco_asyncos_temp.py
similarity index 75%
rename from agent_based/cisco_asyncos_temp.py
rename to source/agent_based/cisco_asyncos_temp.py
index bb38d10..4782bb6 100644
--- a/agent_based/cisco_asyncos_temp.py
+++ b/source/agent_based/cisco_asyncos_temp.py
@@ -22,33 +22,43 @@
 # ASYNCOS-MAIL-MIB::temperatureName.1 = STRING: Ambient
 #
 
-from typing import Mapping, Dict, List, Tuple, NamedTuple
+from typing import Dict
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
-    check_levels,
+    HostLabel,
     SNMPTree,
+    Service,
     contains,
     get_value_store,
+    register,
 )
 
 from cmk.base.plugins.agent_based.utils.temperature import (
-    check_temperature,
     TempParamType,
+    check_temperature,
 )
 
 
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
+
 #
 # [['22', 'FP_TEMP_SENSOR']]
 #
-def parse_cisco_asyncos_temp(string_table: StringTable) -> dict:
+def parse_cisco_asyncos_temp(string_table: StringTable) -> Dict:
     sensors = {}
     for sensor in string_table:
         try:
@@ -70,7 +80,7 @@ def check_cisco_asyncos_temp(item, params: TempParamType, section) -> CheckResul
         yield from check_temperature(
             section[item],
             params=params,
-            unique_name='check_cisco_asyncos_temp.%s' % item,
+            unique_name=f'check_cisco_asyncos_temp.{item}',
             value_store=get_value_store(),
         )
     except KeyError:
@@ -80,13 +90,14 @@ def check_cisco_asyncos_temp(item, params: TempParamType, section) -> CheckResul
 register.snmp_section(
     name='cisco_asyncos_temp',
     parse_function=parse_cisco_asyncos_temp,
-    fetch= SNMPTree(
-            base='.1.3.6.1.4.1.15497.1.1.1.9.1',  # ASYNCOS-MAIL-MIB::temperatureEntry
-            oids=[
-                '2',  # degreesCelsius
-                '3',  # temperatureName
-            ]
-        ),
+    host_label_function=host_label_cisco_asyncos,
+    fetch=SNMPTree(
+        base='.1.3.6.1.4.1.15497.1.1.1.9.1',  # ASYNCOS-MAIL-MIB::temperatureEntry
+        oids=[
+            '2',  # degreesCelsius
+            '3',  # temperatureName
+        ]
+    ),
     detect=contains('.1.3.6.1.2.1.1.1.0', 'AsyncOS'),
 )
 
diff --git a/agent_based/cisco_asyncos_updates.py b/source/agent_based/cisco_asyncos_updates.py
similarity index 82%
rename from agent_based/cisco_asyncos_updates.py
rename to source/agent_based/cisco_asyncos_updates.py
index db567ce..f6c8bf5 100644
--- a/agent_based/cisco_asyncos_updates.py
+++ b/source/agent_based/cisco_asyncos_updates.py
@@ -137,22 +137,24 @@
 # ASYNCOS-MAIL-MIB::updateFailures.14 = Counter32: 0
 # ASYNCOS-MAIL-MIB::updateFailures.15 = Counter32: 0
 #
-from typing import Mapping, List, NamedTuple
+from typing import List, Mapping, NamedTuple
 
 from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
-    DiscoveryResult,
     CheckResult,
+    DiscoveryResult,
+    HostLabelGenerator,
     StringTable,
 )
 
 from cmk.base.plugins.agent_based.agent_based_api.v1 import (
-    register,
-    Service,
+    HostLabel,
+    Metric,
     Result,
     SNMPTree,
-    contains,
+    Service,
     State,
-    Metric,
+    contains,
+    register,
 )
 
 from cmk.base.item_state import (
@@ -160,9 +162,20 @@ from cmk.base.item_state import (
     set_item_state,
 )
 
+
 class CiscoAsyncosUpdate(NamedTuple):
     updates: int
-    updatefailures: int
+    update_failures: int
+
+
+def host_label_cisco_asyncos(section: int) -> HostLabelGenerator:
+    """Host label function
+    Labels:
+        cmk/os_family:
+            This label is set to "AsyncOS" for all Cisco Ironport devices
+    """
+    yield HostLabel(name="cmk/os_family", value="AsyncOS")
+
 
 # [[
 #   ['File Reputation', '6', '38'],
@@ -186,10 +199,11 @@ def parse_cisco_asyncos_updates(string_table: List[StringTable]) -> Mapping[str,
     for feature, updates, update_failures in string_table[0]:
         features.update({feature: CiscoAsyncosUpdate(
             updates=int(updates),
-            updatefailures=int(update_failures)
-        ) })
+            update_failures=int(update_failures)
+        )})
     return features
 
+
 # {
 #  'File Reputation': CiscoAsyncosUpdate(updates=6, updatefailures=38),
 #  'IronPort Anti-Spam': CiscoAsyncosUpdate(updates=12, updatefailures=9),
@@ -207,25 +221,24 @@ def parse_cisco_asyncos_updates(string_table: List[StringTable]) -> Mapping[str,
 #  'support_request': CiscoAsyncosUpdate(updates=1, updatefailures=0),
 #  'timezones': CiscoAsyncosUpdate(updates=1, updatefailures=0)
 # }
-def discovery_cisco_asyncos_updates(section:Mapping[str, CiscoAsyncosUpdate])-> DiscoveryResult:
+def discovery_cisco_asyncos_updates(section: Mapping[str, CiscoAsyncosUpdate]) -> DiscoveryResult:
     yield Service()
 
-def check_cisco_asyncos_updates(params, section:Mapping[str, CiscoAsyncosUpdate]) -> CheckResult:
+
+def check_cisco_asyncos_updates(params, section: Mapping[str, CiscoAsyncosUpdate]) -> CheckResult:
     features_ignore = params.get('features_ignore', [])
     warn, crit = params.get('failedLevel')
     ignore_count = 0
     for feature in section.keys():
-        failed = section[feature].updatefailures
+        failed = section[feature].update_failures
         passed = section[feature].updates
 
         # read counters
-        passedLast = get_item_state('cisco_asyncos_updates_%s_passedLast' % feature)
-        failedLast = get_item_state('cisco_asyncos_updates_%s_failedLast' % feature)
-        failedAttempts = get_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature)
+        passed_last = get_item_state('cisco_asyncos_updates_%s_passedLast' % feature)
+        failed_last = get_item_state('cisco_asyncos_updates_%s_failedLast' % feature)
+        failed_attempts = get_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature)
 
-        # print('Feature %s, passed: %s/%s failed: %s/%s, Attempts: %s' % (feature, passed,passedLast, failed,failedLast, failedAttempts))
-
-        if (passedLast == None) or (failedLast == None) or (failedAttempts == None):  # or (lastState == None):
+        if (passed_last is None) or (failed_last is None) or (failed_attempts is None):  # or (lastState is None):
             # init counters
             set_item_state('cisco_asyncos_updates_%s_passedLast' % feature, passed)
             set_item_state('cisco_asyncos_updates_%s_failedLast' % feature, failed)
@@ -233,50 +246,52 @@ def check_cisco_asyncos_updates(params, section:Mapping[str, CiscoAsyncosUpdate]
         else:
             set_item_state('cisco_asyncos_updates_%s_passedLast' % feature, passed)
             set_item_state('cisco_asyncos_updates_%s_failedLast' % feature, failed)
-            passedLast = int(passedLast)
-            failedLast = int(failedLast)
-            failedAttempts = int(failedAttempts)
+            passed_last = int(passed_last)
+            failed_last = int(failed_last)
+            failed_attempts = int(failed_attempts)
 
             # reset counter if overrun
-            if failed < failedLast:
+            if failed < failed_last:
                 set_item_state('cisco_asyncos_updates_%s_failedLast' % feature, failed)
-                failedLast = failed
-            if passed < passedLast:
+                failed_last = failed
+            if passed < passed_last:
                 set_item_state('cisco_asyncos_updates_%s_passedLast' % feature, passed)
-                passedLast = passed
+                passed_last = passed
 
-            if passed > passedLast:
+            if passed > passed_last:
                 # reset error counter after passed update attempt
                 set_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature, 0)
             else:
-                failedAttempts = failedAttempts + failed - failedLast
-                set_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature, failedAttempts)
+                failed_attempts = failed_attempts + failed - failed_last
+                set_item_state('cisco_asyncos_updates_%s_failedAttempts' % feature, failed_attempts)
+                base_message = f'{feature}: {passed}/{failed}'
                 if feature in features_ignore:
-                    yield Result(state=State.OK, notice='%s: %d/%d updates/update failures. Feature ignored!' % (feature, passed, failed))
+                    yield Result(state=State.OK, notice=f'{base_message} updates/update failures. Feature ignored!')
                     ignore_count += 1
                 else:
-                    if failedAttempts >= crit:
-                        yield Result(state=State.CRIT, notice='%s: %d/%d passed/failed.' % (feature, passed, failed))
-                        lastState=-1
-                    elif failedAttempts >= warn:
-                        yield Result(state=State.WARN, notice='%s: %d/%d passed/failed.' % (feature, passed, failed))
-                        lastState=-1
+                    if failed_attempts >= crit:
+                        yield Result(state=State.CRIT, notice=f'{base_message} passed/failed.')
+                        last_state = -1
+                    elif failed_attempts >= warn:
+                        yield Result(state=State.WARN, notice=f'{base_message} passed/failed.')
+                        last_state = -1
                     else:
-                        yield Result(state=State.OK, notice='%s: %d/%d passed/failed.' % (feature, passed, failed))
-                        lastState=1
+                        yield Result(state=State.OK, notice=f'{base_message} passed/failed.')
+                        last_state = 1
 
                     yield Metric(
                         name=feature.replace(' ', '_'),
-                        value=lastState,
-                        boundaries=(2,-2)
+                        value=last_state,
+                        boundaries=(2, -2)
                     )
 
-    yield Result(state=State.OK, summary='%d/%d Features found/ignored' % (len(section.keys()), ignore_count))
+    yield Result(state=State.OK, summary=f'{len(section.keys())}/{ignore_count} Features found/ignored')
 
 
 register.snmp_section(
     name='cisco_asyncos_updates',
     parse_function=parse_cisco_asyncos_updates,
+    host_label_function=host_label_cisco_asyncos,
     fetch=[
         SNMPTree(
             base='.1.3.6.1.4.1.15497.1.1.1.13.1',  # ASYNCOS-MAIL-MIB::updateEntry
@@ -298,4 +313,3 @@ register.check_plugin(
     check_default_parameters={'failedLevel': (5, 10)},
     check_ruleset_name='cisco_asyncos_updates',
 )
-
diff --git a/source/gui/metrics/cisco_asyncos.py b/source/gui/metrics/cisco_asyncos.py
new file mode 100644
index 0000000..68350c8
--- /dev/null
+++ b/source/gui/metrics/cisco_asyncos.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# License: GNU General Public License v2
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2024-04-18
+# File  : cisco_asyncos.py (metrics)
+#
+
+from cmk.gui.i18n import _
+
+from cmk.gui.plugins.metrics.utils import (
+    metric_info,
+    graph_info,
+    perfometer_info
+)
+
+#
+# asyncos bandwidth
+#
+
+metric_info['bandwidth_now'] = {
+    'title': _('Current bandwidth'),
+    'unit': 'bytes/s',
+    'color': '31/a',
+}
+
+graph_info['cisco_asyncos.bandwidth_now'] = {
+    'title': _('Cisco AsyncOS current bandwidth'),
+    'metrics': [
+        ('bandwidth_now', 'area'),
+    ],
+}
+
+perfometer_info.append({
+    'type': 'logarithmic',
+    'metric': 'bandwidth_now',
+    'half_value': 1073741824,  # 1GByte
+    'exponent': 2,
+})
+
+#
+# asyncos dns
+#
+
+metric_info['outstandingDNSRequests'] = {
+    'title': _('Outstanding DNS requests'),
+    'unit': 'count',
+    'color': '31/a',
+}
+metric_info['pendingDNSRequests'] = {
+    'title': _('Pending DNS requests'),
+    'unit': 'count',
+    'color': '21/a',
+}
+graph_info['cisco_asyncos.dns'] = {
+    'title': _('Cisco AsyncOS DNS requests'),
+    'metrics': [
+        ('outstandingDNSRequests', 'area'),
+        ('pendingDNSRequests', '-area'),
+    ],
+}
+perfometer_info.append(('stacked', [
+    {
+        'type': 'logarithmic',
+        'metric': 'outstandingDNSRequests',
+        'half_value': 100,
+        'exponent': 2,
+    },
+    {
+        'type': 'logarithmic',
+        'metric': 'pendingDNSRequests',
+        'half_value': 100,
+        'exponent': 2,
+    }
+]))
+
+#
+# asyncos message queue
+#
+
+metric_info['work_queue_size'] = {
+    'title': _('Work queue size'),
+    'unit': 'count',
+    'color': '11/a',
+}
+
+graph_info['cisco_asyncos.queue'] = {
+    'title': _('Cisco AsyncOS work queue'),
+    'metrics': [
+        ('work_queue_size', 'area'),
+    ],
+}
+perfometer_info.append({
+    'type': 'logarithmic',
+    'metric': 'work_queue_size',
+    'half_value': 100,
+    'exponent': 2,
+})
diff --git a/source/gui/wato/cisco_asyncos_dns.py b/source/gui/wato/cisco_asyncos_dns.py
new file mode 100644
index 0000000..02b399b
--- /dev/null
+++ b/source/gui/wato/cisco_asyncos_dns.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+#
+from cmk.gui.i18n import _
+from cmk.gui.valuespec import (
+    Dictionary,
+    Integer,
+    TextAscii,
+    Tuple,
+)
+
+from cmk.gui.plugins.wato.utils import (
+    CheckParameterRulespecWithItem,
+    RulespecGroupCheckParametersNetworking,
+    rulespec_registry,
+)
+
+
+def _parameter_valuespec_cisco_asyncos_dns():
+    return Dictionary(elements=[
+        ('levels_outstanding',
+         Tuple(
+             title=_('Upper levels for outstanding DNS requests'),
+             help=_('Number of DNS requests that have been sent but for which no reply has been received.'),
+             elements=[
+                 Integer(title=_('Warning at'), unit=_('# of requests')),
+                 Integer(title=_('Critical at'), unit=_('# of requests')),
+             ])
+         ),
+        ('levels_pending',
+         Tuple(
+             title=_('Upper levels for pending DNS requests'),
+             help=_('Number of DNS requests waiting to be sent.'),
+             elements=[
+                 Integer(title=_('Warning'), unit=_('# of requests')),
+                 Integer(title=_('Critical'), unit=_('# of requests')),
+             ])
+         ),
+    ])
+
+
+rulespec_registry.register(
+    CheckParameterRulespecWithItem(
+        check_group_name='cisco_asyncos_dns',
+        group=RulespecGroupCheckParametersNetworking,
+        item_spec=lambda: TextAscii(title=_('Cisco AsyncOS DNS'), ),
+        match_type='dict',
+        parameter_valuespec=_parameter_valuespec_cisco_asyncos_dns,
+        title=lambda: _('Cisco AsyncOS DNS'),
+    ))
diff --git a/gui/wato/cisco_asyncos_feature_keys.py b/source/gui/wato/cisco_asyncos_feature_keys.py
similarity index 87%
rename from gui/wato/cisco_asyncos_feature_keys.py
rename to source/gui/wato/cisco_asyncos_feature_keys.py
index 8b38a99..a429360 100644
--- a/gui/wato/cisco_asyncos_feature_keys.py
+++ b/source/gui/wato/cisco_asyncos_feature_keys.py
@@ -6,15 +6,15 @@ from cmk.gui.i18n import _
 from cmk.gui.valuespec import (
     Dictionary,
     Integer,
-    TextAscii,
     ListOfStrings,
+    TextAscii,
     Tuple,
 )
 
 from cmk.gui.plugins.wato.utils import (
     CheckParameterRulespecWithItem,
-    rulespec_registry,
     RulespecGroupCheckParametersNetworking,
+    rulespec_registry,
 )
 
 
@@ -33,8 +33,8 @@ def _parameter_valuespec_cisco_asyncos_feature_keys():
          Tuple(
              title=_('Levels for feature key expiration in days'),
              elements=[
-                 Integer(title=_('Warning'), default_value=30, unit=_('days before expiration')),
-                 Integer(title=_('Critical'), default_value=7, unit=_('days before expiration')),
+                 Integer(title=_('Warning at'), default_value=30, unit=_('days before expiration')),
+                 Integer(title=_('Critical at'), default_value=7, unit=_('days before expiration')),
              ])),
     ])
 
diff --git a/gui/wato/cisco_asyncos_license.py b/source/gui/wato/cisco_asyncos_license.py
similarity index 87%
rename from gui/wato/cisco_asyncos_license.py
rename to source/gui/wato/cisco_asyncos_license.py
index fb7cbca..31c4a61 100644
--- a/gui/wato/cisco_asyncos_license.py
+++ b/source/gui/wato/cisco_asyncos_license.py
@@ -6,15 +6,15 @@ from cmk.gui.i18n import _
 from cmk.gui.valuespec import (
     Dictionary,
     Integer,
-    TextAscii,
     ListOfStrings,
+    TextAscii,
     Tuple,
 )
 
 from cmk.gui.plugins.wato.utils import (
     CheckParameterRulespecWithItem,
-    rulespec_registry,
     RulespecGroupCheckParametersNetworking,
+    rulespec_registry,
 )
 
 
@@ -34,8 +34,8 @@ def _parameter_valuespec_cisco_asyncos_license():
          Tuple(
              title=_('Levels for licence expiration in days'),
              elements=[
-                 Integer(title=_('Warning'), default_value=30, unit=_('days before expiration')),
-                 Integer(title=_('Critical'), default_value=7, unit=_('days before expiration')),
+                 Integer(title=_('Warning at'), default_value=30, unit=_('days before expiration')),
+                 Integer(title=_('Critical at'), default_value=7, unit=_('days before expiration')),
              ])),
     ])
 
diff --git a/gui/wato/cisco_asyncos_queue.py b/source/gui/wato/cisco_asyncos_queue.py
similarity index 82%
rename from gui/wato/cisco_asyncos_queue.py
rename to source/gui/wato/cisco_asyncos_queue.py
index 66d0258..403a5e5 100644
--- a/gui/wato/cisco_asyncos_queue.py
+++ b/source/gui/wato/cisco_asyncos_queue.py
@@ -7,14 +7,13 @@ from cmk.gui.valuespec import (
     Dictionary,
     Integer,
     TextAscii,
-    ListOfStrings,
     Tuple,
 )
 
 from cmk.gui.plugins.wato.utils import (
     CheckParameterRulespecWithItem,
-    rulespec_registry,
     RulespecGroupCheckParametersNetworking,
+    rulespec_registry,
 )
 
 
@@ -24,8 +23,8 @@ def _parameter_valuespec_cisco_asyncos_queue():
          Tuple(
              title=_('Levels for nuber of messages in work queue'),
              elements=[
-                 Integer(title=_('Warning'), default_value=50, unit=_('# of messages')),
-                 Integer(title=_('Critical'), default_value=100, unit=_('# of messages')),
+                 Integer(title=_('Warning at'), default_value=50, unit=_('# of messages')),
+                 Integer(title=_('Critical at'), default_value=100, unit=_('# of messages')),
              ])),
     ])
 
diff --git a/gui/wato/cisco_asyncos_updates.py b/source/gui/wato/cisco_asyncos_updates.py
similarity index 86%
rename from gui/wato/cisco_asyncos_updates.py
rename to source/gui/wato/cisco_asyncos_updates.py
index 4f25b4d..5f1061e 100644
--- a/gui/wato/cisco_asyncos_updates.py
+++ b/source/gui/wato/cisco_asyncos_updates.py
@@ -6,15 +6,15 @@ from cmk.gui.i18n import _
 from cmk.gui.valuespec import (
     Dictionary,
     Integer,
-    TextAscii,
     ListOfStrings,
+    TextAscii,
     Tuple,
 )
 
 from cmk.gui.plugins.wato.utils import (
     CheckParameterRulespecWithItem,
-    rulespec_registry,
     RulespecGroupCheckParametersNetworking,
+    rulespec_registry,
 )
 
 
@@ -32,8 +32,8 @@ def _parameter_valuespec_cisco_asyncos_updates():
          Tuple(
              title=_('Levels for failed attempts'),
              elements=[
-                 Integer(title=_('Warning'), default_value=5, unit=_('# of failed attempts')),
-                 Integer(title=_('Critical'), default_value=10, unit=_('# of failed attempts')),
+                 Integer(title=_('Warning at'), default_value=5, unit=_('# of failed attempts')),
+                 Integer(title=_('Critical at'), default_value=10, unit=_('# of failed attempts')),
              ])),
     ])
 
diff --git a/packages/cisco_asyncos b/source/packages/cisco_asyncos
similarity index 89%
rename from packages/cisco_asyncos
rename to source/packages/cisco_asyncos
index 40f7910..bd98f08 100644
--- a/packages/cisco_asyncos
+++ b/source/packages/cisco_asyncos
@@ -42,10 +42,12 @@
            'gui': ['wato/cisco_asyncos_feature_keys.py',
                    'wato/cisco_asyncos_license.py',
                    'wato/cisco_asyncos_queue.py',
-                   'wato/cisco_asyncos_updates.py']},
+                   'wato/cisco_asyncos_updates.py',
+                   'metrics/cisco_asyncos.py',
+                   'wato/cisco_asyncos_dns.py']},
  'name': 'cisco_asyncos',
  'title': 'Cisco AsyncOS (IronPort) checks',
- 'version': '20230306.v0.3.0',
- 'version.min_required': '2.1.0',
- 'version.packaged': '2.1.0p22',
- 'version.usable_until': None}
\ No newline at end of file
+ 'version': '0.4.1-20240418',
+ 'version.min_required': '2.1.0b1',
+ 'version.packaged': '2.2.0p24',
+ 'version.usable_until': None}
-- 
GitLab