Skip to content

Commit ac310c5

Browse files
author
user
committed
Add test suite
1 parent a72b793 commit ac310c5

16 files changed

Lines changed: 764 additions & 179 deletions

.github/workflows/formatter.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: Formatter
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
formatter:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
- uses: rickstaa/action-black@v1
11+
with:
12+
black_args: "-l 79 ."

.github/workflows/linter.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Linter
2+
3+
on:
4+
workflow_run:
5+
workflows: [Formatter]
6+
types: [completed]
7+
8+
jobs:
9+
linter:
10+
runs-on: ubuntu-latest
11+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
12+
steps:
13+
- name: Check out source repository
14+
uses: actions/checkout@v4
15+
- name: Set up Python environment
16+
uses: actions/setup-python@v4
17+
with:
18+
python-version: "3.11"
19+
- name: flake8 Lint
20+
uses: py-actions/flake8@v2
21+
with:
22+
path: "jumper_extension"

.github/workflows/test.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Tests
2+
3+
on:
4+
- push
5+
- pull_request
6+
- workflow_dispatch # Running workflow manually
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Set up Python 3.11
16+
uses: actions/setup-python@v4
17+
with:
18+
python-version: "3.11"
19+
cache: 'pip'
20+
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install -e .[test]
25+
26+
- name: Run tests with coverage
27+
run: |
28+
pytest --cov=jumper_extension --cov-report=xml tests/
29+
30+
- name: Generate coverage badge
31+
uses: tj-actions/coverage-badge-py@v2
32+
with:
33+
output: coverage.svg

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
__pycache__/
1+
__pycache__/
2+
*.egg-info/
3+
.coverage
4+
htmlcov/
5+
.pytest_cache/
6+
coverage.xml

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# JUmPER Extension
22

3+
![Coverage](./coverage.svg)
4+
35
This is JUmPER IPython extension for real-time performance monitoring in IPython environments and Jupyter notebooks. It allows you to gather performance data on CPU usage, memory consumption, GPU utilization, and I/O operations for individual cells and present it in the notebook/IPython session either as text report or as a plot. The extension can be naturally integrated with [JUmPER Jupyter kernel](https://github.com/score-p/scorep_jupyter_kernel_python/) for most comprehensive analysis of notebook.
46

57
## Installation

jumper_extension/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
from .perfmonitor_magics import load_ipython_extension, unload_ipython_extension
1+
from .perfmonitor_magics import (load_ipython_extension,
2+
unload_ipython_extension)
3+
4+
__all__ = ["load_ipython_extension", "unload_ipython_extension"]

jumper_extension/cell_history.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,40 @@
1-
import time
21
import json
2+
import time
3+
34

45
class CellHistory:
56
def __init__(self):
67
self.cells = []
78
self.cell_counter = 0
89
self.current_cell = None
910
self.cell_timestamps = []
10-
11+
1112
def start_cell(self, raw_cell):
1213
self.current_cell = {
13-
'number': self.cell_counter,
14-
'raw_cell': raw_cell,
15-
'start_time': time.time(),
16-
'end_time': None,
14+
"number": self.cell_counter,
15+
"raw_cell": raw_cell,
16+
"start_time": time.time(),
17+
"end_time": None,
1718
}
1819
self.cell_counter += 1
19-
20+
2021
def end_cell(self, result):
2122
if self.current_cell:
22-
self.current_cell['end_time'] = time.time()
23+
self.current_cell["end_time"] = time.time()
2324
self.cells.append(self.current_cell)
24-
self.cell_timestamps.append((self.current_cell['start_time'], self.current_cell['end_time']))
25+
self.cell_timestamps.append(
26+
(self.current_cell["start_time"], self.current_cell["end_time"])
27+
)
2528
self.current_cell = None
2629

2730
def print(self):
2831
for cell in self.cells:
29-
duration = cell['end_time'] - cell['start_time']
32+
duration = cell["end_time"] - cell["start_time"]
3033
print(f"Cell #{cell['number']} - Duration: {duration:.2f}s")
3134
print("-" * 40)
32-
print(cell['raw_cell'])
35+
print(cell["raw_cell"])
3336
print("=" * 40)
3437

35-
def export(self, filename='cell_history.json'):
36-
with open(filename, 'w') as f:
37-
json.dump(self.cells, f, indent=2)
38+
def export(self, filename="cell_history.json"):
39+
with open(filename, "w") as f:
40+
json.dump(self.cells, f, indent=2)

jumper_extension/perfmonitor_magics.py

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
from IPython.core.magic import Magics, magics_class, line_magic
1+
from IPython.core.magic import Magics, line_magic, magics_class
2+
3+
from .cell_history import CellHistory
24
from .performance_monitor import PerformanceMonitor
35
from .performance_visualizer import PerformanceVisualizer
4-
from .cell_history import CellHistory
56

67
_perfmonitor_magics = None
78

9+
810
@magics_class
911
class perfmonitorMagics(Magics):
1012
def __init__(self, shell):
@@ -16,7 +18,7 @@ def __init__(self, shell):
1618

1719
def pre_run_cell(self, info):
1820
self.cell_history.start_cell(info.raw_cell)
19-
21+
2022
def post_run_cell(self, result):
2123
self.cell_history.end_cell(result.result)
2224
if self.monitor and self.print_perfreports:
@@ -55,19 +57,20 @@ def perfmonitor_start(self, line):
5557
if self.monitor and self.monitor.running:
5658
print("Performance monitoring already running")
5759
return
58-
60+
5961
interval = 1.0
6062
if line:
6163
try:
6264
interval = float(line)
6365
except ValueError:
6466
print(f"Invalid interval value: {line}")
6567
return
66-
68+
6769
self.monitor = PerformanceMonitor(interval=interval)
6870
self.monitor.start()
6971
self.visualizer = PerformanceVisualizer(
70-
self.monitor.cpu_handles, self.monitor.memory, self.monitor.gpu_memory)
72+
self.monitor.cpu_handles, self.monitor.memory, self.monitor.gpu_memory
73+
)
7174

