<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Re: How can I show the OS count per platform on a Dynatrace Managed (on-prem) dashboard? in Dashboarding</title>
    <link>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288891#M5483</link>
    <description>&lt;P&gt;Hi, you can try by API.&lt;BR /&gt;here is script, with make api call and save OS version to file.&lt;BR /&gt;Then you can send result as a metric and make present it on dashboard:&lt;/P&gt;&lt;LI-CODE lang="python"&gt;#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import csv
import sys
from collections import Counter
from typing import Dict, List, Optional, Any, Tuple

import requests
from requests.adapters import HTTPAdapter, Retry

# === CONFIGURATION (edit as needed) ===
DT_BASE_URL = "https://dynatrace.example.com/e/xxx"  # Your base URL (Managed/SaaS) with /e/&amp;lt;env-id&amp;gt;
DT_API_TOKEN = "API TOKEN"  # Token with entities.read permission

# Default output files:
DEFAULT_SUMMARY_CSV = "hosts_by_os_summary.csv"
DEFAULT_HOSTS_CSV   = "hosts_by_os_hosts.csv"


# === Platform normalization based on osType/osVersion ===
def normalize_platform(os_type: Optional[str], os_version: Optional[str]) -&amp;gt; str:
    ot = (os_type or "").strip().upper()
    ov = (os_version or "").strip().lower()

    if ot == "WINDOWS":
        return "Windows"
    if ot == "AIX":
        return "AIX"
    if ot == "SOLARIS":
        return "Solaris"
    if ot in ("ZOS", "Z/OS"):
        return "z/OS"

    if ot == "LINUX":
        if "oracle" in ov or " ol " in f" {ov} ":
            return "Oracle Linux"
        if "red hat" in ov or "rhel" in ov:
            return "Red Hat Enterprise Linux"
        if "ubuntu" in ov:
            return "Ubuntu"
        if "debian" in ov:
            return "Debian"
        if "suse" in ov or "sles" in ov:
            return "SUSE Linux"
        if "centos" in ov:
            return "CentOS"
        if "almalinux" in ov:
            return "AlmaLinux"
        if "rocky" in ov:
            return "Rocky Linux"
        return "Linux (Other)"

    if ot:
        return ot.title()
    return "Unknown"


def make_session(api_token: str, verify: Optional[bool or str]) -&amp;gt; requests.Session:
    """Create a requests session with retries and headers."""
    s = requests.Session()
    retries = Retry(
        total=5,
        backoff_factor=0.5,
        status_forcelist=(429, 500, 502, 503, 504),
        allowed_methods=("GET", "HEAD"),
    )
    s.mount("https://", HTTPAdapter(max_retries=retries))
    s.mount("http://", HTTPAdapter(max_retries=retries))
    s.headers.update({
        "Authorization": f"Api-Token {api_token}",
        "Accept": "application/json; charset=utf-8",
    })
    # Keep TLS verification setting on the session
    s.verify = verify
    return s


def fetch_hosts(session: requests.Session,
                base_url: str,
                page_size: int = 500,
                management_zone: Optional[str] = None) -&amp;gt; List[Dict[str, Any]]:
    """
    Fetch all HOST entities with osType, osVersion (and detectedName via properties)
    """
    url = f"{base_url.rstrip('/')}/api/v2/entities"
    entity_selector = 'type("HOST")'
    if management_zone:
        entity_selector += f',managementZone("{management_zone}")'

    params = {
        "entitySelector": entity_selector,
        # NOTE: displayName must NOT be included in fields=; properties.* are allowed.
        "fields": "properties.osType,properties.osVersion,properties.detectedName",
        "pageSize": page_size,
    }

    results: List[Dict[str, Any]] = []
    next_key: Optional[str] = None

    while True:
        if next_key:
            resp = session.get(url, params={"nextPageKey": next_key})
        else:
            resp = session.get(url, params=params)
        resp.raise_for_status()
        data = resp.json()

        entities = data.get("entities", [])
        results.extend(entities)

        next_key = data.get("nextPageKey")
        if not next_key:
            break

    return results


