github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/cmd/services/m3aggregator/config/runtime.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package config 22 23 import ( 24 "math" 25 "time" 26 27 "github.com/m3db/m3/src/aggregator/aggregator" 28 "github.com/m3db/m3/src/aggregator/runtime" 29 "github.com/m3db/m3/src/cluster/client" 30 "github.com/m3db/m3/src/cluster/kv" 31 kvutil "github.com/m3db/m3/src/cluster/kv/util" 32 33 "go.uber.org/zap" 34 ) 35 36 // RuntimeOptionsConfiguration configures runtime options. 37 type RuntimeOptionsConfiguration struct { 38 KVConfig kv.OverrideConfiguration `yaml:"kvConfig"` 39 WriteValuesPerMetricLimitPerSecondKey string `yaml:"writeValuesPerMetricLimitPerSecondKey" validate:"nonzero"` 40 WriteValuesPerMetricLimitPerSecond int64 `yaml:"writeValuesPerMetricLimitPerSecond"` 41 WriteNewMetricLimitClusterPerSecondKey string `yaml:"writeNewMetricLimitClusterPerSecondKey" validate:"nonzero"` 42 WriteNewMetricLimitClusterPerSecond int64 `yaml:"writeNewMetricLimitClusterPerSecond"` 43 WriteNewMetricNoLimitWarmupDuration time.Duration `yaml:"writeNewMetricNoLimitWarmupDuration"` 44 } 45 46 // NewRuntimeOptionsManager creates a new runtime options manager. 47 func (c RuntimeOptionsConfiguration) NewRuntimeOptionsManager() runtime.OptionsManager { 48 initRuntimeOpts := runtime.NewOptions(). 49 SetWriteValuesPerMetricLimitPerSecond(c.WriteValuesPerMetricLimitPerSecond). 50 SetWriteNewMetricNoLimitWarmupDuration(c.WriteNewMetricNoLimitWarmupDuration) 51 return runtime.NewOptionsManager(initRuntimeOpts) 52 } 53 54 // WatchRuntimeOptionChanges watches runtime option updates. 55 func (c RuntimeOptionsConfiguration) WatchRuntimeOptionChanges( 56 client client.Client, 57 runtimeOptsManager runtime.OptionsManager, 58 placementManager aggregator.PlacementManager, 59 logger *zap.Logger, 60 ) { 61 kvOpts, err := c.KVConfig.NewOverrideOptions() 62 if err != nil { 63 logger.Error("unable to create kv config options", zap.Error(err)) 64 return 65 } 66 store, err := client.Store(kvOpts) 67 if err != nil { 68 logger.Error("unable to create kv store", zap.Error(err)) 69 return 70 } 71 72 var ( 73 valueLimitKey = c.WriteValuesPerMetricLimitPerSecondKey 74 defaultValueLimit = c.WriteValuesPerMetricLimitPerSecond 75 valueLimit int64 76 valueLimitCh <-chan struct{} 77 newMetricClusterLimitKey = c.WriteNewMetricLimitClusterPerSecondKey 78 defaultNewMetricClusterLimit = c.WriteNewMetricLimitClusterPerSecond 79 newMetricClusterLimit int64 80 newMetricPerShardLimit int64 81 newMetricLimitCh <-chan struct{} 82 ) 83 valueLimit, err = retrieveLimit(valueLimitKey, store, defaultValueLimit) 84 if err != nil { 85 logger.Error("unable to retrieve per-metric write value limit from kv", zap.Error(err)) 86 } 87 logger.Info("current write value limit per second", zap.Int64("limit", valueLimit)) 88 89 newMetricClusterLimit, err = retrieveLimit(newMetricClusterLimitKey, store, defaultNewMetricClusterLimit) 90 if err == nil { 91 newMetricPerShardLimit, err = clusterLimitToPerShardLimit(newMetricClusterLimit, placementManager) 92 } 93 if err != nil { 94 logger.Error("unable to determine per-shard write new metric limit", zap.Error(err)) 95 } 96 logger.Info("current write new metric limit per shard per second", 97 zap.Int64("limit", newMetricPerShardLimit)) 98 99 runtimeOpts := runtime.NewOptions(). 100 SetWriteNewMetricNoLimitWarmupDuration(c.WriteNewMetricNoLimitWarmupDuration). 101 SetWriteValuesPerMetricLimitPerSecond(valueLimit). 102 SetWriteNewMetricLimitPerShardPerSecond(newMetricPerShardLimit) 103 runtimeOptsManager.SetRuntimeOptions(runtimeOpts) 104 105 valueLimitWatch, err := store.Watch(valueLimitKey) 106 if err != nil { 107 logger.Error("unable to watch per-metric write value limit", zap.Error(err)) 108 } else { 109 valueLimitCh = valueLimitWatch.C() 110 } 111 newMetricLimitWatch, err := store.Watch(newMetricClusterLimitKey) 112 if err != nil { 113 logger.Error("unable to watch cluster-wide write new metric limit", zap.Error(err)) 114 } else { 115 newMetricLimitCh = newMetricLimitWatch.C() 116 } 117 // If watch creation failed for both, we return immediately. 118 if valueLimitCh == nil && newMetricLimitCh == nil { 119 return 120 } 121 122 utilOpts := kvutil.NewOptions().SetLogger(logger) 123 go func() { 124 for { 125 select { 126 case <-valueLimitCh: 127 valueLimitVal := valueLimitWatch.Get() 128 newValueLimit, err := kvutil.Int64FromValue(valueLimitVal, valueLimitKey, defaultValueLimit, utilOpts) 129 if err != nil { 130 logger.Error("unable to determine per-metric write value limit", zap.Error(err)) 131 continue 132 } 133 currValueLimit := runtimeOpts.WriteValuesPerMetricLimitPerSecond() 134 if newValueLimit == currValueLimit { 135 logger.Info("per-metric write value limit is unchanged, skipping", zap.Int64("limit", newValueLimit)) 136 continue 137 } 138 logger.Info("updating per-metric write value limit", 139 zap.Int64("current", currValueLimit), 140 zap.Int64("new", newValueLimit)) 141 runtimeOpts = runtimeOpts.SetWriteValuesPerMetricLimitPerSecond(newValueLimit) 142 runtimeOptsManager.SetRuntimeOptions(runtimeOpts) 143 case <-newMetricLimitCh: 144 newMetricLimitVal := newMetricLimitWatch.Get() 145 var newNewMetricPerShardLimit int64 146 newNewMetricClusterLimit, err := kvutil.Int64FromValue(newMetricLimitVal, newMetricClusterLimitKey, defaultNewMetricClusterLimit, utilOpts) 147 if err == nil { 148 newNewMetricPerShardLimit, err = clusterLimitToPerShardLimit(newNewMetricClusterLimit, placementManager) 149 } 150 if err != nil { 151 logger.Error("unable to determine per-shard new metric limit", zap.Error(err)) 152 continue 153 } 154 currNewMetricPerShardLimit := runtimeOpts.WriteNewMetricLimitPerShardPerSecond() 155 if newNewMetricPerShardLimit == currNewMetricPerShardLimit { 156 logger.Info("per-shard write new metric limit is unchanged, skipping", 157 zap.Int64("limit", newNewMetricPerShardLimit)) 158 continue 159 } 160 logger.Info("updating per-shard write new metric limit", 161 zap.Int64("current", currNewMetricPerShardLimit), 162 zap.Int64("new", newNewMetricPerShardLimit)) 163 runtimeOpts = runtimeOpts.SetWriteNewMetricLimitPerShardPerSecond(newNewMetricPerShardLimit) 164 runtimeOptsManager.SetRuntimeOptions(runtimeOpts) 165 } 166 } 167 }() 168 } 169 170 func clusterLimitToPerShardLimit( 171 clusterLimit int64, 172 placementManager aggregator.PlacementManager, 173 ) (int64, error) { 174 if clusterLimit < 1 { 175 return 0, nil 176 } 177 placement, err := placementManager.Placement() 178 if err != nil { 179 return clusterLimit, err 180 } 181 numShardsPerReplica := placement.NumShards() 182 numShards := numShardsPerReplica * placement.ReplicaFactor() 183 if numShards < 1 { 184 return clusterLimit, nil 185 } 186 perShardLimit := int64(math.Ceil(float64(clusterLimit) / float64(numShards))) 187 return perShardLimit, nil 188 } 189 190 func retrieveLimit(key string, store kv.Store, defaultLimit int64) (int64, error) { 191 limit := defaultLimit 192 value, err := store.Get(key) 193 if err == nil { 194 limit, err = kvutil.Int64FromValue(value, key, defaultLimit, nil) 195 } 196 return limit, err 197 }