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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Tusk Drift currently supports the following packages and versions:
| HTTPX | all versions |
| aiohttp | all versions |
| urllib3 | all versions |
| grpcio (client-side only) | all versions |
| psycopg | `>=3.1.12` |
| psycopg2 | all versions |
| Redis | `>=4.0.0` |
Expand Down
10 changes: 10 additions & 0 deletions drift/core/drift_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,16 @@ def _init_auto_instrumentations(self) -> None:
except ImportError:
pass

try:
import grpc # type: ignore[unresolved-import]

from ..instrumentation.grpc import GrpcInstrumentation

_ = GrpcInstrumentation()
logger.debug("gRPC instrumentation initialized")
except ImportError:
pass

try:
import django

Expand Down
5 changes: 5 additions & 0 deletions drift/instrumentation/grpc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""gRPC client instrumentation."""

from .instrumentation import GrpcInstrumentation

__all__ = ["GrpcInstrumentation"]
27 changes: 27 additions & 0 deletions drift/instrumentation/grpc/e2e-tests/.tusk/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: 1

service:
id: "grpc-e2e-test-id"
name: "grpc-e2e-test"
port: 8000
start:
command: "python src/app.py"
readiness_check:
command: "curl -f http://localhost:8000/health"
timeout: 45s
interval: 5s

tusk_api:
url: "http://localhost:8000"

test_execution:
concurrent_limit: 10
batch_size: 10
timeout: 30s

recording:
sampling_rate: 1.0
export_spans: false

replay:
enable_telemetry: false
21 changes: 21 additions & 0 deletions drift/instrumentation/grpc/e2e-tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM python-e2e-base:latest

# Copy SDK source for editable install
COPY . /sdk

# Copy test files
COPY drift/instrumentation/grpc/e2e-tests /app

WORKDIR /app

# Install dependencies (requirements.txt uses -e /sdk for SDK)
RUN pip install -q -r requirements.txt

# Make entrypoint executable
RUN chmod +x entrypoint.py

# Create .tusk directories
RUN mkdir -p /app/.tusk/traces /app/.tusk/logs

# Run entrypoint
ENTRYPOINT ["python", "entrypoint.py"]
19 changes: 19 additions & 0 deletions drift/instrumentation/grpc/e2e-tests/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
services:
app:
build:
context: ../../../..
dockerfile: drift/instrumentation/grpc/e2e-tests/Dockerfile
args:
- TUSK_CLI_VERSION=${TUSK_CLI_VERSION:-latest}
environment:
- PORT=8000
- TUSK_ANALYTICS_DISABLED=1
- PYTHONUNBUFFERED=1
working_dir: /app
volumes:
# Mount SDK source for hot reload (no rebuild needed for SDK changes)
- ../../../..:/sdk
# Mount app source for development
- ./src:/app/src
# Mount .tusk folder to persist traces
- ./.tusk:/app/.tusk
67 changes: 67 additions & 0 deletions drift/instrumentation/grpc/e2e-tests/entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""
E2E Test Entrypoint for gRPC Instrumentation

This script orchestrates the full e2e test lifecycle:
1. Setup: Install dependencies, generate proto files
2. Record: Start app in RECORD mode, execute requests
3. Test: Run Tusk CLI tests
4. Teardown: Cleanup and return exit code
"""

import sys
from pathlib import Path

# Add SDK to path for imports
sys.path.insert(0, "/sdk")

from drift.instrumentation.e2e_common.base_runner import E2ETestRunnerBase


class GrpcE2ETestRunner(E2ETestRunnerBase):
"""E2E test runner for gRPC instrumentation."""

def __init__(self):
import os

port = int(os.getenv("PORT", "8000"))
super().__init__(app_port=port)

def setup(self):
"""Phase 1: Setup dependencies and generate proto files."""
self.log("=" * 50, self.Colors.BLUE)
self.log("Phase 1: Setup", self.Colors.BLUE)
self.log("=" * 50, self.Colors.BLUE)

self.log("Installing Python dependencies...", self.Colors.BLUE)
self.run_command(["pip", "install", "-q", "-r", "requirements.txt"])

# Generate proto files
self.log("Generating proto files...", self.Colors.BLUE)
self.run_command(
[
"python",
"-m",
"grpc_tools.protoc",
"-I",
"src/proto",
"--python_out=src",
"--grpc_python_out=src",
"src/proto/greeter.proto",
]
)

self.log("Setup complete", self.Colors.GREEN)

# Use Colors from base class
@property
def Colors(self):
from drift.instrumentation.e2e_common.base_runner import Colors

return Colors


if __name__ == "__main__":
runner = GrpcE2ETestRunner()
exit_code = runner.run()
sys.exit(exit_code)
5 changes: 5 additions & 0 deletions drift/instrumentation/grpc/e2e-tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-e /sdk
Flask>=3.1.2
grpcio>=1.60.0
grpcio-tools>=1.60.0
protobuf>=6.0
64 changes: 64 additions & 0 deletions drift/instrumentation/grpc/e2e-tests/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/bin/bash

# Exit on error
set -e

# Accept optional port parameter (default: 8000)
APP_PORT=${1:-8000}
export APP_PORT

# Generate unique docker compose project name
# Get the instrumentation name (parent directory of e2e-tests)
TEST_NAME="$(basename "$(dirname "$(pwd)")")"
PROJECT_NAME="python-${TEST_NAME}-${APP_PORT}"

# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE}Running Python E2E Test: ${TEST_NAME}${NC}"
echo -e "${BLUE}Port: ${APP_PORT}${NC}"
echo -e "${BLUE}========================================${NC}"
echo ""

# Cleanup function
cleanup() {
echo ""
echo -e "${YELLOW}Cleaning up containers...${NC}"
docker compose -p "$PROJECT_NAME" down -v 2>/dev/null || true
}

# Register cleanup on exit
trap cleanup EXIT

# Build containers
echo -e "${BLUE}Building containers...${NC}"
docker compose -p "$PROJECT_NAME" build --no-cache

# Run the test container
echo -e "${BLUE}Starting test...${NC}"
echo ""

# Run container and capture exit code (always use port 8000 inside container)
# Disable set -e temporarily to capture exit code
set +e
docker compose -p "$PROJECT_NAME" run --rm app
EXIT_CODE=$?
set -e

echo ""
if [ $EXIT_CODE -eq 0 ]; then
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Test passed!${NC}"
echo -e "${GREEN}========================================${NC}"
else
echo -e "${RED}========================================${NC}"
echo -e "${RED}Test failed with exit code ${EXIT_CODE}${NC}"
echo -e "${RED}========================================${NC}"
fi

exit $EXIT_CODE
Loading