github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/quality/sender.go (about)

     1  /*
     2   * Copyright (C) 2019 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package quality
    19  
    20  import (
    21  	"fmt"
    22  	"math/big"
    23  	"os"
    24  	"runtime"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/rs/zerolog/log"
    30  
    31  	"github.com/mysteriumnetwork/node/config"
    32  	"github.com/mysteriumnetwork/node/core/connection/connectionstate"
    33  	"github.com/mysteriumnetwork/node/core/discovery"
    34  	"github.com/mysteriumnetwork/node/eventbus"
    35  	"github.com/mysteriumnetwork/node/identity"
    36  	"github.com/mysteriumnetwork/node/identity/registry"
    37  	"github.com/mysteriumnetwork/node/market"
    38  	"github.com/mysteriumnetwork/node/nat"
    39  	"github.com/mysteriumnetwork/node/nat/behavior"
    40  	"github.com/mysteriumnetwork/node/p2p"
    41  	p2pnat "github.com/mysteriumnetwork/node/p2p/nat"
    42  	sessionEvent "github.com/mysteriumnetwork/node/session/event"
    43  	pingpongEvent "github.com/mysteriumnetwork/node/session/pingpong/event"
    44  	"github.com/mysteriumnetwork/node/trace"
    45  )
    46  
    47  const (
    48  	appName                  = "myst"
    49  	connectionEvent          = "connection_event"
    50  	sessionDataName          = "session_data"
    51  	sessionTokensName        = "session_tokens"
    52  	sessionEventName         = "session_event"
    53  	traceEventName           = "trace_event"
    54  	registerIdentity         = "register_identity"
    55  	unlockEventName          = "unlock"
    56  	proposalEventName        = "proposal_event"
    57  	natMappingEventName      = "nat_mapping"
    58  	pingEventName            = "ping_event"
    59  	residentCountryEventName = "resident_country_event"
    60  	stunDetectionEvent       = "stun_detection_event"
    61  	natTypeDetectionEvent    = "nat_type_detection_event"
    62  	natTraversalMethod       = "nat_traversal_method"
    63  )
    64  
    65  // Transport allows sending events
    66  type Transport interface {
    67  	SendEvent(Event) error
    68  }
    69  
    70  // NewSender creates metrics sender with appropriate transport
    71  func NewSender(transport Transport, appVersion string) *Sender {
    72  	return &Sender{
    73  		Transport:  transport,
    74  		AppVersion: appVersion,
    75  
    76  		sessionsActive: make(map[string]sessionContext),
    77  	}
    78  }
    79  
    80  // Sender builds events and sends them using given transport
    81  type Sender struct {
    82  	Transport  Transport
    83  	AppVersion string
    84  
    85  	identitiesMu       sync.RWMutex
    86  	identitiesUnlocked []identity.Identity
    87  
    88  	sessionsMu     sync.RWMutex
    89  	sessionsActive map[string]sessionContext
    90  }
    91  
    92  // Event contains data about event, which is sent using transport
    93  type Event struct {
    94  	Application appInfo     `json:"application"`
    95  	EventName   string      `json:"eventName"`
    96  	CreatedAt   int64       `json:"createdAt"`
    97  	Context     interface{} `json:"context"`
    98  }
    99  
   100  type appInfo struct {
   101  	Name            string `json:"name"`
   102  	Version         string `json:"version"`
   103  	OS              string `json:"os"`
   104  	Arch            string `json:"arch"`
   105  	LauncherVersion string `json:"launcher_version"`
   106  	HostOS          string `json:"host_os"`
   107  }
   108  
   109  type pingEventContext struct {
   110  	IsProvider bool
   111  	Duration   uint64
   112  	sessionContext
   113  }
   114  
   115  type natMappingContext struct {
   116  	ID           string              `json:"id"`
   117  	Stage        string              `json:"stage"`
   118  	Successful   bool                `json:"successful"`
   119  	ErrorMessage *string             `json:"error_message"`
   120  	Gateways     []map[string]string `json:"gateways,omitempty"`
   121  }
   122  
   123  type sessionEventContext struct {
   124  	IsProvider bool
   125  	Event      string
   126  	sessionContext
   127  }
   128  
   129  type sessionDataContext struct {
   130  	IsProvider bool
   131  	Rx, Tx     uint64
   132  	sessionContext
   133  }
   134  
   135  type sessionTokensContext struct {
   136  	Tokens *big.Int
   137  	sessionContext
   138  }
   139  
   140  type registrationEvent struct {
   141  	Identity string
   142  	Status   string
   143  }
   144  
   145  type sessionTraceContext struct {
   146  	Duration time.Duration
   147  	Stage    string
   148  	sessionContext
   149  }
   150  
   151  type sessionContext struct {
   152  	ID              string
   153  	Consumer        string
   154  	Provider        string
   155  	ServiceType     string
   156  	ProviderCountry string
   157  	ConsumerCountry string
   158  	AccountantID    string
   159  	StartedAt       time.Time
   160  }
   161  
   162  type residentCountryEvent struct {
   163  	ID      string
   164  	Country string
   165  }
   166  
   167  type natTypeEvent struct {
   168  	ID      string
   169  	NATType string
   170  }
   171  
   172  type natMethodEvent struct {
   173  	ID        string
   174  	NATMethod string
   175  	Success   bool
   176  }
   177  
   178  // Subscribe subscribes to relevant events of event bus.
   179  func (s *Sender) Subscribe(bus eventbus.Subscriber) error {
   180  	subscription := map[string]interface{}{
   181  		AppTopicConnectionEvents:                     s.sendConnectionEvent,
   182  		connectionstate.AppTopicConnectionState:      s.sendConnStateEvent,
   183  		connectionstate.AppTopicConnectionSession:    s.sendSessionEvent,
   184  		connectionstate.AppTopicConnectionStatistics: s.sendSessionData,
   185  		discovery.AppTopicProposalAnnounce:           s.sendProposalEvent,
   186  		identity.AppTopicIdentityUnlock:              s.sendUnlockEvent,
   187  		pingpongEvent.AppTopicInvoicePaid:            s.sendSessionEarning,
   188  		registry.AppTopicIdentityRegistration:        s.sendRegistrationEvent,
   189  		sessionEvent.AppTopicSession:                 s.sendServiceSessionEvent,
   190  		trace.AppTopicTraceEvent:                     s.sendTraceEvent,
   191  		sessionEvent.AppTopicDataTransferred:         s.sendServiceDataStatistics,
   192  		AppTopicConsumerPingP2P:                      s.sendConsumerPingDistance,
   193  		AppTopicProviderPingP2P:                      s.sendProviderPingDistance,
   194  		identity.AppTopicResidentCountry:             s.sendResidentCountry,
   195  		p2p.AppTopicSTUN:                             s.sendSTUNDetectionStatus,
   196  		behavior.AppTopicNATTypeDetected:             s.sendNATType,
   197  		p2pnat.AppTopicNATTraversalMethod:            s.sendNATtraversalMethod,
   198  	}
   199  
   200  	for topic, fn := range subscription {
   201  		if err := bus.SubscribeAsync(topic, fn); err != nil {
   202  			return err
   203  		}
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  func (s *Sender) sendNATtraversalMethod(method p2pnat.NATTraversalMethod) {
   210  	s.sendEvent(natTraversalMethod, natMethodEvent{
   211  		ID:        method.Identity,
   212  		NATMethod: method.Method,
   213  		Success:   method.Success,
   214  	})
   215  }
   216  
   217  func (s *Sender) sendNATType(natType nat.NATType) {
   218  	s.identitiesMu.RLock()
   219  	defer s.identitiesMu.RUnlock()
   220  
   221  	for _, id := range s.identitiesUnlocked {
   222  		s.sendEvent(natTypeDetectionEvent, natTypeEvent{
   223  			ID:      id.Address,
   224  			NATType: string(natType),
   225  		})
   226  	}
   227  }
   228  
   229  func (s *Sender) sendSTUNDetectionStatus(status p2p.STUNDetectionStatus) {
   230  	s.sendEvent(stunDetectionEvent, natTypeEvent{
   231  		ID:      status.Identity,
   232  		NATType: status.NATType,
   233  	})
   234  }
   235  
   236  func (s *Sender) sendResidentCountry(e identity.ResidentCountryEvent) {
   237  	s.sendEvent(residentCountryEventName, residentCountryEvent{
   238  		ID:      e.ID,
   239  		Country: e.Country,
   240  	})
   241  }
   242  
   243  func (s *Sender) sendConsumerPingDistance(p PingEvent) {
   244  	s.sendPingDistance(false, p)
   245  }
   246  
   247  func (s *Sender) sendProviderPingDistance(p PingEvent) {
   248  	s.sendPingDistance(true, p)
   249  }
   250  
   251  func (s *Sender) sendPingDistance(isProvider bool, p PingEvent) {
   252  	session, err := s.recoverSessionContext(p.SessionID)
   253  	if err != nil {
   254  		log.Warn().Err(err).Msg("Can't recover session context")
   255  		return
   256  	}
   257  
   258  	s.sendEvent(pingEventName, pingEventContext{
   259  		IsProvider:     isProvider,
   260  		Duration:       uint64(p.Duration),
   261  		sessionContext: session,
   262  	})
   263  }
   264  
   265  func (s *Sender) sendConnectionEvent(e ConnectionEvent) {
   266  	s.sendEvent(connectionEvent, e)
   267  }
   268  
   269  func (s *Sender) sendServiceDataStatistics(e sessionEvent.AppEventDataTransferred) {
   270  	session, err := s.recoverSessionContext(e.ID)
   271  	if err != nil {
   272  		log.Warn().Err(err).Msg("Can't recover session context")
   273  		return
   274  	}
   275  
   276  	s.sendEvent(sessionDataName, sessionDataContext{
   277  		IsProvider:     true,
   278  		Rx:             e.Up,
   279  		Tx:             e.Down,
   280  		sessionContext: session,
   281  	})
   282  }
   283  
   284  // sendSessionData sends transferred information about session.
   285  func (s *Sender) sendSessionData(e connectionstate.AppEventConnectionStatistics) {
   286  	if e.SessionInfo.SessionID == "" {
   287  		return
   288  	}
   289  
   290  	s.sendEvent(sessionDataName, sessionDataContext{
   291  		IsProvider:     false,
   292  		Rx:             e.Stats.BytesReceived,
   293  		Tx:             e.Stats.BytesSent,
   294  		sessionContext: s.toSessionContext(e.SessionInfo),
   295  	})
   296  }
   297  
   298  func (s *Sender) sendSessionEarning(e pingpongEvent.AppEventInvoicePaid) {
   299  	session, err := s.recoverSessionContext(e.SessionID)
   300  	if err != nil {
   301  		log.Warn().Err(err).Msg("Can't recover session context")
   302  		return
   303  	}
   304  
   305  	s.sendEvent(sessionTokensName, sessionTokensContext{
   306  		Tokens:         e.Invoice.AgreementTotal,
   307  		sessionContext: session,
   308  	})
   309  }
   310  
   311  // sendConnStateEvent sends session update events.
   312  func (s *Sender) sendConnStateEvent(e connectionstate.AppEventConnectionState) {
   313  	if e.SessionInfo.SessionID == "" {
   314  		return
   315  	}
   316  
   317  	s.sendEvent(sessionEventName, sessionEventContext{
   318  		Event:          string(e.State),
   319  		sessionContext: s.toSessionContext(e.SessionInfo),
   320  	})
   321  }
   322  
   323  func (s *Sender) sendServiceSessionEvent(e sessionEvent.AppEventSession) {
   324  	if e.Session.ID == "" {
   325  		return
   326  	}
   327  
   328  	sessionContext := sessionContext{
   329  		ID:              e.Session.ID,
   330  		Consumer:        e.Session.ConsumerID.Address,
   331  		Provider:        e.Session.Proposal.ProviderID,
   332  		ServiceType:     e.Session.Proposal.ServiceType,
   333  		ProviderCountry: e.Session.Proposal.Location.Country,
   334  		ConsumerCountry: e.Session.ConsumerLocation.Country,
   335  		AccountantID:    e.Session.HermesID.Hex(),
   336  		StartedAt:       e.Session.StartedAt,
   337  	}
   338  
   339  	switch e.Status {
   340  	case sessionEvent.CreatedStatus:
   341  		s.rememberSessionContext(sessionContext)
   342  	case sessionEvent.RemovedStatus:
   343  		s.forgetSessionContext(sessionContext)
   344  	}
   345  
   346  	s.sendEvent(sessionEventName, sessionEventContext{
   347  		IsProvider:     true,
   348  		Event:          string(e.Status),
   349  		sessionContext: sessionContext,
   350  	})
   351  }
   352  
   353  // sendSessionEvent sends session update events.
   354  func (s *Sender) sendSessionEvent(e connectionstate.AppEventConnectionSession) {
   355  	if e.SessionInfo.SessionID == "" {
   356  		return
   357  	}
   358  
   359  	sessionContext := s.toSessionContext(e.SessionInfo)
   360  
   361  	switch e.Status {
   362  	case connectionstate.SessionCreatedStatus:
   363  		s.rememberSessionContext(sessionContext)
   364  		s.sendEvent(sessionEventName, sessionEventContext{
   365  			IsProvider:     false,
   366  			Event:          e.Status,
   367  			sessionContext: sessionContext,
   368  		})
   369  	case connectionstate.SessionEndedStatus:
   370  		s.sendEvent(sessionEventName, sessionEventContext{
   371  			IsProvider:     false,
   372  			Event:          e.Status,
   373  			sessionContext: sessionContext,
   374  		})
   375  		s.forgetSessionContext(sessionContext)
   376  	}
   377  }
   378  
   379  // sendUnlockEvent sends startup event
   380  func (s *Sender) sendUnlockEvent(ev identity.AppEventIdentityUnlock) {
   381  	s.identitiesMu.Lock()
   382  	defer s.identitiesMu.Unlock()
   383  	s.identitiesUnlocked = append(s.identitiesUnlocked, ev.ID)
   384  
   385  	s.sendEvent(unlockEventName, ev.ID.Address)
   386  }
   387  
   388  // sendProposalEvent sends provider proposal event.
   389  func (s *Sender) sendProposalEvent(p market.ServiceProposal) {
   390  	s.sendEvent(proposalEventName, p)
   391  }
   392  
   393  func (s *Sender) sendRegistrationEvent(r registry.AppEventIdentityRegistration) {
   394  	s.sendEvent(registerIdentity, registrationEvent{
   395  		Identity: r.ID.Address,
   396  		Status:   r.Status.String(),
   397  	})
   398  }
   399  
   400  func (s *Sender) sendTraceEvent(stage trace.Event) {
   401  	session, err := s.recoverSessionContext(stage.ID)
   402  	if err != nil {
   403  		log.Warn().Err(err).Msg("Can't recover session context")
   404  		return
   405  	}
   406  
   407  	s.sendEvent(traceEventName, sessionTraceContext{
   408  		Duration:       stage.Duration,
   409  		Stage:          stage.Key,
   410  		sessionContext: session,
   411  	})
   412  }
   413  
   414  // SendNATMappingSuccessEvent sends event about successful NAT mapping
   415  func (s *Sender) SendNATMappingSuccessEvent(id, stage string, gateways []map[string]string) {
   416  	s.sendEvent(natMappingEventName, natMappingContext{
   417  		ID:         id,
   418  		Stage:      stage,
   419  		Successful: true,
   420  		Gateways:   gateways,
   421  	})
   422  }
   423  
   424  // SendNATMappingFailEvent sends event about failed NAT mapping
   425  func (s *Sender) SendNATMappingFailEvent(id, stage string, gateways []map[string]string, err error) {
   426  	errorMessage := err.Error()
   427  
   428  	s.sendEvent(natMappingEventName, natMappingContext{
   429  		ID:           id,
   430  		Stage:        stage,
   431  		Successful:   false,
   432  		ErrorMessage: &errorMessage,
   433  		Gateways:     gateways,
   434  	})
   435  }
   436  
   437  func (s *Sender) sendEvent(eventName string, context interface{}) {
   438  	guestOS := runtime.GOOS
   439  	if _, err := os.Stat("/.dockerenv"); err == nil {
   440  		guestOS += "(docker)"
   441  	}
   442  	launcherInfo := strings.Split(config.GetString(config.FlagLauncherVersion), "/")
   443  	launcherVersion := launcherInfo[0]
   444  	hostOS := ""
   445  	if len(launcherInfo) > 1 {
   446  		hostOS = launcherInfo[1]
   447  	}
   448  
   449  	err := s.Transport.SendEvent(Event{
   450  		Application: appInfo{
   451  			Name:            appName,
   452  			OS:              guestOS,
   453  			Arch:            runtime.GOARCH,
   454  			Version:         s.AppVersion,
   455  			LauncherVersion: launcherVersion,
   456  			HostOS:          hostOS,
   457  		},
   458  		EventName: eventName,
   459  		CreatedAt: time.Now().Unix(),
   460  		Context:   context,
   461  	})
   462  	if err != nil {
   463  		log.Warn().Err(err).Msg("Failed to send metric: " + eventName)
   464  	}
   465  }
   466  
   467  func (s *Sender) rememberSessionContext(context sessionContext) {
   468  	s.sessionsMu.Lock()
   469  	defer s.sessionsMu.Unlock()
   470  
   471  	s.sessionsActive[context.ID] = context
   472  }
   473  
   474  func (s *Sender) forgetSessionContext(context sessionContext) {
   475  	s.sessionsMu.Lock()
   476  	defer s.sessionsMu.Unlock()
   477  
   478  	delete(s.sessionsActive, context.ID)
   479  }
   480  
   481  func (s *Sender) recoverSessionContext(sessionID string) (sessionContext, error) {
   482  	s.sessionsMu.RLock()
   483  	defer s.sessionsMu.RUnlock()
   484  
   485  	context, found := s.sessionsActive[sessionID]
   486  	if !found {
   487  		return sessionContext{}, fmt.Errorf("unknown session: %s", sessionID)
   488  	}
   489  
   490  	return context, nil
   491  }
   492  
   493  func (s *Sender) toSessionContext(session connectionstate.Status) sessionContext {
   494  	return sessionContext{
   495  		ID:              string(session.SessionID),
   496  		Consumer:        session.ConsumerID.Address,
   497  		Provider:        session.Proposal.ProviderID,
   498  		ServiceType:     session.Proposal.ServiceType,
   499  		ProviderCountry: session.Proposal.Location.Country,
   500  		ConsumerCountry: session.ConsumerLocation.Country,
   501  		AccountantID:    session.HermesID.Hex(),
   502  		StartedAt:       session.StartedAt,
   503  	}
   504  }