Skip to main content

Vector Setup Guide

Vector is a high-performance observability data pipeline that collects, transforms, and routes system metrics. This guide walks through installing and configuring Vector for Actvt remote monitoring.

Automated Installation Available

This guide covers manual installation. For a faster setup, use our automated installation script:

curl -L https://actvt.io/install | bash

What is Vector?

Vector is an open-source tool built in Rust that:

  • Collects system metrics (CPU, memory, GPU, network)
  • Transforms raw data into standardized formats
  • Routes processed metrics to various destinations
  • Performs with minimal resource usage

Installation

Step 1: Install Vector via Package Repository

Vector provides official package repositories for easy installation and updates. Choose your distribution:

For Debian/Ubuntu Systems

# Add Vector repository and install script
bash -c "$(curl -L https://setup.vector.dev)"

# Update package list and install Vector
sudo apt-get update
sudo apt-get install vector

For RHEL/CentOS/Amazon Linux Systems

# Add Vector repository and install script
bash -c "$(curl -L https://setup.vector.dev)"

# Install Vector (use dnf on newer systems)
sudo yum install vector

Step 2: Verify Installation

The package installation automatically:

  • Creates the vector system user and group
  • Sets up systemd service files
  • Creates configuration directories with proper permissions
  • Installs Vector binary to /usr/bin/vector

Verify the installation:

# Check Vector version
vector --version

# Verify systemd service is available
sudo systemctl status vector

# Check that Vector user was created
id vector

You should see output like:

vector 0.34.0 (x86_64-unknown-linux-gnu)
● vector.service - Vector
Loaded: loaded (/lib/systemd/system/vector.service; disabled; vendor preset: enabled)
Active: inactive (dead)
uid=999(vector) gid=999(vector) groups=999(vector)

Configuration

Step 3: Create Vector Configuration

The package installation automatically creates:

  • /etc/vector/ directory for configuration files
  • /var/log/vector/ directory for logs (when needed)
  • Proper ownership and permissions

Now create the main configuration file:

# Create configuration file
sudo nano /etc/vector/vector.toml

Copy and paste this complete configuration:

###############################################################################
# vector.toml Production Configuration #
###############################################################################

################################ 1. SOURCES ###################################

# Real-time system metrics
[sources.system_metrics]
type = "host_metrics"
collectors = ["cpu", "memory", "disk", "filesystem", "network", "host", "load"]
scrape_interval_secs = 1

# System information (static details collected less frequently)
[sources.system_info]
type = "exec"
command = [
"sh", "-c",
"echo \"{\\\"os\\\":\\\"$(grep PRETTY_NAME /etc/os-release | cut -d'=' -f2 | tr -d '\\\"')\\\",\\\"arch\\\":\\\"$(uname -m)\\\",\\\"domain\\\":\\\"$(hostname -f 2>/dev/null || hostname)\\\",\\\"ipv4\\\":\\\"$(ip route get 1 2>/dev/null | awk '{print $7;exit}' || hostname -I 2>/dev/null | awk '{print $1}' || echo 'unknown')\\\",\\\"ipv4_public\\\":\\\"$(curl -s --max-time 5 ifconfig.me || curl -s --max-time 5 icanhazip.com || echo 'unknown')\\\"}\""
]
mode = "scheduled"

[sources.system_info.scheduled]
exec_interval_secs = 15

# Optional: GPU metrics (requires nvidia-smi)
[sources.gpu_metrics]
type = "exec"
command = [
"nvidia-smi",
"--query-gpu=utilization.gpu,memory.used,memory.total,gpu_name,temperature.gpu,power.draw,power.limit,fan.speed,clocks.current.graphics,clocks.current.memory,utilization.encoder,utilization.decoder,pstate",
"--format=csv,noheader,nounits"
]
mode = "scheduled"

[sources.gpu_metrics.scheduled]
exec_interval_secs = 1

################################# 2. NORMALISE ################################

[transforms.metrics_to_logs]
type = "metric_to_log"
inputs = ["system_metrics"]

