units.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. """
  2. Netdata Unit converter tool.
  3. """
  4. import csv
  5. import sys
  6. #
  7. # Mapping Rules:
  8. # * Map to pre-defined UCUM units as much as possible
  9. # * Metrics that count things and can be expressed as "3 apples" in a sentence,
  10. # should have the singular thing to be counted in curly bracket. Like
  11. # "{apple}".
  12. # * Other dimensionless values, like a state should map to "1". E.g. you
  13. # cannot say "1 status" or "0 bool", so no unit should be displayed after
  14. # the value.
  15. #
  16. #
  17. # Mapping from existing (constituent) units to UCUM units
  18. #
  19. mapping = {
  20. "%": "%",
  21. "% of time working": "%",
  22. "Ah": "A.h",
  23. "Ampere": "A",
  24. "Amps": "A",
  25. "B": "By",
  26. "Celsius": "Cel",
  27. "Fahrenheit": "[degF]",
  28. "GiB": "GiBy",
  29. "Hz": "Hz",
  30. "Joule": "J",
  31. "KB": "KiBy",
  32. "KiB": "KiBy",
  33. "MHz": "MHz",
  34. "Mbps": "Mibit/s",
  35. "MB": "MiBy",
  36. "MiB": "MiBy",
  37. "Minute": "min",
  38. "Minutes": "min",
  39. "Percent": "%",
  40. "RPM": "{rotation}/min",
  41. "Status": "1", # bit field, dimensionless but no suffix
  42. "V": "V",
  43. "Volts": "V",
  44. "Watt": "W",
  45. "Watts": "W",
  46. "Wh": "W.h",
  47. "active connections": "{connection}",
  48. "bool": "1",
  49. "boolean": "1",
  50. "byte": "By",
  51. "bytes": "By",
  52. "celsius": "Cel",
  53. "children": "{child}",
  54. "context switches": "{context switch}",
  55. "count": "1",
  56. "cpu time": "1",
  57. "current threads": "{thread}",
  58. "cycle count": "{cycle}",
  59. "dBm": "dB[mW]",
  60. "dests": "{destination}",
  61. "difference": "1",
  62. "directories": "{directory}",
  63. "exit status": "1",
  64. "entries": "{entry}",
  65. "expired": "{object}",
  66. "failed disks": "{disk}",
  67. "failed servers": "{server}",
  68. "flushes": "{flush}",
  69. "health servers": "{server}",
  70. "hours": "h",
  71. "is degraded": "1",
  72. "kilobit": "Kibit",
  73. "kilobits": "Kibit",
  74. "kilobyte": "KiBy",
  75. "kilobytes": "KiBy",
  76. "merged operations": "{operation}",
  77. "microseconds": "us",
  78. "microseconds lost": "us",
  79. "milisecondds": "ms",
  80. "miliseconds": "ms",
  81. "milliseconds": "ms",
  82. "misses": "{miss}",
  83. "ms": "ms",
  84. "ns": "ns",
  85. "num": "1",
  86. "nuked": "{object}",
  87. "number": "1",
  88. "octet": "By",
  89. "open files": "{file}",
  90. "open pipes": "{pipe}",
  91. "ops": "{operation}",
  92. "percent": "%",
  93. "percentage": "%",
  94. "pps": "{packet}/s",
  95. "prefetches": "{prefetch}",
  96. "processes": "{process}",
  97. "queries": "{query}",
  98. "replies": "{reply}",
  99. "retries": "{retry}",
  100. "s": "s",
  101. "searches": "{search}",
  102. "sec": "s",
  103. "seconds": "s",
  104. "status": "1",
  105. "switches": "{switch}",
  106. "temperature": "Cel",
  107. "total": "1",
  108. "unsynchronised blocks": "{block}",
  109. "value": "1",
  110. "z": "1",
  111. # From go.d.plugin
  112. "assemblies": "{assembly}",
  113. "classes": "{class}",
  114. "conns": "{connection}",
  115. "difficulty": "1",
  116. "dispatches": "{dispatch}",
  117. "fetches": "{fetch}",
  118. "gpa": "{modification}",
  119. "hmode": "1",
  120. "indexes": "{index}",
  121. "log2": "1",
  122. "mac addresses": "{mac address}",
  123. "message bacthes": "{batch}",
  124. "message batches": "{batch}",
  125. "millicpu": "m{cpu}",
  126. "pgfaults": "{page fault}",
  127. "pmode": "1",
  128. "ppm": "[ppm]",
  129. "publishes": "{publish}",
  130. "queue_length": "1",
  131. "queue_processes": "{queue process}",
  132. "queued_size": "1",
  133. "ratio": "1",
  134. "running_containers": "{container}",
  135. "running_pod": "{pod}",
  136. "running_pods": "{pod}",
  137. "state": "1",
  138. "stratum": "1",
  139. "token_requests": "{request}",
  140. "us": "us",
  141. "watches": "{watch}",
  142. "xid": "1",
  143. }
  144. #
  145. # Units for specific metric names
  146. #
  147. overrides = {
  148. "varnish.threads_total": "{thread}",
  149. "dovecot.sessions": "{session}",
  150. "dovecot.faults": "{page fault}",
  151. "dovecot.logins": "{login}",
  152. "dovecot.lookup": "{lookup}/s",
  153. "mem.pgfaults": "{page fault}/s",
  154. "system.pgfaults": "{page fault}/s",
  155. "smartd_log.erase_fail_count": "{erase}",
  156. "smartd_log.program_fail_count": "{program}",
  157. "mysql.thread_cache_misses": "{thread}",
  158. "mysql.galera_cluster_weight": "1",
  159. "hdfs.load": "{concurrent file accesses}",
  160. "docker.container_writeable_layer_size": "By",
  161. }
  162. def convert_unit(metric_name: str, unit: str) -> str:
  163. """
  164. Map an old unit to a UCUM-conforming one.
  165. 1. If the metric name exists in `overrides`, use that.
  166. 2. Otherwise:
  167. - Split the unit in components separated by a slash
  168. - Strip the whitespace.
  169. 3. For each part:
  170. - Check if the unit appears in `mapping` and then use that.
  171. - Otherwise:
  172. - Strip a final s to make it singular
  173. - Put in between curly braces.
  174. - Lowercase
  175. 4. Join parts with a slash.
  176. """
  177. try:
  178. result = overrides[metric_name]
  179. except KeyError:
  180. parts = [part.strip() for part in unit.split("/")]
  181. result_parts = []
  182. for part in parts:
  183. try:
  184. part = mapping[part]
  185. except KeyError:
  186. if part[-1] == "s":
  187. part = part[:-1]
  188. part = f"{{{part}}}".lower()
  189. result_parts.append(part)
  190. result = "/".join(result_parts)
  191. return result
  192. def main() -> None:
  193. """
  194. Open file as CSV and print out rows with converted units for verification.
  195. The CSV is expected to follow the metrics.csv format.
  196. """
  197. with open(sys.argv[1], encoding="utf-8") as csvfile:
  198. rows = csv.reader(csvfile, delimiter=",", quotechar='"')
  199. for row in rows:
  200. if row[0] == "metric" and row[3] == "unit":
  201. continue
  202. print("-----")
  203. print(f"Metric: {row[0]}")
  204. print(f" Scope: {row[1]}")
  205. print(f" Dimensions: {row[2]}")
  206. old_unit = row[3]
  207. converted_unit = convert_unit(row[0], old_unit)
  208. print(f" Unit: {old_unit} ----> {converted_unit}")
  209. print(f" Description: {row[4]}")
  210. print(f" Chart type: {row[5]}")
  211. print(f" Labels: {row[6]}")
  212. print(f" Plugin: {row[7]}")
  213. print(f" Module: {row[8]}")
  214. if __name__ == "__main__":
  215. main()