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 }