# System information transform
[transforms.format_system_info]
type = "remap"
inputs = ["system_info"]
source = '''
parsed = parse_json!(.message)
.metric_type = "system_info"
.os = parsed.os
.arch = parsed.arch
.domain = parsed.domain
.ipv4 = parsed.ipv4
.ipv4_public = parsed.ipv4_public
.host = get_env_var("HOSTNAME") ?? "unknown-host"
.timestamp = format_timestamp!(now(), format: "%+")
'''

# GPU metrics transform (parses CSV: utilization,memory.used,memory.total,gpu_name,temperature,power.draw,power.limit,fan.speed,clocks.graphics,clocks.memory,encoder,decoder,pstate)
[transforms.format_gpu]
type = "remap"
inputs = ["gpu_metrics"]
source = '''
parts = split!(.message, ",")
gpu_utilization = to_float!(parts[0])
gpu_name = strip_whitespace!(parts[3])

# GPU utilization metric
.metric_type = "gpu_utilization"
.value = gpu_utilization
.gpu_name = gpu_name
.host = get_env_var("HOSTNAME") ?? "unknown-host"
.timestamp = format_timestamp!(now(), format: "%+")
'''

[transforms.format_gpu_memory]
type = "remap"
inputs = ["gpu_metrics"]
source = '''
parts = split!(.message, ",")
memory_used_mb = to_float!(parts[1])
memory_total_mb = to_float!(parts[2])
gpu_name = strip_whitespace!(parts[3])

# GPU memory metric
.metric_type = "gpu_memory"
.memory_used = memory_used_mb
.memory_total = memory_total_mb
.memory_percent = if memory_total_mb > 0 { ((memory_used_mb / memory_total_mb) ?? 0.0) * 100.0 } else { 0.0 }
.gpu_name = gpu_name
.host = get_env_var("HOSTNAME") ?? "unknown-host"
.timestamp = format_timestamp!(now(), format: "%+")
'''

# GPU temperature transform
[transforms.format_gpu_temperature]
type = "remap"
inputs = ["gpu_metrics"]
source = '''
parts = split!(.message, ",")
temperature_celsius = to_float!(parts[4])
gpu_name = strip_whitespace!(parts[3])

# GPU temperature metric
.metric_type = "gpu_temperature"
.value = temperature_celsius
.gpu_name = gpu_name
.host = get_env_var("HOSTNAME") ?? "unknown-host"
.timestamp = format_timestamp!(now(), format: "%+")
'''

# GPU clock frequency transform
[transforms.format_gpu_clocks]
type = "remap"
inputs = ["gpu_metrics"]
source = '''
parts = split!(.message, ",")
clock_graphics_mhz = to_float!(parts[8])
clock_memory_mhz = to_float!(parts[9])
gpu_name = strip_whitespace!(parts[3])

# GPU clocks metric
.metric_type = "gpu_clocks"
.clock_graphics = clock_graphics_mhz
.clock_memory = clock_memory_mhz
.gpu_name = gpu_name
.host = get_env_var("HOSTNAME") ?? "unknown-host"
.timestamp = format_timestamp!(now(), format: "%+")
'''

# GPU power metrics transform
[transforms.format_gpu_power]
type = "remap"
inputs = ["gpu_metrics"]
source = '''
parts = split!(.message, ",")
power_draw_watts = to_float!(parts[5])
power_limit_watts = to_float!(parts[6])
gpu_name = strip_whitespace!(parts[3])

# GPU power metric
.metric_type = "gpu_power"
.power_draw = power_draw_watts
.power_limit = power_limit_watts
.power_percent = if power_limit_watts > 0 { ((power_draw_watts / power_limit_watts) ?? 0.0) * 100.0 } else { 0.0 }
.gpu_name = gpu_name
.host = get_env_var("HOSTNAME") ?? "unknown-host"
.timestamp = format_timestamp!(now(), format: "%+")
'''

# GPU encoder/decoder utilization transform
[transforms.format_gpu_encoder_decoder]
type = "remap"
inputs = ["gpu_metrics"]
source = '''
parts = split!(.message, ",")
encoder_utilization = to_float!(parts[10])
decoder_utilization = to_float!(parts[11])
gpu_name = strip_whitespace!(parts[3])

# GPU encoder/decoder metric
.metric_type = "gpu_encoder_decoder"
.encoder_utilization = encoder_utilization
.decoder_utilization = decoder_utilization
.gpu_name = gpu_name
.host = get_env_var("HOSTNAME") ?? "unknown-host"
.timestamp = format_timestamp!(now(), format: "%+")
'''

