diff --git a/Cargo.lock b/Cargo.lock index 3e9a4ab32d32a..5b222de2ebfad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4472,6 +4472,7 @@ dependencies = [ "jsonwebtoken 8.3.0", "once_cell", "prometheus", + "prost 0.13.4", "rand 0.7.3", "rand_core 0.5.1", "reqwest 0.11.23", @@ -4480,6 +4481,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml 0.8.26", + "snap", "thiserror 1.0.69", "tokio", "tracing", @@ -17668,6 +17670,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + [[package]] name = "socket2" version = "0.4.10" diff --git a/crates/aptos-telemetry-service/Cargo.toml b/crates/aptos-telemetry-service/Cargo.toml index c2e352ed24055..0c0124bb287c3 100644 --- a/crates/aptos-telemetry-service/Cargo.toml +++ b/crates/aptos-telemetry-service/Cargo.toml @@ -35,6 +35,7 @@ gcp-bigquery-client = { workspace = true } jsonwebtoken = { workspace = true } once_cell = { workspace = true } prometheus = { workspace = true } +prost = { workspace = true } rand = { workspace = true } rand_core = { workspace = true } reqwest = { workspace = true } @@ -43,6 +44,7 @@ reqwest-retry = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true } +snap = "1.1" thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/crates/aptos-telemetry-service/e2e-test/cleanup.sh b/crates/aptos-telemetry-service/e2e-test/cleanup.sh index 3aee778702180..2f3b2546e7fcb 100755 --- a/crates/aptos-telemetry-service/e2e-test/cleanup.sh +++ b/crates/aptos-telemetry-service/e2e-test/cleanup.sh @@ -46,6 +46,26 @@ else echo -e "${YELLOW}No node process found${NC}" fi fi + +# Stop telemetry service +if [ -f "$TEST_DIR/telemetry.pid" ]; then + TELEMETRY_PID=$(cat "$TEST_DIR/telemetry.pid") + if ps -p $TELEMETRY_PID > /dev/null 2>&1; then + kill $TELEMETRY_PID + echo -e "${GREEN}✓ Telemetry service stopped (PID: $TELEMETRY_PID)${NC}" + else + echo -e "${YELLOW}Telemetry process not running${NC}" + fi + rm "$TEST_DIR/telemetry.pid" +else + # Try to find and kill any telemetry service process on port 8082 + if lsof -Pi :8082 -sTCP:LISTEN -t >/dev/null 2>&1; then + kill $(lsof -t -i:8082) 2>/dev/null || true + echo -e "${GREEN}✓ Stopped process on port 8082${NC}" + else + echo -e "${YELLOW}No telemetry service process found${NC}" + fi +fi echo "" # Ask about data removal diff --git a/crates/aptos-telemetry-service/e2e-test/docker-compose.yaml b/crates/aptos-telemetry-service/e2e-test/docker-compose.yaml index e496c0a67d5ac..257d9a3730aae 100644 --- a/crates/aptos-telemetry-service/e2e-test/docker-compose.yaml +++ b/crates/aptos-telemetry-service/e2e-test/docker-compose.yaml @@ -1,9 +1,15 @@ # Docker Compose setup for E2E testing of Aptos Telemetry Service -# Runs VictoriaMetrics and Loki locally for telemetry data ingestion testing +# +# Architecture: +# - VictoriaMetrics: Primary metrics backend (accepts Prometheus text format via /api/v1/import/prometheus) +# - Prometheus: Secondary metrics store (accepts remote write via /api/v1/write) +# - Loki: Log aggregation backend +# - Grafana: Visualization (queries all backends) version: '3.8' services: - # VictoriaMetrics - Time-series database for metrics + # VictoriaMetrics - Primary metrics backend for telemetry ingestion + # Accepts Prometheus text format via /api/v1/import/prometheus (simpler than protobuf) victoria-metrics: image: victoriametrics/victoria-metrics:latest container_name: telemetry-victoria-metrics @@ -23,6 +29,28 @@ services: timeout: 5s retries: 3 + # Prometheus - Secondary metrics backend for remote write + prometheus: + image: prom/prometheus:latest + container_name: telemetry-prometheus + ports: + - "9090:9090" # HTTP API for queries and remote write + volumes: + - ./prometheus.yaml:/etc/prometheus/prometheus.yml + - prometheus-data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--storage.tsdb.retention.time=30d' + - '--web.enable-remote-write-receiver' # Enable receiving remote writes + - '--web.enable-lifecycle' # Enable /-/reload endpoint + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/-/healthy"] + interval: 10s + timeout: 5s + retries: 3 + # Loki - Log aggregation system loki: image: grafana/loki:3.0.0 @@ -56,11 +84,14 @@ services: restart: unless-stopped depends_on: - victoria-metrics + - prometheus - loki volumes: victoria-data: driver: local + prometheus-data: + driver: local loki-data: driver: local grafana-data: diff --git a/crates/aptos-telemetry-service/e2e-test/grafana-datasources.yaml b/crates/aptos-telemetry-service/e2e-test/grafana-datasources.yaml index ed519f78ccfbd..b33c07ad8a8a9 100644 --- a/crates/aptos-telemetry-service/e2e-test/grafana-datasources.yaml +++ b/crates/aptos-telemetry-service/e2e-test/grafana-datasources.yaml @@ -1,10 +1,16 @@ # Grafana datasources for E2E testing -# Auto-configures VictoriaMetrics and Loki datasources +# +# Architecture: +# - VictoriaMetrics (default): Receives telemetry metrics via text format import +# - Prometheus: Scrapes VictoriaMetrics, provides alternative PromQL endpoint +# - Loki: Receives telemetry logs +# +# Both VictoriaMetrics and Prometheus support PromQL, so queries work on either. apiVersion: 1 datasources: - # VictoriaMetrics (Prometheus-compatible) datasource + # VictoriaMetrics - Primary metrics backend (receives telemetry via import) - name: VictoriaMetrics type: prometheus access: proxy @@ -15,7 +21,18 @@ datasources: timeInterval: 10s editable: true - # Loki datasource + # Prometheus - Secondary metrics backend (receives telemetry via remote write) + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: false + jsonData: + httpMethod: POST + timeInterval: 15s + editable: true + + # Loki datasource - log aggregation - name: Loki type: loki access: proxy diff --git a/crates/aptos-telemetry-service/e2e-test/helpers.sh b/crates/aptos-telemetry-service/e2e-test/helpers.sh new file mode 100755 index 0000000000000..51e1efb02dd27 --- /dev/null +++ b/crates/aptos-telemetry-service/e2e-test/helpers.sh @@ -0,0 +1,246 @@ +#!/bin/bash +# Helper functions for E2E testing +# Source this file to use the helper functions + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# Directories +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +TEST_DIR="$SCRIPT_DIR/test-data" +MOVE_DIR="$SCRIPT_DIR/move" + +# ============================================================================ +# Set up APTOS_CMD +# ============================================================================ +setup_aptos_cmd() { + if [ -n "$APTOS_CMD" ]; then + return + fi + if command -v aptos &> /dev/null; then + export APTOS_CMD="aptos" + else + echo -e "${YELLOW}Warning: Aptos CLI not found in PATH, using cargo${NC}" + export APTOS_CMD="cargo run -p aptos --" + fi +} + +# ============================================================================ +# Load environment from .env file +# ============================================================================ +load_env() { + if [ -f "$TEST_DIR/.env" ]; then + set -a + source "$TEST_DIR/.env" + set +a + return 0 + else + echo -e "${RED}Error: .env file not found at $TEST_DIR/.env${NC}" + return 1 + fi +} + +# ============================================================================ +# Create a new account +# Args: $1 = account_name (used for key file and profile naming) +# Returns: Sets CREATED_ACCOUNT_ADDRESS and CREATED_ACCOUNT_KEY_HEX +# ============================================================================ +create_account() { + local account_name="$1" + + if [ -z "$account_name" ]; then + echo -e "${RED}Error: account_name required${NC}" + return 1 + fi + + setup_aptos_cmd + mkdir -p "$TEST_DIR" + cd "$TEST_DIR" + + local key_file="$TEST_DIR/${account_name}.key" + local profile_name="telemetry-e2e-${account_name}" + + # Generate key + $APTOS_CMD key generate --output-file "$key_file" --key-type ed25519 2>/dev/null + + # Get the key hex + CREATED_ACCOUNT_KEY_HEX="0x$(cat "$key_file")" + + # Initialize profile (this also funds the account via faucet in local testnet) + $APTOS_CMD init \ + --profile "$profile_name" \ + --network local \ + --assume-yes \ + --private-key "$CREATED_ACCOUNT_KEY_HEX" 2>/dev/null + + # Extract the address from config + CREATED_ACCOUNT_ADDRESS=$(yq e ".profiles.\"$profile_name\".account" "$TEST_DIR/.aptos/config.yaml" 2>/dev/null | tr -d '"') + + if [ -z "$CREATED_ACCOUNT_ADDRESS" ]; then + echo -e "${RED}Error: Failed to create account $account_name${NC}" + return 1 + fi + + echo -e "${GREEN}✓ Created account:${NC} $account_name" + echo " Address: $CREATED_ACCOUNT_ADDRESS" + + export CREATED_ACCOUNT_ADDRESS + export CREATED_ACCOUNT_KEY_HEX +} + +# ============================================================================ +# Add a member to the telemetry registry +# Args: $1 = member_address, $2 = ip (optional), $3 = port (optional), +# $4 = node_id (optional), $5 = datacenter (optional) +# ============================================================================ +add_member_to_registry() { + local member_address="$1" + local ip="${2:-127.0.0.1}" + local port="${3:-9000}" + local node_id="${4:-0x$(openssl rand -hex 16)}" + local datacenter="${5:-dc_local}" + + if [ -z "$member_address" ]; then + echo -e "${RED}Error: member_address required${NC}" + return 1 + fi + + # Load env if DEPLOYER_ADDRESS not set + if [ -z "$DEPLOYER_ADDRESS" ]; then + load_env || return 1 + fi + + setup_aptos_cmd + + $APTOS_CMD move run \ + --profile telemetry-service-e2e-test \ + --function-id "${DEPLOYER_ADDRESS}::telemetry_registry::add_member" \ + --args "address:$member_address" "string:$ip" "string:$port" "string:$node_id" "string:$datacenter" \ + --assume-yes \ + --url http://localhost:8080 \ + 2>/dev/null + + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Added member to registry:${NC} $member_address" + else + echo -e "${RED}✗ Failed to add member:${NC} $member_address" + return 1 + fi +} + +# ============================================================================ +# Send telemetry data using test-client +# Args: $1 = private_key_hex, $2 = iterations (optional, default 1) +# ============================================================================ +send_telemetry() { + local private_key="$1" + local iterations="${2:-1}" + + if [ -z "$private_key" ]; then + echo -e "${RED}Error: private_key required${NC}" + return 1 + fi + + cd "$SCRIPT_DIR/test-client" + + # Run test client (iterations flag is on the 'all' subcommand) + cargo run -q -p telemetry-test-client -- \ + -p "$private_key" \ + all --iterations "$iterations" 2>&1 + + local exit_code=$? + if [ $exit_code -eq 0 ]; then + echo -e "${GREEN}✓ Telemetry sent successfully${NC}" + else + echo -e "${RED}✗ Failed to send telemetry (exit code: $exit_code)${NC}" + return 1 + fi +} + +# ============================================================================ +# Verify metrics in VictoriaMetrics +# Args: $1 = metric_name +# ============================================================================ +verify_metrics_victoria() { + local metric_name="$1" + + # Use series endpoint which finds metrics even if not recently updated + local result=$(curl -s "http://localhost:8428/api/v1/series?match[]=$metric_name" | jq -r '.data | length') + + if [ "$result" -gt 0 ]; then + echo -e "${GREEN}✓ Metric '$metric_name' found in VictoriaMetrics ($result series)${NC}" + return 0 + else + echo -e "${RED}✗ Metric '$metric_name' not found in VictoriaMetrics${NC}" + return 1 + fi +} + +# ============================================================================ +# Verify metrics in Prometheus +# Args: $1 = metric_name +# ============================================================================ +verify_metrics_prometheus() { + local metric_name="$1" + + # Use series endpoint which finds metrics even if not recently updated + local result=$(curl -s "http://localhost:9090/api/v1/series?match[]=$metric_name" | jq -r '.data | length') + + if [ "$result" -gt 0 ]; then + echo -e "${GREEN}✓ Metric '$metric_name' found in Prometheus ($result series)${NC}" + return 0 + else + echo -e "${RED}✗ Metric '$metric_name' not found in Prometheus${NC}" + return 1 + fi +} + +# ============================================================================ +# Query Loki logs +# Args: $1 = label query (e.g., {contract_name="e2e_test_contract"}) +# ============================================================================ +verify_logs_loki() { + local query="$1" + + # URL encode the query + local encoded_query=$(echo "$query" | jq -sRr @uri) + local result=$(curl -s "http://localhost:3100/loki/api/v1/query?query=$encoded_query" | jq -r '.data.result | length') + + if [ "$result" -gt 0 ]; then + echo -e "${GREEN}✓ Logs found in Loki ($result streams)${NC}" + return 0 + else + echo -e "${YELLOW}⚠ No logs found in Loki for query: $query${NC}" + return 1 + fi +} + +# ============================================================================ +# Print a section header +# ============================================================================ +print_section() { + local title="$1" + echo "" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${CYAN} $title${NC}" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" +} + +# Export all functions +export -f setup_aptos_cmd +export -f load_env +export -f create_account +export -f add_member_to_registry +export -f send_telemetry +export -f verify_metrics_victoria +export -f verify_metrics_prometheus +export -f verify_logs_loki +export -f print_section + diff --git a/crates/aptos-telemetry-service/e2e-test/move/Move.toml b/crates/aptos-telemetry-service/e2e-test/move/Move.toml index f65d0e23c98c9..f384a8b152865 100644 --- a/crates/aptos-telemetry-service/e2e-test/move/Move.toml +++ b/crates/aptos-telemetry-service/e2e-test/move/Move.toml @@ -4,6 +4,7 @@ version = "1.0.0" authors = ["Aptos Labs"] [addresses] +# The address gets replaced by the test setup script telemetry_deployer = "_" [dependencies.AptosFramework] diff --git a/crates/aptos-telemetry-service/e2e-test/prometheus.yaml b/crates/aptos-telemetry-service/e2e-test/prometheus.yaml new file mode 100644 index 0000000000000..e7a0d0c9185f9 --- /dev/null +++ b/crates/aptos-telemetry-service/e2e-test/prometheus.yaml @@ -0,0 +1,19 @@ +# Prometheus configuration for E2E testing + +global: + scrape_interval: 15s + evaluation_interval: 15s + +# Alerting (disabled for testing) +alerting: + alertmanagers: [] + +# Rule files (none for testing) +rule_files: [] + +# Scrape configurations +scrape_configs: + # Self-monitoring + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] diff --git a/crates/aptos-telemetry-service/e2e-test/run-test.sh b/crates/aptos-telemetry-service/e2e-test/run-test.sh new file mode 100755 index 0000000000000..927e7cd4240f5 --- /dev/null +++ b/crates/aptos-telemetry-service/e2e-test/run-test.sh @@ -0,0 +1,242 @@ +#!/bin/bash +# E2E Test Runner Script +# Sends telemetry data from multiple test accounts +# +# Usage: +# ./run-test.sh # Run full test with 10 additional accounts +# ./run-test.sh --accounts 5 # Create and test with 5 additional accounts +# ./run-test.sh --skip-create # Skip creating new accounts, use existing +# ./run-test.sh --iterations 3 # Send telemetry 3 times per account + +set -e + +# Source helpers +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/helpers.sh" + +# Default values +NUM_ACCOUNTS=10 +SKIP_CREATE=false +ITERATIONS=1 +VERIFY_ONLY=false + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + -a|--accounts) + NUM_ACCOUNTS="$2" + shift 2 + ;; + -n|--iterations) + ITERATIONS="$2" + shift 2 + ;; + --skip-create) + SKIP_CREATE=true + shift + ;; + --verify) + VERIFY_ONLY=true + shift + ;; + -h|--help) + echo "E2E Test Runner" + echo "" + echo "Usage:" + echo " ./run-test.sh Run full test" + echo " ./run-test.sh --accounts N Create N additional accounts (default: 10)" + echo " ./run-test.sh --iterations N Send telemetry N times per account (default: 1)" + echo " ./run-test.sh --skip-create Skip creating accounts, use existing" + echo " ./run-test.sh --verify Only verify metrics (no telemetry send)" + echo " ./run-test.sh --help Show this help" + exit 0 + ;; + *) + echo -e "${RED}Unknown option: $1${NC}" + exit 1 + ;; + esac +done + +# ============================================================================ +# Main Test Execution +# ============================================================================ + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE} Aptos Telemetry Service E2E Test ${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# Load environment +print_section "Loading Environment" +load_env || exit 1 + +echo "Contract Address: $DEPLOYER_ADDRESS" +echo "Test Account: $TEST_ACCOUNT_ADDRESS" +echo "" + +# Initialize aptos command +setup_aptos_cmd + +# Skip to verification if requested +if [ "$VERIFY_ONLY" = true ]; then + print_section "Verifying Metrics" + verify_metrics_victoria "test_iteration_metric" + verify_metrics_prometheus "test_iteration_metric" + verify_logs_loki '{contract_name="e2e_test_contract"}' + exit 0 +fi + +# ============================================================================ +# Phase 1: Test with existing account +# ============================================================================ +print_section "Phase 1: Testing with Existing Account" + +echo "Sending telemetry from existing test account..." +echo " Address: $TEST_ACCOUNT_ADDRESS" +echo " Key: ${TEST_ACCOUNT_KEY_HEX:0:20}..." +echo "" + +send_telemetry "$TEST_ACCOUNT_KEY_HEX" "$ITERATIONS" + +# Verify initial telemetry +echo "" +echo "Verifying telemetry data..." +sleep 2 # Give backends time to ingest +verify_metrics_victoria "test_iteration_metric" +verify_metrics_prometheus "test_iteration_metric" + +# ============================================================================ +# Phase 2: Create additional accounts +# ============================================================================ +if [ "$SKIP_CREATE" = false ] && [ "$NUM_ACCOUNTS" -gt 0 ]; then + print_section "Phase 2: Creating $NUM_ACCOUNTS Additional Accounts" + + # Store account info for later + ADDITIONAL_ACCOUNTS=() + ADDITIONAL_KEYS=() + + for i in $(seq 1 $NUM_ACCOUNTS); do + echo "" + echo -e "${YELLOW}Creating account $i/$NUM_ACCOUNTS...${NC}" + + # Create account with unique name + create_account "test-member-$i" + + # Store for later use + ADDITIONAL_ACCOUNTS+=("$CREATED_ACCOUNT_ADDRESS") + ADDITIONAL_KEYS+=("$CREATED_ACCOUNT_KEY_HEX") + done + + echo "" + echo -e "${GREEN}✓ Created $NUM_ACCOUNTS accounts${NC}" + + # ============================================================================ + # Phase 3: Add all accounts to registry + # ============================================================================ + print_section "Phase 3: Adding Accounts to Registry" + + for i in $(seq 0 $((NUM_ACCOUNTS - 1))); do + local_addr="${ADDITIONAL_ACCOUNTS[$i]}" + local_port=$((9001 + i)) + local_dc="dc_test_$((i % 3))" # Rotate through 3 datacenters + + echo "" + echo -e "${YELLOW}Adding account $((i + 1))/$NUM_ACCOUNTS to registry...${NC}" + add_member_to_registry "$local_addr" "127.0.0.1" "$local_port" "0xnode$i" "$local_dc" + done + + echo "" + echo -e "${GREEN}✓ All accounts added to registry${NC}" + + # ============================================================================ + # Phase 4: Send telemetry from all new accounts + # ============================================================================ + print_section "Phase 4: Sending Telemetry from All New Accounts" + + # Wait for allowlist cache to refresh (configured at 10s in telemetry-config.yaml) + echo "Waiting for allowlist cache to refresh..." + sleep 12 + + SUCCESS_COUNT=0 + FAIL_COUNT=0 + + for i in $(seq 0 $((NUM_ACCOUNTS - 1))); do + local_key="${ADDITIONAL_KEYS[$i]}" + local_addr="${ADDITIONAL_ACCOUNTS[$i]}" + + echo "" + echo -e "${YELLOW}[$((i + 1))/$NUM_ACCOUNTS]${NC} Sending telemetry from ${local_addr:0:16}..." + + if send_telemetry "$local_key" "$ITERATIONS"; then + ((SUCCESS_COUNT++)) + else + ((FAIL_COUNT++)) + fi + done + + echo "" + echo -e "${BLUE}Results:${NC} $SUCCESS_COUNT succeeded, $FAIL_COUNT failed" +fi + +# ============================================================================ +# Phase 5: Final Verification +# ============================================================================ +print_section "Phase 5: Final Verification" + +echo "Waiting for metrics to be ingested..." +sleep 3 + +echo "" +echo "Checking VictoriaMetrics..." +VICTORIA_COUNT=$(curl -s "http://localhost:8428/api/v1/series?match[]=test_iteration_metric" | jq -r '.data | length') +echo -e " Found ${GREEN}$VICTORIA_COUNT${NC} time series in VictoriaMetrics" + +echo "" +echo "Checking Prometheus..." +PROMETHEUS_COUNT=$(curl -s "http://localhost:9090/api/v1/series?match[]=test_iteration_metric" | jq -r '.data | length') +echo -e " Found ${GREEN}$PROMETHEUS_COUNT${NC} time series in Prometheus" + +echo "" +echo "Checking Loki..." +LOKI_COUNT=$(curl -s 'http://localhost:3100/loki/api/v1/query?query=%7Bcontract_name%3D%22e2e_test_contract%22%7D' | jq -r '.data.result | length') +echo -e " Found ${GREEN}$LOKI_COUNT${NC} log streams in Loki" + +# ============================================================================ +# Summary +# ============================================================================ +print_section "Test Summary" + +TOTAL_ACCOUNTS=$((1 + ${NUM_ACCOUNTS:-0})) +if [ "$SKIP_CREATE" = true ]; then + TOTAL_ACCOUNTS=1 +fi + +echo -e "${BLUE}Test Configuration:${NC}" +echo " • Total accounts tested: $TOTAL_ACCOUNTS" +echo " • Iterations per account: $ITERATIONS" +echo " • Contract address: $DEPLOYER_ADDRESS" +echo "" + +echo -e "${BLUE}Metrics Backends:${NC}" +echo " • VictoriaMetrics: $VICTORIA_COUNT series" +echo " • Prometheus: $PROMETHEUS_COUNT series" +echo " • Loki: $LOKI_COUNT streams" +echo "" + +# Determine overall pass/fail +if [ "$VICTORIA_COUNT" -gt 0 ] && [ "$PROMETHEUS_COUNT" -gt 0 ]; then + echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN} ✓ E2E TEST PASSED ${NC}" + echo -e "${GREEN}========================================${NC}" + exit 0 +else + echo -e "${RED}========================================${NC}" + echo -e "${RED} ✗ E2E TEST FAILED ${NC}" + echo -e "${RED}========================================${NC}" + echo "" + echo "Check telemetry service logs:" + echo " tail -f $TEST_DIR/telemetry.log" + exit 1 +fi + diff --git a/crates/aptos-telemetry-service/e2e-test/setup.sh b/crates/aptos-telemetry-service/e2e-test/setup.sh index 4b3037ce34d9d..b13ded7ed0530 100755 --- a/crates/aptos-telemetry-service/e2e-test/setup.sh +++ b/crates/aptos-telemetry-service/e2e-test/setup.sh @@ -1,6 +1,13 @@ #!/bin/bash # E2E Test Setup Script # Sets up local test environment for Aptos Telemetry Service with custom contract authentication +# +# Usage: +# ./setup.sh # Run all steps (1-7) +# ./setup.sh --step 5 # Run only step 5 +# ./setup.sh -s 3 # Run only step 3 +# ./setup.sh --from 4 # Run steps 4-7 +# ./setup.sh --help # Show help set -e # Exit on error @@ -17,202 +24,409 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" TEST_DIR="$SCRIPT_DIR/test-data" MOVE_DIR="$SCRIPT_DIR/move" -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}Aptos Telemetry Service E2E Test Setup${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" - -# Step 1: Check prerequisites -echo -e "${YELLOW}[1/7]${NC} Checking prerequisites..." - -if ! command -v docker &> /dev/null; then - echo -e "${RED}Error: Docker is not installed${NC}" - echo "Please install Docker: https://docs.docker.com/get-docker/" - exit 1 -fi - -if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then - echo -e "${RED}Error: Docker Compose is not installed${NC}" - echo "Please install Docker Compose: https://docs.docker.com/compose/install/" - exit 1 -fi +# Default values +RUN_STEP="" +RUN_FROM="" + +# Parse arguments +show_help() { + echo "E2E Test Setup Script" + echo "" + echo "Usage:" + echo " ./setup.sh Run all steps (1-8)" + echo " ./setup.sh --step N Run only step N" + echo " ./setup.sh -s N Run only step N" + echo " ./setup.sh --from N Run steps N through 8" + echo " ./setup.sh --skip-node Skip starting the Aptos test node" + echo " ./setup.sh --help Show this help" + echo "" + echo "Steps:" + echo " 1 - Check prerequisites" + echo " 2 - Start Docker services (VictoriaMetrics, Prometheus, Loki, Grafana)" + echo " 3 - Start Aptos test node" + echo " 4 - Create test accounts" + echo " 5 - Deploy Move contract" + echo " 6 - Add test member to registry" + echo " 7 - Set up environment variables" + echo " 8 - Start telemetry service (background)" + echo "" + echo "Examples:" + echo " ./setup.sh --step 5 # Redeploy the Move contract" + echo " ./setup.sh --from 4 # Recreate accounts and redeploy" + echo " ./setup.sh -s 2 # Restart Docker services only" +} -if ! command -v cargo &> /dev/null; then - echo -e "${RED}Error: Cargo is not installed${NC}" - echo "Please install Rust: https://rustup.rs/" - exit 1 -fi +while [[ $# -gt 0 ]]; do + case $1 in + -s|--step) + RUN_STEP="$2" + shift 2 + ;; + --from) + RUN_FROM="$2" + shift 2 + ;; + -h|--help) + show_help + exit 0 + ;; + --skip-node) + SKIP_NODE="true" + shift 1 + ;; + *) + echo -e "${RED}Unknown option: $1${NC}" + show_help + exit 1 + ;; + esac +done -# Check if aptos CLI is available -if ! command -v aptos &> /dev/null; then - echo -e "${YELLOW}Warning: Aptos CLI not found in PATH${NC}" - echo "Attempting to use cargo to run aptos CLI..." - APTOS_CMD="cargo run -p aptos --" -else - APTOS_CMD="aptos" -fi +# Determine which steps to run +should_run_step() { + local step=$1 + if [ -n "$RUN_STEP" ]; then + [ "$step" -eq "$RUN_STEP" ] + elif [ -n "$RUN_FROM" ]; then + [ "$step" -ge "$RUN_FROM" ] + else + return 0 # Run all steps + fi +} -echo -e "${GREEN}✓ All prerequisites met${NC}" -echo "" +# Load environment if it exists (for running individual steps) +# Falls back to reading from config files if .env doesn't exist +load_env() { + if [ -f "$TEST_DIR/.env" ]; then + echo -e "${BLUE}Loading existing environment from $TEST_DIR/.env${NC}" + source "$TEST_DIR/.env" + else + echo -e "${BLUE}Loading environment from config files...${NC}" + # Load from .aptos/config.yaml if it exists + if [ -f "$TEST_DIR/.aptos/config.yaml" ]; then + export DEPLOYER_ADDRESS=$(yq e '.profiles.telemetry-service-e2e-test.account' "$TEST_DIR/.aptos/config.yaml" 2>/dev/null | tr -d '"' || echo "") + export TEST_ACCOUNT_ADDRESS=$(yq e '.profiles.telemetry-service-e2e-test-member.account' "$TEST_DIR/.aptos/config.yaml" 2>/dev/null | tr -d '"' || echo "") + echo " DEPLOYER_ADDRESS=$DEPLOYER_ADDRESS" + echo " TEST_ACCOUNT_ADDRESS=$TEST_ACCOUNT_ADDRESS" + fi + # Load keys from key files if they exist + if [ -f "$TEST_DIR/deployer.key" ]; then + export DEPLOYER_KEY_HEX="0x$(cat $TEST_DIR/deployer.key)" + fi + if [ -f "$TEST_DIR/test-member.key" ]; then + export TEST_ACCOUNT_KEY_HEX="0x$(cat $TEST_DIR/test-member.key)" + fi + fi +} -# Step 2: Start Docker services (VictoriaMetrics, Loki, Grafana) -echo -e "${YELLOW}[2/7]${NC} Starting Docker services (VictoriaMetrics, Loki, Grafana)..." -cd "$SCRIPT_DIR" -docker-compose up -d +# Set up APTOS_CMD (called once at startup) +setup_aptos_cmd() { + if [ -n "$APTOS_CMD" ]; then + return + fi + if command -v aptos &> /dev/null; then + export APTOS_CMD="aptos" + else + echo -e "${YELLOW}Warning: Aptos CLI not found in PATH, using cargo${NC}" + export APTOS_CMD="cargo run -p aptos --" + fi +} -# Wait for services to be healthy -echo "Waiting for services to be ready..." -sleep 5 +# ============================================================================ +# Step 1: Check prerequisites +# ============================================================================ +step1_check_prerequisites() { + echo -e "${YELLOW}[1/8]${NC} Checking prerequisites..." -# Check VictoriaMetrics health -for i in {1..30}; do - if curl -s http://localhost:8428/health > /dev/null; then - echo -e "${GREEN}✓ VictoriaMetrics is ready${NC}" - break - fi - if [ $i -eq 30 ]; then - echo -e "${RED}Error: VictoriaMetrics failed to start${NC}" - docker-compose logs victoria-metrics + if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker is not installed${NC}" + echo "Please install Docker: https://docs.docker.com/get-docker/" exit 1 fi - sleep 1 -done -# Check Loki health -for i in {1..30}; do - if curl -s http://localhost:3100/ready > /dev/null; then - echo -e "${GREEN}✓ Loki is ready${NC}" - break + if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then + echo -e "${RED}Error: Docker Compose is not installed${NC}" + echo "Please install Docker Compose: https://docs.docker.com/compose/install/" + exit 1 fi - if [ $i -eq 30 ]; then - echo -e "${RED}Error: Loki failed to start${NC}" - docker-compose logs loki + + if ! command -v cargo &> /dev/null; then + echo -e "${RED}Error: Cargo is not installed${NC}" + echo "Please install Rust: https://rustup.rs/" exit 1 fi - sleep 1 -done -echo -e "${GREEN}✓ All Docker services started${NC}" -echo "" + echo -e "${GREEN}✓ All prerequisites met${NC}" + echo "" +} -# Step 3: Create test directory and start aptos-node in test mode -echo -e "${YELLOW}[3/7]${NC} Starting Aptos test node..." -mkdir -p "$TEST_DIR" +# ============================================================================ +# Step 2: Start Docker services +# ============================================================================ +step2_start_docker() { + echo -e "${YELLOW}[2/8]${NC} Starting Docker services (VictoriaMetrics, Prometheus, Loki, Grafana)..." + cd "$SCRIPT_DIR" + docker-compose up -d -# Kill any existing aptos-node process on port 8080 -if lsof -Pi :8080 -sTCP:LISTEN -t >/dev/null 2>&1; then - echo "Killing existing process on port 8080..." - kill $(lsof -t -i:8080) 2>/dev/null || true - sleep 2 -fi + # Wait for services to be healthy + echo "Waiting for services to be ready..." + sleep 5 -# Start aptos-node in test mode in the background -export TELEMETRY_SERVICE_URL=http://localhost:8082 -cd "$PROJECT_ROOT/../.." # Navigate to aptos-core root -echo "Starting aptos local testnet..." -nohup $APTOS_CMD node run-local-testnet --faucet-port 8081 --force-restart --assume-yes --test-dir "$TEST_DIR" --no-txn-stream --skip-metadata-apply > "$TEST_DIR/node.log" 2>&1 & -NODE_PID=$! -echo $NODE_PID > "$TEST_DIR/node.pid" - -# Wait for node to be ready -echo "Waiting for node to be ready (this may take a minute)..." -for i in {1..60}; do - if curl -s http://localhost:8080/v1/-/healthy > /dev/null 2>&1; then - echo -e "${GREEN}✓ Aptos node is ready${NC}" - break + # Check VictoriaMetrics health + for i in {1..30}; do + if curl -s http://localhost:8428/health > /dev/null; then + echo -e "${GREEN}✓ VictoriaMetrics is ready${NC}" + break + fi + if [ $i -eq 30 ]; then + echo -e "${RED}Error: VictoriaMetrics failed to start${NC}" + docker-compose logs victoria-metrics + exit 1 + fi + sleep 1 + done + + # Check Prometheus health + for i in {1..30}; do + if curl -s http://localhost:9090/-/healthy > /dev/null; then + echo -e "${GREEN}✓ Prometheus is ready${NC}" + break + fi + if [ $i -eq 30 ]; then + echo -e "${RED}Error: Prometheus failed to start${NC}" + docker-compose logs prometheus + exit 1 + fi + sleep 1 + done + + # Check Loki health + for i in {1..30}; do + if curl -s http://localhost:3100/ready > /dev/null; then + echo -e "${GREEN}✓ Loki is ready${NC}" + break + fi + if [ $i -eq 30 ]; then + echo -e "${RED}Error: Loki failed to start${NC}" + docker-compose logs loki + exit 1 + fi + sleep 1 + done + + echo -e "${GREEN}✓ All Docker services started${NC}" + echo "" +} + +# ============================================================================ +# Step 3: Start Aptos test node +# ============================================================================ +step3_start_node() { + if [ -n "$SKIP_NODE" ]; then + echo -e "${YELLOW}[3/8]${NC} Skipping Aptos test node...${NC}" + echo "" + return fi - if [ $i -eq 60 ]; then - echo -e "${RED}Error: Aptos node failed to start${NC}" - echo "Check logs at: $TEST_DIR/node.log" - cat "$TEST_DIR/node.log" - exit 1 + + echo -e "${YELLOW}[3/8]${NC} Starting Aptos test node..." + mkdir -p "$TEST_DIR" + + # Kill any existing aptos-node process on port 8080 + if lsof -Pi :8080 -sTCP:LISTEN -t >/dev/null 2>&1; then + echo "Killing existing process on port 8080..." + kill $(lsof -t -i:8080) 2>/dev/null || true + sleep 2 fi - sleep 5 -done -sleep 30 + # Start aptos-node in test mode in the background + export TELEMETRY_SERVICE_URL=http://localhost:8082 + cd "$PROJECT_ROOT/../.." # Navigate to aptos-core root + echo "Starting aptos local testnet..." + ulimit -n unlimited 2>/dev/null || true # For the node + nohup $APTOS_CMD node run-local-testnet --faucet-port 8081 --force-restart --assume-yes --test-dir "$TEST_DIR" --no-txn-stream --skip-metadata-apply > "$TEST_DIR/node.log" 2>&1 & + NODE_PID=$! + echo $NODE_PID > "$TEST_DIR/node.pid" + + # Wait for node to be ready + echo "Waiting for node to be ready (this may take a minute)..." + for i in {1..60}; do + if curl -s http://localhost:8080/v1/-/healthy > /dev/null 2>&1 && curl -s http://localhost:8080/v1/consensus_health_check > /dev/null 2>&1; then + echo -e "${GREEN}✓ Aptos node is ready${NC}" + break + fi + if [ $i -eq 60 ]; then + echo -e "${RED}Error: Aptos node failed to start${NC}" + echo "Check logs at: $TEST_DIR/node.log" + cat "$TEST_DIR/node.log" + exit 1 + fi + sleep 5 + done + + echo "Waiting for DB to bootstrap..." + for i in {1..60}; do + RESPONSE=$(curl -s http://localhost:8080/v1 2>/dev/null || echo "") + if [[ "$RESPONSE" != *"bootstrapping"* ]] && [[ -n "$RESPONSE" ]]; then + echo -e "${GREEN}✓ DB bootstrap complete${NC}" + break + fi + if [ $i -eq 60 ]; then + echo -e "${RED}Error: DB bootstrap timed out${NC}" + exit 1 + fi + echo "Still bootstrapping..." + sleep 5 + done + + # Get the chain ID + CHAIN_ID=$(curl -s http://localhost:8080/v1 | grep -o '"chain_id":[0-9]*' | cut -d':' -f2 || echo "4") + echo "Node chain ID: $CHAIN_ID" + echo "" +} -# Get the chain ID (should be 4 for testnet by default) -CHAIN_ID=$(curl -s http://localhost:8080/v1 | grep -o '"chain_id":[0-9]*' | cut -d':' -f2 || echo "4") -echo "Node chain ID: $CHAIN_ID" -echo "" +# ============================================================================ +# Step 4: Create test accounts +# ============================================================================ +step4_create_accounts() { + echo -e "${YELLOW}[4/8]${NC} Creating test accounts..." + + mkdir -p "$TEST_DIR" + cd "$TEST_DIR" + + # Create deployer account profile + echo "Creating deployer account..." + $APTOS_CMD key generate --output-file $TEST_DIR/deployer.key --key-type ed25519 + DEPLOYER_KEY_HEX="0x$(cat $TEST_DIR/deployer.key)" + $APTOS_CMD init --profile telemetry-service-e2e-test --network local --assume-yes --private-key "$DEPLOYER_KEY_HEX" 2>&1 | tee init.log || true + DEPLOYER_ADDRESS=$(yq e '.profiles.telemetry-service-e2e-test.account' "$TEST_DIR/.aptos/config.yaml" | tr -d '"' || echo "") + echo "Deployer account address: $DEPLOYER_ADDRESS" + + # Create test member account profile + echo "Creating test member account..." + $APTOS_CMD key generate --output-file $TEST_DIR/test-member.key --key-type ed25519 + TEST_ACCOUNT_KEY_HEX="0x$(cat $TEST_DIR/test-member.key)" + $APTOS_CMD init --profile telemetry-service-e2e-test-member --network local --assume-yes --private-key "$TEST_ACCOUNT_KEY_HEX" 2>&1 | tee -a init.log || true + TEST_ACCOUNT_ADDRESS=$(yq e '.profiles.telemetry-service-e2e-test-member.account' "$TEST_DIR/.aptos/config.yaml" | tr -d '"' || echo "") + echo "Test member account address: $TEST_ACCOUNT_ADDRESS" + + # Copy config file to move dir for consistency + mkdir -p "$MOVE_DIR/.aptos" + cp "$TEST_DIR/.aptos/config.yaml" "$MOVE_DIR/.aptos/config.yaml" + + # Export for subsequent steps + export DEPLOYER_ADDRESS + export DEPLOYER_KEY_HEX + export TEST_ACCOUNT_ADDRESS + export TEST_ACCOUNT_KEY_HEX + + echo -e "${GREEN}✓ Test accounts created${NC}" + echo "" +} -# Step 4: Create test accounts (while still in test-data directory) -echo -e "${YELLOW}[4/7]${NC} Creating test accounts..." -cd "$TEST_DIR" - -# Create deployer account profile -echo "Creating deployer account..." -$APTOS_CMD key generate --output-file $TEST_DIR/deployer.key --key-type ed25519 -DEPLOYER_KEY_HEX="0x$(cat $TEST_DIR/deployer.key)" # USE FOR TEST ONLY -$APTOS_CMD init --profile telemetry-service-e2e-test --network local --assume-yes --private-key "$DEPLOYER_KEY_HEX" 2>&1 | tee init.log || true -DEPLOYER_ADDRESS=$(yq e '.profiles.telemetry-service-e2e-test.account' "$TEST_DIR/.aptos/config.yaml" | tr -d '"' || echo "") -echo "Deployer account address: $DEPLOYER_ADDRESS" - -# Create test member account profile -echo "Creating test member account..." -$APTOS_CMD key generate --output-file $TEST_DIR/test-member.key --key-type ed25519 -TEST_ACCOUNT_KEY_HEX="0x$(cat $TEST_DIR/test-member.key)" # USE FOR TEST ONLY -$APTOS_CMD init --profile telemetry-service-e2e-test-member --network local --assume-yes --private-key "$TEST_ACCOUNT_KEY_HEX" 2>&1 | tee -a init.log || true -TEST_ACCOUNT_ADDRESS=$(yq e '.profiles.telemetry-service-e2e-test-member.account' "$TEST_DIR/.aptos/config.yaml" | tr -d '"' || echo "") -echo "Test member account address: $TEST_ACCOUNT_ADDRESS" - -# Copy config file to move dir for consistency -mkdir -p "$MOVE_DIR/.aptos" -cp "$TEST_DIR/.aptos/config.yaml" "$MOVE_DIR/.aptos/config.yaml" - -echo -e "${GREEN}✓ Test accounts created${NC}" -echo "" +# ============================================================================ +# Step 5: Deploy Move contract +# ============================================================================ +step5_deploy_contract() { + echo -e "${YELLOW}[5/8]${NC} Deploying Move contract..." + + # Load env if running standalone + if [ -z "$DEPLOYER_ADDRESS" ]; then + load_env + fi + + if [ -z "$DEPLOYER_ADDRESS" ]; then + echo -e "${RED}Error: DEPLOYER_ADDRESS not set. Run step 4 first or ensure .env exists.${NC}" + exit 1 + fi -# Step 5: Deploy the Move contract -echo -e "${YELLOW}[5/7]${NC} Deploying Move contract..." -cd "$MOVE_DIR" + cd "$MOVE_DIR" -# Create a temporary Move.toml with the correct address -sed "s/telemetry_deployer = \"_\"/telemetry_deployer = \"$DEPLOYER_ADDRESS\"/" Move.toml > Move.toml.tmp -mv Move.toml.tmp Move.toml + # Create a temporary Move.toml with the correct address + sed "s/telemetry_deployer = \".*\"/telemetry_deployer = \"$DEPLOYER_ADDRESS\"/" Move.toml > Move.toml.tmp + mv Move.toml.tmp Move.toml -echo "Compiling Move contract..." -$APTOS_CMD move compile --named-addresses telemetry_deployer=$DEPLOYER_ADDRESS --dev --skip-checks-on-test-code --language-version 2.3 + echo "Compiling Move contract..." + $APTOS_CMD move compile --named-addresses telemetry_deployer=$DEPLOYER_ADDRESS --dev --skip-checks-on-test-code --language-version 2.3 -echo "Publishing Move contract..." -$APTOS_CMD move publish \ - --profile telemetry-service-e2e-test \ - --named-addresses telemetry_deployer=$DEPLOYER_ADDRESS \ - --assume-yes \ - --url http://localhost:8080 + echo "Publishing Move contract..." + $APTOS_CMD move publish \ + --profile telemetry-service-e2e-test \ + --named-addresses telemetry_deployer=$DEPLOYER_ADDRESS \ + --assume-yes \ + --url http://localhost:8080 -echo "Initializing registry..." -$APTOS_CMD move run \ - --profile telemetry-service-e2e-test \ - --function-id ${DEPLOYER_ADDRESS}::telemetry_registry::initialize \ - --assume-yes \ - --url http://localhost:8080 + echo "Initializing registry..." + $APTOS_CMD move run \ + --profile telemetry-service-e2e-test \ + --function-id ${DEPLOYER_ADDRESS}::telemetry_registry::initialize \ + --assume-yes \ + --url http://localhost:8080 -echo -e "${GREEN}✓ Contract deployed and initialized at $DEPLOYER_ADDRESS${NC}" -echo "" + echo -e "${GREEN}✓ Contract deployed and initialized at $DEPLOYER_ADDRESS${NC}" + echo -e "${YELLOW}View it at https://explorer.aptoslabs.com/account/$DEPLOYER_ADDRESS/modules/packages/TelemetryRegistry?network=local${NC}" + echo "" +} -# Step 6: Add test member to the registry -echo -e "${YELLOW}[6/7]${NC} Adding test member to registry..." +# ============================================================================ +# Step 6: Add test member to registry +# ============================================================================ +step6_add_member() { + echo -e "${YELLOW}[6/8]${NC} Adding test member to registry..." -$APTOS_CMD move run \ - --profile telemetry-service-e2e-test \ - --function-id ${DEPLOYER_ADDRESS}::telemetry_registry::add_member \ - --args address:$TEST_ACCOUNT_ADDRESS string:"127.0.0.1" string:"9000" string:"0xtest123" string:"dc_local" \ - --assume-yes \ - --url http://localhost:8080 + # Load env if running standalone + if [ -z "$DEPLOYER_ADDRESS" ] || [ -z "$TEST_ACCOUNT_ADDRESS" ]; then + load_env + fi -echo -e "${GREEN}✓ Test member added to registry${NC}" -echo "" + if [ -z "$DEPLOYER_ADDRESS" ] || [ -z "$TEST_ACCOUNT_ADDRESS" ]; then + echo -e "${RED}Error: DEPLOYER_ADDRESS or TEST_ACCOUNT_ADDRESS not set. Run steps 4-5 first.${NC}" + exit 1 + fi + + $APTOS_CMD move run \ + --profile telemetry-service-e2e-test \ + --function-id ${DEPLOYER_ADDRESS}::telemetry_registry::add_member \ + --args address:$TEST_ACCOUNT_ADDRESS string:"127.0.0.1" string:"9000" string:"0xtest123" string:"dc_local" \ + --assume-yes \ + --url http://localhost:8080 + + echo -e "${GREEN}✓ Test member added to registry${NC}" + echo -e "${YELLOW}View it at https://explorer.aptoslabs.com/account/$DEPLOYER_ADDRESS/modules/packages/TelemetryRegistry?network=local${NC}" + echo "" +} +# ============================================================================ # Step 7: Set up environment variables -echo -e "${YELLOW}[7/7]${NC} Setting up environment variables..." +# ============================================================================ +step7_setup_env() { + echo -e "${YELLOW}[7/8]${NC} Setting up environment variables..." + + # Load existing env values if running standalone + if [ -z "$DEPLOYER_ADDRESS" ]; then + if [ -f "$TEST_DIR/.aptos/config.yaml" ]; then + DEPLOYER_ADDRESS=$(yq e '.profiles.telemetry-service-e2e-test.account' "$TEST_DIR/.aptos/config.yaml" | tr -d '"' || echo "") + TEST_ACCOUNT_ADDRESS=$(yq e '.profiles.telemetry-service-e2e-test-member.account' "$TEST_DIR/.aptos/config.yaml" | tr -d '"' || echo "") + fi + if [ -f "$TEST_DIR/deployer.key" ]; then + DEPLOYER_KEY_HEX="0x$(cat $TEST_DIR/deployer.key)" + fi + if [ -f "$TEST_DIR/test-member.key" ]; then + TEST_ACCOUNT_KEY_HEX="0x$(cat $TEST_DIR/test-member.key)" + fi + fi + + # Get chain ID from node + CHAIN_ID=$(curl -s http://localhost:8080/v1 | grep -o '"chain_id":[0-9]*' | cut -d':' -f2 || echo "4") -# Generate x25519 private key for SERVER_PRIVATE_KEY (32 bytes hex encoded) -SERVER_PRIVATE_KEY=$(openssl rand -hex 32) + # Generate x25519 private key for SERVER_PRIVATE_KEY (32 bytes hex encoded) + SERVER_PRIVATE_KEY=$(openssl rand -hex 32) -# Create dummy GCP credentials file for local testing (BigQuery won't actually be used) -cat > "$TEST_DIR/dummy-gcp-credentials.json" << 'GCPEOF' + # Generate JWT signing key (32 bytes hex encoded) + JWT_SIGNING_KEY=$(openssl rand -hex 32) + + # Create dummy GCP credentials file for local testing + mkdir -p "$TEST_DIR" + cat > "$TEST_DIR/dummy-gcp-credentials.json" << 'GCPEOF' { "type": "service_account", "project_id": "local-test", @@ -225,14 +439,17 @@ cat > "$TEST_DIR/dummy-gcp-credentials.json" << 'GCPEOF' } GCPEOF -# Create .env file -cat > "$TEST_DIR/.env" << EOF + # Create .env file + cat > "$TEST_DIR/.env" << EOF # E2E Test Environment Variables # Generated by setup.sh # Server private key for telemetry service (x25519, hex encoded) SERVER_PRIVATE_KEY=$SERVER_PRIVATE_KEY +# JWT signing key for token generation (32 bytes hex encoded) +JWT_SIGNING_KEY=$JWT_SIGNING_KEY + # GCP credentials (dummy for local testing - BigQuery not used) GOOGLE_APPLICATION_CREDENTIALS=$TEST_DIR/dummy-gcp-credentials.json @@ -259,46 +476,182 @@ TELEMETRY_SERVICE_URL=http://localhost:8082 FAUCET_URL=http://localhost:8081 NODE_REST_API=http://localhost:8080 VICTORIA_METRICS_URL=http://localhost:8428 +PROMETHEUS_URL=http://localhost:9090 LOKI_URL=http://localhost:3100 GRAFANA_URL=http://localhost:3000 EOF -echo -e "${GREEN}✓ Environment file created at $TEST_DIR/.env${NC}" -echo "" + echo -e "${GREEN}✓ Environment file created at $TEST_DIR/.env${NC}" + echo "" +} +# ============================================================================ +# Step 8: Start the telemetry service (background) +# ============================================================================ +step8_start_telemetry() { + echo -e "${YELLOW}[8/8]${NC} Starting telemetry service..." + + # Load environment variables + if [ -f "$TEST_DIR/.env" ]; then + set -a + source "$TEST_DIR/.env" + set +a + else + echo -e "${RED}Error: .env file not found. Run step 7 first.${NC}" + exit 1 + fi -# Summary and next steps -echo -e "${GREEN}========================================${NC}" -echo -e "${GREEN}✓ E2E Test Setup Complete!${NC}" -echo -e "${GREEN}========================================${NC}" -echo "" -echo -e "${BLUE}Services Running:${NC}" -echo " • Aptos Node: http://localhost:8080 (PID: $NODE_PID)" -echo " • Faucet: http://localhost:8081" -echo " • VictoriaMetrics: http://localhost:8428" -echo " • Loki: http://localhost:3100" -echo " • Grafana: http://localhost:3000 (admin/admin)" -echo "" -echo -e "${BLUE}Test Data:${NC}" -echo " • Test directory: $TEST_DIR" -echo " • Contract address: $DEPLOYER_ADDRESS" -echo " • Test account: $TEST_ACCOUNT_ADDRESS" -echo " • Environment file: $TEST_DIR/.env" -echo "" -echo -e "${BLUE}Next Steps:${NC}" -echo " 1. Start the telemetry service:" -echo " cd $PROJECT_ROOT" -echo " source $TEST_DIR/.env" -echo " cargo run --release -- -f $SCRIPT_DIR/telemetry-config.yaml" -echo "" -echo " 2. Run the E2E test:" -echo " cd $SCRIPT_DIR" -echo " ./run-test.sh" -echo "" -echo " 3. View telemetry data in Grafana:" -echo " Open http://localhost:3000 in your browser" -echo "" -echo -e "${YELLOW}To stop all services:${NC}" -echo " cd $SCRIPT_DIR" -echo " ./cleanup.sh" + # Stop any existing telemetry service + if [ -f "$TEST_DIR/telemetry.pid" ]; then + EXISTING_PID=$(cat "$TEST_DIR/telemetry.pid") + if ps -p $EXISTING_PID > /dev/null 2>&1; then + echo "Stopping existing telemetry service (PID: $EXISTING_PID)..." + kill $EXISTING_PID 2>/dev/null || true + sleep 2 + # Force kill if still running + if ps -p $EXISTING_PID > /dev/null 2>&1; then + kill -9 $EXISTING_PID 2>/dev/null || true + sleep 1 + fi + echo -e "${GREEN}✓ Existing telemetry service stopped${NC}" + fi + rm -f "$TEST_DIR/telemetry.pid" + fi + + # Also check if something else is using port 8082 + if lsof -Pi :8082 -sTCP:LISTEN -t >/dev/null 2>&1; then + echo "Stopping process on port 8082..." + kill $(lsof -t -i:8082) 2>/dev/null || true + sleep 2 + # Force kill if still running + if lsof -Pi :8082 -sTCP:LISTEN -t >/dev/null 2>&1; then + kill -9 $(lsof -t -i:8082) 2>/dev/null || true + sleep 1 + fi + echo -e "${GREEN}✓ Process on port 8082 stopped${NC}" + fi + + cd "$PROJECT_ROOT" + + # Start telemetry service in background, logging to file + echo "Starting telemetry service..." + RUST_LOG=debug nohup cargo run -- -f "$SCRIPT_DIR/telemetry-config.yaml" > "$TEST_DIR/telemetry.log" 2>&1 & + TELEMETRY_PID=$! + echo $TELEMETRY_PID > "$TEST_DIR/telemetry.pid" + + # Wait for telemetry service to be ready + echo "Waiting for telemetry service to be ready..." + for i in {1..30}; do + if curl -s http://localhost:8082/api/v1/health > /dev/null 2>&1; then + echo -e "${GREEN}✓ Telemetry service is ready (PID: $TELEMETRY_PID)${NC}" + break + fi + if [ $i -eq 30 ]; then + echo -e "${RED}Error: Telemetry service failed to start${NC}" + echo "Check logs at: $TEST_DIR/telemetry.log" + tail -50 "$TEST_DIR/telemetry.log" + exit 1 + fi + sleep 2 + done + + echo -e "${BLUE}Telemetry service logs:${NC} $TEST_DIR/telemetry.log" + echo -e "${YELLOW}Tail logs with: tail -f $TEST_DIR/telemetry.log${NC}" + echo "" +} + +# ============================================================================ +# Print summary +# ============================================================================ +print_summary() { + # Get node PID if available + NODE_PID="" + if [ -f "$TEST_DIR/node.pid" ]; then + NODE_PID=$(cat "$TEST_DIR/node.pid") + fi + + # Get telemetry PID if available + TELEMETRY_PID="" + if [ -f "$TEST_DIR/telemetry.pid" ]; then + TELEMETRY_PID=$(cat "$TEST_DIR/telemetry.pid") + fi + + # Load env for display + load_env 2>/dev/null || true + + echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN}✓ E2E Test Setup Complete!${NC}" + echo -e "${GREEN}========================================${NC}" + echo "" + echo -e "${BLUE}Services Running:${NC}" + echo " • Aptos Node: http://localhost:8080 ${NODE_PID:+(PID: $NODE_PID)}" + echo " • Faucet: http://localhost:8081" + echo " • Telemetry Service: http://localhost:8082 ${TELEMETRY_PID:+(PID: $TELEMETRY_PID)}" + echo " • VictoriaMetrics: http://localhost:8428" + echo " • Prometheus: http://localhost:9090" + echo " • Loki: http://localhost:3100" + echo " • Grafana: http://localhost:3000 (admin/admin)" + echo "" + echo -e "${BLUE}Module deployed at:${NC}" + echo " • TelemetryRegistry: https://explorer.aptoslabs.com/account/$DEPLOYER_ADDRESS/modules/packages/TelemetryRegistry?network=local" + echo " • View functions: https://explorer.aptoslabs.com/account/$DEPLOYER_ADDRESS/modules/view/telemetry_registry/get_all_members?network=local" + echo "" + echo -e "${BLUE}Test Data:${NC}" + echo " • Test directory: $TEST_DIR" + echo " • Contract address: ${DEPLOYER_ADDRESS:-}" + echo " • Test account: ${TEST_ACCOUNT_ADDRESS:-}" + echo " • Environment file: $TEST_DIR/.env" + echo "" + echo -e "${BLUE}Log Files:${NC}" + echo " • Node logs: $TEST_DIR/node.log" + echo " • Telemetry logs: $TEST_DIR/telemetry.log" + echo "" + echo -e "${BLUE}Next Steps:${NC}" + echo " 1. Run the E2E test (in another terminal):" + echo " cd $SCRIPT_DIR" + echo " ./run-test.sh" + echo "" + echo " 2. View telemetry data in Grafana:" + echo " Open http://localhost:3000 in your browser" + echo "" + echo -e "${YELLOW}To stop all services:${NC}" + echo " cd $SCRIPT_DIR" + echo " ./cleanup.sh" + echo "" +} + +# ============================================================================ +# Main execution +# ============================================================================ + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE}Aptos Telemetry Service E2E Test Setup${NC}" +echo -e "${BLUE}========================================${NC}" + +if [ -n "$RUN_STEP" ]; then + echo -e "${BLUE}Running step $RUN_STEP only${NC}" +elif [ -n "$RUN_FROM" ]; then + echo -e "${BLUE}Running steps $RUN_FROM through 8${NC}" +else + echo -e "${BLUE}Running all steps (1-8)${NC}" +fi echo "" +# Set up APTOS_CMD globally (used by steps 3-6) +setup_aptos_cmd + +# Run the requested steps +should_run_step 1 && step1_check_prerequisites +should_run_step 2 && step2_start_docker +should_run_step 3 && step3_start_node +should_run_step 4 && step4_create_accounts +should_run_step 5 && step5_deploy_contract +should_run_step 6 && step6_add_member +should_run_step 7 && step7_setup_env + +# Step 8 starts the telemetry service in background +should_run_step 8 && step8_start_telemetry + +# Print summary if running all steps or the last step +if [ -z "$RUN_STEP" ] || [ "$RUN_STEP" -eq 8 ]; then + print_summary +fi diff --git a/crates/aptos-telemetry-service/e2e-test/telemetry-config.yaml b/crates/aptos-telemetry-service/e2e-test/telemetry-config.yaml index e13d789c84d9e..8ed0d87f172e6 100644 --- a/crates/aptos-telemetry-service/e2e-test/telemetry-config.yaml +++ b/crates/aptos-telemetry-service/e2e-test/telemetry-config.yaml @@ -55,40 +55,73 @@ custom_contract_configs: # Custom node type for JWT claims node_type_name: "TelemetryTestNode" - # Metrics sink - local VictoriaMetrics + # Multiple metrics sinks - supports different backend types simultaneously + # Use `metrics_sink` (singular) for backwards compatibility with single sink + # Use `metrics_sinks` (array) for multiple sinks with different backend types + # + # Backend types (specify FULL endpoint path in URL): + # - victoria_metrics: Prometheus text format (e.g., http://host:8428/api/v1/import/prometheus) + # - prometheus_remote_write: Protobuf + snappy (e.g., http://host:9090/api/v1/write) + # + # Authentication options (auth_type field): + # - bearer: Bearer token auth (default) - use keys_env_var + # - basic: Basic auth (username:password) - use basic_auth_env_var + # - none: No authentication metrics_sinks: - endpoint_urls: - local: "http://127.0.0.1:8428/api/v1/import/prometheus" - # No auth required for local testing - keys_env_var: "TEST_METRICS_KEYS" + # VictoriaMetrics sink (Prometheus text format) + - endpoint_urls: + victoria_metrics: "http://127.0.0.1:8428/api/v1/import/prometheus" + auth_type: none + keys_env_var: "TEST_METRICS_KEYS" + backend_type: victoria_metrics + + # Prometheus Remote Write sink (protobuf + snappy) + - endpoint_urls: + prometheus_remote_write: "http://127.0.0.1:9090/api/v1/write" + auth_type: none + keys_env_var: "TEST_METRICS_KEYS" + backend_type: prometheus_remote_write # Logs sink - local Loki + # Authentication options same as metrics: auth_type (bearer/basic/none) logs_sink: endpoint_url: "http://127.0.0.1:3100/loki/api/v1/push" - # No auth required for local testing - key_env_var: "TEST_LOKI_TOKEN" + auth_type: none # No auth for local testing + key_env_var: "TEST_LOKI_TOKEN" # Used when auth_type: bearer + # basic_auth_env_var: "LOKI_BASIC_AUTH" # Used when auth_type: basic + # Format for basic_auth_env_var: "username:password" backend_type: loki # Standard telemetry endpoints (required but not used in custom contract testing) +# Authentication options for all endpoints: +# - auth_type: bearer (default) | basic | none +# - keys_env_var: JSON map for bearer tokens ({"endpoint_name": "token"}) +# - basic_auth_env_var: JSON map for basic auth ({"endpoint_name": "user:pass"}) metrics_endpoints_config: telemetry_service_metrics: endpoint_urls: local: "http://127.0.0.1:8428/api/v1/import/prometheus" + auth_type: none # No auth for local testing keys_env_var: "TEST_METRICS_KEYS" ingest_metrics: endpoint_urls: {} + auth_type: none keys_env_var: "TEST_METRICS_KEYS" untrusted_ingest_metrics: endpoint_urls: {} + auth_type: none keys_env_var: "TEST_METRICS_KEYS" humio_ingest_config: + # Log endpoints also support auth_type: bearer | basic | none known_logs_endpoint: endpoint_url: "http://127.0.0.1:3100/loki/api/v1/push" + auth_type: none # No auth for local testing key_env_var: "TEST_LOKI_TOKEN" backend_type: loki unknown_logs_endpoint: endpoint_url: "http://127.0.0.1:3100/loki/api/v1/push" + auth_type: none key_env_var: "TEST_LOKI_TOKEN" backend_type: loki diff --git a/crates/aptos-telemetry-service/src/clients/humio.rs b/crates/aptos-telemetry-service/src/clients/humio.rs index 6f52488aac0bd..94bd2c1832d36 100644 --- a/crates/aptos-telemetry-service/src/clients/humio.rs +++ b/crates/aptos-telemetry-service/src/clients/humio.rs @@ -15,15 +15,42 @@ pub const PEER_ROLE_TAG_NAME: &str = "peer_role"; pub const CHAIN_ID_TAG_NAME: &str = "chain_id"; pub const RUN_UUID_TAG_NAME: &str = "run_uuid"; +/// Authentication configuration for Humio +#[derive(Clone, Debug)] +pub enum HumioAuth { + /// Bearer token authentication (default for Humio) + Bearer(String), + /// Basic authentication (username, password) + Basic(String, String), +} + +impl HumioAuth { + /// Create basic auth from "username:password" string + pub fn from_basic_auth_string(creds: &str) -> Option { + let parts: Vec<&str> = creds.splitn(2, ':').collect(); + if parts.len() == 2 { + Some(HumioAuth::Basic(parts[0].to_string(), parts[1].to_string())) + } else { + None + } + } +} + #[derive(Clone, Debug)] pub struct IngestClient { inner: DebugIgnore, base_url: Url, - auth_token: String, + auth: HumioAuth, } impl IngestClient { + /// Create a new Humio ingest client with bearer token (backward compatible) pub fn new(base_url: Url, auth_token: String) -> Self { + Self::with_auth(base_url, HumioAuth::Bearer(auth_token)) + } + + /// Create a new Humio ingest client with custom auth configuration + pub fn with_auth(base_url: Url, auth: HumioAuth) -> Self { let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); let inner = ClientBuilder::new(ReqwestClient::new()) .with(RetryTransientMiddleware::new_with_policy(retry_policy)) @@ -31,7 +58,7 @@ impl IngestClient { Self { inner: DebugIgnore(inner), base_url, - auth_token, + auth, } } @@ -44,14 +71,21 @@ impl IngestClient { .map_err(|e| anyhow!("unable to serialize json: {}", e))?; let compressed_bytes = gzip_encoder.finish()?; - self.inner + let req = self + .inner .0 .post(self.base_url.join("api/v1/ingest/humio-unstructured")?) - .bearer_auth(self.auth_token.clone()) .header("Content-Encoding", "gzip") - .body(compressed_bytes) - .send() + .body(compressed_bytes); + + // Add authentication based on configured auth type + let req = match &self.auth { + HumioAuth::Bearer(token) => req.bearer_auth(token), + HumioAuth::Basic(username, password) => req.basic_auth(username, Some(password)), + }; + + req.send() .await - .map_err(|e| anyhow!("failed to post metrics: {}", e)) + .map_err(|e| anyhow!("failed to post logs: {}", e)) } } diff --git a/crates/aptos-telemetry-service/src/clients/loki.rs b/crates/aptos-telemetry-service/src/clients/loki.rs index d7ecbe8a6b262..4061f560ca2db 100644 --- a/crates/aptos-telemetry-service/src/clients/loki.rs +++ b/crates/aptos-telemetry-service/src/clients/loki.rs @@ -29,20 +29,60 @@ struct LokiStream { values: Vec<[String; 2]>, } +/// Authentication configuration for Loki +#[derive(Clone, Debug)] +pub enum LokiAuth { + /// No authentication + None, + /// Bearer token authentication + Bearer(String), + /// Basic authentication (username, password) + Basic(String, String), +} + +impl LokiAuth { + /// Create from optional bearer token (for backward compatibility) + pub fn from_bearer_token(token: Option) -> Self { + match token { + Some(t) if !t.is_empty() => LokiAuth::Bearer(t), + _ => LokiAuth::None, + } + } + + /// Create basic auth from "username:password" string + pub fn from_basic_auth_string(creds: &str) -> Option { + let parts: Vec<&str> = creds.splitn(2, ':').collect(); + if parts.len() == 2 { + Some(LokiAuth::Basic(parts[0].to_string(), parts[1].to_string())) + } else { + None + } + } +} + #[derive(Clone, Debug)] pub struct LokiIngestClient { inner: DebugIgnore, base_url: Url, - auth_token: Option, + auth: LokiAuth, } impl LokiIngestClient { - /// Create a new Loki ingest client + /// Create a new Loki ingest client with optional bearer token (backward compatible) /// /// # Arguments /// * `base_url` - Base URL of Loki (e.g., http://loki:3100) /// * `auth_token` - Optional bearer token for authentication pub fn new(base_url: Url, auth_token: Option) -> Self { + Self::with_auth(base_url, LokiAuth::from_bearer_token(auth_token)) + } + + /// Create a new Loki ingest client with custom auth configuration + /// + /// # Arguments + /// * `base_url` - Base URL of Loki (e.g., http://loki:3100) + /// * `auth` - Authentication configuration + pub fn with_auth(base_url: Url, auth: LokiAuth) -> Self { let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); let inner = ClientBuilder::new(ReqwestClient::new()) .with(RetryTransientMiddleware::new_with_policy(retry_policy)) @@ -50,7 +90,7 @@ impl LokiIngestClient { Self { inner: DebugIgnore(inner), base_url, - auth_token, + auth, } } @@ -89,17 +129,19 @@ impl LokiIngestClient { let json_body = serde_json::to_string(&request) .map_err(|e| anyhow!("unable to serialize json: {}", e))?; - let mut req = self + let req = self .inner .0 .post(self.base_url.join("/loki/api/v1/push")?) .header("Content-Type", "application/json") .body(json_body); - // Add authentication if provided - if let Some(token) = &self.auth_token { - req = req.bearer_auth(token); - } + // Add authentication based on configured auth type + let req = match &self.auth { + LokiAuth::None => req, + LokiAuth::Bearer(token) => req.bearer_auth(token), + LokiAuth::Basic(username, password) => req.basic_auth(username, Some(password)), + }; req.send() .await diff --git a/crates/aptos-telemetry-service/src/clients/mod.rs b/crates/aptos-telemetry-service/src/clients/mod.rs index 76138a659fbbe..2c5536388e259 100644 --- a/crates/aptos-telemetry-service/src/clients/mod.rs +++ b/crates/aptos-telemetry-service/src/clients/mod.rs @@ -3,101 +3,8 @@ pub mod humio; pub mod loki; - -pub mod victoria_metrics_api { - - use anyhow::{anyhow, Result}; - use reqwest::{header::CONTENT_ENCODING, Client as ReqwestClient}; - use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; - use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; - use url::Url; - use warp::hyper::body::Bytes; - - #[derive(Clone)] - pub enum AuthToken { - Bearer(String), - Basic(String, String), - } - - impl From<&String> for AuthToken { - fn from(token: &String) -> Self { - // TODO(ibalajiarun): Auth type must be read from config - if token.split(':').count() == 2 { - let mut parts = token.split(':'); - AuthToken::Basic( - parts.next().unwrap().to_string(), - parts.next().unwrap().to_string(), - ) - } else { - AuthToken::Bearer(token.to_string()) - } - } - } - - impl From<&str> for AuthToken { - fn from(token: &str) -> Self { - AuthToken::from(&token.to_string()) - } - } - - /// Client implementation to export metrics to Victoria Metrics - #[derive(Clone)] - pub struct Client { - inner: ClientWithMiddleware, - base_url: Url, - auth_token: AuthToken, - } - - impl Client { - pub fn new(base_url: Url, auth_token: AuthToken) -> Self { - let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); - let inner = ClientBuilder::new(ReqwestClient::new()) - .with(RetryTransientMiddleware::new_with_policy(retry_policy)) - .build(); - Self { - inner, - base_url, - auth_token, - } - } - - pub fn is_selfhosted_vm_client(&self) -> bool { - self.base_url - .host_str() - .unwrap_or_default() - .contains("aptos-all.vm") - } - - pub async fn post_prometheus_metrics( - &self, - raw_metrics_body: Bytes, - extra_labels: Vec, - encoding: String, - ) -> Result { - let labels: Vec<(String, String)> = extra_labels - .iter() - .map(|label| ("extra_label".into(), label.into())) - .collect(); - - let req = self - .inner - .post(format!("{}api/v1/import/prometheus", self.base_url)); - let req = match &self.auth_token { - AuthToken::Bearer(token) => req.bearer_auth(token.clone()), - AuthToken::Basic(username, password) => { - req.basic_auth(username.clone(), Some(password.clone())) - }, - }; - - req.header(CONTENT_ENCODING, encoding) - .query(&labels) - .body(raw_metrics_body) - .send() - .await - .map_err(|e| anyhow!("failed to post metrics: {}", e)) - } - } -} +pub mod prometheus_remote_write; +pub mod victoria_metrics; pub mod big_query { use gcp_bigquery_client::{ diff --git a/crates/aptos-telemetry-service/src/clients/prometheus_remote_write.rs b/crates/aptos-telemetry-service/src/clients/prometheus_remote_write.rs new file mode 100644 index 0000000000000..44694e50802b6 --- /dev/null +++ b/crates/aptos-telemetry-service/src/clients/prometheus_remote_write.rs @@ -0,0 +1,524 @@ +// Copyright (c) Aptos Foundation +// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE + +//! Prometheus Remote Write client. +//! +//! Implements the Prometheus Remote Write protocol for pushing metrics to Prometheus-compatible +//! backends. This uses the standard protobuf + snappy compression format. +//! +//! ## Protocol +//! +//! - Endpoint: `/api/v1/write` +//! - Content-Type: `application/x-protobuf` +//! - Content-Encoding: `snappy` +//! - Protocol version: `0.1.0` +//! +//! ## Reference +//! +//! - Spec: +//! - Proto: +//! +//! ## Example Usage +//! +//! ```ignore +//! let client = PrometheusRemoteWriteClient::new(url, auth_token); +//! +//! // Parse Prometheus text format metrics and push via Remote Write +//! client.push_metrics(metrics_text, extra_labels).await?; +//! ``` + +pub use super::victoria_metrics::AuthToken; +use anyhow::{anyhow, Result}; +use debug_ignore::DebugIgnore; +use flate2::read::GzDecoder; +use prost::Message; +use reqwest::Client as ReqwestClient; +use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; +use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; +use snap::raw::Encoder as SnappyEncoder; +use std::io::Read; +use url::Url; +use warp::hyper::body::Bytes; + +// ============================================================================ +// Prometheus Remote Write Protobuf Messages +// ============================================================================ +// These match the proto definitions from prometheus/prometheus/prompb/types.proto +// and prompb/remote.proto + +/// A label name-value pair. +#[derive(Clone, PartialEq, Message)] +pub struct Label { + #[prost(string, tag = "1")] + pub name: String, + #[prost(string, tag = "2")] + pub value: String, +} + +/// A single metric sample: timestamp + value. +#[derive(Clone, PartialEq, Message)] +pub struct Sample { + /// Value of the sample. + #[prost(double, tag = "1")] + pub value: f64, + /// Timestamp in milliseconds since epoch. + #[prost(int64, tag = "2")] + pub timestamp: i64, +} + +/// A time series: metric name + labels + samples. +#[derive(Clone, PartialEq, Message)] +pub struct TimeSeries { + /// Labels (including __name__ for metric name). + #[prost(message, repeated, tag = "1")] + pub labels: Vec