@@ -73,8 +73,12 @@ func LoadAppConfig(path string) (*AppConfig, error) {
7373 return nil , fmt .Errorf ("invalid YAML format for config: %w" , err )
7474 }
7575
76- applyEnvOverrides (cfg )
77- normalizePercentages (cfg )
76+ // Apply overrides and capture which percentage fields were set
77+ overriddenStats := applyEnvOverrides (cfg )
78+
79+ // Normalize based on what was overridden
80+ normalizePercentages (cfg , overriddenStats )
81+
7882 applyDefaults (cfg )
7983
8084 return cfg , nil
@@ -110,7 +114,12 @@ func applyDefaults(cfg *AppConfig) {
110114 }
111115}
112116
113- func applyEnvOverrides (cfg * AppConfig ) {
117+ // applyEnvOverrides updates the config from ENV vars and returns a map
118+ // of percentage fields that were explicitly set.
119+ func applyEnvOverrides (cfg * AppConfig ) map [string ]bool {
120+ // Track which percentages are overridden
121+ overrides := make (map [string ]bool )
122+
114123 // 1. Credentials
115124 if v := os .Getenv ("PLGM_USERNAME" ); v != "" {
116125 cfg .ConnectionParams .Username = v
@@ -190,34 +199,48 @@ func applyEnvOverrides(cfg *AppConfig) {
190199 if envDuration := os .Getenv ("PLGM_DURATION" ); envDuration != "" {
191200 cfg .Duration = envDuration
192201 }
202+
203+ // Percentages - we track these to prioritize them in normalization
193204 if p := os .Getenv ("PLGM_FIND_PERCENT" ); p != "" {
194205 if n , err := strconv .Atoi (p ); err == nil && n >= 0 {
195206 cfg .FindPercent = n
207+ overrides ["FindPercent" ] = true
196208 }
197209 }
198210 if p := os .Getenv ("PLGM_UPDATE_PERCENT" ); p != "" {
199211 if n , err := strconv .Atoi (p ); err == nil && n >= 0 {
200212 cfg .UpdatePercent = n
213+ overrides ["UpdatePercent" ] = true
201214 }
202215 }
203216 if p := os .Getenv ("PLGM_DELETE_PERCENT" ); p != "" {
204217 if n , err := strconv .Atoi (p ); err == nil && n >= 0 {
205218 cfg .DeletePercent = n
219+ overrides ["DeletePercent" ] = true
206220 }
207221 }
208222 if p := os .Getenv ("PLGM_INSERT_PERCENT" ); p != "" {
209223 if n , err := strconv .Atoi (p ); err == nil && n >= 0 {
210224 cfg .InsertPercent = n
225+ overrides ["InsertPercent" ] = true
211226 }
212227 }
213228 if p := os .Getenv ("PLGM_AGGREGATE_PERCENT" ); p != "" {
214229 if n , err := strconv .Atoi (p ); err == nil && n >= 0 {
215230 cfg .AggregatePercent = n
231+ overrides ["AggregatePercent" ] = true
216232 }
217233 }
218234 if p := os .Getenv ("PLGM_TRANSACTION_PERCENT" ); p != "" {
219235 if n , err := strconv .Atoi (p ); err == nil && n >= 0 {
220236 cfg .TransactionPercent = n
237+ overrides ["TransactionPercent" ] = true
238+ }
239+ }
240+ if p := os .Getenv ("PLGM_BULK_INSERT_PERCENT" ); p != "" {
241+ if n , err := strconv .Atoi (p ); err == nil && n >= 0 {
242+ cfg .BulkInsertPercent = n
243+ overrides ["BulkInsertPercent" ] = true
221244 }
222245 }
223246
@@ -256,42 +279,165 @@ func applyEnvOverrides(cfg *AppConfig) {
256279 cfg .StatusRefreshRateSec = n
257280 }
258281 }
259- if p := os .Getenv ("PLGM_BULK_INSERT_PERCENT" ); p != "" {
260- if n , err := strconv .Atoi (p ); err == nil && n >= 0 {
261- cfg .BulkInsertPercent = n
262- }
263- }
264282 if v := os .Getenv ("PLGM_INSERT_BATCH_SIZE" ); v != "" {
265283 if n , err := strconv .Atoi (v ); err == nil && n > 0 {
266284 cfg .InsertBatchSize = n
267285 }
268286 }
287+
288+ return overrides
269289}
270290
271- func normalizePercentages (cfg * AppConfig ) {
291+ func normalizePercentages (cfg * AppConfig , pinned map [string ]bool ) {
292+ // 1. Enforce Transaction flag constraint immediately
272293 if ! cfg .UseTransactions {
273294 cfg .TransactionPercent = 0
295+ delete (pinned , "TransactionPercent" )
274296 }
275297
276- total := cfg .FindPercent + cfg .UpdatePercent + cfg .DeletePercent + cfg .InsertPercent + cfg .AggregatePercent + cfg .TransactionPercent + cfg .BulkInsertPercent
277- if total <= 0 {
278- cfg .FindPercent = 100
279- return
298+ // 2. Calculate the total of "pinned" (Environment overridden) stats
299+ pinnedTotal := 0
300+ if pinned ["FindPercent" ] {
301+ pinnedTotal += cfg .FindPercent
302+ }
303+ if pinned ["UpdatePercent" ] {
304+ pinnedTotal += cfg .UpdatePercent
305+ }
306+ if pinned ["DeletePercent" ] {
307+ pinnedTotal += cfg .DeletePercent
308+ }
309+ if pinned ["InsertPercent" ] {
310+ pinnedTotal += cfg .InsertPercent
311+ }
312+ if pinned ["AggregatePercent" ] {
313+ pinnedTotal += cfg .AggregatePercent
314+ }
315+ if pinned ["TransactionPercent" ] {
316+ pinnedTotal += cfg .TransactionPercent
317+ }
318+ if pinned ["BulkInsertPercent" ] {
319+ pinnedTotal += cfg .BulkInsertPercent
280320 }
281321
282- if total != 100 {
283- factor := 100.0 / float64 (total )
284- cfg .FindPercent = int (float64 (cfg .FindPercent ) * factor )
285- cfg .UpdatePercent = int (float64 (cfg .UpdatePercent ) * factor )
286- cfg .DeletePercent = int (float64 (cfg .DeletePercent ) * factor )
287- cfg .InsertPercent = int (float64 (cfg .InsertPercent ) * factor )
288- cfg .AggregatePercent = int (float64 (cfg .AggregatePercent ) * factor )
289- cfg .TransactionPercent = int (float64 (cfg .TransactionPercent ) * factor )
290- cfg .BulkInsertPercent = int (float64 (cfg .BulkInsertPercent ) * factor )
322+ // 3. Logic:
323+ // If Pinned Total >= 100: Zero out non-pinned, scale pinned if > 100.
324+ // If Pinned Total < 100: Distribute remainder among unpinned.
291325
292- finalTotal := cfg .FindPercent + cfg .UpdatePercent + cfg .DeletePercent + cfg .InsertPercent + cfg .AggregatePercent + cfg .TransactionPercent + cfg .BulkInsertPercent
293- if finalTotal != 100 {
294- cfg .FindPercent += (100 - finalTotal )
326+ if pinnedTotal >= 100 {
327+ // Zero out all non-pinned fields
328+ if ! pinned ["FindPercent" ] {
329+ cfg .FindPercent = 0
330+ }
331+ if ! pinned ["UpdatePercent" ] {
332+ cfg .UpdatePercent = 0
295333 }
334+ if ! pinned ["DeletePercent" ] {
335+ cfg .DeletePercent = 0
336+ }
337+ if ! pinned ["InsertPercent" ] {
338+ cfg .InsertPercent = 0
339+ }
340+ if ! pinned ["AggregatePercent" ] {
341+ cfg .AggregatePercent = 0
342+ }
343+ if ! pinned ["TransactionPercent" ] {
344+ cfg .TransactionPercent = 0
345+ }
346+ if ! pinned ["BulkInsertPercent" ] {
347+ cfg .BulkInsertPercent = 0
348+ }
349+
350+ // Normalize if pinned values sum > 100
351+ if pinnedTotal > 100 {
352+ factor := 100.0 / float64 (pinnedTotal )
353+ if pinned ["FindPercent" ] {
354+ cfg .FindPercent = int (float64 (cfg .FindPercent ) * factor )
355+ }
356+ if pinned ["UpdatePercent" ] {
357+ cfg .UpdatePercent = int (float64 (cfg .UpdatePercent ) * factor )
358+ }
359+ if pinned ["DeletePercent" ] {
360+ cfg .DeletePercent = int (float64 (cfg .DeletePercent ) * factor )
361+ }
362+ if pinned ["InsertPercent" ] {
363+ cfg .InsertPercent = int (float64 (cfg .InsertPercent ) * factor )
364+ }
365+ if pinned ["AggregatePercent" ] {
366+ cfg .AggregatePercent = int (float64 (cfg .AggregatePercent ) * factor )
367+ }
368+ if pinned ["TransactionPercent" ] {
369+ cfg .TransactionPercent = int (float64 (cfg .TransactionPercent ) * factor )
370+ }
371+ if pinned ["BulkInsertPercent" ] {
372+ cfg .BulkInsertPercent = int (float64 (cfg .BulkInsertPercent ) * factor )
373+ }
374+ }
375+
376+ } else {
377+ // pinnedTotal < 100. We have space left.
378+ remaining := 100 - pinnedTotal
379+
380+ // Sum of unpinned (default) values
381+ unpinnedTotal := 0
382+ if ! pinned ["FindPercent" ] {
383+ unpinnedTotal += cfg .FindPercent
384+ }
385+ if ! pinned ["UpdatePercent" ] {
386+ unpinnedTotal += cfg .UpdatePercent
387+ }
388+ if ! pinned ["DeletePercent" ] {
389+ unpinnedTotal += cfg .DeletePercent
390+ }
391+ if ! pinned ["InsertPercent" ] {
392+ unpinnedTotal += cfg .InsertPercent
393+ }
394+ if ! pinned ["AggregatePercent" ] {
395+ unpinnedTotal += cfg .AggregatePercent
396+ }
397+ if ! pinned ["TransactionPercent" ] {
398+ unpinnedTotal += cfg .TransactionPercent
399+ }
400+ if ! pinned ["BulkInsertPercent" ] {
401+ unpinnedTotal += cfg .BulkInsertPercent
402+ }
403+
404+ // Scale unpinned values to fill the remaining space
405+ if unpinnedTotal > 0 {
406+ factor := float64 (remaining ) / float64 (unpinnedTotal )
407+
408+ if ! pinned ["FindPercent" ] {
409+ cfg .FindPercent = int (float64 (cfg .FindPercent ) * factor )
410+ }
411+ if ! pinned ["UpdatePercent" ] {
412+ cfg .UpdatePercent = int (float64 (cfg .UpdatePercent ) * factor )
413+ }
414+ if ! pinned ["DeletePercent" ] {
415+ cfg .DeletePercent = int (float64 (cfg .DeletePercent ) * factor )
416+ }
417+ if ! pinned ["InsertPercent" ] {
418+ cfg .InsertPercent = int (float64 (cfg .InsertPercent ) * factor )
419+ }
420+ if ! pinned ["AggregatePercent" ] {
421+ cfg .AggregatePercent = int (float64 (cfg .AggregatePercent ) * factor )
422+ }
423+ if ! pinned ["TransactionPercent" ] {
424+ cfg .TransactionPercent = int (float64 (cfg .TransactionPercent ) * factor )
425+ }
426+ if ! pinned ["BulkInsertPercent" ] {
427+ cfg .BulkInsertPercent = int (float64 (cfg .BulkInsertPercent ) * factor )
428+ }
429+ } else {
430+ // Edge case: Pinned values sum to < 100 (e.g. 80%), but all unpinned defaults are 0.
431+ // We cannot distribute the remaining 20% proportionally among 0s.
432+ // Strategy: Assign the remainder to FindPercent (Selects) to ensure the workload sums to 100%.
433+ cfg .FindPercent += remaining
434+ }
435+ }
436+
437+ // 4. Final check: Ensure total is exactly 100 (fixing integer rounding errors)
438+ finalTotal := cfg .FindPercent + cfg .UpdatePercent + cfg .DeletePercent + cfg .InsertPercent + cfg .AggregatePercent + cfg .TransactionPercent + cfg .BulkInsertPercent
439+ if finalTotal != 100 {
440+ // Add/Subtract difference to FindPercent (simplest safety net)
441+ cfg .FindPercent += (100 - finalTotal )
296442 }
297443}
0 commit comments