Skip to content

Commit d530e4e

Browse files
add show_nodes function to hardware module (#86)
Co-authored-by: Mark Powers <markpowers@uchicago.edu>
1 parent 1ca49d0 commit d530e4e

1 file changed

Lines changed: 84 additions & 0 deletions

File tree

chi/hardware.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
from dataclasses import dataclass
55
from datetime import datetime, timedelta, timezone
66
from typing import List, Optional, Set, Tuple
7+
from ipydatagrid import DataGrid, TextRenderer, Expr
78
from ipywidgets import Box, HTML, Layout
89
from IPython.display import display
10+
import pandas as pd
911

1012
import requests
1113

@@ -291,6 +293,9 @@ def get_nodes(
291293
return nodes
292294

293295

296+
297+
298+
294299
def _parse_blazar_dt(datetime_string):
295300
d = datetime.strptime(datetime_string, "%Y-%m-%dT%H:%M:%S.%f")
296301
return d.replace(tzinfo=timezone.utc)
@@ -313,6 +318,85 @@ def get_node_types() -> List[str]:
313318
get_nodes()
314319
return list(set(node_types))
315320

321+
def _reservable_color(cell):
322+
return "#a2d9fe" if cell.value else "#f69084"
323+
324+
def _gpu_background_color(cell):
325+
return "#d3d3d3" if not cell.value else None
326+
327+
def show_nodes(
328+
nodes: Optional[List[Node]] = None
329+
) -> None:
330+
"""
331+
Display a sortable, filterable table of available nodes.
332+
333+
Args:
334+
nodes (Optional[List[Node]], optional): A list of Node objects to display.
335+
If not provided, defaults to the output of hardware.get_nodes().
336+
337+
Returns:
338+
None
339+
"""
340+
341+
def estimate_column_width(df, column, char_px=7, padding=0):
342+
if column not in df.columns:
343+
raise ValueError(f"Column '{column}' not found in DataFrame.")
344+
max_chars = df[column].astype(str).map(len).max()
345+
return max(max_chars * char_px + padding, 80)
346+
347+
if not nodes:
348+
nodes = get_nodes()
349+
350+
rows = []
351+
for n in nodes:
352+
rows.append({
353+
"Node Name": n.name,
354+
"Type": n.type,
355+
"Clock Speed (GHz)": round(n.cpu.get('clock_speed', 0) / 1e9, 2),
356+
"RAM": n.main_memory.get('humanized_ram_size', 'N/A'),
357+
"GPU Model": (n.gpu or {}).get('gpu_model') or "",
358+
"GPU Count": (n.gpu or {}).get('gpu_count') or "",
359+
"Storage Size": n.storage_devices[0].get('humanized_size', 'N/A') if n.storage_devices else 'N/A',
360+
"Site": n.site,
361+
"Reservable": bool(n.reservable),
362+
})
363+
364+
df = pd.DataFrame(rows)
365+
renderers = {
366+
"Reservable": TextRenderer(
367+
text_color="black",
368+
background_color=Expr(_reservable_color),
369+
),
370+
"GPU Model": TextRenderer(
371+
background_color=Expr(_gpu_background_color),
372+
),
373+
"GPU Count": TextRenderer(
374+
background_color=Expr(_gpu_background_color),
375+
)
376+
}
377+
378+
grid = DataGrid(
379+
df,
380+
layout=Layout(height="400px"),
381+
selection_mode="row",
382+
renderers=renderers,
383+
column_widths={
384+
"Node Name": int(estimate_column_width(df, "Node Name")),
385+
"Site": int(estimate_column_width(df, "Site")),
386+
"Type": int(estimate_column_width(df, "Type")),
387+
"RAM": int(estimate_column_width(df, "RAM")),
388+
"Storage Size": int(estimate_column_width(df, "Storage Size")),
389+
"Clock Speed (GHz)": 55,
390+
"GPU Model": 90,
391+
"GPU Count": 30,
392+
"key": 30,
393+
"Reservable": 55
394+
},
395+
396+
df = pd.DataFrame(rows)
397+
)
398+
399+
display(grid)
316400

317401
@dataclass
318402
class Device:

0 commit comments

Comments
 (0)