# GPU fan speed and performance state transform
[transforms.format_gpu_fan_pstate]
type = "remap"
inputs = ["gpu_metrics"]
source = '''
parts = split!(.message, ",")
fan_speed_percent = to_float!(parts[7])
pstate = strip_whitespace!(parts[12])
gpu_name = strip_whitespace!(parts[3])

# GPU fan and pstate metric
.metric_type = "gpu_fan_pstate"
.fan_speed = fan_speed_percent
.performance_state = pstate
.gpu_name = gpu_name
.host = get_env_var("HOSTNAME") ?? "unknown-host"
.timestamp = format_timestamp!(now(), format: "%+")
'''

[transforms.rewrite_mem_names]
type = "remap"
inputs = ["metrics_to_logs"]
source = '''
if starts_with!(.name, "memory_") {
.name = "host_" + to_string!(.name)
}
'''

################################ 3. FILTER & TAG ##############################

[transforms.format_metrics]
type = "remap"
inputs = ["rewrite_mem_names"]
source = '''
# ----- derive a reliable host field -----
hostname = if !is_null(.host) {
.host
} else if !is_null(.tags.host) {
.tags.host
} else {
get_env_var("HOSTNAME") ?? "unknown-host"
}

# -------- filter + rename the metrics we care about --------
if .name == "host_memory_used_bytes" {
.metric_type = "memory_used"
.value = .gauge.value
.host = hostname

} else if .name == "host_memory_total_bytes" {
.metric_type = "memory_total"
.value = .gauge.value
.host = hostname

} else if (.name == "host_cpu_seconds_total" || .name == "cpu_seconds_total") && .tags.mode == "idle" {
.metric_type = "cpu_idle"
.value = .counter.value
.cpu = .tags.cpu
.host = hostname

} else if (.name == "host_cpu_seconds_total" || .name == "cpu_seconds_total") {
.metric_type = "cpu_total"
.value = .counter.value
.cpu = .tags.cpu
.host = hostname

} else {
abort # drop everything else
}

.timestamp = format_timestamp!(now(), format: "%+")
'''

# Network metrics transform
[transforms.format_network]
type = "remap"
inputs = ["rewrite_mem_names"]
source = '''
hostname = if !is_null(.host) {
.host
} else if !is_null(.tags.host) {
.tags.host
} else {
get_env_var("HOSTNAME") ?? "unknown-host"
}

interface = .tags.device

if .name == "network_receive_bytes_total" {
.metric_type = "network_rx_bytes"
.value = .counter.value
.interface = interface
.host = hostname
} else if .name == "network_transmit_bytes_total" {
.metric_type = "network_tx_bytes"
.value = .counter.value
.interface = interface
.host = hostname
} else if .name == "network_receive_packets_total" {
.metric_type = "network_rx_packets"
.value = .counter.value
.interface = interface
.host = hostname
} else if .name == "network_transmit_packets_total" {
.metric_type = "network_tx_packets"
.value = .counter.value
.interface = interface
.host = hostname
} else if .name == "network_receive_errs_total" {
.metric_type = "network_rx_errors"
.value = .counter.value
.interface = interface
.host = hostname
} else if .name == "network_transmit_errs_total" {
.metric_type = "network_tx_errors"
.value = .counter.value
.interface = interface
.host = hostname
} else if .name == "network_receive_drop_total" {
.metric_type = "network_rx_dropped"
.value = .counter.value
.interface = interface
.host = hostname
} else if .name == "network_transmit_drop_total" {
.metric_type = "network_tx_dropped"
.value = .counter.value
.interface = interface
.host = hostname
} else {
abort
}

.timestamp = format_timestamp!(now(), format: "%+")
'''

