fix(logging): clean up runtime log noise and cache hardware detection

Clean up duplicated code artifacts from recent merges and cache hardware detection to eliminate ~1.3s UI lag when opening settings dialog.
This commit is contained in:
Jatin K Malik
2026-05-02 20:07:13 -07:00
committed by GitHub
parent 2753679872
commit 7c9ef372de
4 changed files with 58 additions and 51 deletions
-4
View File
@@ -283,10 +283,6 @@ def main():
except Exception as e:
logger.debug(f"Could not start IBus daemon: {e}")
config_manager = ConfigManager()
initialize_logging()
logger.info("Logging system initialized")
config_manager = ConfigManager()
saved_settings = config_manager.get_settings().get("speech_recognition", {})
audio_settings = config_manager.get_settings().get("audio", {})
@@ -1600,7 +1600,7 @@ class SpeechRecognitionManager:
)
if self.audio_buffer:
logger.info(f"DEBUG: Enqueuing final buffer with {len(self.audio_buffer)} chunks")
logger.debug(f"Enqueuing final buffer with {len(self.audio_buffer)} chunks")
self._enqueue_audio_segment(self.audio_buffer)
self.audio_buffer = []
@@ -1914,9 +1914,7 @@ class SpeechRecognitionManager:
return
# Process text - either with voice commands or pass through directly
logger.info(
f"DEBUG: _process_audio_buffer got text='{text[:50] if text else '(empty)'}...'"
)
logger.debug(f"_process_audio_buffer got text='{text[:50] if text else '(empty)'}...'")
if text:
if self._voice_commands_enabled:
# Process with voice commands (original behavior)
@@ -1927,13 +1925,13 @@ class SpeechRecognitionManager:
actions = []
# Call text callbacks with processed text
logger.info(
f"DEBUG: processed_text='{processed_text[:50] if processed_text else '(empty)'}...', callbacks={len(self.text_callbacks)}"
logger.debug(
f"processed_text='{processed_text[:50] if processed_text else '(empty)'}...', callbacks={len(self.text_callbacks)}"
)
if processed_text:
for callback in self.text_callbacks:
logger.info(
f"DEBUG: invoking text callback: {callback.__name__ if hasattr(callback, '__name__') else callback}"
logger.debug(
f"invoking text callback: {callback.__name__ if hasattr(callback, '__name__') else callback}"
)
callback(processed_text)
@@ -1944,89 +1942,65 @@ class SpeechRecognitionManager:
def _perform_recognition(self):
"""Perform speech recognition in real-time."""
logger.info("DEBUG: _perform_recognition thread started")
logger.debug("_perform_recognition thread started")
while True:
logger.debug(
f"DEBUG: Recognition loop - should_record={self.should_record}, queue_empty={self._segment_queue.empty()}"
f"Recognition loop - should_record={self.should_record}, queue_empty={self._segment_queue.empty()}"
)
try:
segment = self._segment_queue.get(timeout=0.1)
except queue.Empty:
# Only exit if we're not recording AND queue is empty
if not self.should_record and self._segment_queue.empty():
logger.info(
"DEBUG: Recognition loop - not recording and queue empty, checking for final items..."
logger.debug(
"Recognition loop - not recording and queue empty, checking for final items..."
)
# Give a brief moment for any final items to be enqueued
try:
segment = self._segment_queue.get(timeout=0.5)
except queue.Empty:
logger.info("DEBUG: Recognition loop - no more items, exiting")
logger.debug("Recognition loop - no more items, exiting")
break
else:
logger.debug("DEBUG: Recognition loop - queue timeout, continuing")
logger.debug("Recognition loop - queue timeout, continuing")
continue
if segment is None:
logger.info(
"DEBUG: Recognition loop - got None signal, draining remaining items..."
)
logger.debug("Recognition loop - got None signal, draining remaining items...")
# Drain any remaining items before exiting
while not self._segment_queue.empty():
try:
remaining = self._segment_queue.get_nowait()
if remaining is not None:
logger.info(
f"DEBUG: Recognition loop - processing remaining segment with {len(remaining)} chunks"
logger.debug(
f"Recognition loop - processing remaining segment with {len(remaining)} chunks"
)
self._update_state(RecognitionState.PROCESSING)
self._process_audio_buffer(remaining)
except queue.Empty:
break
logger.info("DEBUG: Recognition loop - exiting after None signal")
logger.debug("Recognition loop - exiting after None signal")
break
logger.info(f"DEBUG: Recognition loop - processing segment with {len(segment)} chunks")
logger.debug(f"Recognition loop - processing segment with {len(segment)} chunks")
self._update_state(RecognitionState.PROCESSING)
self._process_audio_buffer(segment)
if self.should_record:
self._update_state(RecognitionState.LISTENING)
logger.info("DEBUG: _perform_recognition thread exiting")
"""Perform speech recognition in real-time."""
logger.info("DEBUG: _perform_recognition thread started")
while self.should_record or not self._segment_queue.empty():
logger.debug(
f"DEBUG: Recognition loop - should_record={self.should_record}, queue_empty={self._segment_queue.empty()}"
)
try:
segment = self._segment_queue.get(timeout=0.1)
except queue.Empty:
logger.debug("DEBUG: Recognition loop - queue timeout, continuing")
continue
if segment is None:
logger.info("DEBUG: Recognition loop - got None signal, continuing")
continue
logger.info(f"DEBUG: Recognition loop - processing segment with {len(segment)} chunks")
self._update_state(RecognitionState.PROCESSING)
self._process_audio_buffer(segment)
if self.should_record:
self._update_state(RecognitionState.LISTENING)
logger.info("DEBUG: _perform_recognition thread exiting")
logger.debug("_perform_recognition thread exiting")
def _enqueue_audio_segment(self, audio_buffer: list[bytes]):
"""Queue an audio segment for asynchronous transcription."""
segment = audio_buffer.copy()
if not segment:
logger.warning("DEBUG: _enqueue_audio_segment called with empty buffer")
logger.warning("_enqueue_audio_segment called with empty buffer")
return
logger.info(f"DEBUG: _enqueue_audio_segment called with {len(segment)} chunks")
logger.debug(f"_enqueue_audio_segment called with {len(segment)} chunks")
try:
self._segment_queue.put_nowait(segment)
logger.info("DEBUG: Enqueued segment successfully")
logger.debug("Enqueued segment successfully")
except queue.Full:
logger.warning("Transcription queue is full, dropping oldest pending segment")
try:
@@ -8,6 +8,7 @@ for whisper.cpp, supporting Vulkan, CUDA, and CPU backends.
import logging
import os
import subprocess
from functools import lru_cache
from typing import Optional
logger = logging.getLogger(__name__)
@@ -60,10 +61,17 @@ class ComputeBackend:
CPU = "cpu"
@lru_cache(maxsize=1)
def detect_vulkan_support() -> tuple[bool, Optional[str]]:
"""
Detect if Vulkan is available and get device info.
Returns:
Tuple of (is_available, device_name)
"""
"""
Detect if Vulkan is available and get device info.
Returns:
Tuple of (is_available, device_name)
"""
@@ -91,6 +99,7 @@ def detect_vulkan_support() -> tuple[bool, Optional[str]]:
return False, None
@lru_cache(maxsize=1)
def detect_cuda_support() -> tuple[bool, Optional[str]]:
"""
Detect if NVIDIA CUDA is available and get device info.
@@ -119,6 +128,7 @@ def detect_cuda_support() -> tuple[bool, Optional[str]]:
return False, None
@lru_cache(maxsize=1)
def detect_compute_backend() -> tuple[str, str]:
"""
Detect the best available compute backend.
@@ -143,6 +153,7 @@ def detect_compute_backend() -> tuple[str, str]:
return ComputeBackend.CPU, cpu_info
@lru_cache(maxsize=1)
def detect_cpu_info() -> str:
"""
Detect CPU information for CPU backend.
+26
View File
@@ -80,6 +80,32 @@ mock_audio_feedback.play_error_sound = MagicMock()
sys.modules["vocalinux.ui.audio_feedback"] = mock_audio_feedback
@pytest.fixture(autouse=True)
def _clear_hardware_detection_cache():
"""Clear lru_cache on hardware-detection helpers between tests.
Hardware detection results are cached for the lifetime of the process
in production, but tests mock subprocess.run and call these helpers
multiple times with different mock return values; without clearing
the cache, later assertions would see the first call's cached result.
"""
try:
from vocalinux.utils import whispercpp_model_info as _wmi
for fn_name in (
"detect_vulkan_support",
"detect_cuda_support",
"detect_compute_backend",
"detect_cpu_info",
):
fn = getattr(_wmi, fn_name, None)
if fn is not None and hasattr(fn, "cache_clear"):
fn.cache_clear()
except Exception:
pass
yield
@pytest.fixture(autouse=True)
def _cleanup_ibus_server():
"""Stop any leftover IBus socket server threads after each test.