github.com/ergo-services/ergo@v1.999.224/apps/system/metrics.go (about)

     1  package system
     2  
     3  import (
     4  	"crypto/rand"
     5  	"crypto/rsa"
     6  	"crypto/sha256"
     7  	"crypto/x509"
     8  	"encoding/base64"
     9  	"encoding/binary"
    10  	"net"
    11  	"runtime"
    12  	"time"
    13  
    14  	"github.com/ergo-services/ergo/etf"
    15  	"github.com/ergo-services/ergo/gen"
    16  	"github.com/ergo-services/ergo/lib"
    17  	"github.com/ergo-services/ergo/lib/osdep"
    18  	"github.com/ergo-services/ergo/node"
    19  )
    20  
    21  var (
    22  	defaultMetricsPeriod = time.Minute
    23  )
    24  
    25  type systemMetrics struct {
    26  	gen.Server
    27  }
    28  
    29  type systemMetricsState struct {
    30  	// gather last 10 stats
    31  	stats [10]nodeFullStats
    32  	i     int
    33  }
    34  type messageSystemAnonInfo struct{}
    35  type messageSystemGatherStats struct{}
    36  
    37  type nodeFullStats struct {
    38  	timestamp int64
    39  	utime     int64
    40  	stime     int64
    41  
    42  	memAlloc      uint64
    43  	memTotalAlloc uint64
    44  	memFrees      uint64
    45  	memSys        uint64
    46  	memNumGC      uint32
    47  
    48  	node    node.NodeStats
    49  	network []node.NetworkStats
    50  }
    51  
    52  func (sb *systemMetrics) Init(process *gen.ServerProcess, args ...etf.Term) error {
    53  	lib.Log("[%s] SYSTEM_METRICS: Init: %#v", process.NodeName(), args)
    54  	if err := RegisterTypes(); err != nil {
    55  		return err
    56  	}
    57  	options := args[0].(node.System)
    58  	process.State = &systemMetricsState{}
    59  	if options.DisableAnonMetrics == false {
    60  		process.CastAfter(process.Self(), messageSystemAnonInfo{}, defaultMetricsPeriod)
    61  	}
    62  	process.CastAfter(process.Self(), messageSystemGatherStats{}, defaultMetricsPeriod)
    63  	return nil
    64  }
    65  
    66  func (sb *systemMetrics) HandleCast(process *gen.ServerProcess, message etf.Term) gen.ServerStatus {
    67  	lib.Log("[%s] SYSTEM_METRICS: HandleCast: %#v", process.NodeName(), message)
    68  	state := process.State.(*systemMetricsState)
    69  	switch message.(type) {
    70  	case messageSystemAnonInfo:
    71  		ver := process.Env(node.EnvKeyVersion).(node.Version)
    72  		sendAnonInfo(process.NodeName(), ver)
    73  
    74  	case messageSystemGatherStats:
    75  		stats := gatherStats(process)
    76  		if state.i > len(state.stats)-1 {
    77  			state.i = 0
    78  		}
    79  		state.stats[state.i] = stats
    80  		state.i++
    81  		process.CastAfter(process.Self(), messageSystemGatherStats{}, defaultMetricsPeriod)
    82  	}
    83  	return gen.ServerStatusOK
    84  }
    85  
    86  func (sb *systemMetrics) Terminate(process *gen.ServerProcess, reason string) {
    87  	lib.Log("[%s] SYSTEM_METRICS: Terminate with reason %q", process.NodeName(), reason)
    88  }
    89  
    90  // private routines
    91  
    92  func sendAnonInfo(name string, ver node.Version) {
    93  	metricsHost := "metrics.ergo.services"
    94  
    95  	values, err := net.LookupTXT(metricsHost)
    96  	if err != nil || len(values) == 0 {
    97  		return
    98  	}
    99  
   100  	v, err := base64.StdEncoding.DecodeString(values[0])
   101  	if err != nil {
   102  		return
   103  	}
   104  
   105  	pk, err := x509.ParsePKCS1PublicKey([]byte(v))
   106  	if err != nil {
   107  		return
   108  	}
   109  
   110  	c, err := net.Dial("udp", metricsHost+":4411")
   111  	if err != nil {
   112  		return
   113  	}
   114  	defer c.Close()
   115  
   116  	// FIXME get it back before the release
   117  	// nameHash := crc32.Checksum([]byte(name), lib.CRC32Q)
   118  	nameHash := name
   119  
   120  	b := lib.TakeBuffer()
   121  	defer lib.ReleaseBuffer(b)
   122  
   123  	message := MessageSystemAnonMetrics{
   124  		Name:        nameHash,
   125  		Arch:        runtime.GOARCH,
   126  		OS:          runtime.GOOS,
   127  		NumCPU:      runtime.NumCPU(),
   128  		GoVersion:   runtime.Version(),
   129  		ErgoVersion: ver.Release,
   130  	}
   131  	if err := etf.Encode(message, b, etf.EncodeOptions{}); err != nil {
   132  		return
   133  	}
   134  
   135  	hash := sha256.New()
   136  	cipher, err := rsa.EncryptOAEP(hash, rand.Reader, pk, b.B, nil)
   137  	if err != nil {
   138  		return
   139  	}
   140  
   141  	// 2 (magic: 1144) + 2 (length) + len(cipher)
   142  	b.Reset()
   143  	b.Allocate(4)
   144  	b.Append(cipher)
   145  	binary.BigEndian.PutUint16(b.B[0:2], uint16(1144))
   146  	binary.BigEndian.PutUint16(b.B[2:4], uint16(len(cipher)))
   147  	c.Write(b.B)
   148  }
   149  
   150  func gatherStats(process *gen.ServerProcess) nodeFullStats {
   151  	fullStats := nodeFullStats{}
   152  
   153  	// CPU (windows doesn't support this feature)
   154  	fullStats.utime, fullStats.stime = osdep.ResourceUsage()
   155  
   156  	// Memory
   157  	mem := runtime.MemStats{}
   158  	runtime.ReadMemStats(&mem)
   159  	fullStats.memAlloc = mem.Alloc
   160  	fullStats.memTotalAlloc = mem.TotalAlloc
   161  	fullStats.memSys = mem.Sys
   162  	fullStats.memFrees = mem.Frees
   163  	fullStats.memNumGC = mem.NumGC
   164  
   165  	// Network
   166  	node := process.Env(node.EnvKeyNode).(node.Node)
   167  	for _, name := range node.Nodes() {
   168  		ns, err := node.NetworkStats(name)
   169  		if err != nil {
   170  			continue
   171  		}
   172  		fullStats.network = append(fullStats.network, ns)
   173  	}
   174  
   175  	fullStats.node = node.Stats()
   176  	fullStats.timestamp = time.Now().Unix()
   177  	return fullStats
   178  }