A high-performance LLM gateway built in Rust that provides a single OpenAI-compatible endpoint for multiple LLM providers. Designed for production environments where cost optimization, privacy, and reliability matter.
Modern applications need to work with multiple LLM providers, but managing different APIs, handling failures, tracking costs, and ensuring data privacy is complex. Sentinel solves these problems by acting as an intelligent proxy that sits between your application and LLM providers.
Key Benefits:
From Cargo:
cargo install --git https://github.com/fbk2111/sentinel
Using Docker:
docker pull sentinel/sentinel:latest
From Releases: Download the latest binary from releases
sentinel start
This starts both the proxy (port 8080) and dashboard (port 3000).
export OPENAI_API_KEY="your-openai-key"
export ANTHROPIC_API_KEY="your-anthropic-key"
import openai
client = openai.OpenAI(
base_url="http://localhost:8080/v1",
api_key="dummy-key" # Sentinel uses env vars, this can be anything
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello world"}]
)
http://localhost:3000 in your browser to see the dashboard.Sentinel works out of the box with environment variables, but you can customize everything with a configuration file.
# Provider API Keys
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_API_KEY=your-google-key
MISTRAL_API_KEY=your-mistral-key
# Server Configuration
SENTINEL_HOST=127.0.0.1
SENTINEL_PROXY_PORT=8080
SENTINEL_DASHBOARD_PORT=3000
# Features
SENTINEL_CACHE_ENABLED=true
SENTINEL_PII_REDACTION=true
SENTINEL_SEMANTIC_CACHE=false
Create sentinel.toml in your working directory:
[server]
host = "127.0.0.1"
proxy_port = 8080
dashboard_port = 3000
[providers]
primary = "openai"
fallback = ["anthropic", "google"]
# Smart routing options
[routing]
strategy = "cost_optimized" # options: "cost_optimized", "latency_optimized", "balanced"
max_cost_per_token = 0.00003 # reject requests above this cost
[cache]
enabled = true
ttl_seconds = 3600
max_size_mb = 100
semantic_enabled = false # requires embedding model
[privacy]
pii_redaction = true
patterns = ["email", "phone", "ssn", "credit_card", "api_key"]
[limits]
daily_budget_usd = 100.0
requests_per_minute = 1000
[database]
path = "./sentinel.db"
[logging]
level = "info"
format = "json"
One of Sentinel's biggest advantages is intelligent cost optimization. Here's how it works:
Sentinel maintains real-time pricing information and automatically routes requests to the most cost-effective provider that can handle your request:
# Example: This request gets routed to the cheapest provider automatically
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Summarize this text..."}],
"max_tokens": 100
}'
[routing]
strategy = "cost_optimized"
# Define cost preferences
[routing.cost_preferences]
max_input_cost_per_1k_tokens = 0.01
max_output_cost_per_1k_tokens = 0.03
# Fallback if primary is too expensive
fallback_on_cost_exceeded = true
Based on real usage patterns, Sentinel users typically see:
Create docker-compose.yml:
version: '3.8'
services:
sentinel:
image: sentinel/sentinel:latest
ports:
- "8080:8080" # Proxy API
- "3000:3000" # Dashboard
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- SENTINEL_HOST=0.0.0.0
volumes:
- ./sentinel.toml:/app/sentinel.toml
- sentinel_data:/app/data
restart: unless-stopped
volumes:
sentinel_data:
Deploy with:
docker-compose up -d
For production deployments, use the official Docker image with proper configuration:
FROM sentinel/sentinel:latest
# Copy your configuration
COPY sentinel.toml /app/sentinel.toml
# Create non-root user
RUN adduser --disabled-password --gecos '' sentineluser
USER sentineluser
EXPOSE 8080 3000
CMD ["sentinel", "start"]
Build and run:
docker build -t my-sentinel .
docker run -d \
-p 8080:8080 \
-p 3000:3000 \
-e OPENAI_API_KEY=$OPENAI_API_KEY \
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
--name sentinel \
my-sentinel
For Kubernetes environments, use this minimal configuration:
apiVersion: apps/v1
kind: Deployment
metadata:
name: sentinel
spec:
replicas: 3
selector:
matchLabels:
app: sentinel
template:
metadata:
labels:
app: sentinel
spec:
containers:
- name: sentinel
image: sentinel/sentinel:latest
ports:
- containerPort: 8080
- containerPort: 3000
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: llm-secrets
key: openai-key
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: llm-secrets
key: anthropic-key
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
name: sentinel
spec:
selector:
app: sentinel
ports:
- name: proxy
port: 8080
targetPort: 8080
- name: dashboard
port: 3000
targetPort: 3000
import openai
from datetime import datetime
client = openai.OpenAI(
base_url="http://localhost:8080/v1",
api_key="dummy" # Sentinel uses env vars
)
# Regular chat completion
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain quantum computing"}
],
temperature=0.7,
max_tokens=500
)
print(response.choices[0].message.content)
import OpenAI from 'openai';
const openai = new OpenAI({
baseURL: 'http://localhost:8080/v1',
apiKey: 'dummy', // Sentinel uses environment variables
});
async function main() {
const completion = await openai.chat.completions.create({
messages: [{ role: 'user', content: 'Hello world' }],
model: 'gpt-4o',
});
console.log(completion.choices[0].message.content);
}
main();
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dummy" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "Write a haiku about coding"
}
]
}'
import openai
client = openai.OpenAI(base_url="http://localhost:8080/v1", api_key="dummy")
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Tell me a story"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")
Sentinel includes a comprehensive CLI for management and monitoring:
# Start the proxy and dashboard
sentinel start
# Start only the proxy (no dashboard)
sentinel start --proxy-only
# Start only the dashboard
sentinel start --dashboard-only
# View recent request logs
sentinel logs
# Follow logs in real-time
sentinel logs --follow
# View last 100 logs
sentinel logs --tail 100
# Show current configuration
sentinel config
# Validate configuration file
sentinel config --validate
# Show provider health status
sentinel status
# Export request data
sentinel export --format csv --output requests.csv
# Show cost breakdown
sentinel cost --period today
sentinel cost --period week
sentinel cost --period month
# Clear cache
sentinel cache clear
# Run health checks
sentinel health check
Replace your LiteLLM proxy with Sentinel:
Before (LiteLLM):
litellm --model gpt-4 --port 8000
After (Sentinel):
sentinel start
# Your existing code works unchanged!
Simply change your base URL and remove API key management from your code:
Before:
client = openai.OpenAI(api_key="sk-...")
After:
client = openai.OpenAI(
base_url="http://localhost:8080/v1",
api_key="dummy"
)
sentinel.tomlWe welcome contributions! Sentinel is open source and community-driven.
git clone https://github.com/fbk2111/sentinel.git
cd sentinel
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
cargo build
cargo run -- start
cargo test
cargo bench
sentinel/
├── src/
│ ├── main.rs # Application entry point
│ ├── config.rs # Configuration management
│ ├── cli.rs # Command line interface
│ ├── proxy/ # Core proxy logic
│ │ ├── mod.rs
│ │ ├── pii.rs # PII redaction
│ │ └── middleware.rs # Request/response middleware
│ ├── provider/ # LLM provider integrations
│ │ ├── mod.rs
│ │ ├── openai.rs
│ │ ├── anthropic.rs
│ │ └── ...
│ ├── cache/ # Caching implementations
│ ├── cost/ # Cost tracking and optimization
│ ├── router/ # Smart routing logic
│ ├── storage/ # Database and persistence
│ └── ui/ # Dashboard web interface
└── docs # Readme ....
cargo test and cargo clippygit checkout -b feature/your-feature-name
git add .
git commit -m "Add: your feature description"
git push origin feature/your-feature-name
cargo fmt)cargo clippy)| Provider | Status | Models Supported | Streaming | Features |
|---|---|---|---|---|
| OpenAI | ✅ Full | GPT-4o, GPT-4, GPT-3.5 | ✅ | Chat, Embeddings |
| Anthropic | ✅ Full | Claude 3.5 Sonnet, Claude 3 | ✅ | Chat |
| ✅ Full | Gemini Pro, Gemini Flash | ✅ | Chat, Vision | |
| Mistral | ✅ Full | Mistral Large, Medium, Small | ✅ | Chat |
| Cohere | ✅ Full | Command R+, Command R | ✅ | Chat |
| Perplexity | ✅ Full | Sonar models | ✅ | Chat, Search |
| Together AI | ✅ Full | Llama, Mistral, others | ✅ | Chat |
| Ollama | ✅ Full | Any local model | ✅ | Chat, Local hosting |
Want to add support for a new provider? Check out our provider integration guide.
Sentinel is built for production workloads and optimized for minimal latency:
# Run the built-in benchmark suite
cargo run --release --example benchmark_runner
# Results on MacBook Pro M2 (example):
# Average latency: 247μs
# P95 latency: 891μs
# P99 latency: 1.2ms
# Throughput: 12,847 req/s
Security is a core principle of Sentinel:
The dashboard provides comprehensive monitoring, but you can also integrate with external systems:
Sentinel exports metrics in Prometheus format at /metrics:
curl http://localhost:8080/metrics
Key metrics include:
sentinel_requests_total - Total requests by provider and statussentinel_request_duration_seconds - Request latency histogramssentinel_cache_hits_total - Cache hit/miss counterssentinel_costs_usd_total - Total costs by providersentinel_provider_health - Provider health status# Basic health check
curl http://localhost:8080/health
# Detailed health with provider status
curl http://localhost:8080/health/detailed
Sentinel produces structured JSON logs that integrate well with log aggregation systems:
{
"timestamp": "2024-01-15T10:30:45Z",
"level": "INFO",
"request_id": "req_123abc",
"provider": "openai",
"model": "gpt-4o",
"input_tokens": 50,
"output_tokens": 200,
"cost_usd": 0.015,
"latency_ms": 1250,
"cache_hit": false,
"pii_detected": true
}
Q: Sentinel won't start
# Check configuration
sentinel config --validate
# Check if ports are available
lsof -i :8080
lsof -i :3000
# Check logs for specific errors
sentinel logs --follow
Q: Provider authentication failing
# Verify environment variables are set
env | grep -E "(OPENAI|ANTHROPIC|GOOGLE)_API_KEY"
# Test provider health
sentinel status
Q: High memory usage
# Check cache configuration
# Reduce cache size in sentinel.toml:
[cache]
max_size_mb = 50 # Reduce from default 100MB
Q: Slow response times
# Check provider latency in dashboard
# Enable request tracing:
RUST_LOG=sentinel=debug sentinel start
Enable verbose logging for troubleshooting:
RUST_LOG=debug sentinel start
sentinel --version)Sentinel is released under the MIT License.
Built with amazing open source projects:
Special thanks to all contributors and the Rust community.
Ready to optimize your LLM costs and improve reliability?
cargo install --git https://github.com/fbk2111/sentinel
sentinel start
Open http://localhost:3000 and start saving money on your LLM calls today.
4 commits
Rust
90.6%
HTML
8.9%
A high-performance LLM gateway built in Rust that provides a single OpenAI-compatible endpoint for multiple LLM providers. Designed for production environments where cost optimization, privacy, and reliability matter.
Modern applications need to work with multiple LLM providers, but managing different APIs, handling failures, tracking costs, and ensuring data privacy is complex. Sentinel solves these problems by acting as an intelligent proxy that sits between your application and LLM providers.
Key Benefits:
From Cargo:
cargo install --git https://github.com/fbk2111/sentinel
Using Docker:
docker pull sentinel/sentinel:latest
From Releases: Download the latest binary from releases
sentinel start
This starts both the proxy (port 8080) and dashboard (port 3000).
export OPENAI_API_KEY="your-openai-key"
export ANTHROPIC_API_KEY="your-anthropic-key"
import openai
client = openai.OpenAI(
base_url="http://localhost:8080/v1",
api_key="dummy-key" # Sentinel uses env vars, this can be anything
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello world"}]
)
http://localhost:3000 in your browser to see the dashboard.Sentinel works out of the box with environment variables, but you can customize everything with a configuration file.
# Provider API Keys
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_API_KEY=your-google-key
MISTRAL_API_KEY=your-mistral-key
# Server Configuration
SENTINEL_HOST=127.0.0.1
SENTINEL_PROXY_PORT=8080
SENTINEL_DASHBOARD_PORT=3000
# Features
SENTINEL_CACHE_ENABLED=true
SENTINEL_PII_REDACTION=true
SENTINEL_SEMANTIC_CACHE=false
Create sentinel.toml in your working directory:
[server]
host = "127.0.0.1"
proxy_port = 8080
dashboard_port = 3000
[providers]
primary = "openai"
fallback = ["anthropic", "google"]
# Smart routing options
[routing]
strategy = "cost_optimized" # options: "cost_optimized", "latency_optimized", "balanced"
max_cost_per_token = 0.00003 # reject requests above this cost
[cache]
enabled = true
ttl_seconds = 3600
max_size_mb = 100
semantic_enabled = false # requires embedding model
[privacy]
pii_redaction = true
patterns = ["email", "phone", "ssn", "credit_card", "api_key"]
[limits]
daily_budget_usd = 100.0
requests_per_minute = 1000
[database]
path = "./sentinel.db"
[logging]
level = "info"
format = "json"
One of Sentinel's biggest advantages is intelligent cost optimization. Here's how it works:
Sentinel maintains real-time pricing information and automatically routes requests to the most cost-effective provider that can handle your request:
# Example: This request gets routed to the cheapest provider automatically
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Summarize this text..."}],
"max_tokens": 100
}'
[routing]
strategy = "cost_optimized"
# Define cost preferences
[routing.cost_preferences]
max_input_cost_per_1k_tokens = 0.01
max_output_cost_per_1k_tokens = 0.03
# Fallback if primary is too expensive
fallback_on_cost_exceeded = true
Based on real usage patterns, Sentinel users typically see:
Create docker-compose.yml:
version: '3.8'
services:
sentinel:
image: sentinel/sentinel:latest
ports:
- "8080:8080" # Proxy API
- "3000:3000" # Dashboard
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- SENTINEL_HOST=0.0.0.0
volumes:
- ./sentinel.toml:/app/sentinel.toml
- sentinel_data:/app/data
restart: unless-stopped
volumes:
sentinel_data:
Deploy with:
docker-compose up -d
For production deployments, use the official Docker image with proper configuration:
FROM sentinel/sentinel:latest
# Copy your configuration
COPY sentinel.toml /app/sentinel.toml
# Create non-root user
RUN adduser --disabled-password --gecos '' sentineluser
USER sentineluser
EXPOSE 8080 3000
CMD ["sentinel", "start"]
Build and run:
docker build -t my-sentinel .
docker run -d \
-p 8080:8080 \
-p 3000:3000 \
-e OPENAI_API_KEY=$OPENAI_API_KEY \
-e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
--name sentinel \
my-sentinel
For Kubernetes environments, use this minimal configuration:
apiVersion: apps/v1
kind: Deployment
metadata:
name: sentinel
spec:
replicas: 3
selector:
matchLabels:
app: sentinel
template:
metadata:
labels:
app: sentinel
spec:
containers:
- name: sentinel
image: sentinel/sentinel:latest
ports:
- containerPort: 8080
- containerPort: 3000
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: llm-secrets
key: openai-key
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: llm-secrets
key: anthropic-key
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
name: sentinel
spec:
selector:
app: sentinel
ports:
- name: proxy
port: 8080
targetPort: 8080
- name: dashboard
port: 3000
targetPort: 3000
import openai
from datetime import datetime
client = openai.OpenAI(
base_url="http://localhost:8080/v1",
api_key="dummy" # Sentinel uses env vars
)
# Regular chat completion
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain quantum computing"}
],
temperature=0.7,
max_tokens=500
)
print(response.choices[0].message.content)
import OpenAI from 'openai';
const openai = new OpenAI({
baseURL: 'http://localhost:8080/v1',
apiKey: 'dummy', // Sentinel uses environment variables
});
async function main() {
const completion = await openai.chat.completions.create({
messages: [{ role: 'user', content: 'Hello world' }],
model: 'gpt-4o',
});
console.log(completion.choices[0].message.content);
}
main();
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dummy" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": "Write a haiku about coding"
}
]
}'
import openai
client = openai.OpenAI(base_url="http://localhost:8080/v1", api_key="dummy")
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Tell me a story"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")
Sentinel includes a comprehensive CLI for management and monitoring:
# Start the proxy and dashboard
sentinel start
# Start only the proxy (no dashboard)
sentinel start --proxy-only
# Start only the dashboard
sentinel start --dashboard-only
# View recent request logs
sentinel logs
# Follow logs in real-time
sentinel logs --follow
# View last 100 logs
sentinel logs --tail 100
# Show current configuration
sentinel config
# Validate configuration file
sentinel config --validate
# Show provider health status
sentinel status
# Export request data
sentinel export --format csv --output requests.csv
# Show cost breakdown
sentinel cost --period today
sentinel cost --period week
sentinel cost --period month
# Clear cache
sentinel cache clear
# Run health checks
sentinel health check
Replace your LiteLLM proxy with Sentinel:
Before (LiteLLM):
litellm --model gpt-4 --port 8000
After (Sentinel):
sentinel start
# Your existing code works unchanged!
Simply change your base URL and remove API key management from your code:
Before:
client = openai.OpenAI(api_key="sk-...")
After:
client = openai.OpenAI(
base_url="http://localhost:8080/v1",
api_key="dummy"
)
sentinel.tomlWe welcome contributions! Sentinel is open source and community-driven.
git clone https://github.com/fbk2111/sentinel.git
cd sentinel
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
cargo build
cargo run -- start
cargo test
cargo bench
sentinel/
├── src/
│ ├── main.rs # Application entry point
│ ├── config.rs # Configuration management
│ ├── cli.rs # Command line interface
│ ├── proxy/ # Core proxy logic
│ │ ├── mod.rs
│ │ ├── pii.rs # PII redaction
│ │ └── middleware.rs # Request/response middleware
│ ├── provider/ # LLM provider integrations
│ │ ├── mod.rs
│ │ ├── openai.rs
│ │ ├── anthropic.rs
│ │ └── ...
│ ├── cache/ # Caching implementations
│ ├── cost/ # Cost tracking and optimization
│ ├── router/ # Smart routing logic
│ ├── storage/ # Database and persistence
│ └── ui/ # Dashboard web interface
└── docs # Readme ....
cargo test and cargo clippygit checkout -b feature/your-feature-name
git add .
git commit -m "Add: your feature description"
git push origin feature/your-feature-name
cargo fmt)cargo clippy)| Provider | Status | Models Supported | Streaming | Features |
|---|---|---|---|---|
| OpenAI | ✅ Full | GPT-4o, GPT-4, GPT-3.5 | ✅ | Chat, Embeddings |
| Anthropic | ✅ Full | Claude 3.5 Sonnet, Claude 3 | ✅ | Chat |
| ✅ Full | Gemini Pro, Gemini Flash | ✅ | Chat, Vision | |
| Mistral | ✅ Full | Mistral Large, Medium, Small | ✅ | Chat |
| Cohere | ✅ Full | Command R+, Command R | ✅ | Chat |
| Perplexity | ✅ Full | Sonar models | ✅ | Chat, Search |
| Together AI | ✅ Full | Llama, Mistral, others | ✅ | Chat |
| Ollama | ✅ Full | Any local model | ✅ | Chat, Local hosting |
Want to add support for a new provider? Check out our provider integration guide.
Sentinel is built for production workloads and optimized for minimal latency:
# Run the built-in benchmark suite
cargo run --release --example benchmark_runner
# Results on MacBook Pro M2 (example):
# Average latency: 247μs
# P95 latency: 891μs
# P99 latency: 1.2ms
# Throughput: 12,847 req/s
Security is a core principle of Sentinel:
The dashboard provides comprehensive monitoring, but you can also integrate with external systems:
Sentinel exports metrics in Prometheus format at /metrics:
curl http://localhost:8080/metrics
Key metrics include:
sentinel_requests_total - Total requests by provider and statussentinel_request_duration_seconds - Request latency histogramssentinel_cache_hits_total - Cache hit/miss counterssentinel_costs_usd_total - Total costs by providersentinel_provider_health - Provider health status# Basic health check
curl http://localhost:8080/health
# Detailed health with provider status
curl http://localhost:8080/health/detailed
Sentinel produces structured JSON logs that integrate well with log aggregation systems:
{
"timestamp": "2024-01-15T10:30:45Z",
"level": "INFO",
"request_id": "req_123abc",
"provider": "openai",
"model": "gpt-4o",
"input_tokens": 50,
"output_tokens": 200,
"cost_usd": 0.015,
"latency_ms": 1250,
"cache_hit": false,
"pii_detected": true
}
Q: Sentinel won't start
# Check configuration
sentinel config --validate
# Check if ports are available
lsof -i :8080
lsof -i :3000
# Check logs for specific errors
sentinel logs --follow
Q: Provider authentication failing
# Verify environment variables are set
env | grep -E "(OPENAI|ANTHROPIC|GOOGLE)_API_KEY"
# Test provider health
sentinel status
Q: High memory usage
# Check cache configuration
# Reduce cache size in sentinel.toml:
[cache]
max_size_mb = 50 # Reduce from default 100MB
Q: Slow response times
# Check provider latency in dashboard
# Enable request tracing:
RUST_LOG=sentinel=debug sentinel start
Enable verbose logging for troubleshooting:
RUST_LOG=debug sentinel start
sentinel --version)Sentinel is released under the MIT License.
Built with amazing open source projects:
Special thanks to all contributors and the Rust community.
Ready to optimize your LLM costs and improve reliability?
cargo install --git https://github.com/fbk2111/sentinel
sentinel start
Open http://localhost:3000 and start saving money on your LLM calls today.
4 commits
Rust
90.6%
HTML
8.9%