Skip to content

Commit 74038ec

Browse files
authored
feat: enable rust core by default and add non-rust e2e smoke (#70)
1 parent d4a11af commit 74038ec

File tree

8 files changed

+175
-30
lines changed

8 files changed

+175
-30
lines changed

.github/workflows/e2e.yml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,74 @@ jobs:
196196
docker volume prune -f || true
197197
# Clean up networks
198198
docker network prune -f || true
199+
200+
non-rust-smoke:
201+
name: E2E Non-Rust Smoke - requests
202+
runs-on: ubuntu-latest
203+
timeout-minutes: 30
204+
steps:
205+
- name: Checkout
206+
uses: actions/checkout@v4
207+
208+
- name: Install uv
209+
uses: astral-sh/setup-uv@v4
210+
with:
211+
version: "latest"
212+
213+
- name: Setup Python
214+
run: uv python install 3.9
215+
216+
- name: Setup Docker Buildx
217+
uses: docker/setup-buildx-action@v3
218+
with:
219+
driver: docker
220+
221+
- name: Install SDK dependencies
222+
run: uv sync --all-extras
223+
224+
- name: Build SDK
225+
run: uv build
226+
227+
- name: Verify SDK build
228+
run: |
229+
ls -la dist/ || (echo "dist folder not found!" && exit 1)
230+
test -f dist/*.whl || (echo "SDK build incomplete!" && exit 1)
231+
232+
- name: Get latest Tusk CLI version
233+
id: tusk-version
234+
run: |
235+
VERSION=$(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
236+
"https://api.github.com/repos/Use-Tusk/tusk-drift-cli/releases/latest" \
237+
| grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
238+
echo "version=$VERSION" >> $GITHUB_OUTPUT
239+
echo "Latest Tusk CLI version: $VERSION"
240+
241+
- name: Build base image
242+
env:
243+
DOCKER_DEFAULT_PLATFORM: linux/amd64
244+
run: |
245+
docker build \
246+
--build-arg TUSK_CLI_VERSION=${{ steps.tusk-version.outputs.version }} \
247+
-t python-e2e-base:latest \
248+
-f drift/instrumentation/e2e_common/Dockerfile.base \
249+
.
250+
251+
- name: Run non-rust smoke test
252+
env:
253+
DOCKER_DEFAULT_PLATFORM: linux/amd64
254+
TUSK_CLI_VERSION: ${{ steps.tusk-version.outputs.version }}
255+
TUSK_USE_RUST_CORE: "0"
256+
run: |
257+
chmod +x ./drift/instrumentation/requests/e2e-tests/run.sh
258+
cd ./drift/instrumentation/requests/e2e-tests && ./run.sh 8000
259+
260+
- name: Cleanup Docker resources
261+
if: always()
262+
run: |
263+
# Stop all running containers
264+
docker ps -aq | xargs -r docker stop || true
265+
docker ps -aq | xargs -r docker rm || true
266+
# Clean up volumes
267+
docker volume prune -f || true
268+
# Clean up networks
269+
docker network prune -f || true

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,15 @@ Alternatively, you can set up Tusk Drift manually:
9090
1. Install the SDK:
9191

9292
```bash
93+
# Use Rust bindings for better performance
94+
pip install tusk-drift-python-sdk[rust]
95+
96+
# Fallback if no platform-compatible wheel
9397
pip install tusk-drift-python-sdk
9498
```
9599

100+
*For more information about Rust acceleration, refer to [this doc](docs/rust-core-bindings).*
101+
96102
2. Create configuration: Run `tusk init` to create your `.tusk/config.yaml` config file interactively, or create it manually per the [configuration docs](https://github.com/Use-Tusk/tusk-drift-cli/blob/main/docs/configuration.md).
97103

98104
3. Initialize the SDK: Refer to the [initialization guide](docs/initialization.md) to instrument the SDK in your service.

docs/environment-variables.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,23 +136,28 @@ These variables control optional Rust-accelerated paths in the SDK.
136136

137137
| Variable | Description | Default |
138138
| --- | --- | --- |
139-
| `TUSK_USE_RUST_CORE` | Enables Rust binding usage when available (`1`, `true`, `yes`) | `0` (disabled) |
139+
| `TUSK_USE_RUST_CORE` | Controls Rust binding usage. Truthy (`1`, `true`, `yes`, `on`) enables, falsy (`0`, `false`, `no`, `off`) disables. | Enabled when unset |
140140
| `TUSK_SKIP_PROTO_VALIDATION` | Skips expensive protobuf validation in hot path (`1`, `true`, `yes`) | `0` (disabled) |
141141

142142
**Notes:**
143143

144144
- The SDK is fail-open: if Rust bindings are unavailable or a Rust call fails, it falls back to Python implementation.
145+
- `TUSK_USE_RUST_CORE` defaults to enabled when unset.
145146
- `TUSK_USE_RUST_CORE` does not install Rust bindings automatically. The `drift-core-python` package still must be installed in your environment.
147+
- If Rust is enabled but bindings cannot be loaded, the SDK logs startup fallback and continues on Python paths.
146148
- `TUSK_SKIP_PROTO_VALIDATION` is performance-focused and should be used with confidence in parity tests and serialization correctness.
147149

148150
See [`rust-core-bindings.md`](./rust-core-bindings.md) for more details.
149151

150152
**Example usage:**
151153

152154
```bash
153-
# Enable Rust path (if drift-core-python is installed)
155+
# Explicitly enable Rust path (also the default when unset)
154156
TUSK_USE_RUST_CORE=1 python app.py
155157

158+
# Explicitly disable Rust path
159+
TUSK_USE_RUST_CORE=0 python app.py
160+
156161
# Enable Rust path and skip proto validation
157162
TUSK_USE_RUST_CORE=1 TUSK_SKIP_PROTO_VALIDATION=1 python app.py
158163
```

docs/rust-core-bindings.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,22 @@ At a high level:
1414

1515
## Enablement
1616

17-
Set:
17+
Rust is enabled by default when `TUSK_USE_RUST_CORE` is unset.
18+
19+
Use `TUSK_USE_RUST_CORE` to explicitly override behavior:
20+
21+
- Truthy: `1`, `true`, `yes`, `on`
22+
- Falsy: `0`, `false`, `no`, `off`
23+
24+
Examples:
1825

1926
```bash
27+
# Explicitly enable (same as unset)
2028
TUSK_USE_RUST_CORE=1
21-
```
2229

23-
Truthy values are `1`, `true`, and `yes` (case-insensitive). Any other value is treated as disabled.
30+
# Explicitly disable
31+
TUSK_USE_RUST_CORE=0
32+
```
2433

2534
## Installation Requirements
2635

@@ -37,23 +46,13 @@ You can install the SDK with Rust bindings via extras:
3746
pip install "tusk-drift-python-sdk[rust]"
3847
```
3948

40-
## Wheel Platform Coverage
41-
42-
Based on the current `drift-core` publish workflow, prebuilt wheels are built for:
43-
44-
- Linux `x86_64-unknown-linux-gnu`
45-
- Linux `aarch64-unknown-linux-gnu`
46-
- macOS Apple Silicon `aarch64-apple-darwin`
47-
- Windows `x86_64-pc-windows-msvc`
49+
## Platform Compatibility
4850

49-
Likely missing prebuilt wheels (source build fallback required) include:
51+
`drift-core` publishes native artifacts across a defined support matrix. See:
5052

51-
- macOS Intel (`x86_64-apple-darwin`)
52-
- Linux musl targets (e.g. Alpine)
53-
- Windows ARM64
54-
- Other uncommon Python/platform combinations not covered by release artifacts
53+
- [`drift-core` compatibility matrix](https://github.com/Use-Tusk/drift-core/blob/main/docs/compatibility-matrix.md)
5554

56-
If no wheel matches the environment, `pip` may attempt a source build of `drift-core-python`, which typically requires a Rust toolchain and native build prerequisites.
55+
If no compatible wheel exists for your environment, `pip` may attempt a source build of `drift-core-python`, which typically requires a Rust toolchain and native build prerequisites.
5756

5857
## Fallback Behavior
5958

@@ -62,6 +61,7 @@ The bridge module is fail-open:
6261
- Rust calls are guarded.
6362
- On import failures or call exceptions, the corresponding helper returns `None`.
6463
- Calling code then uses the existing Python implementation.
64+
- On startup, the SDK logs whether Rust is enabled/disabled and whether it had to fall back to Python.
6565

6666
This means users do not need Rust installed to run the SDK when Rust acceleration is disabled or unavailable.
6767

@@ -80,7 +80,7 @@ Use with care:
8080

8181
## Practical Guidance
8282

83-
- Default production-safe posture: leave Rust disabled unless you have tested your deployment matrix.
83+
- Default production-safe posture: keep Rust enabled (default) only on tested deployment matrices.
8484
- Performance posture: enable Rust + benchmark on your workloads before broad rollout.
8585
- Reliability posture: keep parity tests and smoke tests in CI to detect drift between Python and Rust paths.
8686

drift/core/drift_sdk.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,34 @@ def _generate_sdk_instance_id(self) -> str:
7878
random_suffix = "".join(random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=9))
7979
return f"sdk-{timestamp_ms}-{random_suffix}"
8080

81+
@staticmethod
82+
def _log_rust_core_startup_status() -> None:
83+
from .rust_core_binding import get_rust_core_startup_status
84+
85+
status = get_rust_core_startup_status()
86+
env_display = status["raw_env"] if status["raw_env"] is not None else "<unset>"
87+
88+
if status["reason"] == "invalid_env_value_defaulted":
89+
logger.warning(
90+
"Invalid TUSK_USE_RUST_CORE value '%s'; defaulting to enabled rust core path.",
91+
env_display,
92+
)
93+
94+
if not status["enabled"]:
95+
logger.info("Rust core path disabled at startup (env=%s, reason=%s).", env_display, status["reason"])
96+
return
97+
98+
if status["binding_loaded"]:
99+
logger.info("Rust core path enabled at startup (env=%s, reason=%s).", env_display, status["reason"])
100+
return
101+
102+
logger.warning(
103+
"Rust core path requested but binding unavailable; falling back to Python path (env=%s, reason=%s, error=%s).",
104+
env_display,
105+
status["reason"],
106+
status["binding_error"],
107+
)
108+
81109
@classmethod
82110
def initialize(
83111
cls,
@@ -156,6 +184,7 @@ def initialize(
156184
logger.debug("SDK disabled via environment variable")
157185
return instance
158186

187+
instance._log_rust_core_startup_status()
159188
logger.debug(f"Initializing in {instance.mode} mode")
160189

161190
effective_transforms = transforms

drift/core/rust_core_binding.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,58 @@
1515

1616
_binding_module = None
1717
_binding_load_attempted = False
18+
_binding_load_error: str | None = None
19+
20+
_RUST_TRUTHY = {"1", "true", "yes", "on"}
21+
_RUST_FALSY = {"0", "false", "no", "off"}
22+
23+
24+
def _rust_env_decision() -> tuple[bool, str, str | None]:
25+
raw = os.getenv("TUSK_USE_RUST_CORE")
26+
if raw is None:
27+
return True, "default_on", None
28+
normalized = raw.strip().lower()
29+
if normalized in _RUST_TRUTHY:
30+
return True, "env_enabled", raw
31+
if normalized in _RUST_FALSY:
32+
return False, "env_disabled", raw
33+
return True, "invalid_env_value_defaulted", raw
1834

1935

2036
def _enabled() -> bool:
21-
return os.getenv("TUSK_USE_RUST_CORE", "0").lower() in {"1", "true", "yes"}
37+
enabled, _, _ = _rust_env_decision()
38+
return enabled
2239

2340

2441
def _load_binding():
25-
global _binding_module, _binding_load_attempted
42+
global _binding_module, _binding_load_attempted, _binding_load_error
2643
if _binding_load_attempted:
2744
return _binding_module
2845
_binding_load_attempted = True
2946
try:
3047
import drift_core as binding
3148

3249
_binding_module = binding
50+
_binding_load_error = None
3351
except Exception as exc: # pragma: no cover - depends on runtime env
3452
logger.debug("Rust core binding not available: %s", exc)
3553
_binding_module = None
54+
_binding_load_error = f"{type(exc).__name__}: {exc}"
3655
return _binding_module
3756

3857

58+
def get_rust_core_startup_status() -> dict[str, Any]:
59+
enabled, reason, raw_env = _rust_env_decision()
60+
loaded = _load_binding() if enabled else None
61+
return {
62+
"enabled": enabled,
63+
"reason": reason,
64+
"raw_env": raw_env,
65+
"binding_loaded": loaded is not None,
66+
"binding_error": _binding_load_error,
67+
}
68+
69+
3970
def normalize_and_hash_jsonable(value: Any) -> tuple[Any, str] | None:
4071
"""Return normalized JSON value and deterministic hash via Rust binding."""
4172
if not _enabled():

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ dependencies = [
4444
flask = ["Flask>=3.1.2"]
4545
fastapi = ["fastapi>=0.115.6", "uvicorn>=0.34.2", "starlette<0.42.0"]
4646
django = ["Django>=4.2"]
47-
rust = ["drift-core-python>=0.1.6"]
47+
rust = ["drift-core-python>=0.1.7"]
4848
dev = [
4949
"Flask>=3.1.2",
5050
"fastapi>=0.115.6",

uv.lock

Lines changed: 10 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)