Interim release: documentation updates and rate limiting improvements
- Add applesauce library reference documentation - Add rate limiting test report for Badger - Add memory monitoring for rate limiter (platform-specific implementations) - Enhance PID-controlled adaptive rate limiting - Update Neo4j and Badger monitors with improved load metrics - Add docker-compose configuration - Update README and configuration options 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit was merged in pull request #3.
This commit is contained in:
149
pkg/ratelimit/memory.go
Normal file
149
pkg/ratelimit/memory.go
Normal file
@@ -0,0 +1,149 @@
|
||||
//go:build !(js && wasm)
|
||||
|
||||
package ratelimit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
"github.com/pbnjay/memory"
|
||||
)
|
||||
|
||||
// MinimumMemoryMB is the minimum memory required to run the relay with rate limiting.
|
||||
const MinimumMemoryMB = 500
|
||||
|
||||
// AutoDetectMemoryFraction is the fraction of available memory to use when auto-detecting.
|
||||
const AutoDetectMemoryFraction = 0.66
|
||||
|
||||
// DefaultMaxMemoryMB is the default maximum memory target when auto-detecting.
|
||||
// This caps the auto-detected value to ensure optimal performance.
|
||||
const DefaultMaxMemoryMB = 1500
|
||||
|
||||
// ErrInsufficientMemory is returned when there isn't enough memory to run the relay.
|
||||
var ErrInsufficientMemory = errors.New("insufficient memory: relay requires at least 500MB of available memory")
|
||||
|
||||
// ProcessMemoryStats contains memory statistics for the current process.
|
||||
// On Linux, these are read from /proc/self/status for accurate RSS values.
|
||||
// On other platforms, these are approximated from Go runtime stats.
|
||||
type ProcessMemoryStats struct {
|
||||
// VmRSS is the resident set size (total physical memory in use) in bytes
|
||||
VmRSS uint64
|
||||
// RssShmem is the shared memory portion of RSS in bytes
|
||||
RssShmem uint64
|
||||
// RssAnon is the anonymous (non-shared) memory in bytes
|
||||
RssAnon uint64
|
||||
// VmHWM is the peak RSS (high water mark) in bytes
|
||||
VmHWM uint64
|
||||
}
|
||||
|
||||
// PhysicalMemoryBytes returns the actual physical memory usage (RSS - shared)
|
||||
func (p ProcessMemoryStats) PhysicalMemoryBytes() uint64 {
|
||||
if p.VmRSS > p.RssShmem {
|
||||
return p.VmRSS - p.RssShmem
|
||||
}
|
||||
return p.VmRSS
|
||||
}
|
||||
|
||||
// PhysicalMemoryMB returns the actual physical memory usage in MB
|
||||
func (p ProcessMemoryStats) PhysicalMemoryMB() uint64 {
|
||||
return p.PhysicalMemoryBytes() / (1024 * 1024)
|
||||
}
|
||||
|
||||
// DetectAvailableMemoryMB returns the available system memory in megabytes.
|
||||
// On Linux, this returns the actual available memory (free + cached).
|
||||
// On other systems, it returns total memory minus the Go runtime's current usage.
|
||||
func DetectAvailableMemoryMB() uint64 {
|
||||
// Use pbnjay/memory for cross-platform memory detection
|
||||
available := memory.FreeMemory()
|
||||
if available == 0 {
|
||||
// Fallback: use total memory
|
||||
available = memory.TotalMemory()
|
||||
}
|
||||
return available / (1024 * 1024)
|
||||
}
|
||||
|
||||
// DetectTotalMemoryMB returns the total system memory in megabytes.
|
||||
func DetectTotalMemoryMB() uint64 {
|
||||
return memory.TotalMemory() / (1024 * 1024)
|
||||
}
|
||||
|
||||
// CalculateTargetMemoryMB calculates the target memory limit based on configuration.
|
||||
// If configuredMB is 0, it auto-detects based on available memory (66% of available, capped at 1.5GB).
|
||||
// If configuredMB is non-zero, it validates that it's achievable.
|
||||
// Returns an error if there isn't enough memory.
|
||||
func CalculateTargetMemoryMB(configuredMB int) (int, error) {
|
||||
availableMB := int(DetectAvailableMemoryMB())
|
||||
|
||||
// If configured to auto-detect (0), calculate target
|
||||
if configuredMB == 0 {
|
||||
// First check if we have minimum available memory
|
||||
if availableMB < MinimumMemoryMB {
|
||||
return 0, ErrInsufficientMemory
|
||||
}
|
||||
|
||||
// Calculate 66% of available
|
||||
targetMB := int(float64(availableMB) * AutoDetectMemoryFraction)
|
||||
|
||||
// If 66% is less than minimum, use minimum (we've already verified we have enough)
|
||||
if targetMB < MinimumMemoryMB {
|
||||
targetMB = MinimumMemoryMB
|
||||
}
|
||||
|
||||
// Cap at default maximum for optimal performance
|
||||
if targetMB > DefaultMaxMemoryMB {
|
||||
targetMB = DefaultMaxMemoryMB
|
||||
}
|
||||
|
||||
return targetMB, nil
|
||||
}
|
||||
|
||||
// If explicitly configured, validate it's achievable
|
||||
if configuredMB < MinimumMemoryMB {
|
||||
return 0, ErrInsufficientMemory
|
||||
}
|
||||
|
||||
// Warn but allow if configured target exceeds available
|
||||
// (the PID controller will throttle as needed)
|
||||
return configuredMB, nil
|
||||
}
|
||||
|
||||
// GetMemoryStats returns current memory statistics for logging.
|
||||
type MemoryStats struct {
|
||||
TotalMB uint64
|
||||
AvailableMB uint64
|
||||
TargetMB int
|
||||
GoAllocatedMB uint64
|
||||
GoSysMB uint64
|
||||
}
|
||||
|
||||
// GetMemoryStats returns current memory statistics.
|
||||
func GetMemoryStats(targetMB int) MemoryStats {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
return MemoryStats{
|
||||
TotalMB: DetectTotalMemoryMB(),
|
||||
AvailableMB: DetectAvailableMemoryMB(),
|
||||
TargetMB: targetMB,
|
||||
GoAllocatedMB: m.Alloc / (1024 * 1024),
|
||||
GoSysMB: m.Sys / (1024 * 1024),
|
||||
}
|
||||
}
|
||||
|
||||
// readProcessMemoryStatsFallback returns memory stats using Go runtime.
|
||||
// This is used on non-Linux platforms or when /proc is unavailable.
|
||||
// The values are approximations and may not accurately reflect OS-level metrics.
|
||||
func readProcessMemoryStatsFallback() ProcessMemoryStats {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
// Use Sys as an approximation of RSS (includes all memory from OS)
|
||||
// HeapAlloc approximates anonymous memory (live heap objects)
|
||||
// We cannot determine shared memory from Go runtime, so leave it at 0
|
||||
return ProcessMemoryStats{
|
||||
VmRSS: m.Sys,
|
||||
RssAnon: m.HeapAlloc,
|
||||
RssShmem: 0, // Cannot determine shared memory from Go runtime
|
||||
VmHWM: 0, // Not available from Go runtime
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user