diff --git a/README.md b/README.md index a48626c77..7c2ae572d 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ docker run --network host --privileged ghcr.io/music-assistant/server You must run the docker container with host network mode and the data volume is `/data`. If you want access to your local music files from within MA, make sure to also mount that, e.g. /media. -Note that accessing remote (SMB) shares can be done from within MA itself using the SMB File provider (but requires the priviliged flag). +Note that accessing remote (SMB) shares can be done from within MA itself using the SMB File provider (but requires the privileged flag). ____________ diff --git a/music_assistant/__main__.py b/music_assistant/__main__.py index 9d40c04a4..c8c13bcbb 100644 --- a/music_assistant/__main__.py +++ b/music_assistant/__main__.py @@ -89,6 +89,7 @@ def main(): hass_options = {} log_level = hass_options.get("log_level", args.log_level).upper() + dev_mode = bool(os.environ.get("PYTHONDEVMODE", "0")) # setup logger logger = setup_logger(data_dir, log_level) @@ -100,7 +101,7 @@ def on_shutdown(loop): async def start_mass(): loop = asyncio.get_running_loop() - if log_level == "DEBUG": + if dev_mode: loop.set_debug(True) await mass.start() diff --git a/music_assistant/common/models/config_entries.py b/music_assistant/common/models/config_entries.py index 8d5bd57b7..c2a3eaac8 100644 --- a/music_assistant/common/models/config_entries.py +++ b/music_assistant/common/models/config_entries.py @@ -17,6 +17,7 @@ CONF_FLOW_MODE, CONF_LOG_LEVEL, CONF_OUTPUT_CHANNELS, + CONF_OUTPUT_CODEC, CONF_VOLUME_NORMALISATION, CONF_VOLUME_NORMALISATION_TARGET, SECURE_STRING_SUBSTITUTE, @@ -367,3 +368,21 @@ class ConfigUpdate(DataClassDictMixin): advanced=True, ), ) + +CONF_ENTRY_OUTPUT_CODEC = ConfigEntry( + key=CONF_OUTPUT_CODEC, + type=ConfigEntryType.STRING, + label="Output codec", + options=[ + ConfigValueOption("FLAC (lossless, compact file size)", "flac"), + ConfigValueOption("M4A AAC (lossy, superior quality)", "aac"), + ConfigValueOption("MP3 (lossy, average quality)", "mp3"), + ConfigValueOption("WAV (lossless, huge file size)", "wav"), + ], + default_value="flac", + description="Define the codec that is sent to the player when streaming audio. " + "By default Music Assistant prefers FLAC because it is lossless, has a " + "respectable filesize and is supported by most player devices. " + "Change this setting only if needed for your device/environment.", + advanced=True, +) diff --git a/music_assistant/common/models/enums.py b/music_assistant/common/models/enums.py index 1b2cda524..ff62c45e1 100644 --- a/music_assistant/common/models/enums.py +++ b/music_assistant/common/models/enums.py @@ -250,6 +250,7 @@ class PlayerFeature(StrEnum): SEEK = "seek" SET_MEMBERS = "set_members" QUEUE = "queue" + CROSSFADE = "crossfade" class EventType(StrEnum): diff --git a/music_assistant/constants.py b/music_assistant/constants.py index fe84ecd19..6c5a3ecf8 100755 --- a/music_assistant/constants.py +++ b/music_assistant/constants.py @@ -45,6 +45,7 @@ CONF_FLOW_MODE: Final[str] = "flow_mode" CONF_LOG_LEVEL: Final[str] = "log_level" CONF_HIDE_GROUP_CHILDS: Final[str] = "hide_group_childs" +CONF_OUTPUT_CODEC: Final[str] = "output_codec" # config default values DEFAULT_HOST: Final[str] = "0.0.0.0" diff --git a/music_assistant/server/controllers/media/base.py b/music_assistant/server/controllers/media/base.py index 971f5fcb4..3c5026624 100644 --- a/music_assistant/server/controllers/media/base.py +++ b/music_assistant/server/controllers/media/base.py @@ -389,7 +389,20 @@ async def get_provider_item( if not force_refresh and (cache := await self.mass.cache.get(cache_key)): return self.item_cls.from_dict(cache) if provider := self.mass.get_provider(provider_instance_id_or_domain): # noqa: SIM102 - if item := await provider.get_item(self.media_type, item_id): + item: MediaItemType = None + try: + item = await provider.get_item(self.media_type, item_id) + except MediaNotFoundError: + # fallback to domain matching + for provider in self.mass.music.providers: + if not provider.available: + continue + if provider_instance_id_or_domain != provider.domain: + continue + with suppress(MediaNotFoundError): + if item := await provider.get_item(self.media_type, item_id): + break + if item: await self.mass.cache.set(cache_key, item.to_dict()) return item raise MediaNotFoundError( @@ -449,7 +462,7 @@ async def dynamic_tracks( continue return await self._get_provider_dynamic_tracks( prov_mapping.item_id, - provider_instance_id_or_domain, + prov_mapping.provider_instance, limit=limit, ) # Fallback to the default implementation diff --git a/music_assistant/server/controllers/streams.py b/music_assistant/server/controllers/streams.py index a0f7240c1..6b52d27a8 100644 --- a/music_assistant/server/controllers/streams.py +++ b/music_assistant/server/controllers/streams.py @@ -643,17 +643,25 @@ def _get_player_ffmpeg_args( "-i", "-", ] - # output args - output_args = [ - # output args - "-f", - output_format.value, + input_args += ["-metadata", 'title="Music Assistant"'] + # select output args + if output_format == ContentType.FLAC: + output_args = ["-f", "flac", "-compression_level", "3"] + elif output_format == ContentType.AAC: + output_args = ["-f", "adts", "-c:a", output_format.value, "-b:a", "320k"] + elif output_format == ContentType.MP3: + output_args = ["-f", "mp3", "-c:a", output_format.value, "-b:a", "320k"] + else: + output_args = ["-f", output_format.value] + + output_args += [ + # append channels "-ac", "1" if conf_channels != "stereo" else "2", + # append sample rate "-ar", str(output_sample_rate), - "-compression_level", - "0", + # output = pipe "-", ] # collect extra and filter args diff --git a/music_assistant/server/helpers/process.py b/music_assistant/server/helpers/process.py index c928416b3..6d8476fda 100644 --- a/music_assistant/server/helpers/process.py +++ b/music_assistant/server/helpers/process.py @@ -114,7 +114,8 @@ async def write(self, data: bytes) -> None: if self.closed or self._proc.stdin.is_closing(): return self._proc.stdin.write(data) - await self._proc.stdin.drain() + with suppress(BrokenPipeError): + await self._proc.stdin.drain() def write_eof(self) -> None: """Write end of file to to process stdin.""" diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64 deleted file mode 100755 index 337acf611..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64 and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64-static index 9b79048d5..ca9f28f5b 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-freebsd-x86_64-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64 deleted file mode 100755 index 668077f70..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64 and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64-static index 95cdc4dd1..b7be884f8 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-aarch64-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm deleted file mode 100755 index 3b240e32b..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm-static index acd8455e2..dc0e0d388 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-arm-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6 b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6 deleted file mode 100755 index ccbeb343e..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6 and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6-static index 7867ce6da..ac3fc9e32 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-armv6-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips deleted file mode 100755 index 8570099c9..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips-static index 068d1324d..5184eba45 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-mips-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc deleted file mode 100755 index 5dbf52a27..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc-static index 2775a988d..5de835859 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-powerpc-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64 deleted file mode 100755 index f8494ef2c..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64 and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64-static index 3c5f0226d..62c6acb45 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-sparc64-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86 b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86 deleted file mode 100755 index 4f856920f..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86 and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86-static index 6397f594b..54066495d 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64 deleted file mode 100755 index 5c511a397..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64 and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64-static index 27575f9c6..52aabc00c 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-linux-x86_64-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos deleted file mode 100755 index 27913281f..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64 deleted file mode 100755 index 67820ec0d..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64 and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64-static index c08c362c2..b1b834eda 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-arm64-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-static index c2ce4274e..5aba57543 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64 deleted file mode 100755 index a5489bf5f..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64 and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64-static index d7b867639..89b7ed637 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-macos-x86_64-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64 b/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64 deleted file mode 100755 index 80a3cd5ab..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64 and /dev/null differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64-static b/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64-static index 0c72982e2..1893e4176 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64-static and b/music_assistant/server/providers/airplay/bin/squeeze2raop-solaris-x86_64-static differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop-static.exe b/music_assistant/server/providers/airplay/bin/squeeze2raop-static.exe index e955ad6a2..74820de34 100755 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop-static.exe and b/music_assistant/server/providers/airplay/bin/squeeze2raop-static.exe differ diff --git a/music_assistant/server/providers/airplay/bin/squeeze2raop.exe b/music_assistant/server/providers/airplay/bin/squeeze2raop.exe deleted file mode 100755 index d3b228497..000000000 Binary files a/music_assistant/server/providers/airplay/bin/squeeze2raop.exe and /dev/null differ diff --git a/music_assistant/server/providers/chromecast/__init__.py b/music_assistant/server/providers/chromecast/__init__.py index c1788f749..8da64c579 100644 --- a/music_assistant/server/providers/chromecast/__init__.py +++ b/music_assistant/server/providers/chromecast/__init__.py @@ -19,7 +19,11 @@ from pychromecast.models import CastInfo from pychromecast.socket_client import CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED -from music_assistant.common.models.config_entries import ConfigEntry, ConfigValueOption +from music_assistant.common.models.config_entries import ( + CONF_ENTRY_OUTPUT_CODEC, + ConfigEntry, + ConfigValueOption, +) from music_assistant.common.models.enums import ( ConfigEntryType, ContentType, @@ -31,7 +35,12 @@ from music_assistant.common.models.errors import PlayerUnavailableError, QueueEmpty from music_assistant.common.models.player import DeviceInfo, Player from music_assistant.common.models.queue_item import QueueItem -from music_assistant.constants import CONF_HIDE_GROUP_CHILDS, CONF_PLAYERS, MASS_LOGO_ONLINE +from music_assistant.constants import ( + CONF_HIDE_GROUP_CHILDS, + CONF_OUTPUT_CODEC, + CONF_PLAYERS, + MASS_LOGO_ONLINE, +) from music_assistant.server.models.player_provider import PlayerProvider from .helpers import CastStatusListener, ChromecastInfo @@ -49,6 +58,7 @@ CONF_ALT_APP = "alt_app" + BASE_PLAYER_CONFIG_ENTRIES = ( ConfigEntry( key=CONF_ALT_APP, @@ -59,6 +69,7 @@ "the playback experience but may not work on non-Google hardware.", advanced=True, ), + CONF_ENTRY_OUTPUT_CODEC, ) @@ -188,13 +199,13 @@ async def cmd_play_media( ) -> None: """Send PLAY MEDIA command to given player.""" castplayer = self.castplayers[player_id] + output_codec = self.mass.config.get_player_config_value(player_id, CONF_OUTPUT_CODEC).value url = await self.mass.streams.resolve_stream_url( queue_item=queue_item, player_id=player_id, seek_position=seek_position, fade_in=fade_in, - # prefer FLAC as it seems to work on all CC players - content_type=ContentType.FLAC, + content_type=ContentType(output_codec), flow_mode=flow_mode, ) castplayer.flow_mode_active = flow_mode diff --git a/music_assistant/server/providers/dlna/__init__.py b/music_assistant/server/providers/dlna/__init__.py index 44e0b55d9..557b3087b 100644 --- a/music_assistant/server/providers/dlna/__init__.py +++ b/music_assistant/server/providers/dlna/__init__.py @@ -23,12 +23,12 @@ from async_upnp_client.search import async_search from async_upnp_client.utils import CaseInsensitiveDict -from music_assistant.common.models.config_entries import ConfigEntry +from music_assistant.common.models.config_entries import CONF_ENTRY_OUTPUT_CODEC, ConfigEntry from music_assistant.common.models.enums import ContentType, PlayerFeature, PlayerState, PlayerType from music_assistant.common.models.errors import PlayerUnavailableError, QueueEmpty from music_assistant.common.models.player import DeviceInfo, Player from music_assistant.common.models.queue_item import QueueItem -from music_assistant.constants import CONF_PLAYERS +from music_assistant.constants import CONF_OUTPUT_CODEC, CONF_PLAYERS from music_assistant.server.helpers.didl_lite import create_didl_metadata from music_assistant.server.models.player_provider import PlayerProvider @@ -46,7 +46,7 @@ PlayerFeature.VOLUME_MUTE, PlayerFeature.VOLUME_SET, ) -PLAYER_CONFIG_ENTRIES = tuple() # we don't have any player config entries (for now) +PLAYER_CONFIG_ENTRIES = (CONF_ENTRY_OUTPUT_CODEC,) _DLNAPlayerProviderT = TypeVar("_DLNAPlayerProviderT", bound="DLNAPlayerProvider") _R = TypeVar("_R") @@ -220,6 +220,10 @@ async def unload(self) -> None: for dlna_player in self.dlnaplayers.values(): tg.create_task(self._device_disconnect(dlna_player)) + def get_player_config_entries(self, player_id: str) -> tuple[ConfigEntry, ...]: # noqa: ARG002 + """Return all (provider/player specific) Config Entries for the given player (if any).""" + return PLAYER_CONFIG_ENTRIES + def on_player_config_changed( self, config: PlayerConfig, changed_keys: set[str] # noqa: ARG002 ) -> None: @@ -258,13 +262,13 @@ async def cmd_play_media( # always clear queue (by sending stop) first if dlna_player.device.can_stop: await self.cmd_stop(player_id) - + output_codec = self.mass.config.get_player_config_value(player_id, CONF_OUTPUT_CODEC).value url = await self.mass.streams.resolve_stream_url( queue_item=queue_item, player_id=dlna_player.udn, seek_position=seek_position, fade_in=fade_in, - content_type=ContentType.FLAC, + content_type=ContentType(output_codec), flow_mode=flow_mode, ) @@ -548,10 +552,13 @@ async def _enqueue_next_track( return # send queue item to dlna queue + output_codec = self.mass.config.get_player_config_value( + dlna_player.player.player_id, CONF_OUTPUT_CODEC + ).value url = await self.mass.streams.resolve_stream_url( queue_item=next_item, player_id=dlna_player.udn, - content_type=ContentType.FLAC, + content_type=ContentType(output_codec), # DLNA pre-caches pretty aggressively so do not yet start the runner auto_start_runner=False, ) diff --git a/music_assistant/server/providers/sonos/__init__.py b/music_assistant/server/providers/sonos/__init__.py index 6e60f26d3..a565ad3ce 100644 --- a/music_assistant/server/providers/sonos/__init__.py +++ b/music_assistant/server/providers/sonos/__init__.py @@ -14,7 +14,7 @@ from soco.events_base import SubscriptionBase from soco.groups import ZoneGroup -from music_assistant.common.models.config_entries import ConfigEntry +from music_assistant.common.models.config_entries import CONF_ENTRY_OUTPUT_CODEC, ConfigEntry from music_assistant.common.models.enums import ( ContentType, MediaType, @@ -42,7 +42,7 @@ PlayerFeature.VOLUME_MUTE, PlayerFeature.VOLUME_SET, ) -PLAYER_CONFIG_ENTRIES = tuple() # we don't have any player config entries (for now) +PLAYER_CONFIG_ENTRIES = (CONF_ENTRY_OUTPUT_CODEC,) async def setup( @@ -227,6 +227,10 @@ async def unload(self) -> None: for player in self.sonosplayers.values(): player.soco.end_direct_control_session + def get_player_config_entries(self, player_id: str) -> tuple[ConfigEntry, ...]: # noqa: ARG002 + """Return all (provider/player specific) Config Entries for the given player (if any).""" + return PLAYER_CONFIG_ENTRIES + def on_player_config_changed( self, config: PlayerConfig, changed_keys: set[str] # noqa: ARG002 ) -> None: diff --git a/script/profiler.py b/script/profiler.py new file mode 100644 index 000000000..c5135bc33 --- /dev/null +++ b/script/profiler.py @@ -0,0 +1,60 @@ +""" +Helper to trace memory usage. + +https://www.red-gate.com/simple-talk/development/python/memory-profiling-in-python-with-tracemalloc/ +""" +import asyncio +import tracemalloc + +# ruff: noqa: D103,E501,E741 + +# list to store memory snapshots +snaps = [] + + +def _take_snapshot(): + snaps.append(tracemalloc.take_snapshot()) + + +async def take_snapshot(): + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, _take_snapshot) + + +def _display_stats(): + stats = snaps[0].statistics("filename") + print("\n*** top 5 stats grouped by filename ***") + for s in stats[:5]: + print(s) + + +async def display_stats(): + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, _display_stats) + + +def compare(): + first = snaps[0] + for snapshot in snaps[1:]: + stats = snapshot.compare_to(first, "lineno") + print("\n*** top 10 stats ***") + for s in stats[:10]: + print(s) + + +def print_trace(): + # pick the last saved snapshot, filter noise + snapshot = snaps[-1].filter_traces( + ( + tracemalloc.Filter(False, ""), + tracemalloc.Filter(False, ""), + tracemalloc.Filter(False, ""), + ) + ) + largest = snapshot.statistics("traceback")[0] + + print( + f"\n*** Trace for largest memory block - ({largest.count} blocks, {largest.size/1024} Kb) ***" + ) + for l in largest.traceback.format(): + print(l)