go.dedis.ch/onet/v4@v4.0.0-pre1/simul/monitor/measure.go (about) 1 package monitor 2 3 import ( 4 "encoding/json" 5 "net" 6 "time" 7 8 "sync" 9 10 "go.dedis.ch/onet/v4/log" 11 "golang.org/x/xerrors" 12 ) 13 14 // InvalidHostIndex is the default value when the measure is not assigned 15 // to a specific host 16 const InvalidHostIndex = -1 17 18 var global struct { 19 // Sink is the server address where all measures are transmitted to for 20 // further analysis. 21 sink string 22 23 // Structs are encoded through a json encoder. 24 encoder *json.Encoder 25 connection net.Conn 26 27 sync.Mutex 28 } 29 30 // Measure is an interface for measurements 31 // Usage: 32 // measure := monitor.SingleMeasure("bandwidth") 33 // or 34 // measure := monitor.NewTimeMeasure("round") 35 // measure.Record() 36 type Measure interface { 37 // Record must be called when you want to send the value 38 // over the monitor listening. 39 // Implementation of this interface must RESET the value to `0` at the end 40 // of Record(). `0` means the initial value / meaning this measure had when 41 // created. 42 // Example: TimeMeasure.Record() will reset the time to `time.Now()` 43 // CounterIOMeasure.Record() will reset the counter of the bytes 44 // read / written to 0. 45 // etc 46 Record() 47 } 48 49 // SingleMeasure is a pair name - value we want to send to the monitor. 50 type singleMeasure struct { 51 Name string 52 Value float64 53 Host int 54 } 55 56 // TimeMeasure represents a measure regarding time: It includes the wallclock 57 // time, the cpu time + the user time. 58 type TimeMeasure struct { 59 Wall *singleMeasure 60 CPU *singleMeasure 61 User *singleMeasure 62 // non exported fields 63 // name of the time measure (basename) 64 name string 65 host int 66 // last time 67 lastWallTime time.Time 68 } 69 70 // ConnectSink connects to the given endpoint and initialises a json 71 // encoder. It can be the address of a proxy or a monitoring process. 72 // Returns an error if it could not connect to the endpoint. 73 func ConnectSink(addr string) error { 74 global.Lock() 75 defer global.Unlock() 76 if global.connection != nil { 77 return xerrors.New("Already connected to an endpoint") 78 } 79 log.Lvl3("Connecting to:", addr) 80 conn, err := net.Dial("tcp", addr) 81 if err != nil { 82 return xerrors.Errorf("dial: %v", err) 83 } 84 log.Lvl3("Connected to sink:", addr) 85 global.sink = addr 86 global.connection = conn 87 global.encoder = json.NewEncoder(conn) 88 return nil 89 } 90 91 // RecordSingleMeasure sends the pair name - value to the monitor directly. 92 func RecordSingleMeasure(name string, value float64) { 93 RecordSingleMeasureWithHost(name, value, InvalidHostIndex) 94 } 95 96 // RecordSingleMeasureWithHost sends the pair name - value with the host index 97 // to the monitor directly. 98 func RecordSingleMeasureWithHost(name string, value float64, host int) { 99 sm := newSingleMeasureWithHost(name, value, host) 100 sm.Record() 101 } 102 103 func newSingleMeasure(name string, value float64) *singleMeasure { 104 return newSingleMeasureWithHost(name, value, InvalidHostIndex) 105 } 106 107 func newSingleMeasureWithHost(name string, value float64, host int) *singleMeasure { 108 return &singleMeasure{ 109 Name: name, 110 Value: value, 111 Host: host, 112 } 113 } 114 115 func (s *singleMeasure) Record() { 116 if err := send(s); err != nil { 117 log.Error("Error sending SingleMeasure", s.Name, " to monitor:", err) 118 } 119 } 120 121 // NewTimeMeasure return *TimeMeasure 122 func NewTimeMeasure(name string) *TimeMeasure { 123 return NewTimeMeasureWithHost(name, InvalidHostIndex) 124 } 125 126 // NewTimeMeasureWithHost makes a time measure bounded to a host index. 127 func NewTimeMeasureWithHost(name string, host int) *TimeMeasure { 128 tm := &TimeMeasure{name: name, host: host} 129 tm.reset() 130 return tm 131 } 132 133 // Record sends the measurements to the monitor: 134 // 135 // - wall time: *name*_wall 136 // 137 // - system time: *name*_system 138 // 139 // - user time: *name*_user 140 func (tm *TimeMeasure) Record() { 141 // Wall time measurement 142 tm.Wall = newSingleMeasureWithHost(tm.name+"_wall", float64(time.Since(tm.lastWallTime))/1.0e9, tm.host) 143 // CPU time measurement 144 tm.CPU.Value, tm.User.Value = getDiffRTime(tm.CPU.Value, tm.User.Value) 145 // send data 146 tm.Wall.Record() 147 tm.CPU.Record() 148 tm.User.Record() 149 // reset timers 150 tm.reset() 151 152 } 153 154 // reset reset the time fields of this time measure 155 func (tm *TimeMeasure) reset() { 156 cpuTimeSys, cpuTimeUser := getRTime() 157 tm.CPU = newSingleMeasureWithHost(tm.name+"_system", cpuTimeSys, tm.host) 158 tm.User = newSingleMeasureWithHost(tm.name+"_user", cpuTimeUser, tm.host) 159 tm.lastWallTime = time.Now() 160 } 161 162 // CounterIO is an interface that can be used to count how many bytes does an 163 // object have written and how many bytes does it have read. For example it is 164 // implemented by cothority/network/ Conn + Host to know how many bytes a 165 // connection / Host has written /read. 166 type CounterIO interface { 167 // Rx returns the number of bytes read by this interface. 168 Rx() uint64 169 // Tx returns the number of bytes transmitted / written by this interface. 170 Tx() uint64 171 // MsgRx returns the number of messages read by this interface. 172 MsgRx() uint64 173 // MsgTx returns the number of messages transmitted / written by this interface. 174 MsgTx() uint64 175 } 176 177 // CounterIOMeasure is a struct that takes a CounterIO and can send the 178 // measurements to the monitor. Each time Record() is called, the measurements 179 // are put back to 0 (while the CounterIO still sends increased bytes number). 180 type CounterIOMeasure struct { 181 name string 182 host int 183 counter CounterIO 184 baseTx uint64 185 baseRx uint64 186 baseMsgTx uint64 187 baseMsgRx uint64 188 } 189 190 // NewCounterIOMeasure returns a CounterIOMeasure fresh. The base value are set 191 // to the current value of counter.Rx() and counter.Tx(). 192 func NewCounterIOMeasure(name string, counter CounterIO) *CounterIOMeasure { 193 return NewCounterIOMeasureWithHost(name, counter, InvalidHostIndex) 194 } 195 196 // NewCounterIOMeasureWithHost returns a CounterIOMeasure bounded to a host index. The 197 // base value are set to the current value of counter.Rx() and counter.Tx(). 198 func NewCounterIOMeasureWithHost(name string, counter CounterIO, host int) *CounterIOMeasure { 199 return &CounterIOMeasure{ 200 name: name, 201 host: host, 202 counter: counter, 203 baseTx: counter.Tx(), 204 baseRx: counter.Rx(), 205 baseMsgTx: counter.MsgTx(), 206 baseMsgRx: counter.MsgRx(), 207 } 208 } 209 210 // Reset sets the base to the current value of the counter. 211 func (cm *CounterIOMeasure) Reset() { 212 cm.baseTx = cm.counter.Tx() 213 cm.baseRx = cm.counter.Rx() 214 cm.baseMsgTx = cm.counter.MsgTx() 215 cm.baseMsgRx = cm.counter.MsgRx() 216 } 217 218 // Record send the actual number of bytes read and written (**name**_written & 219 // **name**_read) and reset the counters. 220 func (cm *CounterIOMeasure) Record() { 221 // creates the read measure 222 bRx := cm.counter.Rx() 223 // TODO Later on, we might want to do a check on the conversion between 224 // uint64 -> float64, as the MAX values are not the same. 225 read := newSingleMeasureWithHost(cm.name+"_rx", float64(bRx-cm.baseRx), cm.host) 226 // creates the written measure 227 bTx := cm.counter.Tx() 228 written := newSingleMeasureWithHost(cm.name+"_tx", float64(bTx-cm.baseTx), cm.host) 229 230 bMsgRx := cm.counter.MsgRx() 231 readMsg := newSingleMeasureWithHost(cm.name+"_msg_rx", float64(bMsgRx-cm.baseMsgRx), cm.host) 232 bMsgTx := cm.counter.MsgTx() 233 writtenMsg := newSingleMeasureWithHost(cm.name+"_msg_tx", float64(bMsgTx-cm.baseMsgTx), cm.host) 234 235 // send them 236 read.Record() 237 written.Record() 238 readMsg.Record() 239 writtenMsg.Record() 240 241 // reset counters 242 cm.baseRx = bRx 243 cm.baseTx = bTx 244 cm.baseMsgRx = bMsgRx 245 cm.baseMsgTx = bMsgTx 246 } 247 248 // Send transmits the given struct over the network. 249 func send(v interface{}) error { 250 global.Lock() 251 defer global.Unlock() 252 if global.connection == nil { 253 return xerrors.New("monitor's sink connection not initialized") 254 } 255 // For a large number of clients (˜10'000), the connection phase 256 // can take some time. This is a linear backoff to enable connection 257 // even when there are a lot of request: 258 var ok bool 259 var err error 260 for wait := 500; wait < 1000; wait += 100 { 261 if err = global.encoder.Encode(v); err == nil { 262 ok = true 263 break 264 } 265 log.Lvl1("Couldn't send to monitor-sink:", err) 266 time.Sleep(time.Duration(wait) * time.Millisecond) 267 continue 268 } 269 if !ok { 270 return xerrors.New("Could not send any measures") 271 } 272 return nil 273 } 274 275 // EndAndCleanup sends a message to end the logging and closes the connection 276 func EndAndCleanup() { 277 if err := send(newSingleMeasure("end", 0)); err != nil { 278 log.Error("Error while sending 'end' message:", err) 279 } 280 global.Lock() 281 defer global.Unlock() 282 if err := global.connection.Close(); err != nil { 283 // at least tell that we could not close the connection: 284 log.Error("Could not close connection:", err) 285 } 286 global.connection = nil 287 } 288 289 // Returns the difference of the given system- and user-time. 290 func getDiffRTime(tSys, tUsr float64) (tDiffSys, tDiffUsr float64) { 291 nowSys, nowUsr := getRTime() 292 return nowSys - tSys, nowUsr - tUsr 293 }