Skip to content

Commit 060d490

Browse files
committed
MyPy clean. Fixed Pydantic annotations. Removed currently unused RDP code for now.
1 parent c57a60c commit 060d490

8 files changed

+40
-106
lines changed

mypy.ini

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[mypy]
22
scripts_are_modules = True
33
show_traceback = True
4+
plugins = pydantic.mypy
45

56
# Options to make the checking stricter.
67
check_untyped_defs = True

scalene/scalene_analysis.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def walk(
207207
@staticmethod
208208
def strip_magic_line(source: str) -> str:
209209
try:
210-
from IPython import get_ipython # type: ignore
210+
from IPython import get_ipython
211211
get_ipython()
212212
# The above line will fail if not running in a notebook,
213213
# in which case we return the original source unchanged.

scalene/scalene_json.py

+26-88
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@
77
from enum import Enum
88
from operator import itemgetter
99
from pathlib import Path
10-
from pydantic import BaseModel, Field, NonNegativeFloat, NonNegativeInt, PositiveInt, StrictBool, ValidationError, confloat, model_validator
10+
from pydantic import BaseModel, Field, NonNegativeFloat, NonNegativeInt, PositiveInt, StrictBool, ValidationError, model_validator
1111
from typing import Any, Callable, Dict, List, Optional
1212

1313
from scalene.scalene_leak_analysis import ScaleneLeakAnalysis
1414
from scalene.scalene_statistics import Filename, LineNumber, ScaleneStatistics
1515
from scalene.scalene_analysis import ScaleneAnalysis
1616

17-
import numpy as np
1817

1918
class GPUDevice(str, Enum):
2019
nvidia = "GPU"
@@ -23,26 +22,26 @@ class GPUDevice(str, Enum):
2322

2423
class FunctionDetail(BaseModel):
2524
line: str
26-
lineno: PositiveInt
25+
lineno: LineNumber
2726
memory_samples: List[List[Any]]
2827
n_avg_mb: NonNegativeFloat
2928
n_copy_mb_s: NonNegativeFloat
30-
n_core_utilization: float = Field(confloat(ge=0, le=1))
31-
n_cpu_percent_c: float = Field(confloat(ge=0, le=100))
32-
n_cpu_percent_python: float = Field(confloat(ge=0, le=100))
29+
n_core_utilization : float = Field(..., ge=0, le=1)
30+
n_cpu_percent_c: float = Field(..., ge=0, le=100)
31+
n_cpu_percent_python: float = Field(..., ge=0, le=100)
3332
n_gpu_avg_memory_mb: NonNegativeFloat
3433
n_gpu_peak_memory_mb: NonNegativeFloat
35-
n_gpu_percent: float = Field(confloat(ge=0, le=100))
34+
n_gpu_percent: float = Field(..., ge=0, le=100)
3635
n_growth_mb: NonNegativeFloat
3736
n_peak_mb: NonNegativeFloat
3837
n_malloc_mb: NonNegativeFloat
3938
n_mallocs: NonNegativeInt
40-
n_python_fraction: float = Field(confloat(ge=0, le=1))
41-
n_sys_percent: float = Field(confloat(ge=0, le=100))
42-
n_usage_fraction: float = Field(confloat(ge=0, le=1))
39+
n_python_fraction: float = Field(..., ge=0, le=1)
40+
n_sys_percent: float = Field(..., ge=0, le=100)
41+
n_usage_fraction: float = Field(..., ge=0, le=1)
4342

4443
@model_validator(mode="after")
45-
def check_cpu_percentages(cls, values):
44+
def check_cpu_percentages(cls, values: Any) -> Any:
4645
total_cpu_usage = math.floor(
4746
values.n_cpu_percent_c
4847
+ values.n_cpu_percent_python
@@ -56,15 +55,15 @@ def check_cpu_percentages(cls, values):
5655

5756

5857
@model_validator(mode="after")
59-
def check_gpu_memory(cls, values):
58+
def check_gpu_memory(cls, values: Any) -> Any:
6059
if values.n_gpu_avg_memory_mb > values.n_gpu_peak_memory_mb:
6160
raise ValueError(
6261
"n_gpu_avg_memory_mb must be less than or equal to n_gpu_peak_memory_mb"
6362
)
6463
return values
6564

6665
@model_validator(mode="after")
67-
def check_cpu_memory(cls, values):
66+
def check_cpu_memory(cls, values: Any) -> Any:
6867
if values.n_avg_mb > values.n_peak_mb:
6968
raise ValueError(
7069
"n_avg_mb must be less than or equal to n_peak_mb"
@@ -159,79 +158,18 @@ def __init__(self) -> None:
159158
self.gpu = False
160159
self.gpu_device = ""
161160

162-
def rdp(self, points, epsilon):
163-
"""
164-
Ramer-Douglas-Peucker algorithm implementation using NumPy
165-
"""
166-
167-
def perpendicular_distance(point, start, end):
168-
if np.all(start == end):
169-
return np.linalg.norm(point - start)
170-
return np.abs(
171-
np.cross(end - start, start - point)
172-
/ np.linalg.norm(end - start)
173-
)
174-
175-
def recursive_rdp(points, start: int, end: int, epsilon: float):
176-
dmax = 0.0
177-
index = start
178-
for i in range(start + 1, end):
179-
d = perpendicular_distance(
180-
points[i], points[start], points[end]
181-
)
182-
if d > dmax:
183-
index = i
184-
dmax = d
185-
if dmax > epsilon:
186-
results1 = recursive_rdp(points, start, index, epsilon)
187-
results2 = recursive_rdp(points, index, end, epsilon)
188-
return results1[:-1] + results2
189-
else:
190-
return [points[start], points[end]]
191-
192-
points = np.array(points)
193-
start = 0
194-
end = len(points) - 1
195-
return np.array(recursive_rdp(points, start, end, epsilon))
196-
197161
def compress_samples(
198162
self, samples: List[Any], max_footprint: float
199163
) -> Any:
200-
# Try to reduce the number of samples with the
201-
# Ramer-Douglas-Peucker algorithm, which attempts to
202-
# preserve the shape of the graph. If that fails to bring
203-
# the number of samples below our maximum, randomly
204-
# downsample (epsilon calculation from
205-
# https://stackoverflow.com/questions/57052434/can-i-guess-the-appropriate-epsilon-for-rdp-ramer-douglas-peucker)
206164
if len(samples) <= self.max_sparkline_samples:
207165
return samples
208166

209-
if True:
210-
# FIXME: bypassing RDP for now
211-
# return samples[:self.max_sparkline_samples]
212-
213-
new_samples = sorted(
214-
random.sample(
215-
list(map(tuple, samples)), self.max_sparkline_samples
216-
)
167+
new_samples = sorted(
168+
random.sample(
169+
list(map(tuple, samples)), self.max_sparkline_samples
217170
)
218-
return new_samples
219-
220-
else:
221-
epsilon = (len(samples) / (3 * self.max_sparkline_samples)) * 2
222-
223-
# Use NumPy for RDP algorithm
224-
new_samples = self.rdp(np.array(samples), epsilon)
225-
226-
if len(new_samples) > self.max_sparkline_samples:
227-
new_samples = sorted(
228-
random.sample(
229-
list(map(tuple, new_samples)),
230-
self.max_sparkline_samples,
231-
)
232-
)
233-
234-
return new_samples
171+
)
172+
return new_samples
235173

236174
# Profile output methods
237175
def output_profile_line(
@@ -355,24 +293,24 @@ def output_profile_line(
355293
)
356294

357295
payload = {
358-
"lineno": line_no,
359296
"line": line,
297+
"lineno": line_no,
298+
"memory_samples": stats.per_line_footprint_samples[fname][line_no],
299+
"n_avg_mb": n_avg_mb,
300+
"n_copy_mb_s": n_copy_mb_s,
360301
"n_core_utilization": mean_core_util,
361302
"n_cpu_percent_c": n_cpu_percent_c,
362303
"n_cpu_percent_python": n_cpu_percent_python,
363-
"n_sys_percent": n_sys_percent,
364-
"n_gpu_percent": n_gpu_percent,
365304
"n_gpu_avg_memory_mb": n_gpu_mem_samples.mean(),
366305
"n_gpu_peak_memory_mb": n_gpu_mem_samples.peak(),
367-
"n_peak_mb": n_peak_mb,
306+
"n_gpu_percent": n_gpu_percent,
368307
"n_growth_mb": n_peak_mb, # For backwards compatibility
369-
"n_avg_mb": n_avg_mb,
370-
"n_mallocs": n_mallocs,
308+
"n_peak_mb": n_peak_mb,
371309
"n_malloc_mb": n_malloc_mb,
372-
"n_usage_fraction": n_usage_fraction,
310+
"n_mallocs": n_mallocs,
373311
"n_python_fraction": n_python_fraction,
374-
"n_copy_mb_s": n_copy_mb_s,
375-
"memory_samples": stats.per_line_footprint_samples[fname][line_no],
312+
"n_sys_percent": n_sys_percent,
313+
"n_usage_fraction": n_usage_fraction,
376314
}
377315
try:
378316
FunctionDetail(**payload)

scalene/scalene_magics.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from scalene.scalene_parseargs import ScaleneParseArgs
1818

1919
@magics_class
20-
class ScaleneMagics(Magics): # type: ignore
20+
class ScaleneMagics(Magics):
2121
"""IPython (Jupyter) support for magics for Scalene (%scrun and %%scalene)."""
2222

2323
def run_code(self, args: ScaleneArguments, code: str) -> None:

scalene/scalene_profiler.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1721,6 +1721,7 @@ def disable_signals(retry: bool = True) -> None:
17211721
Scalene.timer_signals = False
17221722
return
17231723
try:
1724+
assert Scalene.__signals.cpu_timer_signal is not None
17241725
Scalene.__orig_setitimer(Scalene.__signals.cpu_timer_signal, 0)
17251726
for sig in [
17261727
Scalene.__signals.malloc_signal,
@@ -1938,7 +1939,7 @@ def main() -> None:
19381939

19391940
Scalene.__accelerator = ScaleneAppleGPU()
19401941
else:
1941-
from scalene.scalene_nvidia_gpu import ScaleneNVIDIAGPU # type: ignore
1942+
from scalene.scalene_nvidia_gpu import ScaleneNVIDIAGPU
19421943

19431944
Scalene.__accelerator = ScaleneNVIDIAGPU()
19441945

scalene/scalene_signals.py

+6-13
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ class ScaleneSignals:
1111
"""
1212

1313
def __init__(self) -> None:
14+
# Declare these here, then configure them.
15+
self.cpu_signal : signal.Signals
16+
self.cpu_timer_signal : int
1417
# Configure timer signals using set_timer_signals method (defined below).
1518
self.set_timer_signals(use_virtual_time=True)
1619
# Set profiling signals depending upon the platform.
@@ -37,8 +40,8 @@ def set_timer_signals(self, use_virtual_time: bool = True) -> None:
3740
If True, sets virtual timer signals, otherwise sets real timer signals.
3841
"""
3942
if sys.platform == "win32":
43+
self.cpu_timer_signal = signal.SIGBREAK # Note: on Windows, this is unused, so any signal will do
4044
self.cpu_signal = signal.SIGBREAK
41-
self.cpu_timer_signal = None
4245
return
4346
if use_virtual_time:
4447
self.cpu_timer_signal = signal.ITIMER_VIRTUAL
@@ -49,26 +52,16 @@ def set_timer_signals(self, use_virtual_time: bool = True) -> None:
4952

5053
def get_timer_signals(self) -> Tuple[int, signal.Signals]:
5154
"""
52-
Return the signals used for CPU profiling.
53-
54-
Returns:
55-
--------
56-
Tuple[int, signal.Signals]
57-
Returns 2-tuple of the integers representing the CPU timer signal and the CPU signal.
55+
Returns 2-tuple of the integers representing the CPU timer signal and the CPU signal.
5856
"""
5957
return self.cpu_timer_signal, self.cpu_signal
6058

6159
def get_lifecycle_signals(self) -> Tuple[signal.Signals, signal.Signals]:
6260
return (self.start_profiling_signal, self.stop_profiling_signal)
6361

64-
def get_all_signals(self) -> List[int]:
62+
def get_all_signals(self) -> List[signal.Signals]:
6563
"""
6664
Return all the signals used for controlling profiling, except the CPU timer.
67-
68-
Returns:
69-
--------
70-
List[int]
71-
Returns a list of integers representing all the profiling signals except the CPU timer.
7265
"""
7366
return [
7467
self.start_profiling_signal,

scalene/scalene_statistics.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pickle
66
import time
77
from collections import defaultdict
8+
from pydantic import PositiveInt
89
from typing import (
910
Any,
1011
Dict,
@@ -22,7 +23,7 @@
2223

2324
Address = NewType("Address", str)
2425
Filename = NewType("Filename", str)
25-
LineNumber = NewType("LineNumber", int)
26+
LineNumber = NewType("LineNumber", PositiveInt)
2627
ByteCodeIndex = NewType("ByteCodeIndex", int)
2728
T = TypeVar("T")
2829

scalene/scalene_utility.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def get_fully_qualified_name(frame: FrameType) -> Filename:
7070
version = sys.version_info
7171
if version.major >= 3 and version.minor >= 11:
7272
# Introduced in Python 3.11
73-
fn_name = Filename(frame.f_code.co_qualname) # type: ignore
73+
fn_name = Filename(frame.f_code.co_qualname)
7474
return fn_name
7575
f = frame
7676
# Manually search for an enclosing class.

0 commit comments

Comments
 (0)