github.com/m3db/m3@v1.5.0/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  }