1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
//! System defaults and configuration constants for Tale
//!
//! This module provides all configuration constants, system defaults, and
//! preset configurations for the Tale log processing tool. It consolidates
//! what were previously separate constants.rs and production_defaults.rs
//! modules.
use std::time::Duration;
/// Basic I/O and processing constants
pub mod io {
use super::Duration;
/// How long we wait before flushing data to stdout when tailing.
pub const TAIL_FLUSH_INTERVAL: Duration = Duration::from_millis(250);
/// Flush stdout when we've written at least this many lines.
pub const FLUSH_LINE_COUNT: u16 = 40;
/// Default capacity for line strings.
pub const LINE_CAPACITY: usize = 512;
/// Buffer size for reading from stdin/files.
pub const READ_BUFFER_SIZE: usize = 8192;
/// Default capacity for output byte buffers.
pub const OUTPUT_BUFFER_CAPACITY: usize = 1024;
/// The initial chunk size to use for adaptive chunked readers.
pub const INITIAL_CHUNK_SIZE: usize = 32 * 1024; // 32K bytes
}
/// File processing thresholds and decision constants
pub mod processing {
/// Large offset threshold - offsets above this suggest large file
/// operations
pub const LARGE_OFFSET_THRESHOLD: u64 = 10_000;
/// File size requiring chunked processing when combined with large offset
pub const CHUNKED_WITH_OFFSET_FILE_SIZE: u64 = 100 * 1024 * 1024; // 100MB
/// File size that always requires chunked processing regardless of offset
pub const ALWAYS_CHUNKED_FILE_SIZE: u64 = 1024 * 1024 * 1024; // 1GB
/// Number of chunks between strategy adaptation checks
pub const ADAPTATION_INTERVAL: usize = 20;
/// The traditional unix block size in bytes.
pub const BLOCK_SIZE: u64 = 512;
}
/// Memory management constants
pub mod memory {
/// Memory limit for line buffering in negative line offset mode.
pub const MEMORY_LIMIT_BYTES: usize = 10 * 1024 * 1024; // 10MB
}
/// System defaults for chunked file processing and configuration management
pub struct SystemDefaults;
impl SystemDefaults {
/// Default memory budget as percentage of system memory
///
/// Benchmark results show 10% provides good balance:
/// - Sufficient for most workloads
/// - Leaves headroom for system operations
/// - Scales appropriately with system size
pub const DEFAULT_MEMORY_PERCENTAGE: f64 = 10.0;
/// Minimum memory budget (absolute floor)
///
/// Below this, performance degrades significantly
pub const MIN_MEMORY_BUDGET: usize = 5 * 1024 * 1024; // 5MB
/// Maximum memory budget (safety ceiling)
///
/// Even on large systems, cap at reasonable limit
pub const MAX_MEMORY_BUDGET: usize = 500 * 1024 * 1024; // 500MB
/// Default chunk size for initial processing
///
/// Based on benchmarks:
/// - 32KB provides good initial performance
/// - Small enough for quick adaptation
/// - Large enough for efficient I/O
pub const DEFAULT_CHUNK_SIZE: usize = 32 * 1024; // 32KB (block-aligned)
/// Minimum chunk size (emergency floor)
///
/// Below this, overhead dominates performance
pub const MIN_CHUNK_SIZE: usize = 4 * 1024; // 4KB (one block)
/// Maximum chunk size (performance ceiling)
///
/// Larger chunks show diminishing returns
pub const MAX_CHUNK_SIZE: usize = 4 * 1024 * 1024; // 4MB
/// Optimal chunk size for different file sizes
pub fn optimal_chunk_for_file(file_size: u64) -> usize {
// Use 4KB block size (standard for most modern filesystems)
const BLOCK_SIZE: usize = 4096;
let base_size = match file_size {
// Tiny files (< 100KB): Minimal chunking
0..=102_400 => Self::MIN_CHUNK_SIZE,
// Small files (100KB - 1MB): Small chunks
102_401..=1_048_576 => 8 * 1024,
// Medium files (1MB - 10MB): Standard chunks
1_048_577..=10_485_760 => Self::DEFAULT_CHUNK_SIZE,
// Large files (10MB - 100MB): Larger chunks
10_485_761..=104_857_600 => 128 * 1024,
// Very large files (100MB - 1GB): Big chunks
104_857_601..=1_073_741_824 => 512 * 1024,
// Huge files (> 1GB): Maximum chunks
_ => 1024 * 1024,
};
// Ensure block alignment
base_size.div_ceil(BLOCK_SIZE) * BLOCK_SIZE
}
/// Get default strategy based on context
pub fn default_strategy() -> &'static str {
// Conservative strategy proved best in benchmarks:
// - Only 20% slower than adaptive in best case
// - Much more predictable memory usage
// - Better handling of memory pressure
"conservative"
}
/// Should use chunked processing by default?
pub fn should_chunk_by_default(file_size: u64) -> bool {
// Use chunked processing for files > 1MB
// Benchmarks show minimal overhead, better memory control
file_size > 1_048_576
}
/// Default batch window for multi-file processing (ms)
pub const DEFAULT_BATCH_WINDOW_MS: u64 = 250;
/// Default line buffer capacity
pub const DEFAULT_LINE_CAPACITY: usize = 512;
/// Default output buffer capacity
pub const DEFAULT_OUTPUT_BUFFER_CAPACITY: usize = 4096;
/// Adaptation interval (chunks between adaptation checks)
pub const ADAPTATION_INTERVAL: usize = 20;
/// Memory pressure thresholds (validated through benchmarking)
pub const MEMORY_PRESSURE_LOW_THRESHOLD: f64 = 0.60; // < 60%: Normal
pub const MEMORY_PRESSURE_MODERATE_THRESHOLD: f64 = 0.85; // 60-85%: Moderate
pub const MEMORY_PRESSURE_HIGH_THRESHOLD: f64 = 0.95; // 85-95%: High
// > 95%: Critical
/// Chunk size reduction factors per pressure level
pub const PRESSURE_FACTOR_LOW: f64 = 1.0; // No reduction
pub const PRESSURE_FACTOR_MODERATE: f64 = 0.8; // 20% reduction
pub const PRESSURE_FACTOR_HIGH: f64 = 0.5; // 50% reduction
pub const PRESSURE_FACTOR_CRITICAL: f64 = 0.25; // 75% reduction
/// Emergency allocation factor
pub const EMERGENCY_ALLOCATION_FACTOR: f64 = 0.25; // 25% of requested size
}
/// System configuration presets
pub enum ConfigPreset {
/// Optimized for small files and low memory systems
LowMemory,
/// Balanced performance and memory usage
Balanced,
/// Optimized for maximum performance
HighPerformance,
/// Optimized for memory-constrained environments
Conservative,
}
/// Configuration settings for each preset
pub struct PresetSettings {
pub memory_percentage: f64,
pub max_memory_mb: usize,
pub default_chunk_kb: usize,
pub max_chunk_kb: usize,
pub strategy: &'static str,
pub force_chunked: bool,
pub adaptation_interval: usize,
}
impl ConfigPreset {
/// Get settings for this preset
pub fn settings(&self) -> PresetSettings {
match self {
ConfigPreset::LowMemory => PresetSettings {
memory_percentage: 5.0,
max_memory_mb: 50,
default_chunk_kb: 16,
max_chunk_kb: 256,
strategy: "conservative",
force_chunked: true,
adaptation_interval: 10,
},
ConfigPreset::Balanced => PresetSettings {
memory_percentage: 10.0,
max_memory_mb: 200,
default_chunk_kb: 32,
max_chunk_kb: 2048,
strategy: "conservative",
force_chunked: false,
adaptation_interval: 20,
},
ConfigPreset::HighPerformance => PresetSettings {
memory_percentage: 20.0,
max_memory_mb: 500,
default_chunk_kb: 128,
max_chunk_kb: 4096,
strategy: "adaptive",
force_chunked: false,
adaptation_interval: 30,
},
ConfigPreset::Conservative => PresetSettings {
memory_percentage: 5.0,
max_memory_mb: 100,
default_chunk_kb: 16,
max_chunk_kb: 512,
strategy: "static",
force_chunked: true,
adaptation_interval: 10,
},
}
}
/// Detect best preset based on system resources
pub fn auto_detect() -> Self {
if let Some(memory_stats) = memory_stats::memory_stats() {
let total_memory_mb = memory_stats.physical_mem / (1024 * 1024);
match total_memory_mb {
// Very low memory systems (< 2GB)
0..=2048 => ConfigPreset::LowMemory,
// Low-mid memory systems (2-8GB)
2049..=8192 => ConfigPreset::Conservative,
// Standard systems (8-16GB)
8193..=16384 => ConfigPreset::Balanced,
// High memory systems (> 16GB)
_ => ConfigPreset::HighPerformance,
}
} else {
// Fallback to balanced if we can't detect
ConfigPreset::Balanced
}
}
}
/// Get production configuration based on environment or auto-detection
pub fn get_system_config() -> PresetSettings {
// Check for environment override
if let Ok(preset_name) = std::env::var("TALE_PRESET") {
let preset = match preset_name.to_lowercase().as_str() {
"low" | "lowmemory" | "low_memory" => ConfigPreset::LowMemory,
"balanced" | "balance" => ConfigPreset::Balanced,
"high" | "highperformance" | "high_performance" => ConfigPreset::HighPerformance,
"conservative" | "conserve" => ConfigPreset::Conservative,
_ => {
eprintln!("Warning: Unknown TALE_PRESET '{}', using auto-detection", preset_name);
ConfigPreset::auto_detect()
}
};
return preset.settings();
}
// Auto-detect based on system
ConfigPreset::auto_detect().settings()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chunk_sizes_are_block_aligned() {
const BLOCK_SIZE: usize = 4096;
let tiny = SystemDefaults::optimal_chunk_for_file(50_000);
assert_eq!(tiny % BLOCK_SIZE, 0);
assert_eq!(tiny, SystemDefaults::MIN_CHUNK_SIZE);
let small = SystemDefaults::optimal_chunk_for_file(500_000);
assert_eq!(small % BLOCK_SIZE, 0);
assert_eq!(small, 8 * 1024);
let medium = SystemDefaults::optimal_chunk_for_file(5_000_000);
assert_eq!(medium % BLOCK_SIZE, 0);
assert_eq!(medium, SystemDefaults::DEFAULT_CHUNK_SIZE);
let large = SystemDefaults::optimal_chunk_for_file(50_000_000);
assert_eq!(large % BLOCK_SIZE, 0);
assert_eq!(large, 128 * 1024);
let huge = SystemDefaults::optimal_chunk_for_file(2_000_000_000);
assert_eq!(huge % BLOCK_SIZE, 0);
assert_eq!(huge, 1024 * 1024);
}
#[test]
fn chunking_decisions_are_good() {
// Should not chunk tiny files
assert!(!SystemDefaults::should_chunk_by_default(100_000));
// Should chunk files > 1MB
assert!(SystemDefaults::should_chunk_by_default(2_000_000));
}
#[test]
fn presets_are_as_expected() {
let low_mem = ConfigPreset::LowMemory.settings();
assert_eq!(low_mem.memory_percentage, 5.0);
assert_eq!(low_mem.strategy, "conservative");
assert!(low_mem.force_chunked);
let balanced = ConfigPreset::Balanced.settings();
assert_eq!(balanced.memory_percentage, 10.0);
assert_eq!(balanced.strategy, "conservative");
assert!(!balanced.force_chunked);
let high_perf = ConfigPreset::HighPerformance.settings();
assert_eq!(high_perf.memory_percentage, 20.0);
assert_eq!(high_perf.strategy, "adaptive");
assert!(!high_perf.force_chunked);
}
#[test]
fn we_have_processing_constants() {
use super::processing::*;
// Verify constants are reasonable
assert_eq!(LARGE_OFFSET_THRESHOLD, 10_000);
assert_eq!(CHUNKED_WITH_OFFSET_FILE_SIZE, 100 * 1024 * 1024);
assert_eq!(ALWAYS_CHUNKED_FILE_SIZE, 1024 * 1024 * 1024);
assert_eq!(ADAPTATION_INTERVAL, 20);
}
#[test]
fn we_have_io_constants() {
use super::io::*;
// Verify reasonable defaults
assert_eq!(TAIL_FLUSH_INTERVAL.as_millis(), 250);
assert_eq!(FLUSH_LINE_COUNT, 40);
assert_eq!(READ_BUFFER_SIZE, 8192);
assert_eq!(INITIAL_CHUNK_SIZE, 32 * 1024);
}
}