github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/app/metrics/metrics.go (about)

     1  package metrics
     2  
     3  import (
     4  	"context"
     5  	"expvar"
     6  	"net/http"
     7  	_ "net/http/pprof"
     8  	"strings"
     9  
    10  	"github.com/xmplusdev/xmcore/app/observatory"
    11  	"github.com/xmplusdev/xmcore/app/stats"
    12  	"github.com/xmplusdev/xmcore/common"
    13  	"github.com/xmplusdev/xmcore/common/net"
    14  	"github.com/xmplusdev/xmcore/common/signal/done"
    15  	"github.com/xmplusdev/xmcore/core"
    16  	"github.com/xmplusdev/xmcore/features/extension"
    17  	"github.com/xmplusdev/xmcore/features/outbound"
    18  	feature_stats "github.com/xmplusdev/xmcore/features/stats"
    19  )
    20  
    21  type MetricsHandler struct {
    22  	ohm          outbound.Manager
    23  	statsManager feature_stats.Manager
    24  	observatory  extension.Observatory
    25  	tag          string
    26  }
    27  
    28  // NewMetricsHandler creates a new MetricsHandler based on the given config.
    29  func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
    30  	c := &MetricsHandler{
    31  		tag: config.Tag,
    32  	}
    33  	common.Must(core.RequireFeatures(ctx, func(om outbound.Manager, sm feature_stats.Manager) {
    34  		c.statsManager = sm
    35  		c.ohm = om
    36  	}))
    37  	expvar.Publish("stats", expvar.Func(func() interface{} {
    38  		manager, ok := c.statsManager.(*stats.Manager)
    39  		if !ok {
    40  			return nil
    41  		}
    42  		resp := map[string]map[string]map[string]int64{
    43  			"inbound":  {},
    44  			"outbound": {},
    45  			"user":     {},
    46  		}
    47  		manager.VisitCounters(func(name string, counter feature_stats.Counter) bool {
    48  			nameSplit := strings.Split(name, ">>>")
    49  			typeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]
    50  			if item, found := resp[typeName][tagOrUser]; found {
    51  				item[direction] = counter.Value()
    52  			} else {
    53  				resp[typeName][tagOrUser] = map[string]int64{
    54  					direction: counter.Value(),
    55  				}
    56  			}
    57  			return true
    58  		})
    59  		return resp
    60  	}))
    61  	expvar.Publish("observatory", expvar.Func(func() interface{} {
    62  		if c.observatory == nil {
    63  			common.Must(core.RequireFeatures(ctx, func(observatory extension.Observatory) error {
    64  				c.observatory = observatory
    65  				return nil
    66  			}))
    67  			if c.observatory == nil {
    68  				return nil
    69  			}
    70  		}
    71  		resp := map[string]*observatory.OutboundStatus{}
    72  		if o, err := c.observatory.GetObservation(context.Background()); err != nil {
    73  			return err
    74  		} else {
    75  			for _, x := range o.(*observatory.ObservationResult).GetStatus() {
    76  				resp[x.OutboundTag] = x
    77  			}
    78  		}
    79  		return resp
    80  	}))
    81  	return c, nil
    82  }
    83  
    84  func (p *MetricsHandler) Type() interface{} {
    85  	return (*MetricsHandler)(nil)
    86  }
    87  
    88  func (p *MetricsHandler) Start() error {
    89  	listener := &OutboundListener{
    90  		buffer: make(chan net.Conn, 4),
    91  		done:   done.New(),
    92  	}
    93  
    94  	go func() {
    95  		if err := http.Serve(listener, http.DefaultServeMux); err != nil {
    96  			newError("failed to start metrics server").Base(err).AtError().WriteToLog()
    97  		}
    98  	}()
    99  
   100  	if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
   101  		newError("failed to remove existing handler").WriteToLog()
   102  	}
   103  
   104  	return p.ohm.AddHandler(context.Background(), &Outbound{
   105  		tag:      p.tag,
   106  		listener: listener,
   107  	})
   108  }
   109  
   110  func (p *MetricsHandler) Close() error {
   111  	return nil
   112  }
   113  
   114  func init() {
   115  	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
   116  		return NewMetricsHandler(ctx, cfg.(*Config))
   117  	}))
   118  }