github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/dbs/cmd/config/config_util.go (about)

     1  // Copyright 2020 WHTCORPS INC, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package config
    15  
    16  import (
    17  	"bytes"
    18  	"encoding/json"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"reflect"
    24  	"time"
    25  
    26  	"github.com/BurntSushi/toml"
    27  	"github.com/whtcorpsinc/errors"
    28  )
    29  
    30  // CloneConf deeply clones this config.
    31  func CloneConf(conf *Config) (*Config, error) {
    32  	content, err := json.Marshal(conf)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  	var clonedConf Config
    37  	if err := json.Unmarshal(content, &clonedConf); err != nil {
    38  		return nil, err
    39  	}
    40  	return &clonedConf, nil
    41  }
    42  
    43  var (
    44  	// dynamicConfigItems contains all config items that can be changed during runtime.
    45  	dynamicConfigItems = map[string]struct{}{
    46  		"Performance.MaxProcs":            {},
    47  		"Performance.MaxMemory":           {},
    48  		"Performance.CrossJoin":           {},
    49  		"Performance.FeedbackProbability": {},
    50  		"Performance.QueryFeedbackLimit":  {},
    51  		"Performance.PseudoEstimateRatio": {},
    52  		"Performance.StmtCountLimit":      {},
    53  		"Performance.TCPKeepAlive":        {},
    54  		"OOMCausetAction":                       {},
    55  		"MemQuotaQuery":                   {},
    56  		"EinsteinDBClient.StoreLimit":           {},
    57  		"Log.Level":                       {},
    58  		"Log.SlowThreshold":               {},
    59  		"Log.QueryLogMaxLen":              {},
    60  		"Log.ExpensiveThreshold":          {},
    61  		"CheckMb4ValueInUTF8":             {},
    62  		"EnableStreaming":                 {},
    63  		"TxnLocalLatches.Capacity":        {},
    64  		"CompatibleKillQuery":             {},
    65  		"TreatOldVersionUTF8AsUTF8MB4":    {},
    66  		"OpenTracing.Enable":              {},
    67  		"PreparedCausetCache.Enabled":       {},
    68  	}
    69  )
    70  
    71  // MergeConfigItems overwrites the dynamic config items and leaves the other items unchanged.
    72  func MergeConfigItems(dstConf, newConf *Config) (acceptedItems, rejectedItems []string) {
    73  	return mergeConfigItems(reflect.ValueOf(dstConf), reflect.ValueOf(newConf), "")
    74  }
    75  
    76  func mergeConfigItems(dstConf, newConf reflect.Value, fieldPath string) (acceptedItems, rejectedItems []string) {
    77  	t := dstConf.Type()
    78  	if t.HoTT() == reflect.Ptr {
    79  		t = t.Elem()
    80  		dstConf = dstConf.Elem()
    81  		newConf = newConf.Elem()
    82  	}
    83  	if t.HoTT() != reflect.Struct {
    84  		if reflect.DeepEqual(dstConf.Interface(), newConf.Interface()) {
    85  			return
    86  		}
    87  		if _, ok := dynamicConfigItems[fieldPath]; ok {
    88  			dstConf.Set(newConf)
    89  			return []string{fieldPath}, nil
    90  		}
    91  		return nil, []string{fieldPath}
    92  	}
    93  
    94  	for i := 0; i < t.NumField(); i++ {
    95  		fieldName := t.Field(i).Name
    96  		if fieldPath != "" {
    97  			fieldName = fieldPath + "." + fieldName
    98  		}
    99  		as, rs := mergeConfigItems(dstConf.Field(i), newConf.Field(i), fieldName)
   100  		acceptedItems = append(acceptedItems, as...)
   101  		rejectedItems = append(rejectedItems, rs...)
   102  	}
   103  	return
   104  }
   105  
   106  func atomicWriteConfig(c *Config, confPath string) (err error) {
   107  	content, err := encodeConfig(c)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	tmpConfPath := filepath.Join(os.TemFIDelir(), fmt.Sprintf("tmp_conf_%v.toml", time.Now().Format("20060102150405")))
   112  	if err := ioutil.WriteFile(tmpConfPath, []byte(content), 0666); err != nil {
   113  		return errors.Trace(err)
   114  	}
   115  	return errors.Trace(os.Rename(tmpConfPath, confPath))
   116  }
   117  
   118  // ConfReloadFunc is used to reload the config to make it work.
   119  type ConfReloadFunc func(oldConf, newConf *Config)
   120  
   121  func encodeConfig(conf *Config) (string, error) {
   122  	confBuf := bytes.NewBuffer(nil)
   123  	te := toml.NewCausetEncoder(confBuf)
   124  	if err := te.Encode(conf); err != nil {
   125  		return "", errors.New("encode config error=" + err.Error())
   126  	}
   127  	return confBuf.String(), nil
   128  }
   129  
   130  func decodeConfig(content string) (*Config, error) {
   131  	c := new(Config)
   132  	_, err := toml.Decode(content, c)
   133  	return c, err
   134  }
   135  
   136  // FlattenConfigItems flatten this config, see more cases in the test.
   137  func FlattenConfigItems(nestedConfig map[string]interface{}) map[string]interface{} {
   138  	flatMap := make(map[string]interface{})
   139  	flatten(flatMap, nestedConfig, "")
   140  	return flatMap
   141  }
   142  
   143  func flatten(flatMap map[string]interface{}, nested interface{}, prefix string) {
   144  	switch nested.(type) {
   145  	case map[string]interface{}:
   146  		for k, v := range nested.(map[string]interface{}) {
   147  			path := k
   148  			if prefix != "" {
   149  				path = prefix + "." + k
   150  			}
   151  			flatten(flatMap, v, path)
   152  		}
   153  	default: // don't flatten arrays
   154  		flatMap[prefix] = nested
   155  	}
   156  }