def summarize_and_flatten(entities: List[Dict[str, Any]]) -&amp;gt; Tuple[Counter, List[Dict[str, str]]]:
    """
    Returns:
      - Counter per platform
      - rows for detailed CSV (entityId, name, osType, osVersion, platform)
    """
    summary = Counter()
    hosts_rows: List[Dict[str, str]] = []

    for e in entities:
        props = e.get("properties", {}) or {}
        os_type = props.get("osType")
        os_version = props.get("osVersion")
        platform = normalize_platform(os_type, os_version)

        # Prefer displayName, then detectedName, then entityId
        display_name = e.get("displayName") or props.get("detectedName") or e.get("entityId")

        summary[platform] += 1
        hosts_rows.append({
            "entityId": e.get("entityId", ""),
            "name": display_name or "",
            "osType": os_type or "",
            "osVersion": os_version or "",
            "platform": platform,
        })

    # Sort rows by platform then name
    hosts_rows.sort(key=lambda r: (r["platform"].lower(), r["name"].lower()))
    return summary, hosts_rows


def write_summary_csv(path: str, summary: Counter) -&amp;gt; None:
    """Write platform summary to CSV."""
    with open(path, "w", newline="", encoding="utf-8") as f:
        w = csv.writer(f, delimiter=";")
        w.writerow(["platform", "count"])
        for platform, cnt in summary.most_common():
            w.writerow([platform, cnt])


def write_hosts_csv(path: str, rows: List[Dict[str, str]]) -&amp;gt; None:
    """Write detailed host list to CSV."""
    fieldnames = ["entityId", "name", "osType", "osVersion", "platform"]
    with open(path, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=fieldnames, delimiter=";")
        w.writeheader()
        for r in rows:
            w.writerow(r)


def print_console_table(summary: Counter) -&amp;gt; None:
    """Pretty-print a small summary table to console."""
    total = sum(summary.values())
    if total == 0:
        print("No hosts returned (0).")
        return

    width = max((len(k) for k in summary.keys()), default=8)
    print("\nHosts by operating system (summary):")
    print(f"{'Platform'.ljust(width)} | {'Count':&amp;gt;6} | {'Share':&amp;gt;6}")
    print("-" * (width + 22))
    for platform, cnt in summary.most_common():
        share = f"{(cnt/total*100):.1f}%"
        print(f"{platform.ljust(width)} | {str(cnt).rjust(6)} | {share.rjust(6)}")
    print("-" * (width + 22))
    print(f"{'TOTAL'.ljust(width)} | {str(total).rjust(6)} | 100.0%\n")


def parse_args() -&amp;gt; argparse.Namespace:
    p = argparse.ArgumentParser(description="Count hosts by OS (Dynatrace API v2).")
    p.add_argument("--base-url", default=DT_BASE_URL,
                   help="Base environment URL, e.g., https://dynatrace.example.com/e/&amp;lt;env-id&amp;gt;")
    p.add_argument("--token", default=DT_API_TOKEN,
                   help="API token with entities.read permission")
    p.add_argument("--mz", default=None,
                   help="(optional) Management Zone name to filter")
    p.add_argument("--page-size", type=int, default=500,
                   help="Page size (default 500)")
    p.add_argument("--out-summary", default=DEFAULT_SUMMARY_CSV,
                   help=f"CSV file for platform summary (default {DEFAULT_SUMMARY_CSV})")
    p.add_argument("--out-hosts", default=DEFAULT_HOSTS_CSV,
                   help=f"CSV file for host list (default {DEFAULT_HOSTS_CSV})")
    p.add_argument("--verify", default=None,
                   help="Path to trusted CA file (e.g., /etc/ssl/certs/rootCA.pem). "
                        "If omitted, system CA will be used.")
    p.add_argument("--insecure", action="store_true",
                   help="Disable TLS verification (NOT recommended).")
    return p.parse_args()


