123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- """
- Netdata Unit converter tool.
- """
- import csv
- import sys
- #
- # Mapping Rules:
- # * Map to pre-defined UCUM units as much as possible
- # * Metrics that count things and can be expressed as "3 apples" in a sentence,
- # should have the singular thing to be counted in curly bracket. Like
- # "{apple}".
- # * Other dimensionless values, like a state should map to "1". E.g. you
- # cannot say "1 status" or "0 bool", so no unit should be displayed after
- # the value.
- #
- #
- # Mapping from existing (constituent) units to UCUM units
- #
- mapping = {
- "%": "%",
- "% of time working": "%",
- "Ah": "A.h",
- "Ampere": "A",
- "Amps": "A",
- "B": "By",
- "Celsius": "Cel",
- "Fahrenheit": "[degF]",
- "GiB": "GiBy",
- "Hz": "Hz",
- "Joule": "J",
- "KB": "KiBy",
- "KiB": "KiBy",
- "MHz": "MHz",
- "Mbps": "Mibit/s",
- "MB": "MiBy",
- "MiB": "MiBy",
- "Minute": "min",
- "Minutes": "min",
- "Percent": "%",
- "RPM": "{rotation}/min",
- "Status": "1", # bit field, dimensionless but no suffix
- "V": "V",
- "Volts": "V",
- "Watt": "W",
- "Watts": "W",
- "Wh": "W.h",
- "active connections": "{connection}",
- "bool": "1",
- "boolean": "1",
- "byte": "By",
- "bytes": "By",
- "celsius": "Cel",
- "children": "{child}",
- "context switches": "{context switch}",
- "count": "1",
- "cpu time": "1",
- "current threads": "{thread}",
- "cycle count": "{cycle}",
- "dBm": "dB[mW]",
- "dests": "{destination}",
- "difference": "1",
- "directories": "{directory}",
- "exit status": "1",
- "entries": "{entry}",
- "expired": "{object}",
- "failed disks": "{disk}",
- "failed servers": "{server}",
- "flushes": "{flush}",
- "health servers": "{server}",
- "hours": "h",
- "is degraded": "1",
- "kilobit": "Kibit",
- "kilobits": "Kibit",
- "kilobyte": "KiBy",
- "kilobytes": "KiBy",
- "merged operations": "{operation}",
- "microseconds": "us",
- "microseconds lost": "us",
- "milisecondds": "ms",
- "miliseconds": "ms",
- "milliseconds": "ms",
- "misses": "{miss}",
- "ms": "ms",
- "ns": "ns",
- "num": "1",
- "nuked": "{object}",
- "number": "1",
- "octet": "By",
- "open files": "{file}",
- "open pipes": "{pipe}",
- "ops": "{operation}",
- "percent": "%",
- "percentage": "%",
- "pps": "{packet}/s",
- "prefetches": "{prefetch}",
- "processes": "{process}",
- "queries": "{query}",
- "replies": "{reply}",
- "retries": "{retry}",
- "s": "s",
- "searches": "{search}",
- "sec": "s",
- "seconds": "s",
- "status": "1",
- "switches": "{switch}",
- "temperature": "Cel",
- "total": "1",
- "unsynchronised blocks": "{block}",
- "value": "1",
- "z": "1",
- # From go.d.plugin
- "assemblies": "{assembly}",
- "classes": "{class}",
- "conns": "{connection}",
- "difficulty": "1",
- "dispatches": "{dispatch}",
- "fetches": "{fetch}",
- "gpa": "{modification}",
- "hmode": "1",
- "indexes": "{index}",
- "log2": "1",
- "mac addresses": "{mac address}",
- "message bacthes": "{batch}",
- "message batches": "{batch}",
- "millicpu": "m{cpu}",
- "pgfaults": "{page fault}",
- "pmode": "1",
- "ppm": "[ppm]",
- "publishes": "{publish}",
- "queue_length": "1",
- "queue_processes": "{queue process}",
- "queued_size": "1",
- "ratio": "1",
- "running_containers": "{container}",
- "running_pod": "{pod}",
- "running_pods": "{pod}",
- "state": "1",
- "stratum": "1",
- "token_requests": "{request}",
- "us": "us",
- "watches": "{watch}",
- "xid": "1",
- }
- #
- # Units for specific metric names
- #
- overrides = {
- "varnish.threads_total": "{thread}",
- "dovecot.sessions": "{session}",
- "dovecot.faults": "{page fault}",
- "dovecot.logins": "{login}",
- "dovecot.lookup": "{lookup}/s",
- "mem.pgfaults": "{page fault}/s",
- "system.pgfaults": "{page fault}/s",
- "smartd_log.erase_fail_count": "{erase}",
- "smartd_log.program_fail_count": "{program}",
- "mysql.thread_cache_misses": "{thread}",
- "mysql.galera_cluster_weight": "1",
- "hdfs.load": "{concurrent file accesses}",
- "docker.container_writeable_layer_size": "By",
- }
- def convert_unit(metric_name: str, unit: str) -> str:
- """
- Map an old unit to a UCUM-conforming one.
- 1. If the metric name exists in `overrides`, use that.
- 2. Otherwise:
- - Split the unit in components separated by a slash
- - Strip the whitespace.
- 3. For each part:
- - Check if the unit appears in `mapping` and then use that.
- - Otherwise:
- - Strip a final s to make it singular
- - Put in between curly braces.
- - Lowercase
- 4. Join parts with a slash.
- """
- try:
- result = overrides[metric_name]
- except KeyError:
- parts = [part.strip() for part in unit.split("/")]
- result_parts = []
- for part in parts:
- try:
- part = mapping[part]
- except KeyError:
- if part[-1] == "s":
- part = part[:-1]
- part = f"{{{part}}}".lower()
- result_parts.append(part)
- result = "/".join(result_parts)
- return result
- def main() -> None:
- """
- Open file as CSV and print out rows with converted units for verification.
- The CSV is expected to follow the metrics.csv format.
- """
- with open(sys.argv[1], encoding="utf-8") as csvfile:
- rows = csv.reader(csvfile, delimiter=",", quotechar='"')
- for row in rows:
- if row[0] == "metric" and row[3] == "unit":
- continue
- print("-----")
- print(f"Metric: {row[0]}")
- print(f" Scope: {row[1]}")
- print(f" Dimensions: {row[2]}")
- old_unit = row[3]
- converted_unit = convert_unit(row[0], old_unit)
- print(f" Unit: {old_unit} ----> {converted_unit}")
- print(f" Description: {row[4]}")
- print(f" Chart type: {row[5]}")
- print(f" Labels: {row[6]}")
- print(f" Plugin: {row[7]}")
- print(f" Module: {row[8]}")
- if __name__ == "__main__":
- main()
|