Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions memtest/Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
.PHONY: build test lint format clean

UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
LIB_FILE := libmemtest.dylib
PRELOAD_ENV := DYLD_INSERT_LIBRARIES
else
LIB_FILE := libmemtest.so
PRELOAD_ENV := LD_PRELOAD
endif

build:
cargo build
cp target/debug/libmemtest.so python/memtest/
cp target/debug/$(LIB_FILE) python/memtest/
pip install -e .

build-release:
Expand All @@ -11,7 +20,7 @@ build-release:
pip install -e .

test:
LD_PRELOAD=./python/memtest/libmemtest.so pytest python/tests/ -v
$(PRELOAD_ENV)=./python/memtest/$(LIB_FILE) pytest python/tests/ -v

lint:
cargo clippy -- -D warnings
Expand All @@ -27,3 +36,4 @@ clean:
find . -type d -name __pycache__ -exec rm -rf {} +
find . -type f -name "*.pyc" -delete
find . -type f -name "*.so" -delete
find . -type f -name "*.dylib" -delete
6 changes: 6 additions & 0 deletions memtest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ To activate the memory tracking, you need to set the `LD_PRELOAD` environment va
export LD_PRELOAD=$(lance-memtest)
```

On macOS, use `DYLD_INSERT_LIBRARIES` instead:

```shell
export DYLD_INSERT_LIBRARIES=$(lance-memtest)
```

Then you can write Python code that tracks memory allocations:

```python
Expand Down
33 changes: 21 additions & 12 deletions memtest/python/memtest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
__version__ = "0.1.0"

# Platform support check
_SUPPORTED_PLATFORM = platform.system() == "Linux"
_SUPPORTED_PLATFORM = platform.system() in ("Linux", "Darwin")
if not _SUPPORTED_PLATFORM:
warnings.warn(
f"lance-memtest only supports Linux (current platform: {platform.system()}). "
f"lance-memtest only supports Linux/macOS (current platform: {platform.system()}). "
"Memory statistics will not be available.",
RuntimeWarning,
stacklevel=2,
Expand Down Expand Up @@ -41,7 +41,12 @@ def _load_library():
# Find the library relative to this module
module_dir = Path(__file__).parent

lib_path = module_dir / "libmemtest.so"
if platform.system() == "Linux":
lib_filename = "libmemtest.so"
else:
lib_filename = "libmemtest.dylib"

lib_path = module_dir / lib_filename
if lib_path.exists():
lib = ctypes.CDLL(str(lib_path))

Expand Down Expand Up @@ -74,16 +79,16 @@ def _empty_stats() -> Dict[str, int]:


def get_library_path() -> Optional[Path]:
"""Get the path to the memtest shared library for use with LD_PRELOAD.
"""Get the path to the memtest shared library for use with preloading.

Returns:
Path to the .so file that can be used with LD_PRELOAD, or None on
unsupported platforms.
Path to the library that can be used with `LD_PRELOAD` (Linux) or
`DYLD_INSERT_LIBRARIES` (macOS), or None on unsupported platforms.

Example:
>>> lib_path = get_library_path()
>>> if lib_path:
... os.environ['LD_PRELOAD'] = str(lib_path)
... os.environ['LD_PRELOAD'] = str(lib_path) # Linux
"""
return _lib_path

Expand Down Expand Up @@ -206,10 +211,11 @@ def print_stats(stats: Optional[Dict[str, int]] = None) -> None:


def is_preloaded() -> bool:
"""Check if libmemtest.so is preloaded and actively tracking allocations.
"""Check if libmemtest is preloaded and actively tracking allocations.

Returns:
True if the library is preloaded via LD_PRELOAD, False otherwise.
True if the library is preloaded via `LD_PRELOAD` (Linux) or
`DYLD_INSERT_LIBRARIES` (macOS), False otherwise.

Example:
>>> if is_preloaded():
Expand All @@ -218,15 +224,18 @@ def is_preloaded() -> bool:
"""
import os

ld_preload = os.environ.get("LD_PRELOAD", "")
return "libmemtest" in ld_preload
if platform.system() == "Linux":
preload = os.environ.get("LD_PRELOAD", "")
else:
preload = os.environ.get("DYLD_INSERT_LIBRARIES", "")
return "libmemtest" in preload


def is_supported() -> bool:
"""Check if memory tracking is supported on this platform.

Returns:
True if on Linux (the only supported platform), False otherwise.
True if on Linux/macOS, False otherwise.

Example:
>>> if is_supported():
Expand Down
12 changes: 9 additions & 3 deletions memtest/python/memtest/__main__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
"""CLI for lance-memtest."""

import sys
from memtest import get_library_path
import memtest


def main():
"""Main CLI entry point."""
args = sys.argv[1:]

if not args or args[0] == "path":
lib_path = get_library_path()
lib_path = memtest.get_library_path()
if lib_path is None:
print(
"lance-memtest is not supported on this platform",
Expand All @@ -18,9 +18,15 @@ def main():
return 1
print(lib_path)
return 0
if args[0] == "stats":
memtest.print_stats()
return 0
if args[0] == "reset":
memtest.reset_stats()
return 0
else:
print(f"Unknown command: {args[0]}", file=sys.stderr)
print("Usage: lance-memtest [path]", file=sys.stderr)
print("Usage: lance-memtest [path|stats|reset]", file=sys.stderr)
return 1


Expand Down
11 changes: 9 additions & 2 deletions memtest/python/tests/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Basic tests for memtest functionality."""

import platform
import subprocess
import sys

Expand All @@ -10,7 +11,10 @@ def test_get_library_path():
"""Test that we can get the library path."""
lib_path = memtest.get_library_path()
assert lib_path.exists()
assert lib_path.suffix == ".so"
if platform.system() == "Linux":
assert lib_path.suffix == ".so"
else:
assert lib_path.suffix == ".dylib"


def test_get_stats():
Expand Down Expand Up @@ -110,7 +114,10 @@ def test_cli_path():
)

assert result.returncode == 0
assert ".so" in result.stdout
if platform.system() == "Linux":
assert ".so" in result.stdout
else:
assert ".dylib" in result.stdout


def test_cli_stats():
Expand Down
8 changes: 6 additions & 2 deletions memtest/python/tests/test_integration.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Integration tests for memtest with real allocations."""

import os
import platform
import subprocess
import sys
import tempfile
Expand All @@ -10,7 +11,7 @@


def test_preload_environment():
"""Test that LD_PRELOAD works correctly."""
"""Test that preloading works correctly."""
lib_path = memtest.get_library_path()

# Create a small Python script that uses memtest
Expand All @@ -36,7 +37,10 @@ def test_preload_environment():

try:
env = os.environ.copy()
env["LD_PRELOAD"] = str(lib_path)
if platform.system() == "Linux":
env["LD_PRELOAD"] = str(lib_path)
else:
env["DYLD_INSERT_LIBRARIES"] = str(lib_path)

result = subprocess.run(
[sys.executable, script_path],
Expand Down
Loading