renderer.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. from math import ceil
  2. __all__ = ["Renderer"]
  3. STANDARD_DICT = {
  4. "Memory": "Memory",
  5. "#Proc": "#Proc",
  6. "Temp": "Temp",
  7. "Util": "Util",
  8. }
  9. SHORTENED_DICT = {
  10. "Memory": "Mem",
  11. "#Proc": "#Pr",
  12. "Temp": "T",
  13. "Util": "Ut",
  14. }
  15. def build_line(fill_char, length):
  16. return "|" + fill_char * (length - 2) + "|"
  17. def fill_line(line, length, fill_char=" "):
  18. return line + fill_char * (length - len(line) - 1) + "|"
  19. class Renderer:
  20. def __init__(
  21. self,
  22. progress_bar_width=50,
  23. columns=1,
  24. gpu_inner_spacings=True,
  25. gpu_outer_spacings=True,
  26. node_names=None,
  27. display_power=True,
  28. display_users=False,
  29. dict_type="default",
  30. available_gpus_only=False,
  31. ) -> None:
  32. assert dict_type in ("default", "shortened")
  33. self.progress_bar_width = progress_bar_width
  34. self.columns = columns
  35. self.gpu_inner_spacings = gpu_inner_spacings
  36. self.gpu_outer_spacings = gpu_outer_spacings
  37. self.node_names = node_names
  38. self.display_power = display_power
  39. self.display_users = display_users
  40. self.dict_type = dict_type
  41. self.available_gpus_only=available_gpus_only
  42. if dict_type == "default":
  43. self.act_dict = STANDARD_DICT
  44. else:
  45. self.act_dict = SHORTENED_DICT
  46. def render_info_dict(self, info_dict):
  47. line_blocks = []
  48. line_len = -1
  49. for node_dict in sorted(info_dict, key=lambda n: n["name"]):
  50. if self.node_names is None or node_dict["name"] in self.node_names:
  51. lines = self.render_node(node_dict)
  52. if lines:
  53. line_blocks.append(lines)
  54. if len(lines[-1]) > line_len:
  55. line_len = len(lines[-1])
  56. if line_len > -1:
  57. first_line = build_line("=", line_len)
  58. else: # No gpus found with current settings.
  59. return "--- No available GPUs found ---"
  60. final_lines = []
  61. n_rows = ceil(len(line_blocks) / self.columns)
  62. calc_index = lambda row, col, n_rows, n_cols: row * n_cols + col
  63. # Format rows and columns
  64. for row in range(n_rows):
  65. lines = []
  66. max_size = -1
  67. # Find max size of the blocks in the same row:
  68. for col in range(self.columns):
  69. idx = calc_index(row, col, n_rows, self.columns)
  70. if idx < len(line_blocks):
  71. if len(line_blocks[idx]) > max_size:
  72. max_size = len(line_blocks[idx])
  73. for col in range(self.columns):
  74. idx = calc_index(row, col, n_rows, self.columns)
  75. if idx < len(line_blocks):
  76. if len(lines) == 0:
  77. lines.extend(
  78. self.expand_rendered_block_to_size(
  79. line_blocks[idx],
  80. max_size
  81. )
  82. )
  83. else:
  84. for i, line in enumerate(self.expand_rendered_block_to_size(line_blocks[idx], max_size)):
  85. lines[i] += line
  86. final_lines.extend(lines)
  87. final_lines.insert(0, first_line * min(self.columns, len(line_blocks)))
  88. #lines.append("=" * len(lines[-1]))
  89. return "\n".join(final_lines)
  90. def render_node(self, node_dict):
  91. name = node_dict["name"]
  92. mem_used = node_dict["latest_info"]["used_memory_mb"]
  93. mem_total = node_dict["total_memory_mb"]
  94. utilization = node_dict["latest_info"]["cpu_utilization"]
  95. temp = node_dict["latest_info"]["temperature"]
  96. head_line = "|- Node: " + name + " "
  97. info_line = f"| CPU: {utilization:>4.1f}% {self.act_dict['Memory']}: {mem_used:>6}/{mem_total:<6} MB {self.act_dict['Temp']}: {temp:>3}°C"
  98. lines = []
  99. line_len = -1
  100. for i, gpu_dict in enumerate(node_dict["gpus"]):
  101. if "detached" in gpu_dict and gpu_dict["detached"]:
  102. continue
  103. new_lines = self.get_rendered_gpu_lines(gpu_dict)
  104. if line_len == -1:
  105. line_len = len(new_lines[-1])
  106. if not self.available_gpus_only or \
  107. (len(gpu_dict["running_processes"]) == 0 and \
  108. gpu_dict["latest_info"]["used_memory_mb"] < 100):
  109. lines.extend(new_lines)
  110. # If no GPUs are available for this node, skip the GPU display
  111. if self.available_gpus_only and len(lines) == 0:
  112. return None
  113. head_line = fill_line(head_line, line_len, fill_char="-")
  114. info_line = fill_line(info_line, line_len)
  115. pad_line = build_line("-", line_len)
  116. pad_line_empty = build_line(" ", line_len)
  117. lines.append(build_line("=", line_len))
  118. if self.gpu_outer_spacings:
  119. lines.insert(0, pad_line_empty)
  120. lines.insert(0, pad_line)
  121. if self.gpu_outer_spacings:
  122. lines.insert(0, pad_line_empty)
  123. lines.insert(0, info_line)
  124. if self.gpu_outer_spacings:
  125. lines.insert(0, pad_line_empty)
  126. lines.insert(0, head_line)
  127. return lines
  128. def get_rendered_gpu_lines(self, gpu_dict):
  129. gpu_type = self.maybe_shorten_gpu_name(gpu_dict["type"].replace("NVIDIA", "").strip())
  130. index = gpu_dict["index"]
  131. mem_used = gpu_dict["latest_info"]["used_memory_mb"]
  132. mem_total = gpu_dict["total_memory_mb"]
  133. utilization = gpu_dict["latest_info"]["utilization"]
  134. temp = gpu_dict["latest_info"]["temperature"]
  135. n_processes = len(gpu_dict["running_processes"])
  136. power = gpu_dict["latest_info"]["power_draw"]
  137. mem_used_percent = int(self.progress_bar_width * mem_used / mem_total)
  138. rest_mem = self.progress_bar_width - mem_used_percent
  139. line_util = "| [" + \
  140. "=" * mem_used_percent + \
  141. " " * rest_mem + "]" + \
  142. f"{mem_used:>6}/{mem_total:<6} MB, {self.act_dict['Util']}: {int(utilization):>3}% |"
  143. line_meta = f"| " \
  144. f"GPU #{index} ({gpu_type}): " + \
  145. f"{self.act_dict['#Proc']}: {n_processes} " + \
  146. f"{self.act_dict['Temp']}: {temp:>3}°C " + \
  147. (f"Pow: {int(power):>3} W" if self.display_power else "")
  148. line_meta = fill_line(line_meta, len(line_util))
  149. empty_line = build_line(" ", len(line_meta))
  150. lines = [line_meta, line_util]
  151. if self.display_users:
  152. lines.append(self.get_rendered_users_line(gpu_dict["running_processes"], len(line_meta)))
  153. if self.gpu_outer_spacings:
  154. lines.append(empty_line)
  155. else:
  156. if self.gpu_inner_spacings:
  157. lines.append(empty_line)
  158. return lines
  159. def get_rendered_users_line(self, processes_list, line_len):
  160. user_names = [p_dict["user_name"] for p_dict in processes_list]
  161. line = "| Users: " + ", ".join(user_names)
  162. line = fill_line(line, line_len)
  163. return line
  164. def expand_rendered_block_to_size(self, block_lines, max_size):
  165. buffer_line = "|" + " " * (len(block_lines[-1]) - 2) + "|"
  166. block_lines_new = block_lines[:-1] + [buffer_line] * (max_size - len(block_lines)) + block_lines[-1:]
  167. return block_lines_new
  168. def maybe_shorten_gpu_name(self, name):
  169. if self.dict_type == "shortened":
  170. name = name.replace("GeForce", "").strip()
  171. if len(name) > 13:
  172. name = name[:10] + "..."
  173. return name