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:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user