def main() -&amp;gt; int:
    args = parse_args()

    # Basic param validation
    if not args.base_url or "example.com" in args.base_url:
        print("Please set --base-url or adjust DT_BASE_URL in the script.", file=sys.stderr)
        return 2
    if not args.token or args.token.endswith("REPLACE_ME"):
        print("Please set --token or adjust DT_API_TOKEN in the script.", file=sys.stderr)
        return 2

    # TLS verification
    verify: Optional[bool or str]
    if args.insecure:
        verify = False
    elif args.verify:
        verify = args.verify
    else:
        verify = True  # system CA

    # Create session
    session = make_session(args.token, verify)

    # Fetch hosts
    try:
        entities = fetch_hosts(
            session=session,
            base_url=args.base_url,
            page_size=args.page_size,
            management_zone=args.mz,
        )
    except requests.HTTPError as e:
        print(f"HTTP error: {e} | body: {getattr(e.response, 'text', '')}", file=sys.stderr)
        return 1
    except requests.RequestException as e:
        print(f"✖ Network error: {e}", file=sys.stderr)
        return 1

    # Summarize
    summary, host_rows = summarize_and_flatten(entities)

    # Print console table
    print_console_table(summary)

    # Write CSV outputs
    try:
        write_summary_csv(args.out_summary, summary)
        write_hosts_csv(args.out_hosts, host_rows)
    except OSError as e:
        print(f"File write error: {e}", file=sys.stderr)
        return 1

    print(f"Summary CSV saved: {args.out_summary}")
    print(f"Hosts CSV saved:   {args.out_hosts}")
    return 0


if __name__ == "__main__":
    sys.exit(main())&lt;/LI-CODE&gt;</description>
    <pubDate>Thu, 30 Oct 2025 20:05:21 GMT</pubDate>
    <dc:creator>t_pawlak</dc:creator>
    <dc:date>2025-10-30T20:05:21Z</dc:date>
    <item>
      <title>How to Display OS Count Per Platform on a Dynatrace Managed Dashboard</title>
      <link>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288855#M5477</link>
      <description>&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Hi everyone,&lt;/P&gt;
