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 }