github.com/rohankumardubey/proxyfs@v0.0.0-20210108201508-653efa9ab00e/statslogger/config.go (about)

     1  package statslogger
     2  
     3  import (
     4  	"runtime"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/swiftstack/ProxyFS/bucketstats"
     9  	"github.com/swiftstack/ProxyFS/conf"
    10  	"github.com/swiftstack/ProxyFS/logger"
    11  	"github.com/swiftstack/ProxyFS/stats"
    12  	"github.com/swiftstack/ProxyFS/swiftclient"
    13  	"github.com/swiftstack/ProxyFS/transitions"
    14  )
    15  
    16  type globalsStruct struct {
    17  	collectChan    <-chan time.Time // time to collect swiftclient stats
    18  	logChan        <-chan time.Time // time to log statistics
    19  	stopChan       chan bool        // time to shutdown and go home
    20  	doneChan       chan bool        // shutdown complete
    21  	statsLogPeriod time.Duration    // time between statistics logging
    22  	verbose        bool             // verbosity of logging
    23  	collectTicker  *time.Ticker     // ticker for collectChan (if any)
    24  	logTicker      *time.Ticker     // ticker for logChan (if any)
    25  }
    26  
    27  var globals globalsStruct
    28  
    29  func init() {
    30  	transitions.Register("statslogger", &globals)
    31  }
    32  
    33  func parseConfMap(confMap conf.ConfMap) (err error) {
    34  	globals.statsLogPeriod, err = confMap.FetchOptionValueDuration("StatsLogger", "Period")
    35  	if err != nil {
    36  		logger.Warnf("config variable 'StatsLogger.Period' defaulting to '10m': %v", err)
    37  		globals.statsLogPeriod = time.Duration(10 * time.Minute)
    38  	}
    39  
    40  	// statsLogPeriod must be >= 1 sec, except 0 means disabled
    41  	if globals.statsLogPeriod < time.Second && globals.statsLogPeriod != 0 {
    42  		logger.Warnf("config variable 'StatsLogger.Period' value is non-zero and less then 1 min; defaulting to '10m'")
    43  		globals.statsLogPeriod = time.Duration(10 * time.Minute)
    44  	}
    45  
    46  	globals.verbose, err = confMap.FetchOptionValueBool("StatsLogger", "Verbose")
    47  	if err != nil {
    48  		logger.Warnf("config variable 'StatsLogger.Verbose' defaulting to 'true': %v", err)
    49  		globals.verbose = true
    50  	}
    51  
    52  	err = nil
    53  	return
    54  }
    55  
    56  // Up initializes the package and must successfully return before any API
    57  // functions are invoked
    58  func (dummy *globalsStruct) Up(confMap conf.ConfMap) (err error) {
    59  	err = parseConfMap(confMap)
    60  	if err != nil {
    61  		// parseConfMap() has logged an error
    62  		return
    63  	}
    64  
    65  	if globals.statsLogPeriod == 0 {
    66  		return
    67  	}
    68  
    69  	// collect info about free connections from SwiftClient once per second
    70  	globals.collectTicker = time.NewTicker(1 * time.Second)
    71  	globals.collectChan = globals.collectTicker.C
    72  
    73  	// record statistics in the log periodically
    74  	globals.logTicker = time.NewTicker(globals.statsLogPeriod)
    75  	globals.logChan = globals.logTicker.C
    76  
    77  	globals.stopChan = make(chan bool)
    78  	globals.doneChan = make(chan bool)
    79  
    80  	go statsLogger()
    81  	return
    82  }
    83  
    84  func (dummy *globalsStruct) VolumeGroupCreated(confMap conf.ConfMap, volumeGroupName string, activePeer string, virtualIPAddr string) (err error) {
    85  	return nil
    86  }
    87  func (dummy *globalsStruct) VolumeGroupMoved(confMap conf.ConfMap, volumeGroupName string, activePeer string, virtualIPAddr string) (err error) {
    88  	return nil
    89  }
    90  func (dummy *globalsStruct) VolumeGroupDestroyed(confMap conf.ConfMap, volumeGroupName string) (err error) {
    91  	return nil
    92  }
    93  func (dummy *globalsStruct) VolumeCreated(confMap conf.ConfMap, volumeName string, volumeGroupName string) (err error) {
    94  	return nil
    95  }
    96  func (dummy *globalsStruct) VolumeMoved(confMap conf.ConfMap, volumeName string, volumeGroupName string) (err error) {
    97  	return nil
    98  }
    99  func (dummy *globalsStruct) VolumeDestroyed(confMap conf.ConfMap, volumeName string) (err error) {
   100  	return nil
   101  }
   102  func (dummy *globalsStruct) ServeVolume(confMap conf.ConfMap, volumeName string) (err error) {
   103  	return nil
   104  }
   105  func (dummy *globalsStruct) UnserveVolume(confMap conf.ConfMap, volumeName string) (err error) {
   106  	return nil
   107  }
   108  func (dummy *globalsStruct) VolumeToBeUnserved(confMap conf.ConfMap, volumeName string) (err error) {
   109  	return nil
   110  }
   111  func (dummy *globalsStruct) SignaledStart(confMap conf.ConfMap) (err error) {
   112  	return nil
   113  }
   114  
   115  func (dummy *globalsStruct) SignaledFinish(confMap conf.ConfMap) (err error) {
   116  	// read the new confmap; if the log period has changed or there was an
   117  	// error shutdown the old logger prior to starting a new one
   118  	oldLogPeriod := globals.statsLogPeriod
   119  	err = parseConfMap(confMap)
   120  	if err != nil {
   121  		logger.ErrorWithError(err, "cannot parse confMap")
   122  		if oldLogPeriod != 0 {
   123  			globals.logTicker.Stop()
   124  			globals.logTicker = nil
   125  
   126  			globals.stopChan <- true
   127  			_ = <-globals.doneChan
   128  		}
   129  		return
   130  	}
   131  
   132  	// if no change required, just return
   133  	if globals.statsLogPeriod == oldLogPeriod {
   134  		return
   135  	}
   136  
   137  	logger.Infof("statslogger log period changing from %d sec to %d sec",
   138  		oldLogPeriod/time.Second, globals.statsLogPeriod/time.Second)
   139  	// shutdown the old logger (if any) and start a new one (if any)
   140  	if oldLogPeriod != 0 {
   141  		globals.logTicker.Stop()
   142  		globals.logTicker = nil
   143  
   144  		globals.stopChan <- true
   145  		_ = <-globals.doneChan
   146  	}
   147  
   148  	err = dummy.Up(confMap)
   149  	return
   150  }
   151  
   152  func (dummy *globalsStruct) Down(confMap conf.ConfMap) (err error) {
   153  	// shutdown the stats logger (if any)
   154  	logger.Infof("statslogger.Down() called")
   155  	if globals.statsLogPeriod != 0 {
   156  		globals.logTicker.Stop()
   157  		globals.logTicker = nil
   158  
   159  		globals.stopChan <- true
   160  		_ = <-globals.doneChan
   161  	}
   162  
   163  	// err is already nil
   164  	return
   165  }
   166  
   167  // the statsLogger collects the free connection statistics every collectChan tick
   168  // and then logs a batch of statistics, including free connection statistics,
   169  // every logChan tick ("statslogger.period" in the conf file.
   170  //
   171  func statsLogger() {
   172  	var (
   173  		chunkedConnectionStats    SimpleStats
   174  		nonChunkedConnectionStats SimpleStats
   175  		oldStatsMap               map[string]uint64
   176  		newStatsMap               map[string]uint64
   177  		oldMemStats               runtime.MemStats
   178  		newMemStats               runtime.MemStats
   179  	)
   180  
   181  	chunkedConnectionStats.Clear()
   182  	nonChunkedConnectionStats.Clear()
   183  	chunkedConnectionStats.Sample(swiftclient.ChunkedConnectionFreeCnt())
   184  	nonChunkedConnectionStats.Sample(swiftclient.NonChunkedConnectionFreeCnt())
   185  
   186  	// memstats "stops the world"
   187  	oldStatsMap = stats.Dump()
   188  	runtime.ReadMemStats(&oldMemStats)
   189  
   190  	// print an initial round of absolute stats
   191  	logStats("total", &chunkedConnectionStats, &nonChunkedConnectionStats, &oldMemStats, oldStatsMap)
   192  
   193  mainloop:
   194  	for stopRequest := false; !stopRequest; {
   195  		select {
   196  		case <-globals.stopChan:
   197  			// print final stats and then exit
   198  			stopRequest = true
   199  
   200  		case <-globals.collectChan:
   201  			chunkedConnectionStats.Sample(swiftclient.ChunkedConnectionFreeCnt())
   202  			nonChunkedConnectionStats.Sample(swiftclient.NonChunkedConnectionFreeCnt())
   203  			continue mainloop
   204  
   205  		case <-globals.logChan:
   206  			// fall through to do the logging
   207  		}
   208  
   209  		runtime.ReadMemStats(&newMemStats)
   210  		newStatsMap = stats.Dump()
   211  
   212  		// collect an extra connection stats sample to ensure we have at least one
   213  		chunkedConnectionStats.Sample(swiftclient.ChunkedConnectionFreeCnt())
   214  		nonChunkedConnectionStats.Sample(swiftclient.NonChunkedConnectionFreeCnt())
   215  
   216  		// print absolute stats and then deltas
   217  		logStats("total", &chunkedConnectionStats, &nonChunkedConnectionStats, &newMemStats, newStatsMap)
   218  
   219  		oldMemStats.Sys = newMemStats.Sys - oldMemStats.Sys
   220  		oldMemStats.TotalAlloc = newMemStats.TotalAlloc - oldMemStats.TotalAlloc
   221  		oldMemStats.HeapInuse = newMemStats.HeapInuse - oldMemStats.HeapInuse
   222  		oldMemStats.HeapIdle = newMemStats.HeapIdle - oldMemStats.HeapIdle
   223  		oldMemStats.HeapReleased = newMemStats.HeapReleased - oldMemStats.HeapReleased
   224  		oldMemStats.StackSys = newMemStats.StackSys - oldMemStats.StackSys
   225  		oldMemStats.MSpanSys = newMemStats.MSpanSys - oldMemStats.MSpanSys
   226  		oldMemStats.MCacheSys = newMemStats.MCacheSys - oldMemStats.MCacheSys
   227  		oldMemStats.BuckHashSys = newMemStats.BuckHashSys - oldMemStats.BuckHashSys
   228  		oldMemStats.GCSys = newMemStats.GCSys - oldMemStats.GCSys
   229  		oldMemStats.OtherSys = newMemStats.OtherSys - oldMemStats.OtherSys
   230  
   231  		oldMemStats.NextGC = newMemStats.NextGC - oldMemStats.NextGC
   232  		oldMemStats.NumGC = newMemStats.NumGC - oldMemStats.NumGC
   233  		oldMemStats.NumForcedGC = newMemStats.NumForcedGC - oldMemStats.NumForcedGC
   234  		oldMemStats.PauseTotalNs = newMemStats.PauseTotalNs - oldMemStats.PauseTotalNs
   235  		oldMemStats.GCCPUFraction = newMemStats.GCCPUFraction - oldMemStats.GCCPUFraction
   236  
   237  		for key := range newStatsMap {
   238  			oldStatsMap[key] = newStatsMap[key] - oldStatsMap[key]
   239  		}
   240  		logStats("delta", nil, nil, &oldMemStats, oldStatsMap)
   241  
   242  		if globals.verbose {
   243  			logVerboseStats(&newMemStats, newStatsMap)
   244  		}
   245  
   246  		oldMemStats = newMemStats
   247  		oldStatsMap = newStatsMap
   248  
   249  		// clear the connection stats
   250  		chunkedConnectionStats.Clear()
   251  		nonChunkedConnectionStats.Clear()
   252  	}
   253  
   254  	globals.doneChan <- true
   255  	return
   256  }
   257  
   258  // Write interesting statistics to the log in a semi-human readable format
   259  //
   260  // statsType is "total" or "delta" indicating whether statsMap and memStats are
   261  // absolute or relative to the previous sample (doesn't apply to chunkedStats
   262  // and nonChunkedStats, though they can be nil).
   263  //
   264  func logStats(statsType string, chunkedStats *SimpleStats, nonChunkedStats *SimpleStats,
   265  	memStats *runtime.MemStats, statsMap map[string]uint64) {
   266  
   267  	// if we have connection statistics, log them
   268  	if chunkedStats != nil || nonChunkedStats != nil {
   269  		logger.Infof("ChunkedFreeConnections: min=%d mean=%d max=%d  NonChunkedFreeConnections: min=%d mean=%d max=%d",
   270  			chunkedStats.Min(), chunkedStats.Mean(), chunkedStats.Max(),
   271  			nonChunkedStats.Min(), nonChunkedStats.Mean(), nonChunkedStats.Max())
   272  	}
   273  
   274  	// memory allocation info (see runtime.MemStats for definitions)
   275  	// no GC stats logged at this point
   276  	logger.Infof("Memory in Kibyte (%s): Sys=%d StackSys=%d MSpanSys=%d MCacheSys=%d BuckHashSys=%d GCSys=%d OtherSys=%d",
   277  		statsType,
   278  		int64(memStats.Sys)/1024, int64(memStats.StackSys)/1024,
   279  		int64(memStats.MSpanSys)/1024, int64(memStats.MCacheSys)/1024,
   280  		int64(memStats.BuckHashSys)/1024, int64(memStats.GCSys)/1024, int64(memStats.OtherSys)/1024)
   281  	logger.Infof("Memory in Kibyte (%s): HeapInuse=%d HeapIdle=%d HeapReleased=%d Cumulative TotalAlloc=%d",
   282  		statsType,
   283  		int64(memStats.HeapInuse)/1024, int64(memStats.HeapIdle)/1024,
   284  		int64(memStats.HeapReleased)/1024, int64(memStats.TotalAlloc)/1024)
   285  	logger.Infof("GC Stats (%s): NumGC=%d  NumForcedGC=%d  NextGC=%d KiB  PauseTotalMsec=%d  GC_CPU=%4.2f%%",
   286  		statsType,
   287  		memStats.NumGC, memStats.NumForcedGC, int64(memStats.NextGC)/1024,
   288  		memStats.PauseTotalNs/1000000, memStats.GCCPUFraction*100)
   289  
   290  	// selected proxyfs statistics that show filesystem or swift activity; consolidate all
   291  	// opps that query an account as SwiftAccountQueryOps, all opps that modify an account as
   292  	// accountModifyOps, etc. A chunked put counts as 1 SwiftObjModifyOps but is also counted
   293  	// separated in chunked put statistics.
   294  	accountQueryOps := statsMap[stats.SwiftAccountGetOps] + statsMap[stats.SwiftAccountHeadOps]
   295  	accountModifyOps := (statsMap[stats.SwiftAccountDeleteOps] + statsMap[stats.SwiftAccountPostOps] +
   296  		statsMap[stats.SwiftAccountPutOps])
   297  
   298  	containerQueryOps := statsMap[stats.SwiftContainerGetOps] + statsMap[stats.SwiftContainerHeadOps]
   299  	containerModifyOps := (statsMap[stats.SwiftContainerDeleteOps] + statsMap[stats.SwiftContainerPostOps] +
   300  		statsMap[stats.SwiftContainerPutOps])
   301  
   302  	objectQueryOps := (statsMap[stats.SwiftObjGetOps] + statsMap[stats.SwiftObjHeadOps] +
   303  		statsMap[stats.SwiftObjContentLengthOps] + statsMap[stats.SwiftObjLoadOps] +
   304  		statsMap[stats.SwiftObjTailOps])
   305  	objectModifyOps := (statsMap[stats.SwiftObjDeleteOps] + statsMap[stats.SwiftObjCopyOps] +
   306  		statsMap[stats.SwiftObjPutCtxFetchOps])
   307  
   308  	chunkedPutFetchOps := statsMap[stats.SwiftObjPutCtxFetchOps]
   309  	chunkedPutQueryOps := statsMap[stats.SwiftObjPutCtxReadOps]
   310  	chunkedPutModifyOps := statsMap[stats.SwiftObjPutCtxSendChunkOps]
   311  	chunkedPutCloseOPs := statsMap[stats.SwiftObjPutCtxCloseOps]
   312  
   313  	logger.Infof("Swift Client Ops (%s): Account QueryOps=%d ModifyOps=%d Container QueryOps=%d ModifyOps=%d Object QueryOps=%d ModifyOps=%d",
   314  		statsType, accountQueryOps, accountModifyOps,
   315  		containerQueryOps, containerModifyOps, objectQueryOps, objectModifyOps)
   316  	logger.Infof("Swift Client ChunkedPut Ops (%s): FetchOps=%d ReadOps=%d SendOps=%d CloseOps=%d",
   317  		statsType, chunkedPutFetchOps, chunkedPutQueryOps, chunkedPutModifyOps, chunkedPutCloseOPs)
   318  }
   319  
   320  func logVerboseStats(memStats *runtime.MemStats, statsMap map[string]uint64) {
   321  	var (
   322  		bucketstatsValue       string
   323  		bucketstatsValues      string
   324  		bucketstatsValuesSlice []string
   325  		i                      int
   326  		pauseNsAccumulator     uint64
   327  		statKey                string
   328  		statValue              uint64
   329  		varboseStatKey         string
   330  		varboseStatValue       uint64
   331  		varboseStatsMap        map[string]uint64
   332  	)
   333  
   334  	varboseStatsMap = make(map[string]uint64)
   335  
   336  	// General statistics.
   337  	varboseStatsMap["go_runtime_MemStats_Alloc"] = memStats.Alloc
   338  	varboseStatsMap["go_runtime_MemStats_TotalAlloc"] = memStats.TotalAlloc
   339  	varboseStatsMap["go_runtime_MemStats_Sys"] = memStats.Sys
   340  	varboseStatsMap["go_runtime_MemStats_Lookups"] = memStats.Lookups
   341  	varboseStatsMap["go_runtime_MemStats_Mallocs"] = memStats.Mallocs
   342  	varboseStatsMap["go_runtime_MemStats_Frees"] = memStats.Frees
   343  
   344  	// Main allocation heap statistics.
   345  	varboseStatsMap["go_runtime_MemStats_HeapAlloc"] = memStats.HeapAlloc
   346  	varboseStatsMap["go_runtime_MemStats_HeapSys"] = memStats.HeapSys
   347  	varboseStatsMap["go_runtime_MemStats_HeapIdle"] = memStats.HeapIdle
   348  	varboseStatsMap["go_runtime_MemStats_HeapInuse"] = memStats.HeapInuse
   349  	varboseStatsMap["go_runtime_MemStats_HeapReleased"] = memStats.HeapReleased
   350  	varboseStatsMap["go_runtime_MemStats_HeapObjects"] = memStats.HeapObjects
   351  
   352  	// Low-level fixed-size structure allocator statistics.
   353  	//	Inuse is bytes used now.
   354  	//	Sys is bytes obtained from system.
   355  	varboseStatsMap["go_runtime_MemStats_StackInuse"] = memStats.StackInuse
   356  	varboseStatsMap["go_runtime_MemStats_StackSys"] = memStats.StackSys
   357  	varboseStatsMap["go_runtime_MemStats_MSpanInuse"] = memStats.MSpanInuse
   358  	varboseStatsMap["go_runtime_MemStats_MSpanSys"] = memStats.MSpanSys
   359  	varboseStatsMap["go_runtime_MemStats_MCacheInuse"] = memStats.MCacheInuse
   360  	varboseStatsMap["go_runtime_MemStats_MCacheSys"] = memStats.MCacheSys
   361  	varboseStatsMap["go_runtime_MemStats_BuckHashSys"] = memStats.BuckHashSys
   362  	varboseStatsMap["go_runtime_MemStats_GCSys"] = memStats.GCSys
   363  	varboseStatsMap["go_runtime_MemStats_OtherSys"] = memStats.OtherSys
   364  
   365  	// Garbage collector statistics (fixed portion).
   366  	varboseStatsMap["go_runtime_MemStats_LastGC"] = memStats.LastGC
   367  	varboseStatsMap["go_runtime_MemStats_PauseTotalNs"] = memStats.PauseTotalNs
   368  	varboseStatsMap["go_runtime_MemStats_NumGC"] = uint64(memStats.NumGC)
   369  	varboseStatsMap["go_runtime_MemStats_GCCPUPercentage"] = uint64(100.0 * memStats.GCCPUFraction)
   370  
   371  	// Garbage collector statistics (go_runtime_MemStats_PauseAverageNs).
   372  	if 0 == memStats.NumGC {
   373  		varboseStatsMap["go_runtime_MemStats_PauseAverageNs"] = 0
   374  	} else {
   375  		pauseNsAccumulator = 0
   376  		if memStats.NumGC < 255 {
   377  			for i = 0; i < int(memStats.NumGC); i++ {
   378  				pauseNsAccumulator += memStats.PauseNs[i]
   379  			}
   380  			varboseStatsMap["go_runtime_MemStats_PauseAverageNs"] = pauseNsAccumulator / uint64(memStats.NumGC)
   381  		} else {
   382  			for i = 0; i < 256; i++ {
   383  				pauseNsAccumulator += memStats.PauseNs[i]
   384  			}
   385  			varboseStatsMap["go_runtime_MemStats_PauseAverageNs"] = pauseNsAccumulator / 256
   386  		}
   387  	}
   388  
   389  	for statKey, statValue = range statsMap {
   390  		varboseStatKey = strings.Replace(statKey, ".", "_", -1)
   391  		varboseStatKey = strings.Replace(varboseStatKey, "-", "_", -1)
   392  		varboseStatsMap[varboseStatKey] = statValue
   393  	}
   394  
   395  	for varboseStatKey, varboseStatValue = range varboseStatsMap {
   396  		logger.Infof("metrics %s: %d", varboseStatKey, varboseStatValue)
   397  	}
   398  
   399  	bucketstatsValues = bucketstats.SprintStats(bucketstats.StatFormatParsable1, "*", "*")
   400  
   401  	bucketstatsValuesSlice = strings.Split(bucketstatsValues, "\n")
   402  
   403  	for _, bucketstatsValue = range bucketstatsValuesSlice {
   404  		logger.Infof("stats %s", bucketstatsValue)
   405  	}
   406  }