# Storage/Filesystem metrics transform
[transforms.format_storage]
type = "remap"
inputs = ["rewrite_mem_names"]
source = '''
hostname = if !is_null(.host) {
.host
} else if !is_null(.tags.host) {
.tags.host
} else {
get_env_var("HOSTNAME") ?? "unknown-host"
}

filesystem = .tags.filesystem
device = .tags.device

if .name == "filesystem_total_bytes" {
.metric_type = "disk_total"
.value = .gauge.value
.filesystem = filesystem
.device = device
.host = hostname
} else if .name == "filesystem_used_bytes" {
.metric_type = "disk_used"
.value = .gauge.value
.filesystem = filesystem
.device = device
.host = hostname
} else if .name == "filesystem_free_bytes" {
.metric_type = "disk_free"
.value = .gauge.value
.filesystem = filesystem
.device = device
.host = hostname
} else if .name == "disk_read_bytes_total" {
.metric_type = "disk_read_bytes"
.value = .counter.value
.device = device
.host = hostname
} else if .name == "disk_written_bytes_total" {
.metric_type = "disk_write_bytes"
.value = .counter.value
.device = device
.host = hostname
} else {
abort
}

.timestamp = format_timestamp!(now(), format: "%+")
'''

# System uptime and load metrics transform
[transforms.format_system]
type = "remap"
inputs = ["rewrite_mem_names"]
source = '''
hostname = if !is_null(.host) {
.host
} else if !is_null(.tags.host) {
.tags.host
} else {
get_env_var("HOSTNAME") ?? "unknown-host"
}

if .name == "uptime" {
.metric_type = "uptime"
.value = .gauge.value
.host = hostname
} else if .name == "boot_time" {
.metric_type = "boot_time"
.value = .gauge.value
.host = hostname
} else if .name == "load1" {
.metric_type = "load_1min"
.value = .gauge.value
.host = hostname
} else if .name == "load5" {
.metric_type = "load_5min"
.value = .gauge.value
.host = hostname
} else if .name == "load15" {
.metric_type = "load_15min"
.value = .gauge.value
.host = hostname
} else if .name == "host_logical_cpus" {
.metric_type = "cpu_logical_count"
.value = .gauge.value
.host = hostname
} else if .name == "host_physical_cpus" {
.metric_type = "cpu_physical_count"
.value = .gauge.value
.host = hostname
} else {
abort
}

.timestamp = format_timestamp!(now(), format: "%+")
'''

################################ 4. PRODUCTION SINK ###########################

[sinks.websocket_out]
type = "websocket_server"
inputs = ["format_metrics", "format_network", "format_storage", "format_system", "format_system_info", "format_gpu", "format_gpu_memory", "format_gpu_temperature", "format_gpu_clocks", "format_gpu_power", "format_gpu_encoder_decoder", "format_gpu_fan_pstate"]
address = "0.0.0.0:4096"

encoding.codec = "json"
encoding.timestamp_format = "rfc3339"

[sinks.websocket_out.tls]
enabled = true
crt_file = "/etc/vector/certs/server.crt"
key_file = "/etc/vector/certs/server.key"
# Optional: Enable mTLS client certificate verification (disabled by default)
# ca_file = "/etc/vector/certs/mtls/ca.crt"
# verify_certificate = true

################################ 5. API #######################################

[api]
enabled = true

Configuration Explanation

Sources Section

  • system_metrics: Collects comprehensive system metrics every second:
    • CPU usage per core
    • Memory usage
    • Disk I/O
    • Filesystem usage
    • Network interface statistics
    • System host information
    • Load averages
  • system_info: Collects static system information every 15 seconds (OS, architecture, domain, IPv4 address)
  • gpu_metrics: Runs nvidia-smi command for comprehensive GPU metrics including utilization, memory, temperature, power, clocks, encoder/decoder usage, fan speed, and performance state (optional)

Transforms Section

  • metrics_to_logs: Converts metrics to log format for processing
  • format_system_info: Processes system information (OS, architecture, domain, IPv4 address)
  • format_gpu: Processes GPU utilization from nvidia-smi CSV output
  • format_gpu_memory: Processes GPU memory usage from nvidia-smi CSV output
  • format_gpu_temperature: Processes GPU temperature in Celsius
  • format_gpu_clocks: Processes GPU graphics and memory clock frequencies in MHz
  • format_gpu_power: Processes GPU power draw and limits in Watts
  • format_gpu_encoder_decoder: Processes GPU encoder and decoder utilization percentages
  • format_gpu_fan_pstate: Processes GPU fan speed percentage and performance state (P0-P15)
  • rewrite_mem_names: Normalizes memory metric names with "host_" prefix
  • format_metrics: Filters and formats CPU and memory metrics
  • format_network: Processes network interface metrics (rx/tx bytes, packets, errors, dropped)
  • format_storage: Processes filesystem and disk I/O metrics
  • format_system: Processes system uptime, load averages, and CPU counts

