go.dedis.ch/onet/v4@v4.0.0-pre1/simul/monitor/monitor.go (about)

     1  // Package monitor package handle the logging, collection and computation of
     2  // statistical data. Every application can send some Measure (for the moment,
     3  // we mostly measure the CPU time but it can be applied later for any kind of
     4  // measures). The Monitor receives them and updates a Stats struct. This Stats
     5  // struct can hold many different kinds of Measurements (the measure of a
     6  // specific action such as "round time" or "verify time" etc). These
     7  // measurements contain Values which compute the actual min/max/dev/avg values.
     8  //
     9  // The Proxy allows to relay Measure from
    10  // clients to the listening Monitor. A starter feature is also the DataFilter
    11  // which can apply some filtering rules to the data before making any
    12  // statistics about them.
    13  package monitor
    14  
    15  import (
    16  	"encoding/json"
    17  	"fmt"
    18  	"io"
    19  	"net"
    20  	"strconv"
    21  	"strings"
    22  	"sync"
    23  
    24  	"go.dedis.ch/onet/v4/log"
    25  	"golang.org/x/xerrors"
    26  )
    27  
    28  // This file handles the collection of measurements, aggregates them and
    29  // write CSV file reports
    30  
    31  // Sink is the address where to listen for the monitor. The endpoint can be a
    32  // monitor.Proxy or a direct connection with measure.go
    33  const Sink = "0.0.0.0"
    34  
    35  // DefaultSinkPort is the default port where a monitor will listen and a proxy
    36  // will contact the monitor.
    37  const DefaultSinkPort = 10000
    38  
    39  // Monitor struct is used to collect measures and make the statistics about
    40  // them. It takes a stats object so it update that in a concurrent-safe manner
    41  // for each new measure it receives.
    42  type Monitor struct {
    43  	listener     net.Listener
    44  	listenerLock *sync.Mutex
    45  
    46  	// Current conections
    47  	conns map[string]net.Conn
    48  	// and the mutex to play with it
    49  	mutexConn sync.Mutex
    50  
    51  	// Current stats
    52  	stats   *Stats
    53  	buckets *BucketStats
    54  
    55  	// channel to give new measures
    56  	measures chan *singleMeasure
    57  
    58  	// channel to notify the end of a connection
    59  	// send the name of the connection when finishd
    60  	done chan string
    61  
    62  	SinkPort     uint16
    63  	sinkPortChan chan uint16
    64  }
    65  
    66  // NewMonitor returns a new monitor given the stats
    67  func NewMonitor(stats *Stats) *Monitor {
    68  	return &Monitor{
    69  		conns:        make(map[string]net.Conn),
    70  		stats:        stats,
    71  		buckets:      newBucketStats(),
    72  		SinkPort:     DefaultSinkPort,
    73  		measures:     make(chan *singleMeasure),
    74  		done:         make(chan string),
    75  		listenerLock: new(sync.Mutex),
    76  		sinkPortChan: make(chan uint16, 1),
    77  	}
    78  }
    79  
    80  // InsertBucket creates a bucket at the given index that will use the rules
    81  // to filter the incoming measures
    82  func (m *Monitor) InsertBucket(index int, rules []string, stats *Stats) {
    83  	m.buckets.Set(index, rules, stats)
    84  }
    85  
    86  // Listen will start listening for incoming connections on this address
    87  // It needs the stats struct pointer to update when measures come
    88  // Return an error if something went wrong during the connection setup
    89  func (m *Monitor) Listen() error {
    90  	ln, err := net.Listen("tcp", Sink+":"+strconv.Itoa(int(m.SinkPort)))
    91  	if err != nil {
    92  		return xerrors.Errorf("Error while monitor is binding address: %v", err)
    93  	}
    94  	if m.SinkPort == 0 {
    95  		_, p, _ := net.SplitHostPort(ln.Addr().String())
    96  		var p2 uint16
    97  		fmt.Sscanf(p, "%d", &p2)
    98  		m.sinkPortChan <- p2
    99  	}
   100  	m.listenerLock.Lock()
   101  	m.listener = ln
   102  	m.listenerLock.Unlock()
   103  	log.Lvl2("Monitor listening for stats on", Sink, ":", m.SinkPort)
   104  	finished := false
   105  	go func() {
   106  		for {
   107  			if finished {
   108  				break
   109  			}
   110  			conn, err := ln.Accept()
   111  			if err != nil {
   112  				operr, ok := err.(*net.OpError)
   113  				// We cant accept anymore we closed the listener
   114  				if ok && operr.Op == "accept" {
   115  					break
   116  				}
   117  				log.Lvl2("Error while monitor accept connection:", operr)
   118  				continue
   119  			}
   120  			log.Lvl3("Monitor: new connection from", conn.RemoteAddr().String())
   121  			m.mutexConn.Lock()
   122  			m.conns[conn.RemoteAddr().String()] = conn
   123  			go m.handleConnection(conn)
   124  			m.mutexConn.Unlock()
   125  		}
   126  	}()
   127  	for !finished {
   128  		select {
   129  		// new stats
   130  		case measure := <-m.measures:
   131  			m.update(measure)
   132  		// end of a peer conn
   133  		case peer := <-m.done:
   134  			m.mutexConn.Lock()
   135  			log.Lvl3("Connections left:", len(m.conns))
   136  			delete(m.conns, peer)
   137  			// end of monitoring,
   138  			if len(m.conns) == 0 {
   139  				m.listenerLock.Lock()
   140  				if err := m.listener.Close(); err != nil {
   141  					log.Lvl2("Couldn't close listener:",
   142  						err)
   143  				}
   144  				m.listener = nil
   145  				finished = true
   146  				m.listenerLock.Unlock()
   147  			}
   148  			m.mutexConn.Unlock()
   149  		}
   150  	}
   151  	log.Lvl2("Monitor finished waiting")
   152  	m.mutexConn.Lock()
   153  	m.conns = make(map[string]net.Conn)
   154  	m.mutexConn.Unlock()
   155  	return nil
   156  }
   157  
   158  // Stop will close every connections it has
   159  // And will stop updating the stats
   160  func (m *Monitor) Stop() {
   161  	log.Lvl2("Monitor Stop")
   162  	m.listenerLock.Lock()
   163  	if m.listener != nil {
   164  		if err := m.listener.Close(); err != nil {
   165  			log.Error("Couldn't close listener:", err)
   166  		}
   167  	}
   168  	m.listenerLock.Unlock()
   169  	m.mutexConn.Lock()
   170  	for _, c := range m.conns {
   171  		if err := c.Close(); err != nil {
   172  			log.Error("Couldn't close connection:", err)
   173  		}
   174  	}
   175  	m.mutexConn.Unlock()
   176  }
   177  
   178  // handleConnection will decode the data received and aggregates it into its
   179  // stats
   180  func (m *Monitor) handleConnection(conn net.Conn) {
   181  	dec := json.NewDecoder(conn)
   182  	nerr := 0
   183  	for {
   184  		measure := &singleMeasure{}
   185  		if err := dec.Decode(measure); err != nil {
   186  			// if end of connection
   187  			if err == io.EOF || strings.Contains(err.Error(), "closed") {
   188  				break
   189  			}
   190  			// otherwise log it
   191  			log.Lvl2("Error: monitor decoding from", conn.RemoteAddr().String(), ":", err)
   192  			nerr++
   193  			if nerr > 1 {
   194  				log.Lvl2("Monitor: too many errors from", conn.RemoteAddr().String(), ": Abort.")
   195  				break
   196  			}
   197  		}
   198  
   199  		log.Lvlf3("Monitor: received a Measure from %s: %+v", conn.RemoteAddr().String(), measure)
   200  		// Special case where the measurement is indicating a FINISHED step
   201  		switch strings.ToLower(measure.Name) {
   202  		case "end":
   203  			log.Lvl3("Finishing monitor")
   204  			break
   205  		default:
   206  			m.measures <- measure
   207  		}
   208  	}
   209  	m.done <- conn.RemoteAddr().String()
   210  }
   211  
   212  // updateBucket will add that specific measure to all the bucket
   213  // that match the network address.
   214  func (m *Monitor) update(meas *singleMeasure) {
   215  	// global stats
   216  	m.stats.Update(meas)
   217  	// per bucket stats if defined
   218  	m.buckets.Update(meas)
   219  }