&lt;P&gt;I’m using &lt;STRONG&gt;Dynatrace Managed (on-prem)&lt;/STRONG&gt; and I’d like to create a &lt;STRONG&gt;dashboard tile&lt;/STRONG&gt; that shows how many hosts there are per &lt;STRONG&gt;operating system/platform&lt;/STRONG&gt; — for example:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;P&gt;Red Hat Enterprise Linux&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;
&lt;P&gt;Orcale Linux&lt;/P&gt;
&lt;/LI&gt;
&lt;LI&gt;
&lt;P&gt;Windows&lt;/P&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;I’ve tried using metric expressions such as:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE&gt;&lt;SPAN&gt;builtin&lt;SPAN class=""&gt;:host&lt;/SPAN&gt;&lt;SPAN class=""&gt;.availability&lt;/SPAN&gt;&lt;SPAN class=""&gt;.state&lt;/SPAN&gt;:&lt;SPAN class=""&gt;splitBy&lt;/SPAN&gt;(&lt;SPAN class=""&gt;"osVersion"&lt;/SPAN&gt;):count&lt;/SPAN&gt;&lt;/PRE&gt;
&lt;P&gt;and&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;PRE&gt;&lt;SPAN&gt;builtin&lt;SPAN class=""&gt;:host&lt;/SPAN&gt;&lt;SPAN class=""&gt;.availability&lt;/SPAN&gt;&lt;SPAN class=""&gt;.state&lt;/SPAN&gt;:&lt;SPAN class=""&gt;splitBy&lt;/SPAN&gt;(&lt;SPAN class=""&gt;"osType"&lt;/SPAN&gt;):count&lt;/SPAN&gt;&lt;/PRE&gt;
&lt;P&gt;but both return no data in my Managed environment.&lt;/P&gt;
&lt;P&gt;It seems that osType or osVersion are not available as dimensions for metrics in the on-prem version.&lt;/P&gt;
&lt;P&gt;Is there an alternative metric or configuration that can provide this?&lt;/P&gt;
&lt;P&gt;I currently have three tiles configured based on "CPU system," but I don't know what "CPU System" does. I'd prefer it to be counted based on "how many Oracle Linux/RHEL/Windows OS systems are present?"&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="NumberOS1.jpg" style="width: 400px;"&gt;&lt;img src="https://community.dynatrace.com/t5/image/serverpage/image-id/30771i159D65C551140511/image-size/medium?v=v2&amp;amp;px=400" role="button" title="NumberOS1.jpg" alt="NumberOS1.jpg" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="NumberOS2.jpg" style="width: 400px;"&gt;&lt;img src="https://community.dynatrace.com/t5/image/serverpage/image-id/30772i68B900DBC0FA73AC/image-size/medium?v=v2&amp;amp;px=400" role="button" title="NumberOS2.jpg" alt="NumberOS2.jpg" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;  &lt;/P&gt;</description>
      <pubDate>Fri, 14 Nov 2025 11:56:04 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288855#M5477</guid>
      <dc:creator>Hugo1984</dc:creator>
      <dc:date>2025-11-14T11:56:04Z</dc:date>
    </item>
    <item>
      <title>Re: How can I show the OS count per platform on a Dynatrace Managed (on-prem) dashboard?</title>
      <link>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288875#M5479</link>
      <description>&lt;P&gt;Hi,&lt;/P&gt;&lt;P&gt;What about ingest &lt;A title="custom host metadata" href="https://docs.dynatrace.com/docs/shortlink/oneagentctl#custom-host-metadata" target="_blank" rel="noopener"&gt;custom host metadata&lt;/A&gt; from OneAgent? Then, you can create automatic tagging rules and filtering base on tag.&lt;/P&gt;&lt;P&gt;Best regards&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Thu, 30 Oct 2025 14:40:21 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288875#M5479</guid>
      <dc:creator>AntonPineiro</dc:creator>
      <dc:date>2025-10-30T14:40:21Z</dc:date>
    </item>
    <item>
      <title>Re: How can I show the OS count per platform on a Dynatrace Managed (on-prem) dashboard?</title>
      <link>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288878#M5481</link>
      <description>&lt;P&gt;Hi,&lt;/P&gt;&lt;P&gt;Thank you for your response and advice. I will discuss this with my team.&lt;/P&gt;</description>
      <pubDate>Thu, 30 Oct 2025 15:16:35 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288878#M5481</guid>
      <dc:creator>Hugo1984</dc:creator>
      <dc:date>2025-10-30T15:16:35Z</dc:date>
    </item>
    <item>
      <title>Re: How can I show the OS count per platform on a Dynatrace Managed (on-prem) dashboard?</title>
      <link>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288889#M5482</link>
      <description>&lt;P&gt;It's a little bit tricky, but doable. Dashboards classic may contain only metrics, no code. Using host.availability metric is a good idea. But to filter hosts by a specific os version, the &lt;A href="https://docs.dynatrace.com/docs/shortlink/api-entities-v2-selector#attribute" target="_blank"&gt;Entity selector&lt;/A&gt;&amp;nbsp;is not flexible enough, as it allows only an exact match on osVersion value as it's an attribute.&lt;BR /&gt;&lt;BR /&gt;However, there is a simple workaround. Create auto tags for each OS type you need and put os version you want based on conditions:&lt;/P&gt;&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Julius_Loman_0-1761853365988.png" style="width: 400px;"&gt;&lt;img src="https://community.dynatrace.com/t5/image/serverpage/image-id/30778i34CD95AC21AB49BA/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Julius_Loman_0-1761853365988.png" alt="Julius_Loman_0-1761853365988.png" /&gt;&lt;/span&gt;&lt;/P&gt;&lt;P&gt;And then create a data explorer tile with the host availability metric and utilising the tag. Be sure to choose aggregation, as we want count and for fold aggregation avg or last value (depends on what you want to display):&lt;/P&gt;&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Julius_Loman_1-1761853429504.png" style="width: 400px;"&gt;&lt;img src="https://community.dynatrace.com/t5/image/serverpage/image-id/30779i7627916D43CB082C/image-size/medium?v=v2&amp;amp;px=400" role="button" title="Julius_Loman_1-1761853429504.png" alt="Julius_Loman_1-1761853429504.png" /&gt;&lt;/span&gt;&lt;/P&gt;&lt;P&gt;Hope this helps.&lt;/P&gt;&lt;P&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/P&gt;</description>
      <pubDate>Thu, 30 Oct 2025 19:45:29 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288889#M5482</guid>
      <dc:creator>Julius_Loman</dc:creator>
      <dc:date>2025-10-30T19:45:29Z</dc:date>
    </item>
    <item>
      <title>Re: How can I show the OS count per platform on a Dynatrace Managed (on-prem) dashboard?</title>
      <link>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288891#M5483</link>
      <description>&lt;P&gt;Hi, you can try by API.&lt;BR /&gt;here is script, with make api call and save OS version to file.&lt;BR /&gt;Then you can send result as a metric and make present it on dashboard:&lt;/P&gt;&lt;LI-CODE lang="python"&gt;#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import csv
