这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install Rust 1.81
uses: dtolnay/rust-toolchain@1.81.0
- name: Install Rust 1.82
uses: dtolnay/rust-toolchain@1.82.0

- name: Install system dependencies
run: |
Expand Down
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ Add the following to your nginx configuration:
load_module /path/to/libngx_vts_rust.so;

http {
# Enable VTS zone tracking globally
vts_status_zone on;
# Configure shared memory zone for VTS statistics
vts_zone main 10m;

server {
listen 80;
Expand All @@ -81,7 +81,10 @@ http {
### Available Directives

- **`vts_status`**: Enable the VTS status endpoint for this location
- **`vts_status_zone on|off`**: Enable/disable zone tracking (default: on)
- **`vts_zone <zone_name> <size>`**: Configure a shared memory zone for VTS statistics storage
- `zone_name`: Name of the shared memory zone (e.g., "main")
- `size`: Size of the shared memory zone (e.g., "1m", "10m")
- Example: `vts_zone main 10m`

## Usage

Expand Down Expand Up @@ -138,22 +141,28 @@ nginx_vts_server_request_seconds{zone="example.com",type="max"} 2.5

The module consists of several key components:

- **Statistics Collection** (`src/stats.rs`): Core data structures and management
- **HTTP Handlers** (`src/handlers.rs`): Request processing and JSON output
- **Configuration** (`src/config.rs`): Module configuration and directives
- **Main Module** (`src/lib.rs`): Nginx module integration and request hooks
- **VTS Node System** (`src/vts_node.rs`): Core statistics data structures and management
- **Configuration** (`src/config.rs`): Module configuration and directives
- **Main Module** (`src/lib.rs`): Nginx module integration and request handlers
- **Statistics Collection** (`src/stats.rs`): Advanced statistics collection (unused currently)

### Statistics Collection
### Shared Memory Configuration

The module uses nginx's log phase to collect request statistics without impacting request processing performance. Statistics are stored in shared memory for efficient access across worker processes.
The `vts_zone` directive configures a shared memory zone that stores VTS statistics:

- **Zone Name**: Identifies the shared memory zone (typically "main")
- **Zone Size**: Allocates memory for statistics storage (e.g., "1m" = 1MB, "10m" = 10MB)
- **Multi-worker Support**: Statistics are shared across all nginx worker processes
- **Persistence**: Statistics persist across configuration reloads

### Request Tracking

Every request is tracked with the following metrics:
- Request count and timing
- Bytes transferred (in/out)
- HTTP status code distribution
- Bytes transferred (in/out)
- HTTP status code distribution (1xx, 2xx, 3xx, 4xx, 5xx)
- Server zone identification
- Request time statistics (total, max, average)

## Monitoring Integration

Expand Down
185 changes: 182 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,26 @@ use ngx::{core, http, http_request_handler, ngx_modules, ngx_string};
use std::os::raw::{c_char, c_void};

mod config;
mod vts_node;

/// VTS main configuration structure (simplified for now)
#[derive(Debug)]
#[allow(dead_code)]
struct VtsMainConfig {
/// Enable VTS tracking
pub enabled: bool,
}

#[allow(dead_code)]
impl VtsMainConfig {
fn new() -> Self {
Self { enabled: true }
}
}

// VTS status request handler that generates traffic status response
http_request_handler!(vts_status_handler, |request: &mut http::Request| {
// Generate VTS status content
// Generate VTS status content (simplified version for now)
let content = generate_vts_status_content();

let mut buf = match request.pool().create_buffer_from_str(&content) {
Expand Down Expand Up @@ -148,8 +164,89 @@ unsafe extern "C" fn ngx_http_set_vts_status(
std::ptr::null_mut()
}

/// Configuration handler for vts_zone directive
///
/// Parses the vts_zone directive arguments: zone_name and size
/// Example: vts_zone main 10m
///
/// # Safety
///
/// This function is called by nginx and must maintain C ABI compatibility
unsafe extern "C" fn ngx_http_set_vts_zone(
cf: *mut ngx_conf_t,
_cmd: *mut ngx_command_t,
_conf: *mut c_void,
) -> *mut c_char {
let cf = &mut *cf;
let args = std::slice::from_raw_parts((*cf.args).elts as *mut ngx_str_t, (*cf.args).nelts);

if args.len() != 3 {
let error_msg = "vts_zone directive requires exactly 2 arguments: zone_name and size\0";
return error_msg.as_ptr() as *mut c_char;
}

// Parse zone name (args[1])
let zone_name_slice = std::slice::from_raw_parts(args[1].data, args[1].len);
let zone_name = match std::str::from_utf8(zone_name_slice) {
Ok(name) => name,
Err(_) => {
let error_msg = "vts_zone: invalid zone name (must be valid UTF-8)\0";
return error_msg.as_ptr() as *mut c_char;
}
};

// Parse zone size (args[2])
let zone_size_slice = std::slice::from_raw_parts(args[2].data, args[2].len);
let zone_size_str = match std::str::from_utf8(zone_size_slice) {
Ok(size) => size,
Err(_) => {
let error_msg = "vts_zone: invalid zone size (must be valid UTF-8)\0";
return error_msg.as_ptr() as *mut c_char;
}
};

// Parse size with units (e.g., "10m", "1g", "512k")
let size_bytes = match parse_size_string(zone_size_str) {
Ok(size) => size,
Err(_) => {
let error_msg = "vts_zone: invalid size format (use format like 10m, 1g, 512k)\0";
return error_msg.as_ptr() as *mut c_char;
}
};

// Create shared memory zone
let zone_name_cstr = match std::ffi::CString::new(zone_name) {
Ok(cstr) => Box::new(cstr), // Store CString in a Box to extend its lifetime
Err(_) => {
let error_msg = "vts_zone: invalid zone name (contains null bytes)\0";
return error_msg.as_ptr() as *mut c_char;
}
};
let mut zone_name_ngx = ngx_str_t {
len: zone_name.len(),
data: zone_name_cstr.as_ptr() as *mut u8,
};
let shm_zone = ngx_shared_memory_add(
cf,
&mut zone_name_ngx,
size_bytes,
&raw const ngx_http_vts_module as *const _ as *mut _,
);

if shm_zone.is_null() {
let error_msg = "vts_zone: failed to allocate shared memory zone\0";
return error_msg.as_ptr() as *mut c_char;
}

// Set initialization callback for the shared memory zone
(*shm_zone).init = Some(vts_init_shm_zone);
(*shm_zone).data = std::ptr::null_mut(); // Will be set during initialization

std::ptr::null_mut()
}

/// Module commands configuration
static mut NGX_HTTP_VTS_COMMANDS: [ngx_command_t; 2] = [
static mut NGX_HTTP_VTS_COMMANDS: [ngx_command_t; 3] = [
ngx_command_t {
name: ngx_string!("vts_status"),
type_: (NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS) as ngx_uint_t,
Expand All @@ -158,10 +255,18 @@ static mut NGX_HTTP_VTS_COMMANDS: [ngx_command_t; 2] = [
offset: 0,
post: std::ptr::null_mut(),
},
ngx_command_t {
name: ngx_string!("vts_zone"),
type_: (NGX_HTTP_MAIN_CONF | NGX_CONF_TAKE2) as ngx_uint_t,
set: Some(ngx_http_set_vts_zone),
conf: 0,
offset: 0,
post: std::ptr::null_mut(),
},
ngx_command_t::empty(),
];

/// Module context configuration
/// Module context configuration (simplified)
#[no_mangle]
static NGX_HTTP_VTS_MODULE_CTX: ngx_http_module_t = ngx_http_module_t {
preconfiguration: None,
Expand Down Expand Up @@ -209,6 +314,53 @@ pub static mut ngx_http_vts_module: ngx_module_t = ngx_module_t {
spare_hook7: 0,
};

/// Parse size string with units (e.g., "10m", "1g", "512k") to bytes
///
/// Supports the following units:
/// - k/K: kilobytes (1024 bytes)
/// - m/M: megabytes (1024*1024 bytes)
/// - g/G: gigabytes (1024*1024*1024 bytes)
/// - No unit: bytes
fn parse_size_string(size_str: &str) -> Result<usize, &'static str> {
if size_str.is_empty() {
return Err("Empty size string");
}

let size_str = size_str.trim();
let (num_str, multiplier) = if let Some(last_char) = size_str.chars().last() {
match last_char.to_ascii_lowercase() {
'k' => (&size_str[..size_str.len() - 1], 1024),
'm' => (&size_str[..size_str.len() - 1], 1024 * 1024),
'g' => (&size_str[..size_str.len() - 1], 1024 * 1024 * 1024),
_ if last_char.is_ascii_digit() => (size_str, 1),
_ => return Err("Invalid size unit"),
}
} else {
return Err("Empty size string");
};

let num: usize = num_str.parse().map_err(|_| "Invalid number")?;

num.checked_mul(multiplier).ok_or("Size overflow")
Copy link

Copilot AI Jul 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic number multipliers (1024, 1024*1024, etc.) should be defined as named constants for better maintainability. Consider defining const KILOBYTE: usize = 1024;, const MEGABYTE: usize = 1024 * 1024;, etc.

Copilot uses AI. Check for mistakes.
}

/// Shared memory zone initialization callback
///
/// # Safety
///
/// This function is called by nginx during shared memory initialization
extern "C" fn vts_init_shm_zone(shm_zone: *mut ngx_shm_zone_t, _data: *mut c_void) -> ngx_int_t {
if shm_zone.is_null() {
return NGX_ERROR as ngx_int_t;
}

// Initialize the shared memory zone for VTS statistics
// For now, just mark it as successfully initialized
// Future implementation would set up red-black trees and data structures here

NGX_OK as ngx_int_t
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -235,4 +387,31 @@ mod tests {
assert!(!time_str.is_empty());
assert_eq!(time_str, "1234567890");
}

#[test]
fn test_parse_size_string() {
// Test bytes (no unit)
assert_eq!(parse_size_string("1024"), Ok(1024));
assert_eq!(parse_size_string("512"), Ok(512));

// Test kilobytes
assert_eq!(parse_size_string("1k"), Ok(1024));
assert_eq!(parse_size_string("1K"), Ok(1024));
assert_eq!(parse_size_string("10k"), Ok(10240));

// Test megabytes
assert_eq!(parse_size_string("1m"), Ok(1024 * 1024));
assert_eq!(parse_size_string("1M"), Ok(1024 * 1024));
assert_eq!(parse_size_string("10m"), Ok(10 * 1024 * 1024));

// Test gigabytes
assert_eq!(parse_size_string("1g"), Ok(1024 * 1024 * 1024));
assert_eq!(parse_size_string("1G"), Ok(1024 * 1024 * 1024));

// Test invalid formats
assert!(parse_size_string("").is_err());
assert!(parse_size_string("abc").is_err());
assert!(parse_size_string("10x").is_err());
assert!(parse_size_string("k").is_err());
}
}
Loading