7275
@line_magic
7376
def perfmonitor_stop(self, line):
@@ -81,16 +84,16 @@ def perfmonitor_plot(self, line):
8184
if not self.monitor:
8285
print("No active performance monitoring session")
8386
return
84-
87+
8588
cell_marks = self._parse_cell_number(line)
8689
if cell_marks is False: # Error parsing
8790
return
88-
91+
8992
df = self.monitor.data.to_dataframe()
9093
if cell_marks:
9194
start_mark, end_mark = cell_marks
92-
df = df[(df['time'] >= start_mark) & (df['time'] <= end_mark)]
93-
df['time'] -= self.monitor.start_time
95+
df = df[(df["time"] >= start_mark) & (df["time"] <= end_mark)]
96+
df["time"] -= self.monitor.start_time
9497

9598
if df.empty:
9699
print("No performance data available")
@@ -110,13 +113,13 @@ def _perfreport(self, cell_marks=None):
110113

111114
if not cell_marks:
112115
cell_marks = self.cell_history.cell_timestamps[-1]
113-
116+
114117
start_mark, end_mark = cell_marks
115118
duration = end_mark - start_mark
116119
print(f"Duration: {duration:.2f}s")
117120

118121
df = self.monitor.data.to_dataframe()
119-
df = df[(df['time'] >= start_mark) & (df['time'] <= end_mark)]
122+
df = df[(df["time"] >= start_mark) & (df["time"] <= end_mark)]
120123
if df.empty:
121124
print("No performance data available")
122125
return
@@ -126,20 +129,23 @@ def _perfreport(self, cell_marks=None):
126129
("CPU Util (Across CPUs)", "cpu_util_avg", "-"),
127130
("Memory (GB)", "memory_usage_gb", f"{self.monitor.memory:.2f}"),
128131
("GPU Util (Across GPUs)", "gpu_util_avg", "-"),
129-
("GPU Memory (GB)", "gpu_mem_avg", f"{self.monitor.gpu_memory:.2f}")
132+
("GPU Memory (GB)", "gpu_mem_avg", f"{self.monitor.gpu_memory:.2f}"),
130133
]
131-
134+
132135
print(f"{'Metric':<25} {'AVG':<8} {'MIN':<8} {'MAX':<8} {'TOTAL':<8}")
133136
print("-" * 65)
134137
for name, col, total in metrics:
135138
if col in df.columns:
136-
print(f"{name:<25} {df[col].mean():<8.2f} {df[col].min():<8.2f} {df[col].max():<8.2f} {total:<8}")
137-
139+
print(
140+
f"{name:<25} {df[col].mean():<8.2f} "
141+
f"{df[col].min():<8.2f} {df[col].max():<8.2f} {total:<8}"
142+
)
143+
138144
@line_magic
139145
def perfmonitor_enable_perfreports(self, line):
140146
self.print_perfreports = True
141147
print("Performance reports enabled for each cell")
142-
148+
143149
@line_magic
144150
def perfmonitor_disable_perfreports(self, line):
145151
self.print_perfreports = False
@@ -157,14 +163,14 @@ def perfmonitor_export_perfdata(self, line):
157163
if not self.monitor:
158164
print("No active performance monitoring session")
159165
return
160-
filename = line.strip() or 'performance_data.csv'
166+
filename = line.strip() or "performance_data.csv"
161167
self.monitor.data.export(filename)
162168
print(f"Performance data exported to {filename}")
163169