import sys
from collections import Counter
from typing import Dict, List, Optional, Any, Tuple

import requests
from requests.adapters import HTTPAdapter, Retry

# === CONFIGURATION (edit as needed) ===
DT_BASE_URL = "https://dynatrace.example.com/e/xxx"  # Your base URL (Managed/SaaS) with /e/&amp;lt;env-id&amp;gt;
DT_API_TOKEN = "API TOKEN"  # Token with entities.read permission

# Default output files:
DEFAULT_SUMMARY_CSV = "hosts_by_os_summary.csv"
DEFAULT_HOSTS_CSV   = "hosts_by_os_hosts.csv"


# === Platform normalization based on osType/osVersion ===
def normalize_platform(os_type: Optional[str], os_version: Optional[str]) -&amp;gt; str:
    ot = (os_type or "").strip().upper()
    ov = (os_version or "").strip().lower()

    if ot == "WINDOWS":
        return "Windows"
    if ot == "AIX":
        return "AIX"
    if ot == "SOLARIS":
        return "Solaris"
    if ot in ("ZOS", "Z/OS"):
        return "z/OS"

    if ot == "LINUX":
        if "oracle" in ov or " ol " in f" {ov} ":
            return "Oracle Linux"
        if "red hat" in ov or "rhel" in ov:
            return "Red Hat Enterprise Linux"
        if "ubuntu" in ov:
            return "Ubuntu"
        if "debian" in ov:
            return "Debian"
        if "suse" in ov or "sles" in ov:
            return "SUSE Linux"
        if "centos" in ov:
            return "CentOS"
        if "almalinux" in ov:
            return "AlmaLinux"
        if "rocky" in ov:
            return "Rocky Linux"
        return "Linux (Other)"

    if ot:
        return ot.title()
    return "Unknown"


def make_session(api_token: str, verify: Optional[bool or str]) -&amp;gt; requests.Session:
    """Create a requests session with retries and headers."""
    s = requests.Session()
    retries = Retry(
        total=5,
        backoff_factor=0.5,
        status_forcelist=(429, 500, 502, 503, 504),
        allowed_methods=("GET", "HEAD"),
    )
    s.mount("https://", HTTPAdapter(max_retries=retries))
    s.mount("http://", HTTPAdapter(max_retries=retries))
    s.headers.update({
        "Authorization": f"Api-Token {api_token}",
        "Accept": "application/json; charset=utf-8",
    })
    # Keep TLS verification setting on the session
    s.verify = verify
    return s


def fetch_hosts(session: requests.Session,
                base_url: str,
                page_size: int = 500,
                management_zone: Optional[str] = None) -&amp;gt; List[Dict[str, Any]]:
    """
    Fetch all HOST entities with osType, osVersion (and detectedName via properties)
    """
    url = f"{base_url.rstrip('/')}/api/v2/entities"
    entity_selector = 'type("HOST")'
    if management_zone:
        entity_selector += f',managementZone("{management_zone}")'

    params = {
        "entitySelector": entity_selector,
        # NOTE: displayName must NOT be included in fields=; properties.* are allowed.
        "fields": "properties.osType,properties.osVersion,properties.detectedName",
        "pageSize": page_size,
    }

    results: List[Dict[str, Any]] = []
    next_key: Optional[str] = None

    while True:
        if next_key:
            resp = session.get(url, params={"nextPageKey": next_key})
        else:
            resp = session.get(url, params=params)
        resp.raise_for_status()
        data = resp.json()

        entities = data.get("entities", [])
        results.extend(entities)

        next_key = data.get("nextPageKey")
        if not next_key:
            break

    return results