Sinks Section

  • websocket_out: Creates WebSocket server on port 4096 with TLS
    • Combines all metric transforms into a single output stream
    • Optional mTLS client certificate verification (disabled by default)

API Section

  • api: Enables Vector's management API (optional)

mTLS (Optional)

For client certificate authentication, see the mTLS Security Guide.

To enable mTLS in vector.toml, add these lines to the [sinks.websocket_out.tls] section:

ca_file  = "/etc/vector/certs/mtls/ca.crt"
verify_certificate = true

Then restart Vector:

sudo systemctl restart vector

GPU Configuration (Optional)

If You Have NVIDIA GPU

If your server has an NVIDIA GPU and you want GPU monitoring:

# Verify nvidia-smi works
nvidia-smi

# Test the specific command Vector will use
nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total,gpu_name,temperature.gpu,power.draw,power.limit,fan.speed,clocks.current.graphics,clocks.current.memory,utilization.encoder,utilization.decoder,pstate --format=csv,noheader,nounits

If this works, your configuration is ready.

If You Don't Have NVIDIA GPU

Edit the configuration to remove GPU sections:

# Edit the configuration file
sudo nano /etc/vector/vector.toml

Remove or comment out these sections:

  1. The entire [sources.gpu_metrics] section
  2. The entire [sources.gpu_metrics.scheduled] section
  3. The entire [transforms.format_gpu] section
  4. The entire [transforms.format_gpu_memory] section
  5. The entire [transforms.format_gpu_temperature] section
  6. The entire [transforms.format_gpu_clocks] section
  7. The entire [transforms.format_gpu_power] section
  8. The entire [transforms.format_gpu_encoder_decoder] section
  9. The entire [transforms.format_gpu_fan_pstate] section
  10. Remove all GPU-related transform names from the inputs line in [sinks.websocket_out]

The inputs line should become:

inputs  = ["format_metrics", "format_network", "format_storage", "format_system", "format_system_info"]

Testing Configuration

Step 4: Validate Configuration

Before running Vector, validate your configuration:

# Validate configuration file
vector validate /etc/vector/vector.toml

You should see:

✓ Validated

If you do not have TLS certificates set up yet, you will see an error

x Sink "websocket_out": Could not open certificate file "/etc/vector/certs/server.crt": No such file or directory

If you see any other errors, review the configuration file for syntax issues. Now proceed to the TLS Configuration Guide to set up certificates before running Vector.

Running Vector

Step 5: Start Vector Service

Enable and start Vector as a systemd service:

# Enable Vector to start on boot
sudo systemctl enable vector

# Start Vector service
sudo systemctl start vector

Step 6: Verify Vector is Running

# Check Vector service status
sudo systemctl status vector

# View Vector logs
sudo journalctl -u vector -f

# Verify WebSocket server is listening
sudo netstat -tlnp | grep 4096

You should see:

  • Service status showing "active (running)"
  • Log messages showing successful startup
  • Port 4096 in LISTEN state

Also from your local machine, you can test the WebSocket connection (replace monitor.yourdomain.com with your server's domain):

# Test WebSocket connection
wscat -c wss://monitor.yourdomain.com:4096

Managing Vector

# Check status
sudo systemctl status vector

# Start / Stop / Restart
sudo systemctl start vector
sudo systemctl stop vector
sudo systemctl restart vector

# View live logs
sudo journalctl -u vector -f

Troubleshooting

See the Troubleshooting Guide for common Vector issues.

Next Steps

Once Vector is successfully installed and configured:

  1. Set up TLS certificates - Required for secure WebSocket connections
  2. Configure firewall - Allow WebSocket connections on port 4096 (see Provider Guides)
  3. Test the connection - Verify everything works
  4. Connect from Actvt - Add server to Actvt

Important: Vector will not accept WebSocket connections until TLS certificates are properly configured. Continue to the TLS Configuration Guide next.