Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Comment thread
jy-tan marked this conversation as resolved.

# 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>=4.25.0
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
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