def summarize_and_flatten(entities: List[Dict[str, Any]]) -&amp;gt; Tuple[Counter, List[Dict[str, str]]]:
    """
    Returns:
      - Counter per platform
      - rows for detailed CSV (entityId, name, osType, osVersion, platform)
    """
    summary = Counter()
    hosts_rows: List[Dict[str, str]] = []

    for e in entities:
        props = e.get("properties", {}) or {}
        os_type = props.get("osType")
        os_version = props.get("osVersion")
        platform = normalize_platform(os_type, os_version)

        # Prefer displayName, then detectedName, then entityId
        display_name = e.get("displayName") or props.get("detectedName") or e.get("entityId")

        summary[platform] += 1
        hosts_rows.append({
            "entityId": e.get("entityId", ""),
            "name": display_name or "",
            "osType": os_type or "",
            "osVersion": os_version or "",
            "platform": platform,
        })

    # Sort rows by platform then name
    hosts_rows.sort(key=lambda r: (r["platform"].lower(), r["name"].lower()))
    return summary, hosts_rows


def write_summary_csv(path: str, summary: Counter) -&amp;gt; None:
    """Write platform summary to CSV."""
    with open(path, "w", newline="", encoding="utf-8") as f:
        w = csv.writer(f, delimiter=";")
        w.writerow(["platform", "count"])
        for platform, cnt in summary.most_common():
            w.writerow([platform, cnt])


def write_hosts_csv(path: str, rows: List[Dict[str, str]]) -&amp;gt; None:
    """Write detailed host list to CSV."""
    fieldnames = ["entityId", "name", "osType", "osVersion", "platform"]
    with open(path, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=fieldnames, delimiter=";")
        w.writeheader()
        for r in rows:
            w.writerow(r)


def print_console_table(summary: Counter) -&amp;gt; None:
    """Pretty-print a small summary table to console."""
    total = sum(summary.values())
    if total == 0:
        print("No hosts returned (0).")
        return

    width = max((len(k) for k in summary.keys()), default=8)
    print("\nHosts by operating system (summary):")
    print(f"{'Platform'.ljust(width)} | {'Count':&amp;gt;6} | {'Share':&amp;gt;6}")
    print("-" * (width + 22))
    for platform, cnt in summary.most_common():
        share = f"{(cnt/total*100):.1f}%"
        print(f"{platform.ljust(width)} | {str(cnt).rjust(6)} | {share.rjust(6)}")
    print("-" * (width + 22))
    print(f"{'TOTAL'.ljust(width)} | {str(total).rjust(6)} | 100.0%\n")


def parse_args() -&amp;gt; argparse.Namespace:
    p = argparse.ArgumentParser(description="Count hosts by OS (Dynatrace API v2).")
    p.add_argument("--base-url", default=DT_BASE_URL,
                   help="Base environment URL, e.g., https://dynatrace.example.com/e/&amp;lt;env-id&amp;gt;")
    p.add_argument("--token", default=DT_API_TOKEN,
                   help="API token with entities.read permission")
    p.add_argument("--mz", default=None,
                   help="(optional) Management Zone name to filter")
    p.add_argument("--page-size", type=int, default=500,
                   help="Page size (default 500)")
    p.add_argument("--out-summary", default=DEFAULT_SUMMARY_CSV,
                   help=f"CSV file for platform summary (default {DEFAULT_SUMMARY_CSV})")
    p.add_argument("--out-hosts", default=DEFAULT_HOSTS_CSV,
                   help=f"CSV file for host list (default {DEFAULT_HOSTS_CSV})")
    p.add_argument("--verify", default=None,
                   help="Path to trusted CA file (e.g., /etc/ssl/certs/rootCA.pem). "
                        "If omitted, system CA will be used.")
    p.add_argument("--insecure", action="store_true",
                   help="Disable TLS verification (NOT recommended).")
    return p.parse_args()


