30 Oct 2025 10:44 AM
Hi everyone,
I’m using Dynatrace Managed (on-prem) and I’d like to create a dashboard tile that shows how many hosts there are per operating system/platform — for example:
Red Hat Enterprise Linux
Orcale Linux
Windows
I’ve tried using metric expressions such as:
builtin:host.availability.state:splitBy("osVersion"):countand
builtin:host.availability.state:splitBy("osType"):countbut both return no data in my Managed environment.
It seems that osType or osVersion are not available as dimensions for metrics in the on-prem version.
Is there an alternative metric or configuration that can provide this?
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?"
Solved! Go to Solution.
30 Oct 2025 02:40 PM
Hi,
What about ingest custom host metadata from OneAgent? Then, you can create automatic tagging rules and filtering base on tag.
Best regards
30 Oct 2025 03:16 PM
Hi,
Thank you for your response and advice. I will discuss this with my team.
30 Oct 2025 07:45 PM
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 Entity selector is not flexible enough, as it allows only an exact match on osVersion value as it's an attribute.
However, there is a simple workaround. Create auto tags for each OS type you need and put os version you want based on conditions:
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):
Hope this helps.
31 Oct 2025 08:18 AM
I think this might work and will discuss it with my team. I'll keep you updated.
Thanks for your response.
31 Oct 2025 01:09 PM
This is the best answer to my question. I've implemented it and it works.
Thanks for your help.
30 Oct 2025 08:05 PM
Hi, you can try by API.
here is script, with make api call and save OS version to file.
Then you can send result as a metric and make present it on dashboard:
#!/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/<env-id>
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]) -> 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]) -> 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) -> 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]]) -> 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) -> 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]]) -> 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) -> 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':>6} | {'Share':>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() -> 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/<env-id>")
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() -> 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())
31 Oct 2025 01:11 PM
Thank you for your help.