164170
@line_magic
165171
def perfmonitor_export_cell_history(self, line):
166172
"""Export cell history"""
167-
filename = line.strip() or 'cell_history.json'
173+
filename = line.strip() or "cell_history.json"
168174
self.cell_history.export(filename)
169175
print(f"Cell history exported to {filename}")
170176

@@ -174,33 +180,35 @@ def perfmonitor_help(self, line):
174180
commands = [
175181
"perfmonitor_help -- show this help",
176182
"perfmonitor_resources -- show available hardware",
177-
"cell_history -- show cell execution history",
183+
"cell_history -- show cell execution history",
178184
"perfmonitor_start [seconds] -- start monitoring",
179185
"perfmonitor_stop -- stop monitoring",
180186
"perfmonitor_perfreport [cell] -- show performance report",
181187
"perfmonitor_plot [cell] -- plot performance data",
182188
"perfmonitor_enable_perfreports -- enable auto-reports",
183189
"perfmonitor_disable_perfreports -- disable auto-reports",
184190
"perfmonitor_export_perfdata [filename] -- export data to CSV",
185-
"perfmonitor_export_cell_history [filename] -- export history to JSON"
191+
"perfmonitor_export_cell_history [filename] -- export history to JSON",
186192
]
187193
print("Available commands:")
188194
for cmd in commands:
189195
print(f" %{cmd}")
190196

197+
191198
def load_ipython_extension(ipython):
192199
global _perfmonitor_magics
193200
_perfmonitor_magics = perfmonitorMagics(ipython)
194-
ipython.events.register('pre_run_cell', _perfmonitor_magics.pre_run_cell)
195-
ipython.events.register('post_run_cell', _perfmonitor_magics.post_run_cell)
201+
ipython.events.register("pre_run_cell", _perfmonitor_magics.pre_run_cell)
202+
ipython.events.register("post_run_cell", _perfmonitor_magics.post_run_cell)
196203
ipython.register_magics(_perfmonitor_magics)
197204
print("Perfmonitor extension loaded.")
198205

206+
199207
def unload_ipython_extension(ipython):
200208
global _perfmonitor_magics
201209
if _perfmonitor_magics:
202-
ipython.events.unregister('pre_run_cell', _perfmonitor_magics.pre_run_cell)
203-
ipython.events.unregister('post_run_cell', _perfmonitor_magics.post_run_cell)
210+
ipython.events.unregister("pre_run_cell", _perfmonitor_magics.pre_run_cell)
211+
ipython.events.unregister("post_run_cell", _perfmonitor_magics.post_run_cell)
204212
if _perfmonitor_magics.monitor:
205213
_perfmonitor_magics.monitor.stop()
206214
_perfmonitor_magics = None

0 commit comments

Comments
 (0)