def main() -&amp;gt; int:
    args = parse_args()

    # Basic param validation
    if not args.base_url or "example.com" in args.base_url:
        print("Please set --base-url or adjust DT_BASE_URL in the script.", file=sys.stderr)
        return 2
    if not args.token or args.token.endswith("REPLACE_ME"):
        print("Please set --token or adjust DT_API_TOKEN in the script.", file=sys.stderr)
        return 2

    # TLS verification
    verify: Optional[bool or str]
    if args.insecure:
        verify = False
    elif args.verify:
        verify = args.verify
    else:
        verify = True  # system CA

    # Create session
    session = make_session(args.token, verify)

    # Fetch hosts
    try:
        entities = fetch_hosts(
            session=session,
            base_url=args.base_url,
            page_size=args.page_size,
            management_zone=args.mz,
        )
    except requests.HTTPError as e:
        print(f"HTTP error: {e} | body: {getattr(e.response, 'text', '')}", file=sys.stderr)
        return 1
    except requests.RequestException as e:
        print(f"✖ Network error: {e}", file=sys.stderr)
        return 1

    # Summarize
    summary, host_rows = summarize_and_flatten(entities)

    # Print console table
    print_console_table(summary)

    # Write CSV outputs
    try:
        write_summary_csv(args.out_summary, summary)
        write_hosts_csv(args.out_hosts, host_rows)
    except OSError as e:
        print(f"File write error: {e}", file=sys.stderr)
        return 1

    print(f"Summary CSV saved: {args.out_summary}")
    print(f"Hosts CSV saved:   {args.out_hosts}")
    return 0


if __name__ == "__main__":
    sys.exit(main())&lt;/LI-CODE&gt;</description>
      <pubDate>Thu, 30 Oct 2025 20:05:21 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288891#M5483</guid>
      <dc:creator>t_pawlak</dc:creator>
      <dc:date>2025-10-30T20:05:21Z</dc:date>
    </item>
    <item>
      <title>Re: How can I show the OS count per platform on a Dynatrace Managed (on-prem) dashboard?</title>
      <link>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288906#M5484</link>
      <description>&lt;P&gt;I think this might work and will discuss it with my team. I'll keep you updated.&lt;/P&gt;&lt;P&gt;Thanks for your response.&lt;/P&gt;</description>
      <pubDate>Fri, 31 Oct 2025 08:18:04 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288906#M5484</guid>
      <dc:creator>Hugo1984</dc:creator>
      <dc:date>2025-10-31T08:18:04Z</dc:date>
    </item>
    <item>
      <title>Re: How can I show the OS count per platform on a Dynatrace Managed (on-prem) dashboard?</title>
      <link>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288914#M5485</link>
      <description>&lt;P&gt;This is the best answer to my question. I've implemented it and it works.&lt;/P&gt;&lt;P&gt;Thanks for your help.&lt;/P&gt;</description>
      <pubDate>Fri, 31 Oct 2025 13:09:25 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288914#M5485</guid>
      <dc:creator>Hugo1984</dc:creator>
      <dc:date>2025-10-31T13:09:25Z</dc:date>
    </item>
    <item>
      <title>Re: How can I show the OS count per platform on a Dynatrace Managed (on-prem) dashboard?</title>
      <link>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288915#M5486</link>
      <description>&lt;P&gt;Thank you for your help.&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Fri, 31 Oct 2025 13:11:17 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Dashboarding/How-to-Display-OS-Count-Per-Platform-on-a-Dynatrace-Managed/m-p/288915#M5486</guid>
      <dc:creator>Hugo1984</dc:creator>
      <dc:date>2025-10-31T13:11:17Z</dc:date>
    </item>
  </channel>
</rss>

