github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/mdbd/daemon.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/gob"
     6  	"errors"
     7  	"os"
     8  	"path"
     9  	"reflect"
    10  	"regexp"
    11  	"runtime"
    12  	"sort"
    13  	"syscall"
    14  	"time"
    15  
    16  	"github.com/Cloud-Foundations/Dominator/lib/json"
    17  	"github.com/Cloud-Foundations/Dominator/lib/log"
    18  	"github.com/Cloud-Foundations/Dominator/lib/mdb"
    19  	"github.com/Cloud-Foundations/tricorder/go/tricorder"
    20  	"github.com/Cloud-Foundations/tricorder/go/tricorder/units"
    21  )
    22  
    23  var (
    24  	latencyBucketer         = tricorder.NewGeometricBucketer(0.1, 100e3)
    25  	loadCpuTimeDistribution *tricorder.CumulativeDistribution
    26  	loadTimeDistribution    *tricorder.CumulativeDistribution
    27  )
    28  
    29  type genericEncoder interface {
    30  	Encode(v interface{}) error
    31  }
    32  
    33  func init() {
    34  	loadCpuTimeDistribution = latencyBucketer.NewCumulativeDistribution()
    35  	if err := tricorder.RegisterMetric("/load-cpu-time", loadCpuTimeDistribution,
    36  		units.Millisecond, "load CPU time durations"); err != nil {
    37  		panic(err)
    38  	}
    39  	loadTimeDistribution = latencyBucketer.NewCumulativeDistribution()
    40  	if err := tricorder.RegisterMetric("/load-time", loadTimeDistribution,
    41  		units.Millisecond, "load durations"); err != nil {
    42  		panic(err)
    43  	}
    44  }
    45  
    46  func runDaemon(generators []generator, mdbFileName, hostnameRegex string,
    47  	datacentre string, fetchInterval uint, updateFunc func(old, new *mdb.Mdb),
    48  	logger log.Logger, debug bool) {
    49  	var prevMdb *mdb.Mdb
    50  	var hostnameRE *regexp.Regexp
    51  	var err error
    52  	if hostnameRegex != ".*" {
    53  		hostnameRE, err = regexp.Compile("^" + hostnameRegex)
    54  		if err != nil {
    55  			logger.Println(err)
    56  			os.Exit(1)
    57  		}
    58  	}
    59  	var cycleStopTime time.Time
    60  	fetchIntervalDuration := time.Duration(fetchInterval) * time.Second
    61  	eventChannel := make(chan struct{}, 1)
    62  	for _, gen := range generators {
    63  		if eGen, ok := gen.(eventGenerator); ok {
    64  			eGen.RegisterEventChannel(eventChannel)
    65  		}
    66  	}
    67  	intervalTimer := time.NewTimer(fetchIntervalDuration)
    68  	for ; ; sleepUntil(eventChannel, intervalTimer, cycleStopTime) {
    69  		cycleStopTime = time.Now().Add(fetchIntervalDuration)
    70  		newMdb, err := loadFromAll(generators, datacentre, logger)
    71  		if err != nil {
    72  			logger.Println(err)
    73  			continue
    74  		}
    75  		newMdb = selectHosts(newMdb, hostnameRE)
    76  		sort.Sort(newMdb)
    77  		if newMdbIsDifferent(prevMdb, newMdb) {
    78  			updateFunc(prevMdb, newMdb)
    79  			if err := writeMdb(newMdb, mdbFileName); err != nil {
    80  				logger.Println(err)
    81  			} else {
    82  				if debug {
    83  					logger.Printf("Wrote new MDB data, %d machines\n",
    84  						len(newMdb.Machines))
    85  				}
    86  				prevMdb = newMdb
    87  			}
    88  		} else if debug {
    89  			logger.Printf("Refreshed MDB data, same %d machines\n",
    90  				len(newMdb.Machines))
    91  		}
    92  	}
    93  }
    94  
    95  func sleepUntil(eventChannel <-chan struct{}, intervalTimer *time.Timer,
    96  	wakeTime time.Time) {
    97  	runtime.GC() // An opportune time to take out the garbage.
    98  	sleepTime := wakeTime.Sub(time.Now())
    99  	if sleepTime < time.Second {
   100  		sleepTime = time.Second
   101  	}
   102  	intervalTimer.Reset(sleepTime)
   103  	select {
   104  	case <-eventChannel:
   105  	case <-intervalTimer.C:
   106  	}
   107  }
   108  
   109  func loadFromAll(generators []generator, datacentre string,
   110  	logger log.Logger) (*mdb.Mdb, error) {
   111  	machineMap := make(map[string]mdb.Machine)
   112  	startTime := time.Now()
   113  	var rusageStart, rusageStop syscall.Rusage
   114  	syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStart)
   115  	for _, gen := range generators {
   116  		mdb, err := gen.Generate(datacentre, logger)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  		for _, machine := range mdb.Machines {
   121  			if oldMachine, ok := machineMap[machine.Hostname]; ok {
   122  				oldMachine.UpdateFrom(machine)
   123  				machineMap[machine.Hostname] = oldMachine
   124  			} else {
   125  				machineMap[machine.Hostname] = machine
   126  			}
   127  		}
   128  	}
   129  	var newMdb mdb.Mdb
   130  	for _, machine := range machineMap {
   131  		newMdb.Machines = append(newMdb.Machines, machine)
   132  	}
   133  	syscall.Getrusage(syscall.RUSAGE_SELF, &rusageStop)
   134  	loadTimeDistribution.Add(time.Since(startTime))
   135  	loadCpuTimeDistribution.Add(time.Duration(
   136  		rusageStop.Utime.Sec)*time.Second +
   137  		time.Duration(rusageStop.Utime.Usec)*time.Microsecond -
   138  		time.Duration(rusageStart.Utime.Sec)*time.Second -
   139  		time.Duration(rusageStart.Utime.Usec)*time.Microsecond)
   140  	return &newMdb, nil
   141  }
   142  
   143  func selectHosts(inMdb *mdb.Mdb, hostnameRE *regexp.Regexp) *mdb.Mdb {
   144  	if hostnameRE == nil {
   145  		return inMdb
   146  	}
   147  	var outMdb mdb.Mdb
   148  	for _, machine := range inMdb.Machines {
   149  		if hostnameRE.MatchString(machine.Hostname) {
   150  			outMdb.Machines = append(outMdb.Machines, machine)
   151  		}
   152  	}
   153  	return &outMdb
   154  }
   155  
   156  func newMdbIsDifferent(prevMdb, newMdb *mdb.Mdb) bool {
   157  	return !reflect.DeepEqual(prevMdb, newMdb)
   158  }
   159  
   160  func writeMdb(mdb *mdb.Mdb, mdbFileName string) error {
   161  	tmpFileName := mdbFileName + "~"
   162  	file, err := os.Create(tmpFileName)
   163  	if err != nil {
   164  		return errors.New("Error opening file " + err.Error())
   165  	}
   166  	defer os.Remove(tmpFileName)
   167  	defer file.Close()
   168  	writer := bufio.NewWriter(file)
   169  	switch path.Ext(mdbFileName) {
   170  	case ".gob":
   171  		if err := gob.NewEncoder(writer).Encode(mdb.Machines); err != nil {
   172  			return err
   173  		}
   174  	default:
   175  		if err := json.WriteWithIndent(writer, "    ",
   176  			mdb.Machines); err != nil {
   177  			return err
   178  		}
   179  	}
   180  	if err := writer.Flush(); err != nil {
   181  		return err
   182  	}
   183  	return os.Rename(tmpFileName, mdbFileName)
   184  }