Skip to content

Instantly share code, notes, and snippets.

@rgm
Created September 24, 2024 19:32
Show Gist options
  • Save rgm/2edf003f30080de563587caec3055939 to your computer and use it in GitHub Desktop.
Save rgm/2edf003f30080de563587caec3055939 to your computer and use it in GitHub Desktop.
A decorator for audibly debugging performance issues.
import math
import os
import struct
import subprocess
import time
import wave
# inspired by https://github.com/kristiandupont/react-geiger
def make_wav(filename: str, amplitude: float):
sample_rate = 44100
volume = max(0.5, amplitude)
duration_sec = 1 / 1000.0
start_frequency_hz = 440 + amplitude * 200
num_samples = int(sample_rate * duration_sec)
with wave.open(filename, "w") as wav:
wav.setparams(
(
1, # 1 channel
2, # 2 bytes per sample
sample_rate,
num_samples,
"NONE",
"not compressed",
),
)
# sine wave w exponential decay in amplitude
for i in range(num_samples):
time_sec = i / sample_rate
sin_val = (
math.sin(2 * math.pi * start_frequency_hz * time_sec)
* volume
* math.exp(-time_sec * 6)
)
sample = int(sin_val * 32767.0) # convert to 16-bit int
wav.writeframes(struct.pack("<h", sample))
def play_geiger_click(amplitude: float): # ∈ [0.0, 1.0]
"""
Create a wav file for this amplitude if it doesn't exist, and play it by
shelling out to Mac afplay.
"""
filename = f"geiger_click_{int(amplitude * 10):02d}.wav"
if not os.path.isfile(filename):
make_wav(filename, amplitude)
subprocess.run(["afplay", filename])
def geiger(min_runtime_ms=50):
"""
A decorator that plays a geiger-counter like click every time the decorated
function runs. Gets louder as the time taken over min_runtime_ms increases.
Usage:
@geiger(100) # tick a sound when my_function takes > 100ms
def my_function():
pass
"""
if min_runtime_ms == 0:
min_runtime_ms = 1 / 1_000 # avoid div by zero
def decorator(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
elapsed_ms = (end - start) * 1000
if elapsed_ms > min_runtime_ms:
amplitude = min(
1,
(elapsed_ms - min_runtime_ms) / (min_runtime_ms * 2),
)
play_geiger_click(amplitude)
return wrapper
return decorator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment