-
Notifications
You must be signed in to change notification settings - Fork 174
feat(flow): implement adaptive throttler #196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kxrxh
wants to merge
56
commits into
reugn:master
Choose a base branch
from
kxrxh:feature/adaptive-throttler
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
56 commits
Select commit
Hold shift + click to select a range
b373d2a
feat(flow): implement adaptive throttler with resource monitoring and…
kxrxh bf9fa92
docs(README): add AdaptiveThrottler description to documentation
kxrxh 55c5fe1
fix(flow): emit partial sliding window on early close (#192)
reugn 2bc6a86
fix: resolve all golangci-lint issues
kxrxh fbc0df7
refactor(adaptive_throttler): streamline main function by removing co…
kxrxh 0d97643
refactor: rename CPUUsageModeReal to CPUUsageModeMeasured
kxrxh 837e3d6
feat(adaptive_throttler): enhance resource monitoring for containeriz…
kxrxh edfb728
Merge pull request #1 from reugn/master
kxrxh fa6e81f
feat(adaptive_throttler): add demo for adaptive throttling pipeline
kxrxh d1d210c
refactor(adaptive_throttler): update config validation message
kxrxh 238ca8b
docs(README): fix punctuation in AdaptiveThrottler description
kxrxh 5c7bc28
refactor(adaptive_throttler): rename resourceMonitorInterface to reso…
kxrxh 754d39e
refactor(resource_monitor): move stats collection to initSampler
kxrxh 135139f
refactor(adaptive_throttler): extract system monitoring into internal…
kxrxh a224484
refactor(sysmonitor): improve CPU time retrieval for current process
kxrxh 04586f7
feat(adaptive_throttler): enhance demo with CPU workload simulation
kxrxh 6ec1760
docs(adaptive_throttler): add clarification comment for constrained v…
kxrxh 0633447
fix(sysmonitor): add safety checks for CPU count and handle memory st…
kxrxh fe90168
refactor(sysmonitor): unify memory reading functions for cgroup v1 an…
kxrxh 5ab4ea1
refactor(sysmonitor): streamline memory reading logic and remove unus…
kxrxh a92689d
refactor(sysmonitor): additional context for errors was added
kxrxh c460ee3
refactor(sysmonitor): remove outdated validation comment in getProces…
kxrxh 4eb9507
fix(sysmonitor): add overflow check for memory value conversion
kxrxh 2e8e86a
fix(sysmonitor): enhance error handling in memory status retrieval
kxrxh 5df55d2
docs(adaptive_throttler): update demo comments to clarify adaptive th…
kxrxh aaf9377
refactor(resource_monitor_test): remove unnecessary helper call in te…
kxrxh 9b7cba6
Merge branch 'feature/adaptive-throttler' of https://github.com/kxrxh…
kxrxh a437518
refactor(resource_monitor): simplify comment for division by zero check
kxrxh 08f6012
chore(dependencies): downgrade Go version to 1.23.0 and update indire…
kxrxh e7a1c81
feat(adaptive_throttler): enforce minimum sample interval to reduce C…
kxrxh 226098e
fix(adaptive_throttler): prevent negative target rate in rate adaptation
kxrxh 0707bb0
docs(adaptive_throttler): clarify comment on MemoryReader function re…
kxrxh df7560a
docs(adaptive_throttler): format comment for CPU usage sampling modes
kxrxh 2964497
feat(resource_monitor): add comment to indicate start of periodic res…
kxrxh a5fb0d4
docs(adaptive_throttler): update MemoryReader comment for clarity on …
kxrxh 95ce780
chore(dependencies): remove go.sum and update go.mod
kxrxh 8fa0e0f
refactor(resource_monitor): move clampPercent and validatePercent fun…
kxrxh 77b55f2
refactor(flow): rename util.go to operators.go for better clarity
kxrxh 9b5f438
refactor(adaptive_throttler): remove unnecessary pointer dereference …
kxrxh 55ca0c5
refactor(resource_monitor): change stats from atomic.Value to atomic.…
kxrxh d1c811e
refactor(adaptive_throttler): remove unnecessary mutex for rate adapt…
kxrxh 5a46a4f
refactor(adaptive_throttler): reorder functions and improved adaptRat…
kxrxh e097d6b
feat(adaptive_throttler): add MaxBufferSize configuration and validat…
kxrxh cd8ad61
refactor(sysmonitor): simplify memory initialization by removing unne…
kxrxh 809f604
fix(sysmonitor): ensure available memory does not exceed total memory
kxrxh 904d9e9
refactor(adaptive_throttler): fix golangci-lint errors
kxrxh 60b20a4
refactor(adaptive_throttler): change validateConfig function to a met…
kxrxh ab90a2c
refactor(adaptive_throttler, resource_monitor): enhance configuration…
kxrxh 7d4abd4
test: add comprehensive test coverage for adaptive throttler and syst…
kxrxh 604b5b5
test(resource_monitor): add tests for clamping and validation of perc…
kxrxh 7dfff93
test(sysmonitor): refactor memory tests to use a helper function to f…
kxrxh 6e24e80
refactor(adaptive_throttler, resource_monitor): implement shared moni…
kxrxh 0028afc
refactor(sysmonitor): remove SetMemoryReader function from memory pla…
kxrxh 7f375ba
refactor: enhance system monitoring and adaptive throttling
kxrxh 153c015
test(sysmonitor): add comprehensive tests for Windows CPU and memory …
kxrxh 583a92b
fix(adaptive_throttler): make close public and ensure proper resource…
kxrxh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "math/rand" | ||
| "sync/atomic" | ||
| "time" | ||
|
|
||
| ext "github.com/reugn/go-streams/extension" | ||
| "github.com/reugn/go-streams/flow" | ||
| ) | ||
|
|
||
| // main demonstrates adaptive throttling with simulated resource pressure. | ||
| // The demo: | ||
| // 1. Produces 250 elements in bursts | ||
| // 2. Processes elements with CPU-intensive work (50ms each) | ||
| // 3. Simulates memory pressure that increases then decreases (creating throttling-recovery cycle) | ||
| // 4. The adaptive throttler adjusts throughput based on CPU/memory usage | ||
| // 5. Shows throttling down to ~1/sec during high memory, then recovery back to 40/sec | ||
| // 6. Stats are logged every 500ms showing rate adaptation | ||
| func main() { | ||
| var elementsProcessed atomic.Int64 | ||
|
|
||
| // Set up demo configuration with memory simulation | ||
| throttler := setupDemoThrottler(&elementsProcessed) | ||
|
|
||
| in := make(chan any) | ||
| out := make(chan any) // Unbuffered channel to prevent apparent bursts | ||
|
|
||
| source := ext.NewChanSource(in) | ||
| sink := ext.NewChanSink(out) | ||
|
|
||
| statsDone := make(chan struct{}) | ||
| go logThrottlerStats(throttler, statsDone) | ||
| defer close(statsDone) | ||
|
|
||
| go func() { | ||
| source. | ||
| Via(throttler). | ||
| Via(flow.NewPassThrough()). | ||
| To(sink) | ||
| }() | ||
|
|
||
| go produceBurst(in, 250) | ||
|
|
||
| var cpuWorkChecksum uint64 | ||
|
|
||
| // Process the output | ||
| elementsReceived := 0 | ||
| for element := range sink.Out { | ||
| fmt.Printf("consumer received %v\n", element) | ||
| elementsProcessed.Add(1) | ||
| elementsReceived++ | ||
|
|
||
| // Perform CPU-intensive work | ||
| burnCPU(50*time.Millisecond, &cpuWorkChecksum) | ||
|
|
||
| time.Sleep(25 * time.Millisecond) | ||
| } | ||
|
|
||
| fmt.Printf("CPU work checksum: %d\n", cpuWorkChecksum) | ||
| fmt.Printf("Total elements produced: 250, Total elements received: %d\n", elementsReceived) | ||
| if elementsReceived == 250 { | ||
| fmt.Println("✅ SUCCESS: All elements processed without dropping!") | ||
| } else { | ||
| fmt.Printf("❌ FAILURE: %d elements were dropped!\n", 250-elementsReceived) | ||
| } | ||
|
|
||
| throttler.Close() | ||
|
|
||
| fmt.Println("adaptive throttling pipeline completed") | ||
| } | ||
|
|
||
| // setupDemoThrottler creates and configures an adaptive throttler with demo settings | ||
| func setupDemoThrottler(elementsProcessed *atomic.Int64) *flow.AdaptiveThrottler { | ||
| config := flow.DefaultAdaptiveThrottlerConfig() | ||
|
|
||
| config.MinRate = 1 | ||
| config.MaxRate = 20 | ||
| config.InitialRate = 20 | ||
| config.SampleInterval = 200 * time.Millisecond | ||
|
|
||
| config.BackoffFactor = 0.5 | ||
| config.RecoveryFactor = 1.5 | ||
|
|
||
| config.MaxMemoryPercent = 35.0 | ||
| config.RecoveryMemoryThreshold = 30.0 | ||
|
|
||
| // Memory Reader Simulation - Creates a cycle: low -> high -> low memory usage | ||
| config.MemoryReader = func() (float64, error) { | ||
| elementCount := elementsProcessed.Load() | ||
|
|
||
| var memoryPercent float64 | ||
| switch { | ||
| case elementCount <= 80: // Phase 1: Low memory, allow high throughput | ||
| memoryPercent = 5.0 + float64(elementCount)*0.1 // 5% to 13% | ||
| case elementCount <= 120: // Phase 2: Increasing memory pressure, cause throttling | ||
| memoryPercent = 15.0 + float64(elementCount-80)*0.6 // 15% to 43% | ||
| case elementCount <= 160: // Phase 3: High memory, keep throttled down to ~1/sec | ||
| memoryPercent = 30.0 + float64(elementCount-120)*0.3 // 30% to 42% | ||
| default: // Phase 4: Memory decreases, allow recovery back to 40/sec | ||
| memoryPercent = 25.0 - float64(elementCount-160)*1.5 // 25% down to ~5% | ||
| if memoryPercent < 5.0 { | ||
| memoryPercent = 5.0 | ||
| } | ||
| } | ||
| return memoryPercent, nil | ||
| } | ||
|
|
||
| throttler, err := flow.NewAdaptiveThrottler(config) | ||
| if err != nil { | ||
| panic(fmt.Sprintf("failed to create adaptive throttler: %v", err)) | ||
| } | ||
| return throttler | ||
| } | ||
|
|
||
| func produceBurst(in chan<- any, total int) { | ||
| defer close(in) | ||
|
|
||
| for i := range total { | ||
| in <- fmt.Sprintf("job-%02d", i) | ||
|
|
||
| if (i+1)%10 == 0 { | ||
| time.Sleep(180 * time.Millisecond) | ||
| continue | ||
| } | ||
| time.Sleep(time.Duration(2+rand.Intn(5)) * time.Millisecond) | ||
| } | ||
| } | ||
|
|
||
| func burnCPU(duration time.Duration, checksum *uint64) { | ||
| start := time.Now() | ||
| for time.Since(start) < duration { | ||
| for i := 0; i < 1000; i++ { | ||
| *checksum += uint64(i * i) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func logThrottlerStats(at *flow.AdaptiveThrottler, done <-chan struct{}) { | ||
| ticker := time.NewTicker(500 * time.Millisecond) | ||
| defer ticker.Stop() | ||
|
|
||
| for { | ||
| select { | ||
| case <-done: | ||
| return | ||
| case <-ticker.C: | ||
| stats := at.GetResourceStats() | ||
| fmt.Printf("[stats] Rate: %.1f/sec, CPU: %.1f%%, Memory: %.1f%%\n", | ||
| at.GetCurrentRate(), stats.CPUUsagePercent, stats.MemoryUsedPercent) | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "math/rand" | ||
| "strings" | ||
| "sync/atomic" | ||
| "time" | ||
|
|
||
| ext "github.com/reugn/go-streams/extension" | ||
| "github.com/reugn/go-streams/flow" | ||
| ) | ||
|
|
||
| // Demo of adaptive throttling with CPU-intensive work. | ||
| // T1 throttles on CPU > 0.01%, T2 throttles on Memory > 50%. | ||
|
|
||
| func editMessage(msg string) string { | ||
| return strings.ToUpper(msg) | ||
| } | ||
|
|
||
| func addTimestamp(msg string) string { | ||
| return fmt.Sprintf("[%s] %s", time.Now().Format("15:04:05"), msg) | ||
| } | ||
|
|
||
| // cpuIntensiveWork simulates processing load that affects CPU usage | ||
| func cpuIntensiveWork(msg string) string { | ||
| // Simulate CPU-intensive work (hashing-like computation) | ||
| var checksum uint64 | ||
| for i := 0; i < 200000; i++ { // Increased from 50000 to 200000 for more CPU load | ||
| checksum += uint64(len(msg)) * uint64(i) //nolint:gosec | ||
| } | ||
| return msg | ||
| } | ||
|
|
||
| func main() { | ||
| var messagesProcessed atomic.Int64 | ||
|
|
||
| // Configure first throttler - CPU-focused with higher initial rate | ||
| throttler1Config := flow.DefaultAdaptiveThrottlerConfig() | ||
| throttler1Config.MaxCPUPercent = 0.01 // Throttle when CPU > 0.01% (extremely low threshold) | ||
| throttler1Config.MaxMemoryPercent = 80.0 // Less strict memory limit | ||
| throttler1Config.InitialRate = 50 // Start at 50/sec | ||
| throttler1Config.MaxRate = 100 // Can go up to 100/sec | ||
| throttler1Config.MinRate = 5 // Minimum 5/sec | ||
| throttler1Config.SampleInterval = 200 * time.Millisecond | ||
| throttler1Config.BackoffFactor = 0.6 // Reduce to 60% when constrained | ||
| throttler1Config.RecoveryFactor = 1.4 // Increase by 40% during recovery | ||
|
|
||
| throttler1, err := flow.NewAdaptiveThrottler(throttler1Config) | ||
| if err != nil { | ||
| panic(fmt.Sprintf("failed to create throttler1: %v", err)) | ||
| } | ||
|
|
||
| // Configure second throttler - Memory-focused with memory simulation | ||
| throttler2Config := flow.DefaultAdaptiveThrottlerConfig() | ||
| throttler2Config.MaxCPUPercent = 80.0 // Less strict CPU limit | ||
| throttler2Config.MaxMemoryPercent = 40.0 // Throttle when memory > 40% | ||
| throttler2Config.InitialRate = 30 // Start at 30/sec | ||
| throttler2Config.MaxRate = 80 // Can go up to 80/sec | ||
| throttler2Config.MinRate = 3 // Minimum 3/sec | ||
| throttler2Config.SampleInterval = 200 * time.Millisecond | ||
| throttler2Config.BackoffFactor = 0.5 // Reduce to 50% when constrained | ||
| throttler2Config.RecoveryFactor = 1.3 // Increase by 30% during recovery | ||
|
|
||
| // Use system memory but with lower threshold to demonstrate throttling | ||
| throttler2Config.MaxMemoryPercent = 50.0 // Lower threshold than T1 | ||
|
|
||
| throttler2, err := flow.NewAdaptiveThrottler(throttler2Config) | ||
| if err != nil { | ||
| panic(fmt.Sprintf("failed to create throttler2: %v", err)) | ||
| } | ||
|
|
||
| in := make(chan any) | ||
|
|
||
| source := ext.NewChanSource(in) | ||
| editMapFlow := flow.NewMap(editMessage, 1) | ||
| cpuWorkFlow := flow.NewMap(cpuIntensiveWork, 1) // Add CPU-intensive work | ||
| timestampFlow := flow.NewMap(addTimestamp, 1) | ||
| sink := ext.NewStdoutSink() | ||
|
|
||
| // Pipeline: Source -> Throttler1 (CPU) -> CPU Work -> Throttler2 (Memory) -> Edit -> Timestamp -> Sink | ||
| go func() { | ||
| source. | ||
| Via(throttler1). // First throttler monitors CPU | ||
| Via(cpuWorkFlow). // CPU-intensive processing | ||
| Via(throttler2). // Second throttler monitors memory | ||
| Via(editMapFlow). // Simple transformation | ||
| Via(timestampFlow). // Add timestamp | ||
| To(sink) | ||
| }() | ||
|
|
||
| // Enhanced stats logging showing throttling behavior | ||
| go func() { | ||
| ticker := time.NewTicker(500 * time.Millisecond) | ||
| defer ticker.Stop() | ||
|
|
||
| for range ticker.C { | ||
| stats1 := throttler1.GetResourceStats() | ||
| stats2 := throttler2.GetResourceStats() | ||
|
|
||
| // Show current rates and resource usage | ||
| fmt.Printf("[stats] T1-CPU: %.1f/s (CPU:%.1f%%), T2-Mem: %.1f/s (Mem:%.1f%%), Total: %d msgs\n", | ||
| throttler1.GetCurrentRate(), | ||
| stats1.CPUUsagePercent, | ||
| throttler2.GetCurrentRate(), | ||
| stats2.MemoryUsedPercent, | ||
| messagesProcessed.Load()) | ||
|
|
||
| // Show throttling status | ||
| throttleReason := "" | ||
| if stats1.CPUUsagePercent > throttler1Config.MaxCPUPercent { | ||
| throttleReason += "T1:CPU-high " | ||
| } | ||
| if stats2.MemoryUsedPercent > throttler2Config.MaxMemoryPercent { | ||
| throttleReason += "T2:Mem-high " | ||
| } | ||
| if throttleReason == "" { | ||
| throttleReason = "No throttling" | ||
| } | ||
| fmt.Printf("[throttle] %s\n", throttleReason) | ||
| } | ||
| }() | ||
|
|
||
| // Producer with bursty traffic to test throttling | ||
| go func() { | ||
| defer close(in) | ||
|
|
||
| for i := 1; i <= 100; i++ { | ||
| message := fmt.Sprintf("MESSAGE-%d", i) | ||
| in <- message | ||
| messagesProcessed.Add(1) | ||
|
|
||
| // Variable delay to create bursts (some fast, some slow) | ||
| var delay time.Duration | ||
| if i%20 == 0 { | ||
| delay = 100 * time.Millisecond // Burst pause every 20 messages | ||
| } else { | ||
| delay = time.Duration(5+rand.Intn(15)) * time.Millisecond //nolint:gosec // 5-20ms between messages | ||
| } | ||
| time.Sleep(delay) | ||
| } | ||
| }() | ||
|
|
||
| sink.AwaitCompletion() | ||
|
|
||
| throttler1.Close() | ||
| throttler2.Close() | ||
|
|
||
| fmt.Printf("Demo completed! Processed %d messages\n", messagesProcessed.Load()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.