get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/server/monitor.go (about) 1 // Copyright 2013-2023 The NATS Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package server 15 16 import ( 17 "bytes" 18 "crypto/sha256" 19 "crypto/tls" 20 "crypto/x509" 21 "encoding/hex" 22 "encoding/json" 23 "fmt" 24 "net" 25 "net/http" 26 "net/url" 27 "os" 28 "path/filepath" 29 "runtime" 30 "runtime/pprof" 31 "sort" 32 "strconv" 33 "strings" 34 "sync/atomic" 35 "time" 36 37 "get.pme.sh/pnats/server/pse" 38 "github.com/nats-io/jwt/v2" 39 ) 40 41 // Connz represents detailed information on current client connections. 42 type Connz struct { 43 ID string `json:"server_id"` 44 Now time.Time `json:"now"` 45 NumConns int `json:"num_connections"` 46 Total int `json:"total"` 47 Offset int `json:"offset"` 48 Limit int `json:"limit"` 49 Conns []*ConnInfo `json:"connections"` 50 } 51 52 // ConnzOptions are the options passed to Connz() 53 type ConnzOptions struct { 54 // Sort indicates how the results will be sorted. Check SortOpt for possible values. 55 // Only the sort by connection ID (ByCid) is ascending, all others are descending. 56 Sort SortOpt `json:"sort"` 57 58 // Username indicates if user names should be included in the results. 59 Username bool `json:"auth"` 60 61 // Subscriptions indicates if subscriptions should be included in the results. 62 Subscriptions bool `json:"subscriptions"` 63 64 // SubscriptionsDetail indicates if subscription details should be included in the results 65 SubscriptionsDetail bool `json:"subscriptions_detail"` 66 67 // Offset is used for pagination. Connz() only returns connections starting at this 68 // offset from the global results. 69 Offset int `json:"offset"` 70 71 // Limit is the maximum number of connections that should be returned by Connz(). 72 Limit int `json:"limit"` 73 74 // Filter for this explicit client connection. 75 CID uint64 `json:"cid"` 76 77 // Filter for this explicit client connection based on the MQTT client ID 78 MQTTClient string `json:"mqtt_client"` 79 80 // Filter by connection state. 81 State ConnState `json:"state"` 82 83 // The below options only apply if auth is true. 84 85 // Filter by username. 86 User string `json:"user"` 87 88 // Filter by account. 89 Account string `json:"acc"` 90 91 // Filter by subject interest 92 FilterSubject string `json:"filter_subject"` 93 } 94 95 // ConnState is for filtering states of connections. We will only have two, open and closed. 96 type ConnState int 97 98 const ( 99 // ConnOpen filters on open clients. 100 ConnOpen = ConnState(iota) 101 // ConnClosed filters on closed clients. 102 ConnClosed 103 // ConnAll returns all clients. 104 ConnAll 105 ) 106 107 // ConnInfo has detailed information on a per connection basis. 108 type ConnInfo struct { 109 Cid uint64 `json:"cid"` 110 Kind string `json:"kind,omitempty"` 111 Type string `json:"type,omitempty"` 112 IP string `json:"ip"` 113 Port int `json:"port"` 114 Start time.Time `json:"start"` 115 LastActivity time.Time `json:"last_activity"` 116 Stop *time.Time `json:"stop,omitempty"` 117 Reason string `json:"reason,omitempty"` 118 RTT string `json:"rtt,omitempty"` 119 Uptime string `json:"uptime"` 120 Idle string `json:"idle"` 121 Pending int `json:"pending_bytes"` 122 InMsgs int64 `json:"in_msgs"` 123 OutMsgs int64 `json:"out_msgs"` 124 InBytes int64 `json:"in_bytes"` 125 OutBytes int64 `json:"out_bytes"` 126 NumSubs uint32 `json:"subscriptions"` 127 Name string `json:"name,omitempty"` 128 Lang string `json:"lang,omitempty"` 129 Version string `json:"version,omitempty"` 130 TLSVersion string `json:"tls_version,omitempty"` 131 TLSCipher string `json:"tls_cipher_suite,omitempty"` 132 TLSPeerCerts []*TLSPeerCert `json:"tls_peer_certs,omitempty"` 133 TLSFirst bool `json:"tls_first,omitempty"` 134 AuthorizedUser string `json:"authorized_user,omitempty"` 135 Account string `json:"account,omitempty"` 136 Subs []string `json:"subscriptions_list,omitempty"` 137 SubsDetail []SubDetail `json:"subscriptions_list_detail,omitempty"` 138 JWT string `json:"jwt,omitempty"` 139 IssuerKey string `json:"issuer_key,omitempty"` 140 NameTag string `json:"name_tag,omitempty"` 141 Tags jwt.TagList `json:"tags,omitempty"` 142 MQTTClient string `json:"mqtt_client,omitempty"` // This is the MQTT client id 143 144 // Internal 145 rtt int64 // For fast sorting 146 } 147 148 // TLSPeerCert contains basic information about a TLS peer certificate 149 type TLSPeerCert struct { 150 Subject string `json:"subject,omitempty"` 151 SubjectPKISha256 string `json:"spki_sha256,omitempty"` 152 CertSha256 string `json:"cert_sha256,omitempty"` 153 } 154 155 // DefaultConnListSize is the default size of the connection list. 156 const DefaultConnListSize = 1024 157 158 // DefaultSubListSize is the default size of the subscriptions list. 159 const DefaultSubListSize = 1024 160 161 const defaultStackBufSize = 10000 162 163 func newSubsDetailList(client *client) []SubDetail { 164 subsDetail := make([]SubDetail, 0, len(client.subs)) 165 for _, sub := range client.subs { 166 subsDetail = append(subsDetail, newClientSubDetail(sub)) 167 } 168 return subsDetail 169 } 170 171 func newSubsList(client *client) []string { 172 subs := make([]string, 0, len(client.subs)) 173 for _, sub := range client.subs { 174 subs = append(subs, string(sub.subject)) 175 } 176 return subs 177 } 178 179 // Connz returns a Connz struct containing information about connections. 180 func (s *Server) Connz(opts *ConnzOptions) (*Connz, error) { 181 var ( 182 sortOpt = ByCid 183 auth bool 184 subs bool 185 subsDet bool 186 offset int 187 limit = DefaultConnListSize 188 cid = uint64(0) 189 state = ConnOpen 190 user string 191 acc string 192 a *Account 193 filter string 194 mqttCID string 195 ) 196 197 if opts != nil { 198 // If no sort option given or sort is by uptime, then sort by cid 199 if opts.Sort != _EMPTY_ { 200 sortOpt = opts.Sort 201 if !sortOpt.IsValid() { 202 return nil, fmt.Errorf("invalid sorting option: %s", sortOpt) 203 } 204 } 205 206 // Auth specifics. 207 auth = opts.Username 208 user = opts.User 209 acc = opts.Account 210 mqttCID = opts.MQTTClient 211 212 subs = opts.Subscriptions 213 subsDet = opts.SubscriptionsDetail 214 offset = opts.Offset 215 if offset < 0 { 216 offset = 0 217 } 218 limit = opts.Limit 219 if limit <= 0 { 220 limit = DefaultConnListSize 221 } 222 // state 223 state = opts.State 224 225 // ByStop only makes sense on closed connections 226 if sortOpt == ByStop && state != ConnClosed { 227 return nil, fmt.Errorf("sort by stop only valid on closed connections") 228 } 229 // ByReason is the same. 230 if sortOpt == ByReason && state != ConnClosed { 231 return nil, fmt.Errorf("sort by reason only valid on closed connections") 232 } 233 // If searching by CID 234 if opts.CID > 0 { 235 cid = opts.CID 236 limit = 1 237 } 238 // If filtering by subject. 239 if opts.FilterSubject != _EMPTY_ && opts.FilterSubject != fwcs { 240 if acc == _EMPTY_ { 241 return nil, fmt.Errorf("filter by subject only valid with account filtering") 242 } 243 filter = opts.FilterSubject 244 } 245 } 246 247 c := &Connz{ 248 Offset: offset, 249 Limit: limit, 250 Now: time.Now().UTC(), 251 } 252 253 // Open clients 254 var openClients []*client 255 // Hold for closed clients if requested. 256 var closedClients []*closedClient 257 258 var clist map[uint64]*client 259 260 if acc != _EMPTY_ { 261 var err error 262 a, err = s.lookupAccount(acc) 263 if err != nil { 264 return c, nil 265 } 266 a.mu.RLock() 267 clist = make(map[uint64]*client, a.numLocalConnections()) 268 for c := range a.clients { 269 if c.kind == CLIENT || c.kind == LEAF { 270 clist[c.cid] = c 271 } 272 } 273 a.mu.RUnlock() 274 } 275 276 // Walk the open client list with server lock held. 277 s.mu.RLock() 278 // Default to all client unless filled in above. 279 if clist == nil { 280 clist = s.clients 281 } 282 283 // copy the server id for monitoring 284 c.ID = s.info.ID 285 286 // Number of total clients. The resulting ConnInfo array 287 // may be smaller if pagination is used. 288 switch state { 289 case ConnOpen: 290 c.Total = len(clist) 291 case ConnClosed: 292 closedClients = s.closed.closedClients() 293 c.Total = len(closedClients) 294 case ConnAll: 295 c.Total = len(clist) 296 closedClients = s.closed.closedClients() 297 c.Total += len(closedClients) 298 } 299 300 // We may need to filter these connections. 301 if acc != _EMPTY_ && len(closedClients) > 0 { 302 var ccc []*closedClient 303 for _, cc := range closedClients { 304 if cc.acc != acc { 305 continue 306 } 307 ccc = append(ccc, cc) 308 } 309 c.Total -= (len(closedClients) - len(ccc)) 310 closedClients = ccc 311 } 312 313 totalClients := c.Total 314 if cid > 0 { // Meaning we only want 1. 315 totalClients = 1 316 } 317 if state == ConnOpen || state == ConnAll { 318 openClients = make([]*client, 0, totalClients) 319 } 320 321 // Data structures for results. 322 var conns []ConnInfo // Limits allocs for actual ConnInfos. 323 var pconns ConnInfos 324 325 switch state { 326 case ConnOpen: 327 conns = make([]ConnInfo, totalClients) 328 pconns = make(ConnInfos, totalClients) 329 case ConnClosed: 330 pconns = make(ConnInfos, totalClients) 331 case ConnAll: 332 conns = make([]ConnInfo, cap(openClients)) 333 pconns = make(ConnInfos, totalClients) 334 } 335 336 // Search by individual CID. 337 if cid > 0 { 338 if state == ConnClosed || state == ConnAll { 339 copyClosed := closedClients 340 closedClients = nil 341 for _, cc := range copyClosed { 342 if cc.Cid == cid { 343 closedClients = []*closedClient{cc} 344 break 345 } 346 } 347 } else if state == ConnOpen || state == ConnAll { 348 client := s.clients[cid] 349 if client != nil { 350 openClients = append(openClients, client) 351 } 352 } 353 } else { 354 // Gather all open clients. 355 if state == ConnOpen || state == ConnAll { 356 for _, client := range clist { 357 // If we have an account specified we need to filter. 358 if acc != _EMPTY_ && (client.acc == nil || client.acc.Name != acc) { 359 continue 360 } 361 // Do user filtering second 362 if user != _EMPTY_ && client.getRawAuthUserLock() != user { 363 continue 364 } 365 // Do mqtt client ID filtering next 366 if mqttCID != _EMPTY_ && client.getMQTTClientID() != mqttCID { 367 continue 368 } 369 openClients = append(openClients, client) 370 } 371 } 372 } 373 s.mu.RUnlock() 374 375 // Filter by subject now if needed. We do this outside of server lock. 376 if filter != _EMPTY_ { 377 var oc []*client 378 for _, c := range openClients { 379 c.mu.Lock() 380 for _, sub := range c.subs { 381 if SubjectsCollide(filter, string(sub.subject)) { 382 oc = append(oc, c) 383 break 384 } 385 } 386 c.mu.Unlock() 387 openClients = oc 388 } 389 } 390 391 // Just return with empty array if nothing here. 392 if len(openClients) == 0 && len(closedClients) == 0 { 393 c.Conns = ConnInfos{} 394 return c, nil 395 } 396 397 // Now whip through and generate ConnInfo entries 398 // Open Clients 399 i := 0 400 for _, client := range openClients { 401 client.mu.Lock() 402 ci := &conns[i] 403 ci.fill(client, client.nc, c.Now, auth) 404 // Fill in subscription data if requested. 405 if len(client.subs) > 0 { 406 if subsDet { 407 ci.SubsDetail = newSubsDetailList(client) 408 } else if subs { 409 ci.Subs = newSubsList(client) 410 } 411 } 412 // Fill in user if auth requested. 413 if auth { 414 ci.AuthorizedUser = client.getRawAuthUser() 415 // Add in account iff not the global account. 416 if client.acc != nil && (client.acc.Name != globalAccountName) { 417 ci.Account = client.acc.Name 418 } 419 ci.JWT = client.opts.JWT 420 ci.IssuerKey = issuerForClient(client) 421 ci.Tags = client.tags 422 ci.NameTag = client.nameTag 423 } 424 client.mu.Unlock() 425 pconns[i] = ci 426 i++ 427 } 428 // Closed Clients 429 var needCopy bool 430 if subs || auth { 431 needCopy = true 432 } 433 for _, cc := range closedClients { 434 // If we have an account specified we need to filter. 435 if acc != _EMPTY_ && cc.acc != acc { 436 continue 437 } 438 // Do user filtering second 439 if user != _EMPTY_ && cc.user != user { 440 continue 441 } 442 // Do mqtt client ID filtering next 443 if mqttCID != _EMPTY_ && cc.MQTTClient != mqttCID { 444 continue 445 } 446 // Copy if needed for any changes to the ConnInfo 447 if needCopy { 448 cx := *cc 449 cc = &cx 450 } 451 // Fill in subscription data if requested. 452 if len(cc.subs) > 0 { 453 if subsDet { 454 cc.SubsDetail = cc.subs 455 } else if subs { 456 cc.Subs = make([]string, 0, len(cc.subs)) 457 for _, sub := range cc.subs { 458 cc.Subs = append(cc.Subs, sub.Subject) 459 } 460 } 461 } 462 // Fill in user if auth requested. 463 if auth { 464 cc.AuthorizedUser = cc.user 465 // Add in account iff not the global account. 466 if cc.acc != _EMPTY_ && (cc.acc != globalAccountName) { 467 cc.Account = cc.acc 468 } 469 } 470 pconns[i] = &cc.ConnInfo 471 i++ 472 } 473 474 // This will trip if we have filtered out client connections. 475 if len(pconns) != i { 476 pconns = pconns[:i] 477 totalClients = i 478 } 479 480 switch sortOpt { 481 case ByCid, ByStart: 482 sort.Sort(byCid{pconns}) 483 case BySubs: 484 sort.Sort(sort.Reverse(bySubs{pconns})) 485 case ByPending: 486 sort.Sort(sort.Reverse(byPending{pconns})) 487 case ByOutMsgs: 488 sort.Sort(sort.Reverse(byOutMsgs{pconns})) 489 case ByInMsgs: 490 sort.Sort(sort.Reverse(byInMsgs{pconns})) 491 case ByOutBytes: 492 sort.Sort(sort.Reverse(byOutBytes{pconns})) 493 case ByInBytes: 494 sort.Sort(sort.Reverse(byInBytes{pconns})) 495 case ByLast: 496 sort.Sort(sort.Reverse(byLast{pconns})) 497 case ByIdle: 498 sort.Sort(sort.Reverse(byIdle{pconns, c.Now})) 499 case ByUptime: 500 sort.Sort(byUptime{pconns, time.Now()}) 501 case ByStop: 502 sort.Sort(sort.Reverse(byStop{pconns})) 503 case ByReason: 504 sort.Sort(byReason{pconns}) 505 case ByRTT: 506 sort.Sort(sort.Reverse(byRTT{pconns})) 507 } 508 509 minoff := c.Offset 510 maxoff := c.Offset + c.Limit 511 512 maxIndex := totalClients 513 514 // Make sure these are sane. 515 if minoff > maxIndex { 516 minoff = maxIndex 517 } 518 if maxoff > maxIndex { 519 maxoff = maxIndex 520 } 521 522 // Now pare down to the requested size. 523 // TODO(dlc) - for very large number of connections we 524 // could save the whole list in a hash, send hash on first 525 // request and allow users to use has for subsequent pages. 526 // Low TTL, say < 1sec. 527 c.Conns = pconns[minoff:maxoff] 528 c.NumConns = len(c.Conns) 529 530 return c, nil 531 } 532 533 // Fills in the ConnInfo from the client. 534 // client should be locked. 535 func (ci *ConnInfo) fill(client *client, nc net.Conn, now time.Time, auth bool) { 536 // For fast sort if required. 537 rtt := client.getRTT() 538 ci.rtt = int64(rtt) 539 540 ci.Cid = client.cid 541 ci.MQTTClient = client.getMQTTClientID() 542 ci.Kind = client.kindString() 543 ci.Type = client.clientTypeString() 544 ci.Start = client.start 545 ci.LastActivity = client.last 546 ci.Uptime = myUptime(now.Sub(client.start)) 547 ci.Idle = myUptime(now.Sub(client.last)) 548 ci.RTT = rtt.String() 549 ci.OutMsgs = client.outMsgs 550 ci.OutBytes = client.outBytes 551 ci.NumSubs = uint32(len(client.subs)) 552 ci.Pending = int(client.out.pb) 553 ci.Name = client.opts.Name 554 ci.Lang = client.opts.Lang 555 ci.Version = client.opts.Version 556 // inMsgs and inBytes are updated outside of the client's lock, so 557 // we need to use atomic here. 558 ci.InMsgs = atomic.LoadInt64(&client.inMsgs) 559 ci.InBytes = atomic.LoadInt64(&client.inBytes) 560 561 // If the connection is gone, too bad, we won't set TLSVersion and TLSCipher. 562 // Exclude clients that are still doing handshake so we don't block in 563 // ConnectionState(). 564 if client.flags.isSet(handshakeComplete) && nc != nil { 565 if conn, ok := nc.(*tls.Conn); ok { 566 cs := conn.ConnectionState() 567 ci.TLSVersion = tlsVersion(cs.Version) 568 ci.TLSCipher = tlsCipher(cs.CipherSuite) 569 if auth && len(cs.PeerCertificates) > 0 { 570 ci.TLSPeerCerts = makePeerCerts(cs.PeerCertificates) 571 } 572 ci.TLSFirst = client.flags.isSet(didTLSFirst) 573 } 574 } 575 576 if client.port != 0 { 577 ci.Port = int(client.port) 578 ci.IP = client.host 579 } 580 } 581 582 func makePeerCerts(pc []*x509.Certificate) []*TLSPeerCert { 583 res := make([]*TLSPeerCert, len(pc)) 584 for i, c := range pc { 585 tmp := sha256.Sum256(c.RawSubjectPublicKeyInfo) 586 ssha := hex.EncodeToString(tmp[:]) 587 tmp = sha256.Sum256(c.Raw) 588 csha := hex.EncodeToString(tmp[:]) 589 res[i] = &TLSPeerCert{Subject: c.Subject.String(), SubjectPKISha256: ssha, CertSha256: csha} 590 } 591 return res 592 } 593 594 // Assume lock is held 595 func (c *client) getRTT() time.Duration { 596 if c.rtt == 0 { 597 // If a real client, go ahead and send ping now to get a value 598 // for RTT. For tests and telnet, or if client is closing, etc skip. 599 if c.opts.Lang != _EMPTY_ { 600 c.sendRTTPingLocked() 601 } 602 return 0 603 } 604 var rtt time.Duration 605 if c.rtt > time.Microsecond && c.rtt < time.Millisecond { 606 rtt = c.rtt.Truncate(time.Microsecond) 607 } else { 608 rtt = c.rtt.Truncate(time.Nanosecond) 609 } 610 return rtt 611 } 612 613 func decodeBool(w http.ResponseWriter, r *http.Request, param string) (bool, error) { 614 str := r.URL.Query().Get(param) 615 if str == _EMPTY_ { 616 return false, nil 617 } 618 val, err := strconv.ParseBool(str) 619 if err != nil { 620 w.WriteHeader(http.StatusBadRequest) 621 w.Write([]byte(fmt.Sprintf("Error decoding boolean for '%s': %v", param, err))) 622 return false, err 623 } 624 return val, nil 625 } 626 627 func decodeUint64(w http.ResponseWriter, r *http.Request, param string) (uint64, error) { 628 str := r.URL.Query().Get(param) 629 if str == _EMPTY_ { 630 return 0, nil 631 } 632 val, err := strconv.ParseUint(str, 10, 64) 633 if err != nil { 634 w.WriteHeader(http.StatusBadRequest) 635 w.Write([]byte(fmt.Sprintf("Error decoding uint64 for '%s': %v", param, err))) 636 return 0, err 637 } 638 return val, nil 639 } 640 641 func decodeInt(w http.ResponseWriter, r *http.Request, param string) (int, error) { 642 str := r.URL.Query().Get(param) 643 if str == _EMPTY_ { 644 return 0, nil 645 } 646 val, err := strconv.Atoi(str) 647 if err != nil { 648 w.WriteHeader(http.StatusBadRequest) 649 w.Write([]byte(fmt.Sprintf("Error decoding int for '%s': %v", param, err))) 650 return 0, err 651 } 652 return val, nil 653 } 654 655 func decodeState(w http.ResponseWriter, r *http.Request) (ConnState, error) { 656 str := r.URL.Query().Get("state") 657 if str == _EMPTY_ { 658 return ConnOpen, nil 659 } 660 switch strings.ToLower(str) { 661 case "open": 662 return ConnOpen, nil 663 case "closed": 664 return ConnClosed, nil 665 case "any", "all": 666 return ConnAll, nil 667 } 668 // We do not understand intended state here. 669 w.WriteHeader(http.StatusBadRequest) 670 err := fmt.Errorf("Error decoding state for %s", str) 671 w.Write([]byte(err.Error())) 672 return 0, err 673 } 674 675 func decodeSubs(w http.ResponseWriter, r *http.Request) (subs bool, subsDet bool, err error) { 676 subsDet = strings.ToLower(r.URL.Query().Get("subs")) == "detail" 677 if !subsDet { 678 subs, err = decodeBool(w, r, "subs") 679 } 680 return 681 } 682 683 // HandleConnz process HTTP requests for connection information. 684 func (s *Server) HandleConnz(w http.ResponseWriter, r *http.Request) { 685 sortOpt := SortOpt(r.URL.Query().Get("sort")) 686 auth, err := decodeBool(w, r, "auth") 687 if err != nil { 688 return 689 } 690 subs, subsDet, err := decodeSubs(w, r) 691 if err != nil { 692 return 693 } 694 offset, err := decodeInt(w, r, "offset") 695 if err != nil { 696 return 697 } 698 limit, err := decodeInt(w, r, "limit") 699 if err != nil { 700 return 701 } 702 cid, err := decodeUint64(w, r, "cid") 703 if err != nil { 704 return 705 } 706 state, err := decodeState(w, r) 707 if err != nil { 708 return 709 } 710 711 user := r.URL.Query().Get("user") 712 acc := r.URL.Query().Get("acc") 713 mqttCID := r.URL.Query().Get("mqtt_client") 714 715 connzOpts := &ConnzOptions{ 716 Sort: sortOpt, 717 Username: auth, 718 Subscriptions: subs, 719 SubscriptionsDetail: subsDet, 720 Offset: offset, 721 Limit: limit, 722 CID: cid, 723 MQTTClient: mqttCID, 724 State: state, 725 User: user, 726 Account: acc, 727 } 728 729 s.mu.Lock() 730 s.httpReqStats[ConnzPath]++ 731 s.mu.Unlock() 732 733 c, err := s.Connz(connzOpts) 734 if err != nil { 735 w.WriteHeader(http.StatusBadRequest) 736 w.Write([]byte(err.Error())) 737 return 738 } 739 b, err := json.MarshalIndent(c, "", " ") 740 if err != nil { 741 s.Errorf("Error marshaling response to /connz request: %v", err) 742 } 743 744 // Handle response 745 ResponseHandler(w, r, b) 746 } 747 748 // Routez represents detailed information on current client connections. 749 type Routez struct { 750 ID string `json:"server_id"` 751 Name string `json:"server_name"` 752 Now time.Time `json:"now"` 753 Import *SubjectPermission `json:"import,omitempty"` 754 Export *SubjectPermission `json:"export,omitempty"` 755 NumRoutes int `json:"num_routes"` 756 Routes []*RouteInfo `json:"routes"` 757 } 758 759 // RoutezOptions are options passed to Routez 760 type RoutezOptions struct { 761 // Subscriptions indicates that Routez will return a route's subscriptions 762 Subscriptions bool `json:"subscriptions"` 763 // SubscriptionsDetail indicates if subscription details should be included in the results 764 SubscriptionsDetail bool `json:"subscriptions_detail"` 765 } 766 767 // RouteInfo has detailed information on a per connection basis. 768 type RouteInfo struct { 769 Rid uint64 `json:"rid"` 770 RemoteID string `json:"remote_id"` 771 RemoteName string `json:"remote_name"` 772 DidSolicit bool `json:"did_solicit"` 773 IsConfigured bool `json:"is_configured"` 774 IP string `json:"ip"` 775 Port int `json:"port"` 776 Start time.Time `json:"start"` 777 LastActivity time.Time `json:"last_activity"` 778 RTT string `json:"rtt,omitempty"` 779 Uptime string `json:"uptime"` 780 Idle string `json:"idle"` 781 Import *SubjectPermission `json:"import,omitempty"` 782 Export *SubjectPermission `json:"export,omitempty"` 783 Pending int `json:"pending_size"` 784 InMsgs int64 `json:"in_msgs"` 785 OutMsgs int64 `json:"out_msgs"` 786 InBytes int64 `json:"in_bytes"` 787 OutBytes int64 `json:"out_bytes"` 788 NumSubs uint32 `json:"subscriptions"` 789 Subs []string `json:"subscriptions_list,omitempty"` 790 SubsDetail []SubDetail `json:"subscriptions_list_detail,omitempty"` 791 Account string `json:"account,omitempty"` 792 Compression string `json:"compression,omitempty"` 793 } 794 795 // Routez returns a Routez struct containing information about routes. 796 func (s *Server) Routez(routezOpts *RoutezOptions) (*Routez, error) { 797 rs := &Routez{Routes: []*RouteInfo{}} 798 rs.Now = time.Now().UTC() 799 800 if routezOpts == nil { 801 routezOpts = &RoutezOptions{} 802 } 803 804 s.mu.Lock() 805 rs.NumRoutes = s.numRoutes() 806 807 // copy the server id for monitoring 808 rs.ID = s.info.ID 809 810 // Check for defined permissions for all connected routes. 811 if perms := s.getOpts().Cluster.Permissions; perms != nil { 812 rs.Import = perms.Import 813 rs.Export = perms.Export 814 } 815 rs.Name = s.getOpts().ServerName 816 817 addRoute := func(r *client) { 818 r.mu.Lock() 819 ri := &RouteInfo{ 820 Rid: r.cid, 821 RemoteID: r.route.remoteID, 822 RemoteName: r.route.remoteName, 823 DidSolicit: r.route.didSolicit, 824 IsConfigured: r.route.routeType == Explicit, 825 InMsgs: atomic.LoadInt64(&r.inMsgs), 826 OutMsgs: r.outMsgs, 827 InBytes: atomic.LoadInt64(&r.inBytes), 828 OutBytes: r.outBytes, 829 NumSubs: uint32(len(r.subs)), 830 Import: r.opts.Import, 831 Export: r.opts.Export, 832 RTT: r.getRTT().String(), 833 Start: r.start, 834 LastActivity: r.last, 835 Uptime: myUptime(rs.Now.Sub(r.start)), 836 Idle: myUptime(rs.Now.Sub(r.last)), 837 Account: string(r.route.accName), 838 Compression: r.route.compression, 839 } 840 841 if len(r.subs) > 0 { 842 if routezOpts.SubscriptionsDetail { 843 ri.SubsDetail = newSubsDetailList(r) 844 } else if routezOpts.Subscriptions { 845 ri.Subs = newSubsList(r) 846 } 847 } 848 849 switch conn := r.nc.(type) { 850 case *net.TCPConn, *tls.Conn: 851 addr := conn.RemoteAddr().(*net.TCPAddr) 852 ri.Port = addr.Port 853 ri.IP = addr.IP.String() 854 } 855 r.mu.Unlock() 856 rs.Routes = append(rs.Routes, ri) 857 } 858 859 // Walk the list 860 s.forEachRoute(func(r *client) { 861 addRoute(r) 862 }) 863 s.mu.Unlock() 864 return rs, nil 865 } 866 867 // HandleRoutez process HTTP requests for route information. 868 func (s *Server) HandleRoutez(w http.ResponseWriter, r *http.Request) { 869 subs, subsDetail, err := decodeSubs(w, r) 870 if err != nil { 871 return 872 } 873 874 opts := RoutezOptions{Subscriptions: subs, SubscriptionsDetail: subsDetail} 875 876 s.mu.Lock() 877 s.httpReqStats[RoutezPath]++ 878 s.mu.Unlock() 879 880 // As of now, no error is ever returned. 881 rs, _ := s.Routez(&opts) 882 b, err := json.MarshalIndent(rs, "", " ") 883 if err != nil { 884 s.Errorf("Error marshaling response to /routez request: %v", err) 885 } 886 887 // Handle response 888 ResponseHandler(w, r, b) 889 } 890 891 // Subsz represents detail information on current connections. 892 type Subsz struct { 893 ID string `json:"server_id"` 894 Now time.Time `json:"now"` 895 *SublistStats 896 Total int `json:"total"` 897 Offset int `json:"offset"` 898 Limit int `json:"limit"` 899 Subs []SubDetail `json:"subscriptions_list,omitempty"` 900 } 901 902 // SubszOptions are the options passed to Subsz. 903 // As of now, there are no options defined. 904 type SubszOptions struct { 905 // Offset is used for pagination. Subsz() only returns connections starting at this 906 // offset from the global results. 907 Offset int `json:"offset"` 908 909 // Limit is the maximum number of subscriptions that should be returned by Subsz(). 910 Limit int `json:"limit"` 911 912 // Subscriptions indicates if subscription details should be included in the results. 913 Subscriptions bool `json:"subscriptions"` 914 915 // Filter based on this account name. 916 Account string `json:"account,omitempty"` 917 918 // Test the list against this subject. Needs to be literal since it signifies a publish subject. 919 // We will only return subscriptions that would match if a message was sent to this subject. 920 Test string `json:"test,omitempty"` 921 } 922 923 // SubDetail is for verbose information for subscriptions. 924 type SubDetail struct { 925 Account string `json:"account,omitempty"` 926 Subject string `json:"subject"` 927 Queue string `json:"qgroup,omitempty"` 928 Sid string `json:"sid"` 929 Msgs int64 `json:"msgs"` 930 Max int64 `json:"max,omitempty"` 931 Cid uint64 `json:"cid"` 932 } 933 934 // Subscription client should be locked and guaranteed to be present. 935 func newSubDetail(sub *subscription) SubDetail { 936 sd := newClientSubDetail(sub) 937 if sub.client.acc != nil { 938 sd.Account = sub.client.acc.Name 939 } 940 return sd 941 } 942 943 // For subs details under clients. 944 func newClientSubDetail(sub *subscription) SubDetail { 945 return SubDetail{ 946 Subject: string(sub.subject), 947 Queue: string(sub.queue), 948 Sid: string(sub.sid), 949 Msgs: sub.nm, 950 Max: sub.max, 951 Cid: sub.client.cid, 952 } 953 } 954 955 // Subsz returns a Subsz struct containing subjects statistics 956 func (s *Server) Subsz(opts *SubszOptions) (*Subsz, error) { 957 var ( 958 subdetail bool 959 test bool 960 offset int 961 testSub string 962 filterAcc string 963 limit = DefaultSubListSize 964 ) 965 966 if opts != nil { 967 subdetail = opts.Subscriptions 968 offset = opts.Offset 969 if offset < 0 { 970 offset = 0 971 } 972 limit = opts.Limit 973 if limit <= 0 { 974 limit = DefaultSubListSize 975 } 976 if opts.Test != _EMPTY_ { 977 testSub = opts.Test 978 test = true 979 if !IsValidLiteralSubject(testSub) { 980 return nil, fmt.Errorf("invalid test subject, must be valid publish subject: %s", testSub) 981 } 982 } 983 if opts.Account != _EMPTY_ { 984 filterAcc = opts.Account 985 } 986 } 987 988 slStats := &SublistStats{} 989 990 // FIXME(dlc) - Make account aware. 991 sz := &Subsz{s.info.ID, time.Now().UTC(), slStats, 0, offset, limit, nil} 992 993 if subdetail { 994 var raw [4096]*subscription 995 subs := raw[:0] 996 s.accounts.Range(func(k, v interface{}) bool { 997 acc := v.(*Account) 998 if filterAcc != _EMPTY_ && acc.GetName() != filterAcc { 999 return true 1000 } 1001 slStats.add(acc.sl.Stats()) 1002 acc.sl.localSubs(&subs, false) 1003 return true 1004 }) 1005 1006 details := make([]SubDetail, len(subs)) 1007 i := 0 1008 // TODO(dlc) - may be inefficient and could just do normal match when total subs is large and filtering. 1009 for _, sub := range subs { 1010 // Check for filter 1011 if test && !matchLiteral(testSub, string(sub.subject)) { 1012 continue 1013 } 1014 if sub.client == nil { 1015 continue 1016 } 1017 sub.client.mu.Lock() 1018 details[i] = newSubDetail(sub) 1019 sub.client.mu.Unlock() 1020 i++ 1021 } 1022 minoff := sz.Offset 1023 maxoff := sz.Offset + sz.Limit 1024 1025 maxIndex := i 1026 1027 // Make sure these are sane. 1028 if minoff > maxIndex { 1029 minoff = maxIndex 1030 } 1031 if maxoff > maxIndex { 1032 maxoff = maxIndex 1033 } 1034 sz.Subs = details[minoff:maxoff] 1035 sz.Total = len(sz.Subs) 1036 } else { 1037 s.accounts.Range(func(k, v interface{}) bool { 1038 acc := v.(*Account) 1039 if filterAcc != _EMPTY_ && acc.GetName() != filterAcc { 1040 return true 1041 } 1042 slStats.add(acc.sl.Stats()) 1043 return true 1044 }) 1045 } 1046 1047 return sz, nil 1048 } 1049 1050 // HandleSubsz processes HTTP requests for subjects stats. 1051 func (s *Server) HandleSubsz(w http.ResponseWriter, r *http.Request) { 1052 s.mu.Lock() 1053 s.httpReqStats[SubszPath]++ 1054 s.mu.Unlock() 1055 1056 subs, err := decodeBool(w, r, "subs") 1057 if err != nil { 1058 return 1059 } 1060 offset, err := decodeInt(w, r, "offset") 1061 if err != nil { 1062 return 1063 } 1064 limit, err := decodeInt(w, r, "limit") 1065 if err != nil { 1066 return 1067 } 1068 testSub := r.URL.Query().Get("test") 1069 // Filtered account. 1070 filterAcc := r.URL.Query().Get("acc") 1071 1072 subszOpts := &SubszOptions{ 1073 Subscriptions: subs, 1074 Offset: offset, 1075 Limit: limit, 1076 Account: filterAcc, 1077 Test: testSub, 1078 } 1079 1080 st, err := s.Subsz(subszOpts) 1081 if err != nil { 1082 w.WriteHeader(http.StatusBadRequest) 1083 w.Write([]byte(err.Error())) 1084 return 1085 } 1086 1087 var b []byte 1088 1089 if len(st.Subs) == 0 { 1090 b, err = json.MarshalIndent(st.SublistStats, "", " ") 1091 } else { 1092 b, err = json.MarshalIndent(st, "", " ") 1093 } 1094 if err != nil { 1095 s.Errorf("Error marshaling response to /subscriptionsz request: %v", err) 1096 } 1097 1098 // Handle response 1099 ResponseHandler(w, r, b) 1100 } 1101 1102 // HandleStacksz processes HTTP requests for getting stacks 1103 func (s *Server) HandleStacksz(w http.ResponseWriter, r *http.Request) { 1104 // Do not get any lock here that would prevent getting the stacks 1105 // if we were to have a deadlock somewhere. 1106 var defaultBuf [defaultStackBufSize]byte 1107 size := defaultStackBufSize 1108 buf := defaultBuf[:size] 1109 n := 0 1110 for { 1111 n = runtime.Stack(buf, true) 1112 if n < size { 1113 break 1114 } 1115 size *= 2 1116 buf = make([]byte, size) 1117 } 1118 // Handle response 1119 ResponseHandler(w, r, buf[:n]) 1120 } 1121 1122 type monitorIPQueue struct { 1123 Pending int `json:"pending"` 1124 InProgress int `json:"in_progress,omitempty"` 1125 } 1126 1127 func (s *Server) HandleIPQueuesz(w http.ResponseWriter, r *http.Request) { 1128 all, err := decodeBool(w, r, "all") 1129 if err != nil { 1130 return 1131 } 1132 qfilter := r.URL.Query().Get("queues") 1133 1134 queues := map[string]monitorIPQueue{} 1135 1136 s.ipQueues.Range(func(k, v any) bool { 1137 var pending, inProgress int 1138 name := k.(string) 1139 queue, ok := v.(interface { 1140 len() int 1141 inProgress() int64 1142 }) 1143 if ok { 1144 pending = queue.len() 1145 inProgress = int(queue.inProgress()) 1146 } 1147 if !all && (pending == 0 && inProgress == 0) { 1148 return true 1149 } else if qfilter != _EMPTY_ && !strings.Contains(name, qfilter) { 1150 return true 1151 } 1152 queues[name] = monitorIPQueue{Pending: pending, InProgress: inProgress} 1153 return true 1154 }) 1155 1156 b, _ := json.MarshalIndent(queues, "", " ") 1157 ResponseHandler(w, r, b) 1158 } 1159 1160 // Varz will output server information on the monitoring port at /varz. 1161 type Varz struct { 1162 ID string `json:"server_id"` 1163 Name string `json:"server_name"` 1164 Version string `json:"version"` 1165 Proto int `json:"proto"` 1166 GitCommit string `json:"git_commit,omitempty"` 1167 GoVersion string `json:"go"` 1168 Host string `json:"host"` 1169 Port int `json:"port"` 1170 AuthRequired bool `json:"auth_required,omitempty"` 1171 TLSRequired bool `json:"tls_required,omitempty"` 1172 TLSVerify bool `json:"tls_verify,omitempty"` 1173 TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` 1174 IP string `json:"ip,omitempty"` 1175 ClientConnectURLs []string `json:"connect_urls,omitempty"` 1176 WSConnectURLs []string `json:"ws_connect_urls,omitempty"` 1177 MaxConn int `json:"max_connections"` 1178 MaxSubs int `json:"max_subscriptions,omitempty"` 1179 PingInterval time.Duration `json:"ping_interval"` 1180 MaxPingsOut int `json:"ping_max"` 1181 HTTPHost string `json:"http_host"` 1182 HTTPPort int `json:"http_port"` 1183 HTTPBasePath string `json:"http_base_path"` 1184 HTTPSPort int `json:"https_port"` 1185 AuthTimeout float64 `json:"auth_timeout"` 1186 MaxControlLine int32 `json:"max_control_line"` 1187 MaxPayload int `json:"max_payload"` 1188 MaxPending int64 `json:"max_pending"` 1189 Cluster ClusterOptsVarz `json:"cluster,omitempty"` 1190 Gateway GatewayOptsVarz `json:"gateway,omitempty"` 1191 LeafNode LeafNodeOptsVarz `json:"leaf,omitempty"` 1192 MQTT MQTTOptsVarz `json:"mqtt,omitempty"` 1193 Websocket WebsocketOptsVarz `json:"websocket,omitempty"` 1194 JetStream JetStreamVarz `json:"jetstream,omitempty"` 1195 TLSTimeout float64 `json:"tls_timeout"` 1196 WriteDeadline time.Duration `json:"write_deadline"` 1197 Start time.Time `json:"start"` 1198 Now time.Time `json:"now"` 1199 Uptime string `json:"uptime"` 1200 Mem int64 `json:"mem"` 1201 Cores int `json:"cores"` 1202 MaxProcs int `json:"gomaxprocs"` 1203 CPU float64 `json:"cpu"` 1204 Connections int `json:"connections"` 1205 TotalConnections uint64 `json:"total_connections"` 1206 Routes int `json:"routes"` 1207 Remotes int `json:"remotes"` 1208 Leafs int `json:"leafnodes"` 1209 InMsgs int64 `json:"in_msgs"` 1210 OutMsgs int64 `json:"out_msgs"` 1211 InBytes int64 `json:"in_bytes"` 1212 OutBytes int64 `json:"out_bytes"` 1213 SlowConsumers int64 `json:"slow_consumers"` 1214 Subscriptions uint32 `json:"subscriptions"` 1215 HTTPReqStats map[string]uint64 `json:"http_req_stats"` 1216 ConfigLoadTime time.Time `json:"config_load_time"` 1217 Tags jwt.TagList `json:"tags,omitempty"` 1218 TrustedOperatorsJwt []string `json:"trusted_operators_jwt,omitempty"` 1219 TrustedOperatorsClaim []*jwt.OperatorClaims `json:"trusted_operators_claim,omitempty"` 1220 SystemAccount string `json:"system_account,omitempty"` 1221 PinnedAccountFail uint64 `json:"pinned_account_fails,omitempty"` 1222 OCSPResponseCache *OCSPResponseCacheVarz `json:"ocsp_peer_cache,omitempty"` 1223 SlowConsumersStats *SlowConsumersStats `json:"slow_consumer_stats"` 1224 } 1225 1226 // JetStreamVarz contains basic runtime information about jetstream 1227 type JetStreamVarz struct { 1228 Config *JetStreamConfig `json:"config,omitempty"` 1229 Stats *JetStreamStats `json:"stats,omitempty"` 1230 Meta *MetaClusterInfo `json:"meta,omitempty"` 1231 } 1232 1233 // ClusterOptsVarz contains monitoring cluster information 1234 type ClusterOptsVarz struct { 1235 Name string `json:"name,omitempty"` 1236 Host string `json:"addr,omitempty"` 1237 Port int `json:"cluster_port,omitempty"` 1238 AuthTimeout float64 `json:"auth_timeout,omitempty"` 1239 URLs []string `json:"urls,omitempty"` 1240 TLSTimeout float64 `json:"tls_timeout,omitempty"` 1241 TLSRequired bool `json:"tls_required,omitempty"` 1242 TLSVerify bool `json:"tls_verify,omitempty"` 1243 PoolSize int `json:"pool_size,omitempty"` 1244 } 1245 1246 // GatewayOptsVarz contains monitoring gateway information 1247 type GatewayOptsVarz struct { 1248 Name string `json:"name,omitempty"` 1249 Host string `json:"host,omitempty"` 1250 Port int `json:"port,omitempty"` 1251 AuthTimeout float64 `json:"auth_timeout,omitempty"` 1252 TLSTimeout float64 `json:"tls_timeout,omitempty"` 1253 TLSRequired bool `json:"tls_required,omitempty"` 1254 TLSVerify bool `json:"tls_verify,omitempty"` 1255 Advertise string `json:"advertise,omitempty"` 1256 ConnectRetries int `json:"connect_retries,omitempty"` 1257 Gateways []RemoteGatewayOptsVarz `json:"gateways,omitempty"` 1258 RejectUnknown bool `json:"reject_unknown,omitempty"` // config got renamed to reject_unknown_cluster 1259 } 1260 1261 // RemoteGatewayOptsVarz contains monitoring remote gateway information 1262 type RemoteGatewayOptsVarz struct { 1263 Name string `json:"name"` 1264 TLSTimeout float64 `json:"tls_timeout,omitempty"` 1265 URLs []string `json:"urls,omitempty"` 1266 } 1267 1268 // LeafNodeOptsVarz contains monitoring leaf node information 1269 type LeafNodeOptsVarz struct { 1270 Host string `json:"host,omitempty"` 1271 Port int `json:"port,omitempty"` 1272 AuthTimeout float64 `json:"auth_timeout,omitempty"` 1273 TLSTimeout float64 `json:"tls_timeout,omitempty"` 1274 TLSRequired bool `json:"tls_required,omitempty"` 1275 TLSVerify bool `json:"tls_verify,omitempty"` 1276 Remotes []RemoteLeafOptsVarz `json:"remotes,omitempty"` 1277 TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` 1278 } 1279 1280 // DenyRules Contains lists of subjects not allowed to be imported/exported 1281 type DenyRules struct { 1282 Exports []string `json:"exports,omitempty"` 1283 Imports []string `json:"imports,omitempty"` 1284 } 1285 1286 // RemoteLeafOptsVarz contains monitoring remote leaf node information 1287 type RemoteLeafOptsVarz struct { 1288 LocalAccount string `json:"local_account,omitempty"` 1289 TLSTimeout float64 `json:"tls_timeout,omitempty"` 1290 URLs []string `json:"urls,omitempty"` 1291 Deny *DenyRules `json:"deny,omitempty"` 1292 TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` 1293 } 1294 1295 // MQTTOptsVarz contains monitoring MQTT information 1296 type MQTTOptsVarz struct { 1297 Host string `json:"host,omitempty"` 1298 Port int `json:"port,omitempty"` 1299 NoAuthUser string `json:"no_auth_user,omitempty"` 1300 AuthTimeout float64 `json:"auth_timeout,omitempty"` 1301 TLSMap bool `json:"tls_map,omitempty"` 1302 TLSTimeout float64 `json:"tls_timeout,omitempty"` 1303 TLSPinnedCerts []string `json:"tls_pinned_certs,omitempty"` 1304 JsDomain string `json:"js_domain,omitempty"` 1305 AckWait time.Duration `json:"ack_wait,omitempty"` 1306 MaxAckPending uint16 `json:"max_ack_pending,omitempty"` 1307 TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` 1308 } 1309 1310 // WebsocketOptsVarz contains monitoring websocket information 1311 type WebsocketOptsVarz struct { 1312 Host string `json:"host,omitempty"` 1313 Port int `json:"port,omitempty"` 1314 Advertise string `json:"advertise,omitempty"` 1315 NoAuthUser string `json:"no_auth_user,omitempty"` 1316 JWTCookie string `json:"jwt_cookie,omitempty"` 1317 HandshakeTimeout time.Duration `json:"handshake_timeout,omitempty"` 1318 AuthTimeout float64 `json:"auth_timeout,omitempty"` 1319 NoTLS bool `json:"no_tls,omitempty"` 1320 TLSMap bool `json:"tls_map,omitempty"` 1321 TLSPinnedCerts []string `json:"tls_pinned_certs,omitempty"` 1322 SameOrigin bool `json:"same_origin,omitempty"` 1323 AllowedOrigins []string `json:"allowed_origins,omitempty"` 1324 Compression bool `json:"compression,omitempty"` 1325 TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` 1326 } 1327 1328 // OCSPResponseCacheVarz contains OCSP response cache information 1329 type OCSPResponseCacheVarz struct { 1330 Type string `json:"cache_type,omitempty"` 1331 Hits int64 `json:"cache_hits,omitempty"` 1332 Misses int64 `json:"cache_misses,omitempty"` 1333 Responses int64 `json:"cached_responses,omitempty"` 1334 Revokes int64 `json:"cached_revoked_responses,omitempty"` 1335 Goods int64 `json:"cached_good_responses,omitempty"` 1336 Unknowns int64 `json:"cached_unknown_responses,omitempty"` 1337 } 1338 1339 // VarzOptions are the options passed to Varz(). 1340 // Currently, there are no options defined. 1341 type VarzOptions struct{} 1342 1343 // SlowConsumersStats contains information about the slow consumers from different type of connections. 1344 type SlowConsumersStats struct { 1345 Clients uint64 `json:"clients"` 1346 Routes uint64 `json:"routes"` 1347 Gateways uint64 `json:"gateways"` 1348 Leafs uint64 `json:"leafs"` 1349 } 1350 1351 func myUptime(d time.Duration) string { 1352 // Just use total seconds for uptime, and display days / years 1353 tsecs := d / time.Second 1354 tmins := tsecs / 60 1355 thrs := tmins / 60 1356 tdays := thrs / 24 1357 tyrs := tdays / 365 1358 1359 if tyrs > 0 { 1360 return fmt.Sprintf("%dy%dd%dh%dm%ds", tyrs, tdays%365, thrs%24, tmins%60, tsecs%60) 1361 } 1362 if tdays > 0 { 1363 return fmt.Sprintf("%dd%dh%dm%ds", tdays, thrs%24, tmins%60, tsecs%60) 1364 } 1365 if thrs > 0 { 1366 return fmt.Sprintf("%dh%dm%ds", thrs, tmins%60, tsecs%60) 1367 } 1368 if tmins > 0 { 1369 return fmt.Sprintf("%dm%ds", tmins, tsecs%60) 1370 } 1371 return fmt.Sprintf("%ds", tsecs) 1372 } 1373 1374 // HandleRoot will show basic info and links to others handlers. 1375 func (s *Server) HandleRoot(w http.ResponseWriter, r *http.Request) { 1376 // This feels dumb to me, but is required: https://code.google.com/p/go/issues/detail?id=4799 1377 if r.URL.Path != s.httpBasePath { 1378 http.NotFound(w, r) 1379 return 1380 } 1381 s.mu.Lock() 1382 s.httpReqStats[RootPath]++ 1383 s.mu.Unlock() 1384 1385 // Calculate source url. If git set go directly to that tag, otherwise just main. 1386 var srcUrl string 1387 if gitCommit == _EMPTY_ { 1388 srcUrl = "https://github.com/nats-io/nats-server" 1389 } else { 1390 srcUrl = fmt.Sprintf("https://github.com/nats-io/nats-server/tree/%s", gitCommit) 1391 } 1392 1393 fmt.Fprintf(w, `<html lang="en"> 1394 <head> 1395 <link rel="shortcut icon" href="https://nats.io/favicon.ico"> 1396 <style type="text/css"> 1397 body { font-family: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif; font-size: 18; font-weight: light-bold; margin-left: 32px } 1398 a { display:block; margin-left: 7px; padding-bottom: 6px; color: rgb(72 72 92); text-decoration: none } 1399 a:hover { font-weight: 600; color: rgb(59 50 202) } 1400 a.help { display:inline; font-weight: 600; color: rgb(59 50 202); font-size: 20} 1401 a.last { padding-bottom: 16px } 1402 a.version { font-size: 14; font-weight: 400; width: 312px; text-align: right; margin-top: -2rem } 1403 a.version:hover { color: rgb(22 22 32) } 1404 1405 </style> 1406 </head> 1407 <body> 1408 <svg xmlns="http://www.w3.org/2000/svg" role="img" width="325" height="110" viewBox="-4.14 -3.89 436.28 119.03"><style>.st1{fill:#fff}.st2{fill:#34a574}</style><path fill="#27aae1" d="M4.3 84.6h42.2L70.7 107V84.6H103v-80H4.3v80zm15.9-61.3h18.5l35.6 33.2V23.3h11.8v42.9H68.2L32 32.4v33.8H20.2V23.3z"/><path d="M32 32.4l36.2 33.8h17.9V23.3H74.3v33.2L38.7 23.3H20.2v42.9H32z" class="st1"/><path d="M159.8 30.7L147 49h25.6z" class="st2"/><path d="M111.3 84.6H210v-80h-98.7v80zm41-61.5H168l30.8 43.2h-14.1l-5.8-8.3h-38.1l-5.8 8.3h-13.5l30.8-43.2z" class="st2"/><path d="M140.8 57.9h38.1l5.8 8.3h14.1L168 23.1h-15.7l-30.8 43.2H135l5.8-8.4zm19-27.2L172.6 49H147l12.8-18.3z" class="st1"/><path fill="#375c93" d="M218.3 84.6H317v-80h-98.7v80zm15.5-61.3h66.7V33h-27.2v33.2h-12.2V33h-27.3v-9.7z"/><path d="M261.1 66.2h12.2V33h27.2v-9.7h-66.7V33h27.3z" class="st1"/><path fill="#8dc63f" d="M325.3 4.6v80H424v-80h-98.7zm76.5 56.7c-3.2 3.2-10.2 5.7-26.8 5.7-12.3 0-24.1-1.9-30.7-4.7v-10c6.3 2.8 20.1 5.5 30.7 5.5 9.3 0 15.8-.3 17.5-2.1.6-.6.7-1.3.7-2 0-.8-.2-1.3-.7-1.8-1-1-2.6-1.7-17.4-2.1-15.7-.4-23.4-2-27-5.6-1.7-1.7-2.6-4.4-2.6-7.5 0-3.3.6-6.2 3.3-8.9 3.6-3.6 10.7-5.3 25.1-5.3 10.8 0 21.6 1.7 27.3 4v10.1c-6.5-2.8-17.8-4.8-27.2-4.8-10.4 0-14.8.6-16.2 2-.5.5-.8 1.1-.8 1.9 0 .9.2 1.5.7 2 1.3 1.3 6.1 1.7 17.3 1.9 16.4.4 23.5 1.8 27 5.2 1.8 1.8 2.8 4.7 2.8 7.7.1 3.2-.6 6.4-3 8.8z"/><path d="M375.2 39.5c-11.2-.2-16-.6-17.3-1.9-.5-.5-.7-1.1-.7-2 0-.8.3-1.4.8-1.9 1.3-1.3 5.8-2 16.2-2 9.4 0 20.7 2 27.2 4.8v-10c-5.7-2.3-16.6-4-27.3-4-14.5 0-21.6 1.8-25.1 5.3-2.7 2.7-3.3 5.6-3.3 8.9 0 3.1 1 5.8 2.6 7.5 3.6 3.6 11.3 5.2 27 5.6 14.8.4 16.4 1.1 17.4 2.1.5.5.7 1 .7 1.8 0 .7-.1 1.3-.7 2-1.8 1.8-8.3 2.1-17.5 2.1-10.6 0-24.3-2.6-30.7-5.5v10.1c6.6 2.8 18.4 4.7 30.7 4.7 16.6 0 23.6-2.5 26.8-5.7 2.4-2.4 3.1-5.6 3.1-8.9 0-3.1-1-5.9-2.8-7.7-3.6-3.5-10.7-4.9-27.1-5.3z" class="st1"/></svg> 1409 1410 <a href=%s class='version'>v%s</a> 1411 1412 </div> 1413 <br/> 1414 <a href=.%s>General</a> 1415 <a href=.%s>JetStream</a> 1416 <a href=.%s>Connections</a> 1417 <a href=.%s>Accounts</a> 1418 <a href=.%s>Account Stats</a> 1419 <a href=.%s>Subscriptions</a> 1420 <a href=.%s>Routes</a> 1421 <a href=.%s>LeafNodes</a> 1422 <a href=.%s>Gateways</a> 1423 <a href=.%s class=last>Health Probe</a> 1424 <a href=https://docs.nats.io/running-a-nats-service/nats_admin/monitoring class="help">Help</a> 1425 </body> 1426 </html>`, 1427 srcUrl, 1428 VERSION, 1429 s.basePath(VarzPath), 1430 s.basePath(JszPath), 1431 s.basePath(ConnzPath), 1432 s.basePath(AccountzPath), 1433 s.basePath(AccountStatzPath), 1434 s.basePath(SubszPath), 1435 s.basePath(RoutezPath), 1436 s.basePath(LeafzPath), 1437 s.basePath(GatewayzPath), 1438 s.basePath(HealthzPath), 1439 ) 1440 } 1441 1442 func (s *Server) updateJszVarz(js *jetStream, v *JetStreamVarz, doConfig bool) { 1443 if doConfig { 1444 js.mu.RLock() 1445 // We want to snapshot the config since it will then be available outside 1446 // of the js lock. So make a copy first, then point to this copy. 1447 cfg := js.config 1448 v.Config = &cfg 1449 js.mu.RUnlock() 1450 } 1451 v.Stats = js.usageStats() 1452 if mg := js.getMetaGroup(); mg != nil { 1453 if ci := s.raftNodeToClusterInfo(mg); ci != nil { 1454 v.Meta = &MetaClusterInfo{Name: ci.Name, Leader: ci.Leader, Peer: getHash(ci.Leader), Size: mg.ClusterSize()} 1455 if ci.Leader == s.info.Name { 1456 v.Meta.Replicas = ci.Replicas 1457 } 1458 } 1459 } 1460 } 1461 1462 // Varz returns a Varz struct containing the server information. 1463 func (s *Server) Varz(varzOpts *VarzOptions) (*Varz, error) { 1464 var rss, vss int64 1465 var pcpu float64 1466 1467 // We want to do that outside of the lock. 1468 pse.ProcUsage(&pcpu, &rss, &vss) 1469 1470 s.mu.RLock() 1471 // We need to create a new instance of Varz (with no reference 1472 // whatsoever to anything stored in the server) since the user 1473 // has access to the returned value. 1474 v := s.createVarz(pcpu, rss) 1475 s.mu.RUnlock() 1476 1477 if js := s.getJetStream(); js != nil { 1478 s.updateJszVarz(js, &v.JetStream, true) 1479 } 1480 1481 return v, nil 1482 } 1483 1484 // Returns a Varz instance. 1485 // Server lock is held on entry. 1486 func (s *Server) createVarz(pcpu float64, rss int64) *Varz { 1487 info := s.info 1488 opts := s.getOpts() 1489 c := &opts.Cluster 1490 gw := &opts.Gateway 1491 ln := &opts.LeafNode 1492 mqtt := &opts.MQTT 1493 ws := &opts.Websocket 1494 clustTlsReq := c.TLSConfig != nil 1495 gatewayTlsReq := gw.TLSConfig != nil 1496 leafTlsReq := ln.TLSConfig != nil 1497 leafTlsVerify := leafTlsReq && ln.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert 1498 leafTlsOCSPPeerVerify := s.ocspPeerVerify && leafTlsReq && ln.tlsConfigOpts.OCSPPeerConfig != nil && ln.tlsConfigOpts.OCSPPeerConfig.Verify 1499 mqttTlsOCSPPeerVerify := s.ocspPeerVerify && mqtt.TLSConfig != nil && mqtt.tlsConfigOpts.OCSPPeerConfig != nil && mqtt.tlsConfigOpts.OCSPPeerConfig.Verify 1500 wsTlsOCSPPeerVerify := s.ocspPeerVerify && ws.TLSConfig != nil && ws.tlsConfigOpts.OCSPPeerConfig != nil && ws.tlsConfigOpts.OCSPPeerConfig.Verify 1501 varz := &Varz{ 1502 ID: info.ID, 1503 Version: info.Version, 1504 Proto: info.Proto, 1505 GitCommit: info.GitCommit, 1506 GoVersion: info.GoVersion, 1507 Name: info.Name, 1508 Host: info.Host, 1509 Port: info.Port, 1510 IP: info.IP, 1511 HTTPHost: opts.HTTPHost, 1512 HTTPPort: opts.HTTPPort, 1513 HTTPBasePath: opts.HTTPBasePath, 1514 HTTPSPort: opts.HTTPSPort, 1515 Cluster: ClusterOptsVarz{ 1516 Name: info.Cluster, 1517 Host: c.Host, 1518 Port: c.Port, 1519 AuthTimeout: c.AuthTimeout, 1520 TLSTimeout: c.TLSTimeout, 1521 TLSRequired: clustTlsReq, 1522 TLSVerify: clustTlsReq, 1523 PoolSize: opts.Cluster.PoolSize, 1524 }, 1525 Gateway: GatewayOptsVarz{ 1526 Name: gw.Name, 1527 Host: gw.Host, 1528 Port: gw.Port, 1529 AuthTimeout: gw.AuthTimeout, 1530 TLSTimeout: gw.TLSTimeout, 1531 TLSRequired: gatewayTlsReq, 1532 TLSVerify: gatewayTlsReq, 1533 Advertise: gw.Advertise, 1534 ConnectRetries: gw.ConnectRetries, 1535 Gateways: []RemoteGatewayOptsVarz{}, 1536 RejectUnknown: gw.RejectUnknown, 1537 }, 1538 LeafNode: LeafNodeOptsVarz{ 1539 Host: ln.Host, 1540 Port: ln.Port, 1541 AuthTimeout: ln.AuthTimeout, 1542 TLSTimeout: ln.TLSTimeout, 1543 TLSRequired: leafTlsReq, 1544 TLSVerify: leafTlsVerify, 1545 TLSOCSPPeerVerify: leafTlsOCSPPeerVerify, 1546 Remotes: []RemoteLeafOptsVarz{}, 1547 }, 1548 MQTT: MQTTOptsVarz{ 1549 Host: mqtt.Host, 1550 Port: mqtt.Port, 1551 NoAuthUser: mqtt.NoAuthUser, 1552 AuthTimeout: mqtt.AuthTimeout, 1553 TLSMap: mqtt.TLSMap, 1554 TLSTimeout: mqtt.TLSTimeout, 1555 JsDomain: mqtt.JsDomain, 1556 AckWait: mqtt.AckWait, 1557 MaxAckPending: mqtt.MaxAckPending, 1558 TLSOCSPPeerVerify: mqttTlsOCSPPeerVerify, 1559 }, 1560 Websocket: WebsocketOptsVarz{ 1561 Host: ws.Host, 1562 Port: ws.Port, 1563 Advertise: ws.Advertise, 1564 NoAuthUser: ws.NoAuthUser, 1565 JWTCookie: ws.JWTCookie, 1566 AuthTimeout: ws.AuthTimeout, 1567 NoTLS: ws.NoTLS, 1568 TLSMap: ws.TLSMap, 1569 SameOrigin: ws.SameOrigin, 1570 AllowedOrigins: copyStrings(ws.AllowedOrigins), 1571 Compression: ws.Compression, 1572 HandshakeTimeout: ws.HandshakeTimeout, 1573 TLSOCSPPeerVerify: wsTlsOCSPPeerVerify, 1574 }, 1575 Start: s.start.UTC(), 1576 MaxSubs: opts.MaxSubs, 1577 Cores: runtime.NumCPU(), 1578 MaxProcs: runtime.GOMAXPROCS(0), 1579 Tags: opts.Tags, 1580 TrustedOperatorsJwt: opts.operatorJWT, 1581 TrustedOperatorsClaim: opts.TrustedOperators, 1582 } 1583 if len(opts.Routes) > 0 { 1584 varz.Cluster.URLs = urlsToStrings(opts.Routes) 1585 } 1586 if l := len(gw.Gateways); l > 0 { 1587 rgwa := make([]RemoteGatewayOptsVarz, l) 1588 for i, r := range gw.Gateways { 1589 rgwa[i] = RemoteGatewayOptsVarz{ 1590 Name: r.Name, 1591 TLSTimeout: r.TLSTimeout, 1592 } 1593 } 1594 varz.Gateway.Gateways = rgwa 1595 } 1596 if l := len(ln.Remotes); l > 0 { 1597 rlna := make([]RemoteLeafOptsVarz, l) 1598 for i, r := range ln.Remotes { 1599 var deny *DenyRules 1600 if len(r.DenyImports) > 0 || len(r.DenyExports) > 0 { 1601 deny = &DenyRules{ 1602 Imports: r.DenyImports, 1603 Exports: r.DenyExports, 1604 } 1605 } 1606 remoteTlsOCSPPeerVerify := s.ocspPeerVerify && r.tlsConfigOpts != nil && r.tlsConfigOpts.OCSPPeerConfig != nil && r.tlsConfigOpts.OCSPPeerConfig.Verify 1607 1608 rlna[i] = RemoteLeafOptsVarz{ 1609 LocalAccount: r.LocalAccount, 1610 URLs: urlsToStrings(r.URLs), 1611 TLSTimeout: r.TLSTimeout, 1612 Deny: deny, 1613 TLSOCSPPeerVerify: remoteTlsOCSPPeerVerify, 1614 } 1615 } 1616 varz.LeafNode.Remotes = rlna 1617 } 1618 1619 // Finish setting it up with fields that can be updated during 1620 // configuration reload and runtime. 1621 s.updateVarzConfigReloadableFields(varz) 1622 s.updateVarzRuntimeFields(varz, true, pcpu, rss) 1623 return varz 1624 } 1625 1626 func urlsToStrings(urls []*url.URL) []string { 1627 sURLs := make([]string, len(urls)) 1628 for i, u := range urls { 1629 sURLs[i] = u.Host 1630 } 1631 return sURLs 1632 } 1633 1634 // Invoked during configuration reload once options have possibly be changed 1635 // and config load time has been set. If s.varz has not been initialized yet 1636 // (because no pooling of /varz has been made), this function does nothing. 1637 // Server lock is held on entry. 1638 func (s *Server) updateVarzConfigReloadableFields(v *Varz) { 1639 if v == nil { 1640 return 1641 } 1642 opts := s.getOpts() 1643 info := &s.info 1644 v.AuthRequired = info.AuthRequired 1645 v.TLSRequired = info.TLSRequired 1646 v.TLSVerify = info.TLSVerify 1647 v.MaxConn = opts.MaxConn 1648 v.PingInterval = opts.PingInterval 1649 v.MaxPingsOut = opts.MaxPingsOut 1650 v.AuthTimeout = opts.AuthTimeout 1651 v.MaxControlLine = opts.MaxControlLine 1652 v.MaxPayload = int(opts.MaxPayload) 1653 v.MaxPending = opts.MaxPending 1654 v.TLSTimeout = opts.TLSTimeout 1655 v.WriteDeadline = opts.WriteDeadline 1656 v.ConfigLoadTime = s.configTime.UTC() 1657 // Update route URLs if applicable 1658 if s.varzUpdateRouteURLs { 1659 v.Cluster.URLs = urlsToStrings(opts.Routes) 1660 s.varzUpdateRouteURLs = false 1661 } 1662 if s.sys != nil && s.sys.account != nil { 1663 v.SystemAccount = s.sys.account.GetName() 1664 } 1665 v.MQTT.TLSPinnedCerts = getPinnedCertsAsSlice(opts.MQTT.TLSPinnedCerts) 1666 v.Websocket.TLSPinnedCerts = getPinnedCertsAsSlice(opts.Websocket.TLSPinnedCerts) 1667 1668 v.TLSOCSPPeerVerify = s.ocspPeerVerify && v.TLSRequired && s.opts.tlsConfigOpts != nil && s.opts.tlsConfigOpts.OCSPPeerConfig != nil && s.opts.tlsConfigOpts.OCSPPeerConfig.Verify 1669 } 1670 1671 func getPinnedCertsAsSlice(certs PinnedCertSet) []string { 1672 if len(certs) == 0 { 1673 return nil 1674 } 1675 res := make([]string, 0, len(certs)) 1676 for cn := range certs { 1677 res = append(res, cn) 1678 } 1679 return res 1680 } 1681 1682 // Updates the runtime Varz fields, that is, fields that change during 1683 // runtime and that should be updated any time Varz() or polling of /varz 1684 // is done. 1685 // Server lock is held on entry. 1686 func (s *Server) updateVarzRuntimeFields(v *Varz, forceUpdate bool, pcpu float64, rss int64) { 1687 v.Now = time.Now().UTC() 1688 v.Uptime = myUptime(time.Since(s.start)) 1689 v.Mem = rss 1690 v.CPU = pcpu 1691 if l := len(s.info.ClientConnectURLs); l > 0 { 1692 v.ClientConnectURLs = append([]string(nil), s.info.ClientConnectURLs...) 1693 } 1694 if l := len(s.info.WSConnectURLs); l > 0 { 1695 v.WSConnectURLs = append([]string(nil), s.info.WSConnectURLs...) 1696 } 1697 v.Connections = len(s.clients) 1698 v.TotalConnections = s.totalClients 1699 v.Routes = s.numRoutes() 1700 v.Remotes = s.numRemotes() 1701 v.Leafs = len(s.leafs) 1702 v.InMsgs = atomic.LoadInt64(&s.inMsgs) 1703 v.InBytes = atomic.LoadInt64(&s.inBytes) 1704 v.OutMsgs = atomic.LoadInt64(&s.outMsgs) 1705 v.OutBytes = atomic.LoadInt64(&s.outBytes) 1706 v.SlowConsumers = atomic.LoadInt64(&s.slowConsumers) 1707 v.SlowConsumersStats = &SlowConsumersStats{ 1708 Clients: s.NumSlowConsumersClients(), 1709 Routes: s.NumSlowConsumersRoutes(), 1710 Gateways: s.NumSlowConsumersGateways(), 1711 Leafs: s.NumSlowConsumersLeafs(), 1712 } 1713 v.PinnedAccountFail = atomic.LoadUint64(&s.pinnedAccFail) 1714 1715 // Make sure to reset in case we are re-using. 1716 v.Subscriptions = 0 1717 s.accounts.Range(func(k, val interface{}) bool { 1718 acc := val.(*Account) 1719 v.Subscriptions += acc.sl.Count() 1720 return true 1721 }) 1722 1723 v.HTTPReqStats = make(map[string]uint64, len(s.httpReqStats)) 1724 for key, val := range s.httpReqStats { 1725 v.HTTPReqStats[key] = val 1726 } 1727 1728 // Update Gateway remote urls if applicable 1729 gw := s.gateway 1730 gw.RLock() 1731 if gw.enabled { 1732 for i := 0; i < len(v.Gateway.Gateways); i++ { 1733 g := &v.Gateway.Gateways[i] 1734 rgw := gw.remotes[g.Name] 1735 if rgw != nil { 1736 rgw.RLock() 1737 // forceUpdate is needed if user calls Varz() programmatically, 1738 // since we need to create a new instance every time and the 1739 // gateway's varzUpdateURLs may have been set to false after 1740 // a web /varz inspection. 1741 if forceUpdate || rgw.varzUpdateURLs { 1742 // Make reuse of backend array 1743 g.URLs = g.URLs[:0] 1744 // rgw.urls is a map[string]*url.URL where the key is 1745 // already in the right format (host:port, without any 1746 // user info present). 1747 for u := range rgw.urls { 1748 g.URLs = append(g.URLs, u) 1749 } 1750 rgw.varzUpdateURLs = false 1751 } 1752 rgw.RUnlock() 1753 } else if g.Name == gw.name && len(gw.ownCfgURLs) > 0 { 1754 // This is a remote that correspond to this very same server. 1755 // We report the URLs that were configured (if any). 1756 // Since we don't support changes to the gateway configuration 1757 // at this time, we could do this only if g.URLs has not been already 1758 // set, but let's do it regardless in case we add support for 1759 // gateway config reload. 1760 g.URLs = g.URLs[:0] 1761 g.URLs = append(g.URLs, gw.ownCfgURLs...) 1762 } 1763 } 1764 } 1765 gw.RUnlock() 1766 1767 if s.ocsprc != nil && s.ocsprc.Type() != "none" { 1768 stats := s.ocsprc.Stats() 1769 if stats != nil { 1770 v.OCSPResponseCache = &OCSPResponseCacheVarz{ 1771 s.ocsprc.Type(), 1772 stats.Hits, 1773 stats.Misses, 1774 stats.Responses, 1775 stats.Revokes, 1776 stats.Goods, 1777 stats.Unknowns, 1778 } 1779 } 1780 } 1781 } 1782 1783 // HandleVarz will process HTTP requests for server information. 1784 func (s *Server) HandleVarz(w http.ResponseWriter, r *http.Request) { 1785 var rss, vss int64 1786 var pcpu float64 1787 1788 // We want to do that outside of the lock. 1789 pse.ProcUsage(&pcpu, &rss, &vss) 1790 1791 // In response to http requests, we want to minimize mem copies 1792 // so we use an object stored in the server. Creating/collecting 1793 // server metrics is done under server lock, but we don't want 1794 // to marshal under that lock. Still, we need to prevent concurrent 1795 // http requests to /varz to update s.varz while marshal is 1796 // happening, so we need a new lock that serialize those http 1797 // requests and include marshaling. 1798 s.varzMu.Lock() 1799 1800 // Use server lock to create/update the server's varz object. 1801 s.mu.Lock() 1802 var created bool 1803 s.httpReqStats[VarzPath]++ 1804 if s.varz == nil { 1805 s.varz = s.createVarz(pcpu, rss) 1806 created = true 1807 } else { 1808 s.updateVarzRuntimeFields(s.varz, false, pcpu, rss) 1809 } 1810 s.mu.Unlock() 1811 // Since locking is jetStream -> Server, need to update jetstream 1812 // varz outside of server lock. 1813 1814 if js := s.getJetStream(); js != nil { 1815 var v JetStreamVarz 1816 // Work on stack variable 1817 s.updateJszVarz(js, &v, created) 1818 // Now update server's varz 1819 s.mu.RLock() 1820 sv := &s.varz.JetStream 1821 if created { 1822 sv.Config = v.Config 1823 } 1824 sv.Stats = v.Stats 1825 sv.Meta = v.Meta 1826 s.mu.RUnlock() 1827 } 1828 1829 // Do the marshaling outside of server lock, but under varzMu lock. 1830 b, err := json.MarshalIndent(s.varz, "", " ") 1831 s.varzMu.Unlock() 1832 1833 if err != nil { 1834 s.Errorf("Error marshaling response to /varz request: %v", err) 1835 } 1836 1837 // Handle response 1838 ResponseHandler(w, r, b) 1839 } 1840 1841 // GatewayzOptions are the options passed to Gatewayz() 1842 type GatewayzOptions struct { 1843 // Name will output only remote gateways with this name 1844 Name string `json:"name"` 1845 1846 // Accounts indicates if accounts with its interest should be included in the results. 1847 Accounts bool `json:"accounts"` 1848 1849 // AccountName will limit the list of accounts to that account name (makes Accounts implicit) 1850 AccountName string `json:"account_name"` 1851 } 1852 1853 // Gatewayz represents detailed information on Gateways 1854 type Gatewayz struct { 1855 ID string `json:"server_id"` 1856 Now time.Time `json:"now"` 1857 Name string `json:"name,omitempty"` 1858 Host string `json:"host,omitempty"` 1859 Port int `json:"port,omitempty"` 1860 OutboundGateways map[string]*RemoteGatewayz `json:"outbound_gateways"` 1861 InboundGateways map[string][]*RemoteGatewayz `json:"inbound_gateways"` 1862 } 1863 1864 // RemoteGatewayz represents information about an outbound connection to a gateway 1865 type RemoteGatewayz struct { 1866 IsConfigured bool `json:"configured"` 1867 Connection *ConnInfo `json:"connection,omitempty"` 1868 Accounts []*AccountGatewayz `json:"accounts,omitempty"` 1869 } 1870 1871 // AccountGatewayz represents interest mode for this account 1872 type AccountGatewayz struct { 1873 Name string `json:"name"` 1874 InterestMode string `json:"interest_mode"` 1875 NoInterestCount int `json:"no_interest_count,omitempty"` 1876 InterestOnlyThreshold int `json:"interest_only_threshold,omitempty"` 1877 TotalSubscriptions int `json:"num_subs,omitempty"` 1878 NumQueueSubscriptions int `json:"num_queue_subs,omitempty"` 1879 } 1880 1881 // Gatewayz returns a Gatewayz struct containing information about gateways. 1882 func (s *Server) Gatewayz(opts *GatewayzOptions) (*Gatewayz, error) { 1883 srvID := s.ID() 1884 now := time.Now().UTC() 1885 gw := s.gateway 1886 gw.RLock() 1887 if !gw.enabled || gw.info == nil { 1888 gw.RUnlock() 1889 gwz := &Gatewayz{ 1890 ID: srvID, 1891 Now: now, 1892 OutboundGateways: map[string]*RemoteGatewayz{}, 1893 InboundGateways: map[string][]*RemoteGatewayz{}, 1894 } 1895 return gwz, nil 1896 } 1897 // Here gateways are enabled, so fill up more. 1898 gwz := &Gatewayz{ 1899 ID: srvID, 1900 Now: now, 1901 Name: gw.name, 1902 Host: gw.info.Host, 1903 Port: gw.info.Port, 1904 } 1905 gw.RUnlock() 1906 1907 gwz.OutboundGateways = s.createOutboundsRemoteGatewayz(opts, now) 1908 gwz.InboundGateways = s.createInboundsRemoteGatewayz(opts, now) 1909 1910 return gwz, nil 1911 } 1912 1913 // Based on give options struct, returns if there is a filtered 1914 // Gateway Name and if we should do report Accounts. 1915 // Note that if Accounts is false but AccountName is not empty, 1916 // then Accounts is implicitly set to true. 1917 func getMonitorGWOptions(opts *GatewayzOptions) (string, bool) { 1918 var name string 1919 var accs bool 1920 if opts != nil { 1921 if opts.Name != _EMPTY_ { 1922 name = opts.Name 1923 } 1924 accs = opts.Accounts 1925 if !accs && opts.AccountName != _EMPTY_ { 1926 accs = true 1927 } 1928 } 1929 return name, accs 1930 } 1931 1932 // Returns a map of gateways outbound connections. 1933 // Based on options, will include a single or all gateways, 1934 // with no/single/or all accounts interest information. 1935 func (s *Server) createOutboundsRemoteGatewayz(opts *GatewayzOptions, now time.Time) map[string]*RemoteGatewayz { 1936 targetGWName, doAccs := getMonitorGWOptions(opts) 1937 1938 if targetGWName != _EMPTY_ { 1939 c := s.getOutboundGatewayConnection(targetGWName) 1940 if c == nil { 1941 return nil 1942 } 1943 outbounds := make(map[string]*RemoteGatewayz, 1) 1944 _, rgw := createOutboundRemoteGatewayz(c, opts, now, doAccs) 1945 outbounds[targetGWName] = rgw 1946 return outbounds 1947 } 1948 1949 var connsa [16]*client 1950 var conns = connsa[:0] 1951 1952 s.getOutboundGatewayConnections(&conns) 1953 1954 outbounds := make(map[string]*RemoteGatewayz, len(conns)) 1955 for _, c := range conns { 1956 name, rgw := createOutboundRemoteGatewayz(c, opts, now, doAccs) 1957 if rgw != nil { 1958 outbounds[name] = rgw 1959 } 1960 } 1961 return outbounds 1962 } 1963 1964 // Returns a RemoteGatewayz for a given outbound gw connection 1965 func createOutboundRemoteGatewayz(c *client, opts *GatewayzOptions, now time.Time, doAccs bool) (string, *RemoteGatewayz) { 1966 var name string 1967 var rgw *RemoteGatewayz 1968 1969 c.mu.Lock() 1970 if c.gw != nil { 1971 rgw = &RemoteGatewayz{} 1972 if doAccs { 1973 rgw.Accounts = createOutboundAccountsGatewayz(opts, c.gw) 1974 } 1975 if c.gw.cfg != nil { 1976 rgw.IsConfigured = !c.gw.cfg.isImplicit() 1977 } 1978 rgw.Connection = &ConnInfo{} 1979 rgw.Connection.fill(c, c.nc, now, false) 1980 name = c.gw.name 1981 } 1982 c.mu.Unlock() 1983 1984 return name, rgw 1985 } 1986 1987 // Returns the list of accounts for this outbound gateway connection. 1988 // Based on the options, it will be a single or all accounts for 1989 // this outbound. 1990 func createOutboundAccountsGatewayz(opts *GatewayzOptions, gw *gateway) []*AccountGatewayz { 1991 if gw.outsim == nil { 1992 return nil 1993 } 1994 1995 var accName string 1996 if opts != nil { 1997 accName = opts.AccountName 1998 } 1999 if accName != _EMPTY_ { 2000 ei, ok := gw.outsim.Load(accName) 2001 if !ok { 2002 return nil 2003 } 2004 a := createAccountOutboundGatewayz(accName, ei) 2005 return []*AccountGatewayz{a} 2006 } 2007 2008 accs := make([]*AccountGatewayz, 0, 4) 2009 gw.outsim.Range(func(k, v interface{}) bool { 2010 name := k.(string) 2011 a := createAccountOutboundGatewayz(name, v) 2012 accs = append(accs, a) 2013 return true 2014 }) 2015 return accs 2016 } 2017 2018 // Returns an AccountGatewayz for this gateway outbound connection 2019 func createAccountOutboundGatewayz(name string, ei interface{}) *AccountGatewayz { 2020 a := &AccountGatewayz{ 2021 Name: name, 2022 InterestOnlyThreshold: gatewayMaxRUnsubBeforeSwitch, 2023 } 2024 if ei != nil { 2025 e := ei.(*outsie) 2026 e.RLock() 2027 a.InterestMode = e.mode.String() 2028 a.NoInterestCount = len(e.ni) 2029 a.NumQueueSubscriptions = e.qsubs 2030 a.TotalSubscriptions = int(e.sl.Count()) 2031 e.RUnlock() 2032 } else { 2033 a.InterestMode = Optimistic.String() 2034 } 2035 return a 2036 } 2037 2038 // Returns a map of gateways inbound connections. 2039 // Each entry is an array of RemoteGatewayz since a given server 2040 // may have more than one inbound from the same remote gateway. 2041 // Based on options, will include a single or all gateways, 2042 // with no/single/or all accounts interest information. 2043 func (s *Server) createInboundsRemoteGatewayz(opts *GatewayzOptions, now time.Time) map[string][]*RemoteGatewayz { 2044 targetGWName, doAccs := getMonitorGWOptions(opts) 2045 2046 var connsa [16]*client 2047 var conns = connsa[:0] 2048 s.getInboundGatewayConnections(&conns) 2049 2050 m := make(map[string][]*RemoteGatewayz) 2051 for _, c := range conns { 2052 c.mu.Lock() 2053 if c.gw != nil && (targetGWName == _EMPTY_ || targetGWName == c.gw.name) { 2054 igws := m[c.gw.name] 2055 if igws == nil { 2056 igws = make([]*RemoteGatewayz, 0, 2) 2057 } 2058 rgw := &RemoteGatewayz{} 2059 if doAccs { 2060 rgw.Accounts = createInboundAccountsGatewayz(opts, c.gw) 2061 } 2062 rgw.Connection = &ConnInfo{} 2063 rgw.Connection.fill(c, c.nc, now, false) 2064 igws = append(igws, rgw) 2065 m[c.gw.name] = igws 2066 } 2067 c.mu.Unlock() 2068 } 2069 return m 2070 } 2071 2072 // Returns the list of accounts for this inbound gateway connection. 2073 // Based on the options, it will be a single or all accounts for 2074 // this inbound. 2075 func createInboundAccountsGatewayz(opts *GatewayzOptions, gw *gateway) []*AccountGatewayz { 2076 if gw.insim == nil { 2077 return nil 2078 } 2079 2080 var accName string 2081 if opts != nil { 2082 accName = opts.AccountName 2083 } 2084 if accName != _EMPTY_ { 2085 e, ok := gw.insim[accName] 2086 if !ok { 2087 return nil 2088 } 2089 a := createInboundAccountGatewayz(accName, e) 2090 return []*AccountGatewayz{a} 2091 } 2092 2093 accs := make([]*AccountGatewayz, 0, 4) 2094 for name, e := range gw.insim { 2095 a := createInboundAccountGatewayz(name, e) 2096 accs = append(accs, a) 2097 } 2098 return accs 2099 } 2100 2101 // Returns an AccountGatewayz for this gateway inbound connection 2102 func createInboundAccountGatewayz(name string, e *insie) *AccountGatewayz { 2103 a := &AccountGatewayz{ 2104 Name: name, 2105 InterestOnlyThreshold: gatewayMaxRUnsubBeforeSwitch, 2106 } 2107 if e != nil { 2108 a.InterestMode = e.mode.String() 2109 a.NoInterestCount = len(e.ni) 2110 } else { 2111 a.InterestMode = Optimistic.String() 2112 } 2113 return a 2114 } 2115 2116 // HandleGatewayz process HTTP requests for route information. 2117 func (s *Server) HandleGatewayz(w http.ResponseWriter, r *http.Request) { 2118 s.mu.Lock() 2119 s.httpReqStats[GatewayzPath]++ 2120 s.mu.Unlock() 2121 2122 accs, err := decodeBool(w, r, "accs") 2123 if err != nil { 2124 return 2125 } 2126 gwName := r.URL.Query().Get("gw_name") 2127 accName := r.URL.Query().Get("acc_name") 2128 if accName != _EMPTY_ { 2129 accs = true 2130 } 2131 2132 opts := &GatewayzOptions{ 2133 Name: gwName, 2134 Accounts: accs, 2135 AccountName: accName, 2136 } 2137 gw, err := s.Gatewayz(opts) 2138 if err != nil { 2139 w.WriteHeader(http.StatusBadRequest) 2140 w.Write([]byte(err.Error())) 2141 return 2142 } 2143 b, err := json.MarshalIndent(gw, "", " ") 2144 if err != nil { 2145 s.Errorf("Error marshaling response to /gatewayz request: %v", err) 2146 } 2147 2148 // Handle response 2149 ResponseHandler(w, r, b) 2150 } 2151 2152 // Leafz represents detailed information on Leafnodes. 2153 type Leafz struct { 2154 ID string `json:"server_id"` 2155 Now time.Time `json:"now"` 2156 NumLeafs int `json:"leafnodes"` 2157 Leafs []*LeafInfo `json:"leafs"` 2158 } 2159 2160 // LeafzOptions are options passed to Leafz 2161 type LeafzOptions struct { 2162 // Subscriptions indicates that Leafz will return a leafnode's subscriptions 2163 Subscriptions bool `json:"subscriptions"` 2164 Account string `json:"account"` 2165 } 2166 2167 // LeafInfo has detailed information on each remote leafnode connection. 2168 type LeafInfo struct { 2169 Name string `json:"name"` 2170 IsSpoke bool `json:"is_spoke"` 2171 Account string `json:"account"` 2172 IP string `json:"ip"` 2173 Port int `json:"port"` 2174 RTT string `json:"rtt,omitempty"` 2175 InMsgs int64 `json:"in_msgs"` 2176 OutMsgs int64 `json:"out_msgs"` 2177 InBytes int64 `json:"in_bytes"` 2178 OutBytes int64 `json:"out_bytes"` 2179 NumSubs uint32 `json:"subscriptions"` 2180 Subs []string `json:"subscriptions_list,omitempty"` 2181 Compression string `json:"compression,omitempty"` 2182 } 2183 2184 // Leafz returns a Leafz structure containing information about leafnodes. 2185 func (s *Server) Leafz(opts *LeafzOptions) (*Leafz, error) { 2186 // Grab leafnodes 2187 var lconns []*client 2188 s.mu.Lock() 2189 if len(s.leafs) > 0 { 2190 lconns = make([]*client, 0, len(s.leafs)) 2191 for _, ln := range s.leafs { 2192 if opts != nil && opts.Account != _EMPTY_ { 2193 ln.mu.Lock() 2194 ok := ln.acc.Name == opts.Account 2195 ln.mu.Unlock() 2196 if !ok { 2197 continue 2198 } 2199 } 2200 lconns = append(lconns, ln) 2201 } 2202 } 2203 s.mu.Unlock() 2204 2205 var leafnodes []*LeafInfo 2206 if len(lconns) > 0 { 2207 leafnodes = make([]*LeafInfo, 0, len(lconns)) 2208 for _, ln := range lconns { 2209 ln.mu.Lock() 2210 lni := &LeafInfo{ 2211 Name: ln.leaf.remoteServer, 2212 IsSpoke: ln.isSpokeLeafNode(), 2213 Account: ln.acc.Name, 2214 IP: ln.host, 2215 Port: int(ln.port), 2216 RTT: ln.getRTT().String(), 2217 InMsgs: atomic.LoadInt64(&ln.inMsgs), 2218 OutMsgs: ln.outMsgs, 2219 InBytes: atomic.LoadInt64(&ln.inBytes), 2220 OutBytes: ln.outBytes, 2221 NumSubs: uint32(len(ln.subs)), 2222 Compression: ln.leaf.compression, 2223 } 2224 if opts != nil && opts.Subscriptions { 2225 lni.Subs = make([]string, 0, len(ln.subs)) 2226 for _, sub := range ln.subs { 2227 lni.Subs = append(lni.Subs, string(sub.subject)) 2228 } 2229 } 2230 ln.mu.Unlock() 2231 leafnodes = append(leafnodes, lni) 2232 } 2233 } 2234 return &Leafz{ 2235 ID: s.ID(), 2236 Now: time.Now().UTC(), 2237 NumLeafs: len(leafnodes), 2238 Leafs: leafnodes, 2239 }, nil 2240 } 2241 2242 // HandleLeafz process HTTP requests for leafnode information. 2243 func (s *Server) HandleLeafz(w http.ResponseWriter, r *http.Request) { 2244 s.mu.Lock() 2245 s.httpReqStats[LeafzPath]++ 2246 s.mu.Unlock() 2247 2248 subs, err := decodeBool(w, r, "subs") 2249 if err != nil { 2250 return 2251 } 2252 l, err := s.Leafz(&LeafzOptions{subs, r.URL.Query().Get("acc")}) 2253 if err != nil { 2254 w.WriteHeader(http.StatusBadRequest) 2255 w.Write([]byte(err.Error())) 2256 return 2257 } 2258 b, err := json.MarshalIndent(l, "", " ") 2259 if err != nil { 2260 s.Errorf("Error marshaling response to /leafz request: %v", err) 2261 } 2262 2263 // Handle response 2264 ResponseHandler(w, r, b) 2265 } 2266 2267 // Leafz represents detailed information on Leafnodes. 2268 type AccountStatz struct { 2269 ID string `json:"server_id"` 2270 Now time.Time `json:"now"` 2271 Accounts []*AccountStat `json:"account_statz"` 2272 } 2273 2274 // LeafzOptions are options passed to Leafz 2275 type AccountStatzOptions struct { 2276 Accounts []string `json:"accounts"` 2277 IncludeUnused bool `json:"include_unused"` 2278 } 2279 2280 // Leafz returns a AccountStatz structure containing summary information about accounts. 2281 func (s *Server) AccountStatz(opts *AccountStatzOptions) (*AccountStatz, error) { 2282 stz := &AccountStatz{ 2283 ID: s.ID(), 2284 Now: time.Now().UTC(), 2285 Accounts: []*AccountStat{}, 2286 } 2287 if opts == nil || len(opts.Accounts) == 0 { 2288 s.accounts.Range(func(key, a interface{}) bool { 2289 acc := a.(*Account) 2290 acc.mu.RLock() 2291 if opts.IncludeUnused || acc.numLocalConnections() != 0 { 2292 stz.Accounts = append(stz.Accounts, acc.statz()) 2293 } 2294 acc.mu.RUnlock() 2295 return true 2296 }) 2297 } else { 2298 for _, a := range opts.Accounts { 2299 if acc, ok := s.accounts.Load(a); ok { 2300 acc := acc.(*Account) 2301 acc.mu.RLock() 2302 if opts.IncludeUnused || acc.numLocalConnections() != 0 { 2303 stz.Accounts = append(stz.Accounts, acc.statz()) 2304 } 2305 acc.mu.RUnlock() 2306 } 2307 } 2308 } 2309 return stz, nil 2310 } 2311 2312 // HandleAccountStatz process HTTP requests for statz information of all accounts. 2313 func (s *Server) HandleAccountStatz(w http.ResponseWriter, r *http.Request) { 2314 s.mu.Lock() 2315 s.httpReqStats[AccountStatzPath]++ 2316 s.mu.Unlock() 2317 2318 unused, err := decodeBool(w, r, "unused") 2319 if err != nil { 2320 return 2321 } 2322 2323 l, err := s.AccountStatz(&AccountStatzOptions{IncludeUnused: unused}) 2324 if err != nil { 2325 w.WriteHeader(http.StatusBadRequest) 2326 w.Write([]byte(err.Error())) 2327 return 2328 } 2329 b, err := json.MarshalIndent(l, "", " ") 2330 if err != nil { 2331 s.Errorf("Error marshaling response to %s request: %v", AccountStatzPath, err) 2332 return 2333 } 2334 2335 // Handle response 2336 ResponseHandler(w, r, b) 2337 } 2338 2339 // ResponseHandler handles responses for monitoring routes. 2340 func ResponseHandler(w http.ResponseWriter, r *http.Request, data []byte) { 2341 handleResponse(http.StatusOK, w, r, data) 2342 } 2343 2344 // handleResponse handles responses for monitoring routes with a specific HTTP status code. 2345 func handleResponse(code int, w http.ResponseWriter, r *http.Request, data []byte) { 2346 // Get callback from request 2347 callback := r.URL.Query().Get("callback") 2348 if callback != _EMPTY_ { 2349 // Response for JSONP 2350 w.Header().Set("Content-Type", "application/javascript") 2351 w.WriteHeader(code) 2352 fmt.Fprintf(w, "%s(%s)", callback, data) 2353 } else { 2354 // Otherwise JSON 2355 w.Header().Set("Content-Type", "application/json") 2356 w.Header().Set("Access-Control-Allow-Origin", "*") 2357 w.WriteHeader(code) 2358 w.Write(data) 2359 } 2360 } 2361 2362 func (reason ClosedState) String() string { 2363 switch reason { 2364 case ClientClosed: 2365 return "Client Closed" 2366 case AuthenticationTimeout: 2367 return "Authentication Timeout" 2368 case AuthenticationViolation: 2369 return "Authentication Failure" 2370 case TLSHandshakeError: 2371 return "TLS Handshake Failure" 2372 case SlowConsumerPendingBytes: 2373 return "Slow Consumer (Pending Bytes)" 2374 case SlowConsumerWriteDeadline: 2375 return "Slow Consumer (Write Deadline)" 2376 case WriteError: 2377 return "Write Error" 2378 case ReadError: 2379 return "Read Error" 2380 case ParseError: 2381 return "Parse Error" 2382 case StaleConnection: 2383 return "Stale Connection" 2384 case ProtocolViolation: 2385 return "Protocol Violation" 2386 case BadClientProtocolVersion: 2387 return "Bad Client Protocol Version" 2388 case WrongPort: 2389 return "Incorrect Port" 2390 case MaxConnectionsExceeded: 2391 return "Maximum Connections Exceeded" 2392 case MaxAccountConnectionsExceeded: 2393 return "Maximum Account Connections Exceeded" 2394 case MaxPayloadExceeded: 2395 return "Maximum Message Payload Exceeded" 2396 case MaxControlLineExceeded: 2397 return "Maximum Control Line Exceeded" 2398 case MaxSubscriptionsExceeded: 2399 return "Maximum Subscriptions Exceeded" 2400 case DuplicateRoute: 2401 return "Duplicate Route" 2402 case RouteRemoved: 2403 return "Route Removed" 2404 case ServerShutdown: 2405 return "Server Shutdown" 2406 case AuthenticationExpired: 2407 return "Authentication Expired" 2408 case WrongGateway: 2409 return "Wrong Gateway" 2410 case MissingAccount: 2411 return "Missing Account" 2412 case Revocation: 2413 return "Credentials Revoked" 2414 case InternalClient: 2415 return "Internal Client" 2416 case MsgHeaderViolation: 2417 return "Message Header Violation" 2418 case NoRespondersRequiresHeaders: 2419 return "No Responders Requires Headers" 2420 case ClusterNameConflict: 2421 return "Cluster Name Conflict" 2422 case DuplicateRemoteLeafnodeConnection: 2423 return "Duplicate Remote LeafNode Connection" 2424 case DuplicateClientID: 2425 return "Duplicate Client ID" 2426 case DuplicateServerName: 2427 return "Duplicate Server Name" 2428 case MinimumVersionRequired: 2429 return "Minimum Version Required" 2430 case ClusterNamesIdentical: 2431 return "Cluster Names Identical" 2432 case Kicked: 2433 return "Kicked" 2434 } 2435 2436 return "Unknown State" 2437 } 2438 2439 // AccountzOptions are options passed to Accountz 2440 type AccountzOptions struct { 2441 // Account indicates that Accountz will return details for the account 2442 Account string `json:"account"` 2443 } 2444 2445 func newExtServiceLatency(l *serviceLatency) *jwt.ServiceLatency { 2446 if l == nil { 2447 return nil 2448 } 2449 return &jwt.ServiceLatency{ 2450 Sampling: jwt.SamplingRate(l.sampling), 2451 Results: jwt.Subject(l.subject), 2452 } 2453 } 2454 2455 type ExtImport struct { 2456 jwt.Import 2457 Invalid bool `json:"invalid"` 2458 Share bool `json:"share"` 2459 Tracking bool `json:"tracking"` 2460 TrackingHdr http.Header `json:"tracking_header,omitempty"` 2461 Latency *jwt.ServiceLatency `json:"latency,omitempty"` 2462 M1 *ServiceLatency `json:"m1,omitempty"` 2463 } 2464 2465 type ExtExport struct { 2466 jwt.Export 2467 ApprovedAccounts []string `json:"approved_accounts,omitempty"` 2468 RevokedAct map[string]time.Time `json:"revoked_activations,omitempty"` 2469 } 2470 2471 type ExtVrIssues struct { 2472 Description string `json:"description"` 2473 Blocking bool `json:"blocking"` 2474 Time bool `json:"time_check"` 2475 } 2476 2477 type ExtMap map[string][]*MapDest 2478 2479 type AccountInfo struct { 2480 AccountName string `json:"account_name"` 2481 LastUpdate time.Time `json:"update_time,omitempty"` 2482 IsSystem bool `json:"is_system,omitempty"` 2483 Expired bool `json:"expired"` 2484 Complete bool `json:"complete"` 2485 JetStream bool `json:"jetstream_enabled"` 2486 LeafCnt int `json:"leafnode_connections"` 2487 ClientCnt int `json:"client_connections"` 2488 SubCnt uint32 `json:"subscriptions"` 2489 Mappings ExtMap `json:"mappings,omitempty"` 2490 Exports []ExtExport `json:"exports,omitempty"` 2491 Imports []ExtImport `json:"imports,omitempty"` 2492 Jwt string `json:"jwt,omitempty"` 2493 IssuerKey string `json:"issuer_key,omitempty"` 2494 NameTag string `json:"name_tag,omitempty"` 2495 Tags jwt.TagList `json:"tags,omitempty"` 2496 Claim *jwt.AccountClaims `json:"decoded_jwt,omitempty"` 2497 Vr []ExtVrIssues `json:"validation_result_jwt,omitempty"` 2498 RevokedUser map[string]time.Time `json:"revoked_user,omitempty"` 2499 Sublist *SublistStats `json:"sublist_stats,omitempty"` 2500 Responses map[string]ExtImport `json:"responses,omitempty"` 2501 } 2502 2503 type Accountz struct { 2504 ID string `json:"server_id"` 2505 Now time.Time `json:"now"` 2506 SystemAccount string `json:"system_account,omitempty"` 2507 Accounts []string `json:"accounts,omitempty"` 2508 Account *AccountInfo `json:"account_detail,omitempty"` 2509 } 2510 2511 // HandleAccountz process HTTP requests for account information. 2512 func (s *Server) HandleAccountz(w http.ResponseWriter, r *http.Request) { 2513 s.mu.Lock() 2514 s.httpReqStats[AccountzPath]++ 2515 s.mu.Unlock() 2516 if l, err := s.Accountz(&AccountzOptions{r.URL.Query().Get("acc")}); err != nil { 2517 w.WriteHeader(http.StatusBadRequest) 2518 w.Write([]byte(err.Error())) 2519 } else if b, err := json.MarshalIndent(l, "", " "); err != nil { 2520 s.Errorf("Error marshaling response to %s request: %v", AccountzPath, err) 2521 w.WriteHeader(http.StatusBadRequest) 2522 w.Write([]byte(err.Error())) 2523 } else { 2524 ResponseHandler(w, r, b) // Handle response 2525 } 2526 } 2527 2528 func (s *Server) Accountz(optz *AccountzOptions) (*Accountz, error) { 2529 a := &Accountz{ 2530 ID: s.ID(), 2531 Now: time.Now().UTC(), 2532 } 2533 if sacc := s.SystemAccount(); sacc != nil { 2534 a.SystemAccount = sacc.GetName() 2535 } 2536 if optz == nil || optz.Account == _EMPTY_ { 2537 a.Accounts = []string{} 2538 s.accounts.Range(func(key, value interface{}) bool { 2539 a.Accounts = append(a.Accounts, key.(string)) 2540 return true 2541 }) 2542 return a, nil 2543 } 2544 aInfo, err := s.accountInfo(optz.Account) 2545 if err != nil { 2546 return nil, err 2547 } 2548 a.Account = aInfo 2549 return a, nil 2550 } 2551 2552 func newExtImport(v *serviceImport) ExtImport { 2553 imp := ExtImport{ 2554 Invalid: true, 2555 Import: jwt.Import{Type: jwt.Service}, 2556 } 2557 if v != nil { 2558 imp.Share = v.share 2559 imp.Tracking = v.tracking 2560 imp.Invalid = v.invalid 2561 imp.Import = jwt.Import{ 2562 Subject: jwt.Subject(v.to), 2563 Account: v.acc.Name, 2564 Type: jwt.Service, 2565 // Deprecated so we duplicate. Use LocalSubject. 2566 To: jwt.Subject(v.from), 2567 LocalSubject: jwt.RenamingSubject(v.from), 2568 } 2569 imp.TrackingHdr = v.trackingHdr 2570 imp.Latency = newExtServiceLatency(v.latency) 2571 if v.m1 != nil { 2572 m1 := *v.m1 2573 imp.M1 = &m1 2574 } 2575 } 2576 return imp 2577 } 2578 2579 func (s *Server) accountInfo(accName string) (*AccountInfo, error) { 2580 var a *Account 2581 if v, ok := s.accounts.Load(accName); !ok { 2582 return nil, fmt.Errorf("Account %s does not exist", accName) 2583 } else { 2584 a = v.(*Account) 2585 } 2586 isSys := a == s.SystemAccount() 2587 a.mu.RLock() 2588 defer a.mu.RUnlock() 2589 var vrIssues []ExtVrIssues 2590 claim, _ := jwt.DecodeAccountClaims(a.claimJWT) // ignore error 2591 if claim != nil { 2592 vr := jwt.ValidationResults{} 2593 claim.Validate(&vr) 2594 vrIssues = make([]ExtVrIssues, len(vr.Issues)) 2595 for i, v := range vr.Issues { 2596 vrIssues[i] = ExtVrIssues{v.Description, v.Blocking, v.TimeCheck} 2597 } 2598 } 2599 collectRevocations := func(revocations map[string]int64) map[string]time.Time { 2600 l := len(revocations) 2601 if l == 0 { 2602 return nil 2603 } 2604 rev := make(map[string]time.Time, l) 2605 for k, v := range revocations { 2606 rev[k] = time.Unix(v, 0) 2607 } 2608 return rev 2609 } 2610 exports := []ExtExport{} 2611 for k, v := range a.exports.services { 2612 e := ExtExport{ 2613 Export: jwt.Export{ 2614 Subject: jwt.Subject(k), 2615 Type: jwt.Service, 2616 }, 2617 ApprovedAccounts: []string{}, 2618 } 2619 if v != nil { 2620 e.Latency = newExtServiceLatency(v.latency) 2621 e.TokenReq = v.tokenReq 2622 e.ResponseType = jwt.ResponseType(v.respType.String()) 2623 for name := range v.approved { 2624 e.ApprovedAccounts = append(e.ApprovedAccounts, name) 2625 } 2626 e.RevokedAct = collectRevocations(v.actsRevoked) 2627 } 2628 exports = append(exports, e) 2629 } 2630 for k, v := range a.exports.streams { 2631 e := ExtExport{ 2632 Export: jwt.Export{ 2633 Subject: jwt.Subject(k), 2634 Type: jwt.Stream, 2635 }, 2636 ApprovedAccounts: []string{}, 2637 } 2638 if v != nil { 2639 e.TokenReq = v.tokenReq 2640 for name := range v.approved { 2641 e.ApprovedAccounts = append(e.ApprovedAccounts, name) 2642 } 2643 e.RevokedAct = collectRevocations(v.actsRevoked) 2644 } 2645 exports = append(exports, e) 2646 } 2647 imports := []ExtImport{} 2648 for _, v := range a.imports.streams { 2649 imp := ExtImport{ 2650 Invalid: true, 2651 Import: jwt.Import{Type: jwt.Stream}, 2652 } 2653 if v != nil { 2654 imp.Invalid = v.invalid 2655 imp.Import = jwt.Import{ 2656 Subject: jwt.Subject(v.from), 2657 Account: v.acc.Name, 2658 Type: jwt.Stream, 2659 LocalSubject: jwt.RenamingSubject(v.to), 2660 } 2661 } 2662 imports = append(imports, imp) 2663 } 2664 for _, v := range a.imports.services { 2665 imports = append(imports, newExtImport(v)) 2666 } 2667 responses := map[string]ExtImport{} 2668 for k, v := range a.exports.responses { 2669 responses[k] = newExtImport(v) 2670 } 2671 mappings := ExtMap{} 2672 for _, m := range a.mappings { 2673 var dests []*MapDest 2674 var src string 2675 if m == nil { 2676 src = "nil" 2677 if _, ok := mappings[src]; ok { // only set if not present (keep orig in case nil is used) 2678 continue 2679 } 2680 dests = append(dests, &MapDest{}) 2681 } else { 2682 src = m.src 2683 for _, d := range m.dests { 2684 dests = append(dests, &MapDest{d.tr.dest, d.weight, _EMPTY_}) 2685 } 2686 for c, cd := range m.cdests { 2687 for _, d := range cd { 2688 dests = append(dests, &MapDest{d.tr.dest, d.weight, c}) 2689 } 2690 } 2691 } 2692 mappings[src] = dests 2693 } 2694 return &AccountInfo{ 2695 accName, 2696 a.updated.UTC(), 2697 isSys, 2698 a.expired, 2699 !a.incomplete, 2700 a.js != nil, 2701 a.numLocalLeafNodes(), 2702 a.numLocalConnections(), 2703 a.sl.Count(), 2704 mappings, 2705 exports, 2706 imports, 2707 a.claimJWT, 2708 a.Issuer, 2709 a.nameTag, 2710 a.tags, 2711 claim, 2712 vrIssues, 2713 collectRevocations(a.usersRevoked), 2714 a.sl.Stats(), 2715 responses, 2716 }, nil 2717 } 2718 2719 // JSzOptions are options passed to Jsz 2720 type JSzOptions struct { 2721 Account string `json:"account,omitempty"` 2722 Accounts bool `json:"accounts,omitempty"` 2723 Streams bool `json:"streams,omitempty"` 2724 Consumer bool `json:"consumer,omitempty"` 2725 Config bool `json:"config,omitempty"` 2726 LeaderOnly bool `json:"leader_only,omitempty"` 2727 Offset int `json:"offset,omitempty"` 2728 Limit int `json:"limit,omitempty"` 2729 RaftGroups bool `json:"raft,omitempty"` 2730 } 2731 2732 // HealthzOptions are options passed to Healthz 2733 type HealthzOptions struct { 2734 // Deprecated: Use JSEnabledOnly instead 2735 JSEnabled bool `json:"js-enabled,omitempty"` 2736 JSEnabledOnly bool `json:"js-enabled-only,omitempty"` 2737 JSServerOnly bool `json:"js-server-only,omitempty"` 2738 Account string `json:"account,omitempty"` 2739 Stream string `json:"stream,omitempty"` 2740 Consumer string `json:"consumer,omitempty"` 2741 Details bool `json:"details,omitempty"` 2742 } 2743 2744 // ProfilezOptions are options passed to Profilez 2745 type ProfilezOptions struct { 2746 Name string `json:"name"` 2747 Debug int `json:"debug"` 2748 } 2749 2750 // StreamDetail shows information about the stream state and its consumers. 2751 type StreamDetail struct { 2752 Name string `json:"name"` 2753 Created time.Time `json:"created"` 2754 Cluster *ClusterInfo `json:"cluster,omitempty"` 2755 Config *StreamConfig `json:"config,omitempty"` 2756 State StreamState `json:"state,omitempty"` 2757 Consumer []*ConsumerInfo `json:"consumer_detail,omitempty"` 2758 Mirror *StreamSourceInfo `json:"mirror,omitempty"` 2759 Sources []*StreamSourceInfo `json:"sources,omitempty"` 2760 RaftGroup string `json:"stream_raft_group,omitempty"` 2761 ConsumerRaftGroups []*RaftGroupDetail `json:"consumer_raft_groups,omitempty"` 2762 } 2763 2764 // RaftGroupDetail shows information details about the Raft group. 2765 type RaftGroupDetail struct { 2766 Name string `json:"name"` 2767 RaftGroup string `json:"raft_group,omitempty"` 2768 } 2769 2770 type AccountDetail struct { 2771 Name string `json:"name"` 2772 Id string `json:"id"` 2773 JetStreamStats 2774 Streams []StreamDetail `json:"stream_detail,omitempty"` 2775 } 2776 2777 // MetaClusterInfo shows information about the meta group. 2778 type MetaClusterInfo struct { 2779 Name string `json:"name,omitempty"` 2780 Leader string `json:"leader,omitempty"` 2781 Peer string `json:"peer,omitempty"` 2782 Replicas []*PeerInfo `json:"replicas,omitempty"` 2783 Size int `json:"cluster_size"` 2784 } 2785 2786 // JSInfo has detailed information on JetStream. 2787 type JSInfo struct { 2788 ID string `json:"server_id"` 2789 Now time.Time `json:"now"` 2790 Disabled bool `json:"disabled,omitempty"` 2791 Config JetStreamConfig `json:"config,omitempty"` 2792 JetStreamStats 2793 Streams int `json:"streams"` 2794 Consumers int `json:"consumers"` 2795 Messages uint64 `json:"messages"` 2796 Bytes uint64 `json:"bytes"` 2797 Meta *MetaClusterInfo `json:"meta_cluster,omitempty"` 2798 2799 // aggregate raft info 2800 AccountDetails []*AccountDetail `json:"account_details,omitempty"` 2801 } 2802 2803 func (s *Server) accountDetail(jsa *jsAccount, optStreams, optConsumers, optCfg, optRaft bool) *AccountDetail { 2804 jsa.mu.RLock() 2805 acc := jsa.account 2806 name := acc.GetName() 2807 id := name 2808 if acc.nameTag != _EMPTY_ { 2809 name = acc.nameTag 2810 } 2811 jsa.usageMu.RLock() 2812 totalMem, totalStore := jsa.storageTotals() 2813 detail := AccountDetail{ 2814 Name: name, 2815 Id: id, 2816 JetStreamStats: JetStreamStats{ 2817 Memory: totalMem, 2818 Store: totalStore, 2819 API: JetStreamAPIStats{ 2820 Total: jsa.apiTotal, 2821 Errors: jsa.apiErrors, 2822 }, 2823 }, 2824 Streams: make([]StreamDetail, 0, len(jsa.streams)), 2825 } 2826 if reserved, ok := jsa.limits[_EMPTY_]; ok { 2827 detail.JetStreamStats.ReservedMemory = uint64(reserved.MaxMemory) 2828 detail.JetStreamStats.ReservedStore = uint64(reserved.MaxStore) 2829 } 2830 2831 jsa.usageMu.RUnlock() 2832 var streams []*stream 2833 if optStreams { 2834 for _, stream := range jsa.streams { 2835 streams = append(streams, stream) 2836 } 2837 } 2838 jsa.mu.RUnlock() 2839 2840 if js := s.getJetStream(); js != nil && optStreams { 2841 for _, stream := range streams { 2842 rgroup := stream.raftGroup() 2843 ci := js.clusterInfo(rgroup) 2844 var cfg *StreamConfig 2845 if optCfg { 2846 c := stream.config() 2847 cfg = &c 2848 } 2849 sdet := StreamDetail{ 2850 Name: stream.name(), 2851 Created: stream.createdTime(), 2852 State: stream.state(), 2853 Cluster: ci, 2854 Config: cfg, 2855 Mirror: stream.mirrorInfo(), 2856 Sources: stream.sourcesInfo(), 2857 } 2858 if optRaft && rgroup != nil { 2859 sdet.RaftGroup = rgroup.Name 2860 sdet.ConsumerRaftGroups = make([]*RaftGroupDetail, 0) 2861 } 2862 if optConsumers { 2863 for _, consumer := range stream.getPublicConsumers() { 2864 cInfo := consumer.info() 2865 if cInfo == nil { 2866 continue 2867 } 2868 if !optCfg { 2869 cInfo.Config = nil 2870 } 2871 sdet.Consumer = append(sdet.Consumer, cInfo) 2872 if optRaft { 2873 crgroup := consumer.raftGroup() 2874 if crgroup != nil { 2875 sdet.ConsumerRaftGroups = append(sdet.ConsumerRaftGroups, 2876 &RaftGroupDetail{cInfo.Name, crgroup.Name}, 2877 ) 2878 } 2879 } 2880 } 2881 } 2882 detail.Streams = append(detail.Streams, sdet) 2883 } 2884 } 2885 return &detail 2886 } 2887 2888 func (s *Server) JszAccount(opts *JSzOptions) (*AccountDetail, error) { 2889 js := s.getJetStream() 2890 if js == nil { 2891 return nil, fmt.Errorf("jetstream not enabled") 2892 } 2893 acc := opts.Account 2894 account, ok := s.accounts.Load(acc) 2895 if !ok { 2896 return nil, fmt.Errorf("account %q not found", acc) 2897 } 2898 js.mu.RLock() 2899 jsa, ok := js.accounts[account.(*Account).Name] 2900 js.mu.RUnlock() 2901 if !ok { 2902 return nil, fmt.Errorf("account %q not jetstream enabled", acc) 2903 } 2904 return s.accountDetail(jsa, opts.Streams, opts.Consumer, opts.Config, opts.RaftGroups), nil 2905 } 2906 2907 // helper to get cluster info from node via dummy group 2908 func (s *Server) raftNodeToClusterInfo(node RaftNode) *ClusterInfo { 2909 if node == nil { 2910 return nil 2911 } 2912 peers := node.Peers() 2913 peerList := make([]string, len(peers)) 2914 for i, p := range peers { 2915 peerList[i] = p.ID 2916 } 2917 group := &raftGroup{ 2918 Name: _EMPTY_, 2919 Peers: peerList, 2920 node: node, 2921 } 2922 return s.getJetStream().clusterInfo(group) 2923 } 2924 2925 // Jsz returns a Jsz structure containing information about JetStream. 2926 func (s *Server) Jsz(opts *JSzOptions) (*JSInfo, error) { 2927 // set option defaults 2928 if opts == nil { 2929 opts = &JSzOptions{} 2930 } 2931 if opts.Limit == 0 { 2932 opts.Limit = 1024 2933 } 2934 if opts.Consumer { 2935 opts.Streams = true 2936 } 2937 if opts.Streams { 2938 opts.Accounts = true 2939 } 2940 2941 jsi := &JSInfo{ 2942 ID: s.ID(), 2943 Now: time.Now().UTC(), 2944 } 2945 2946 js := s.getJetStream() 2947 if js == nil || !js.isEnabled() { 2948 if opts.LeaderOnly { 2949 return nil, fmt.Errorf("%w: not leader", errSkipZreq) 2950 } 2951 2952 jsi.Disabled = true 2953 return jsi, nil 2954 } 2955 2956 js.mu.RLock() 2957 isLeader := js.cluster == nil || js.cluster.isLeader() 2958 js.mu.RUnlock() 2959 2960 if opts.LeaderOnly && !isLeader { 2961 return nil, fmt.Errorf("%w: not leader", errSkipZreq) 2962 } 2963 2964 var accounts []*jsAccount 2965 2966 js.mu.RLock() 2967 jsi.Config = js.config 2968 for _, info := range js.accounts { 2969 accounts = append(accounts, info) 2970 } 2971 js.mu.RUnlock() 2972 2973 if mg := js.getMetaGroup(); mg != nil { 2974 if ci := s.raftNodeToClusterInfo(mg); ci != nil { 2975 jsi.Meta = &MetaClusterInfo{Name: ci.Name, Leader: ci.Leader, Peer: getHash(ci.Leader), Size: mg.ClusterSize()} 2976 if isLeader { 2977 jsi.Meta.Replicas = ci.Replicas 2978 } 2979 } 2980 } 2981 2982 jsi.JetStreamStats = *js.usageStats() 2983 2984 filterIdx := -1 2985 for i, jsa := range accounts { 2986 if jsa.acc().GetName() == opts.Account { 2987 filterIdx = i 2988 } 2989 jsa.mu.RLock() 2990 streams := make([]*stream, 0, len(jsa.streams)) 2991 for _, stream := range jsa.streams { 2992 streams = append(streams, stream) 2993 } 2994 jsa.mu.RUnlock() 2995 jsi.Streams += len(streams) 2996 for _, stream := range streams { 2997 streamState := stream.state() 2998 jsi.Messages += streamState.Msgs 2999 jsi.Bytes += streamState.Bytes 3000 jsi.Consumers += streamState.Consumers 3001 } 3002 } 3003 3004 // filter logic 3005 if filterIdx != -1 { 3006 accounts = []*jsAccount{accounts[filterIdx]} 3007 } else if opts.Accounts { 3008 if opts.Offset != 0 { 3009 sort.Slice(accounts, func(i, j int) bool { 3010 return strings.Compare(accounts[i].acc().Name, accounts[j].acc().Name) < 0 3011 }) 3012 if opts.Offset > len(accounts) { 3013 accounts = []*jsAccount{} 3014 } else { 3015 accounts = accounts[opts.Offset:] 3016 } 3017 } 3018 if opts.Limit != 0 { 3019 if opts.Limit < len(accounts) { 3020 accounts = accounts[:opts.Limit] 3021 } 3022 } 3023 } else { 3024 accounts = []*jsAccount{} 3025 } 3026 if len(accounts) > 0 { 3027 jsi.AccountDetails = make([]*AccountDetail, 0, len(accounts)) 3028 } 3029 // if wanted, obtain accounts/streams/consumer 3030 for _, jsa := range accounts { 3031 detail := s.accountDetail(jsa, opts.Streams, opts.Consumer, opts.Config, opts.RaftGroups) 3032 jsi.AccountDetails = append(jsi.AccountDetails, detail) 3033 } 3034 return jsi, nil 3035 } 3036 3037 // HandleJsz process HTTP requests for jetstream information. 3038 func (s *Server) HandleJsz(w http.ResponseWriter, r *http.Request) { 3039 s.mu.Lock() 3040 s.httpReqStats[JszPath]++ 3041 s.mu.Unlock() 3042 accounts, err := decodeBool(w, r, "accounts") 3043 if err != nil { 3044 return 3045 } 3046 streams, err := decodeBool(w, r, "streams") 3047 if err != nil { 3048 return 3049 } 3050 consumers, err := decodeBool(w, r, "consumers") 3051 if err != nil { 3052 return 3053 } 3054 config, err := decodeBool(w, r, "config") 3055 if err != nil { 3056 return 3057 } 3058 offset, err := decodeInt(w, r, "offset") 3059 if err != nil { 3060 return 3061 } 3062 limit, err := decodeInt(w, r, "limit") 3063 if err != nil { 3064 return 3065 } 3066 leader, err := decodeBool(w, r, "leader-only") 3067 if err != nil { 3068 return 3069 } 3070 rgroups, err := decodeBool(w, r, "raft") 3071 if err != nil { 3072 return 3073 } 3074 3075 l, err := s.Jsz(&JSzOptions{ 3076 r.URL.Query().Get("acc"), 3077 accounts, 3078 streams, 3079 consumers, 3080 config, 3081 leader, 3082 offset, 3083 limit, 3084 rgroups, 3085 }) 3086 if err != nil { 3087 w.WriteHeader(http.StatusBadRequest) 3088 w.Write([]byte(err.Error())) 3089 return 3090 } 3091 b, err := json.MarshalIndent(l, "", " ") 3092 if err != nil { 3093 s.Errorf("Error marshaling response to /jsz request: %v", err) 3094 } 3095 3096 // Handle response 3097 ResponseHandler(w, r, b) 3098 } 3099 3100 type HealthStatus struct { 3101 Status string `json:"status"` 3102 StatusCode int `json:"status_code,omitempty"` 3103 Error string `json:"error,omitempty"` 3104 Errors []HealthzError `json:"errors,omitempty"` 3105 } 3106 3107 type HealthzError struct { 3108 Type HealthZErrorType `json:"type"` 3109 Account string `json:"account,omitempty"` 3110 Stream string `json:"stream,omitempty"` 3111 Consumer string `json:"consumer,omitempty"` 3112 Error string `json:"error,omitempty"` 3113 } 3114 3115 type HealthZErrorType int 3116 3117 const ( 3118 HealthzErrorConn HealthZErrorType = iota 3119 HealthzErrorBadRequest 3120 HealthzErrorJetStream 3121 HealthzErrorAccount 3122 HealthzErrorStream 3123 HealthzErrorConsumer 3124 ) 3125 3126 func (t HealthZErrorType) String() string { 3127 switch t { 3128 case HealthzErrorConn: 3129 return "CONNECTION" 3130 case HealthzErrorBadRequest: 3131 return "BAD_REQUEST" 3132 case HealthzErrorJetStream: 3133 return "JETSTREAM" 3134 case HealthzErrorAccount: 3135 return "ACCOUNT" 3136 case HealthzErrorStream: 3137 return "STREAM" 3138 case HealthzErrorConsumer: 3139 return "CONSUMER" 3140 default: 3141 return "unknown" 3142 } 3143 } 3144 3145 func (t HealthZErrorType) MarshalJSON() ([]byte, error) { 3146 return json.Marshal(t.String()) 3147 } 3148 3149 func (t *HealthZErrorType) UnmarshalJSON(data []byte) error { 3150 switch string(data) { 3151 case `"CONNECTION"`: 3152 *t = HealthzErrorConn 3153 case `"BAD_REQUEST"`: 3154 *t = HealthzErrorBadRequest 3155 case `"JETSTREAM"`: 3156 *t = HealthzErrorJetStream 3157 case `"ACCOUNT"`: 3158 *t = HealthzErrorAccount 3159 case `"STREAM"`: 3160 *t = HealthzErrorStream 3161 case `"CONSUMER"`: 3162 *t = HealthzErrorConsumer 3163 default: 3164 return fmt.Errorf("unknown healthz error type %q", data) 3165 } 3166 return nil 3167 } 3168 3169 // https://datatracker.ietf.org/doc/html/draft-inadarei-api-health-check 3170 func (s *Server) HandleHealthz(w http.ResponseWriter, r *http.Request) { 3171 s.mu.Lock() 3172 s.httpReqStats[HealthzPath]++ 3173 s.mu.Unlock() 3174 3175 jsEnabled, err := decodeBool(w, r, "js-enabled") 3176 if err != nil { 3177 return 3178 } 3179 if jsEnabled { 3180 s.Warnf("Healthcheck: js-enabled deprecated, use js-enabled-only instead") 3181 } 3182 jsEnabledOnly, err := decodeBool(w, r, "js-enabled-only") 3183 if err != nil { 3184 return 3185 } 3186 jsServerOnly, err := decodeBool(w, r, "js-server-only") 3187 if err != nil { 3188 return 3189 } 3190 3191 includeDetails, err := decodeBool(w, r, "details") 3192 if err != nil { 3193 return 3194 } 3195 3196 hs := s.healthz(&HealthzOptions{ 3197 JSEnabled: jsEnabled, 3198 JSEnabledOnly: jsEnabledOnly, 3199 JSServerOnly: jsServerOnly, 3200 Account: r.URL.Query().Get("account"), 3201 Stream: r.URL.Query().Get("stream"), 3202 Consumer: r.URL.Query().Get("consumer"), 3203 Details: includeDetails, 3204 }) 3205 3206 code := http.StatusOK 3207 if hs.Error != _EMPTY_ { 3208 s.Warnf("Healthcheck failed: %q", hs.Error) 3209 code = hs.StatusCode 3210 } 3211 // Remove StatusCode from JSON representation when responding via HTTP 3212 // since this is already in the response. 3213 hs.StatusCode = 0 3214 b, err := json.Marshal(hs) 3215 if err != nil { 3216 s.Errorf("Error marshaling response to /healthz request: %v", err) 3217 } 3218 3219 handleResponse(code, w, r, b) 3220 } 3221 3222 // Generate health status. 3223 func (s *Server) healthz(opts *HealthzOptions) *HealthStatus { 3224 var health = &HealthStatus{Status: "ok"} 3225 3226 // set option defaults 3227 if opts == nil { 3228 opts = &HealthzOptions{} 3229 } 3230 details := opts.Details 3231 defer func() { 3232 // for response with details enabled, set status to either "error" or "ok" 3233 if details { 3234 if len(health.Errors) != 0 { 3235 health.Status = "error" 3236 } else { 3237 health.Status = "ok" 3238 } 3239 } 3240 // if no specific status code was set, set it based on the presence of errors 3241 if health.StatusCode == 0 { 3242 if health.Error != _EMPTY_ || len(health.Errors) != 0 { 3243 health.StatusCode = http.StatusServiceUnavailable 3244 } else { 3245 health.StatusCode = http.StatusOK 3246 } 3247 } 3248 }() 3249 3250 if opts.Account == _EMPTY_ && opts.Stream != _EMPTY_ { 3251 health.StatusCode = http.StatusBadRequest 3252 if !details { 3253 health.Status = "error" 3254 health.Error = fmt.Sprintf("%q must not be empty when checking stream health", "account") 3255 } else { 3256 health.Errors = append(health.Errors, HealthzError{ 3257 Type: HealthzErrorBadRequest, 3258 Error: fmt.Sprintf("%q must not be empty when checking stream health", "account"), 3259 }) 3260 } 3261 return health 3262 } 3263 3264 if opts.Stream == _EMPTY_ && opts.Consumer != _EMPTY_ { 3265 health.StatusCode = http.StatusBadRequest 3266 if !details { 3267 health.Status = "error" 3268 health.Error = fmt.Sprintf("%q must not be empty when checking consumer health", "stream") 3269 } else { 3270 health.Errors = append(health.Errors, HealthzError{ 3271 Type: HealthzErrorBadRequest, 3272 Error: fmt.Sprintf("%q must not be empty when checking consumer health", "stream"), 3273 }) 3274 } 3275 return health 3276 } 3277 3278 if err := s.readyForConnections(time.Millisecond); err != nil { 3279 health.StatusCode = http.StatusInternalServerError 3280 health.Status = "error" 3281 if !details { 3282 health.Error = err.Error() 3283 } else { 3284 health.Errors = append(health.Errors, HealthzError{ 3285 Type: HealthzErrorConn, 3286 Error: err.Error(), 3287 }) 3288 } 3289 return health 3290 } 3291 3292 sopts := s.getOpts() 3293 3294 // If JS is not enabled in the config, we stop. 3295 if !sopts.JetStream { 3296 return health 3297 } 3298 3299 // Access the Jetstream state to perform additional checks. 3300 js := s.getJetStream() 3301 const na = "unavailable" 3302 if !js.isEnabled() { 3303 health.StatusCode = http.StatusServiceUnavailable 3304 health.Status = na 3305 if !details { 3306 health.Error = NewJSNotEnabledError().Error() 3307 } else { 3308 health.Errors = append(health.Errors, HealthzError{ 3309 Type: HealthzErrorJetStream, 3310 Error: NewJSNotEnabledError().Error(), 3311 }) 3312 } 3313 return health 3314 } 3315 // Only check if JS is enabled, skip meta and asset check. 3316 if opts.JSEnabledOnly || opts.JSEnabled { 3317 return health 3318 } 3319 3320 // Clustered JetStream 3321 js.mu.RLock() 3322 cc := js.cluster 3323 js.mu.RUnlock() 3324 3325 // Currently single server we make sure the streams were recovered. 3326 if cc == nil { 3327 sdir := js.config.StoreDir 3328 // Whip through account folders and pull each stream name. 3329 fis, _ := os.ReadDir(sdir) 3330 var accFound, streamFound, consumerFound bool 3331 for _, fi := range fis { 3332 if fi.Name() == snapStagingDir { 3333 continue 3334 } 3335 if opts.Account != _EMPTY_ { 3336 if fi.Name() != opts.Account { 3337 continue 3338 } 3339 accFound = true 3340 } 3341 acc, err := s.LookupAccount(fi.Name()) 3342 if err != nil { 3343 if !details { 3344 health.Status = na 3345 health.Error = fmt.Sprintf("JetStream account '%s' could not be resolved", fi.Name()) 3346 return health 3347 } 3348 health.Errors = append(health.Errors, HealthzError{ 3349 Type: HealthzErrorAccount, 3350 Account: fi.Name(), 3351 Error: fmt.Sprintf("JetStream account '%s' could not be resolved", fi.Name()), 3352 }) 3353 continue 3354 } 3355 sfis, _ := os.ReadDir(filepath.Join(sdir, fi.Name(), "streams")) 3356 for _, sfi := range sfis { 3357 if opts.Stream != _EMPTY_ { 3358 if sfi.Name() != opts.Stream { 3359 continue 3360 } 3361 streamFound = true 3362 } 3363 stream := sfi.Name() 3364 s, err := acc.lookupStream(stream) 3365 if err != nil { 3366 if !details { 3367 health.Status = na 3368 health.Error = fmt.Sprintf("JetStream stream '%s > %s' could not be recovered", acc, stream) 3369 return health 3370 } 3371 health.Errors = append(health.Errors, HealthzError{ 3372 Type: HealthzErrorStream, 3373 Account: acc.Name, 3374 Stream: stream, 3375 Error: fmt.Sprintf("JetStream stream '%s > %s' could not be recovered", acc, stream), 3376 }) 3377 continue 3378 } 3379 if streamFound { 3380 // if consumer option is passed, verify that the consumer exists on stream 3381 if opts.Consumer != _EMPTY_ { 3382 for _, cons := range s.consumers { 3383 if cons.name == opts.Consumer { 3384 consumerFound = true 3385 break 3386 } 3387 } 3388 } 3389 break 3390 } 3391 } 3392 if accFound { 3393 break 3394 } 3395 } 3396 if opts.Account != _EMPTY_ && !accFound { 3397 health.StatusCode = http.StatusNotFound 3398 if !details { 3399 health.Status = na 3400 health.Error = fmt.Sprintf("JetStream account %q not found", opts.Account) 3401 } else { 3402 health.Errors = []HealthzError{ 3403 { 3404 Type: HealthzErrorAccount, 3405 Account: opts.Account, 3406 Error: fmt.Sprintf("JetStream account %q not found", opts.Account), 3407 }, 3408 } 3409 } 3410 return health 3411 } 3412 if opts.Stream != _EMPTY_ && !streamFound { 3413 health.StatusCode = http.StatusNotFound 3414 if !details { 3415 health.Status = na 3416 health.Error = fmt.Sprintf("JetStream stream %q not found on account %q", opts.Stream, opts.Account) 3417 } else { 3418 health.Errors = []HealthzError{ 3419 { 3420 Type: HealthzErrorStream, 3421 Account: opts.Account, 3422 Stream: opts.Stream, 3423 Error: fmt.Sprintf("JetStream stream %q not found on account %q", opts.Stream, opts.Account), 3424 }, 3425 } 3426 } 3427 return health 3428 } 3429 if opts.Consumer != _EMPTY_ && !consumerFound { 3430 health.StatusCode = http.StatusNotFound 3431 if !details { 3432 health.Status = na 3433 health.Error = fmt.Sprintf("JetStream consumer %q not found for stream %q on account %q", opts.Consumer, opts.Stream, opts.Account) 3434 } else { 3435 health.Errors = []HealthzError{ 3436 { 3437 Type: HealthzErrorConsumer, 3438 Account: opts.Account, 3439 Stream: opts.Stream, 3440 Consumer: opts.Consumer, 3441 Error: fmt.Sprintf("JetStream consumer %q not found for stream %q on account %q", opts.Consumer, opts.Stream, opts.Account), 3442 }, 3443 } 3444 } 3445 } 3446 return health 3447 } 3448 3449 // If we are here we want to check for any assets assigned to us. 3450 var meta RaftNode 3451 js.mu.RLock() 3452 meta = cc.meta 3453 js.mu.RUnlock() 3454 3455 // If no meta leader. 3456 if meta == nil || meta.GroupLeader() == _EMPTY_ { 3457 if !details { 3458 health.Status = na 3459 health.Error = "JetStream has not established contact with a meta leader" 3460 } else { 3461 health.Errors = []HealthzError{ 3462 { 3463 Type: HealthzErrorJetStream, 3464 Error: "JetStream has not established contact with a meta leader", 3465 }, 3466 } 3467 } 3468 return health 3469 } 3470 // If we are not current with the meta leader. 3471 if !meta.Healthy() { 3472 if !details { 3473 health.Status = na 3474 health.Error = "JetStream is not current with the meta leader" 3475 } else { 3476 health.Errors = []HealthzError{ 3477 { 3478 Type: HealthzErrorJetStream, 3479 Error: "JetStream is not current with the meta leader", 3480 }, 3481 } 3482 } 3483 return health 3484 } 3485 3486 // If JSServerOnly is true, then do not check further accounts, streams and consumers. 3487 if opts.JSServerOnly { 3488 return health 3489 } 3490 3491 // Range across all accounts, the streams assigned to them, and the consumers. 3492 // If they are assigned to this server check their status. 3493 ourID := meta.ID() 3494 3495 // Copy the meta layer so we do not need to hold the js read lock for an extended period of time. 3496 var streams map[string]map[string]*streamAssignment 3497 js.mu.RLock() 3498 if opts.Account == _EMPTY_ { 3499 // Collect all relevant streams and consumers. 3500 streams = make(map[string]map[string]*streamAssignment, len(cc.streams)) 3501 for acc, asa := range cc.streams { 3502 nasa := make(map[string]*streamAssignment) 3503 for stream, sa := range asa { 3504 // If we are a member and we are not being restored, select for check. 3505 if sa.Group.isMember(ourID) && sa.Restore == nil { 3506 csa := sa.copyGroup() 3507 csa.consumers = make(map[string]*consumerAssignment) 3508 for consumer, ca := range sa.consumers { 3509 if ca.Group.isMember(ourID) { 3510 // Use original here. Not a copy. 3511 csa.consumers[consumer] = ca 3512 } 3513 } 3514 nasa[stream] = csa 3515 } 3516 } 3517 streams[acc] = nasa 3518 } 3519 } else { 3520 streams = make(map[string]map[string]*streamAssignment, 1) 3521 asa, ok := cc.streams[opts.Account] 3522 if !ok { 3523 health.StatusCode = http.StatusNotFound 3524 if !details { 3525 health.Status = na 3526 health.Error = fmt.Sprintf("JetStream account %q not found", opts.Account) 3527 } else { 3528 health.Errors = []HealthzError{ 3529 { 3530 Type: HealthzErrorAccount, 3531 Account: opts.Account, 3532 Error: fmt.Sprintf("JetStream account %q not found", opts.Account), 3533 }, 3534 } 3535 } 3536 js.mu.RUnlock() 3537 return health 3538 } 3539 nasa := make(map[string]*streamAssignment) 3540 if opts.Stream != _EMPTY_ { 3541 sa, ok := asa[opts.Stream] 3542 if !ok || !sa.Group.isMember(ourID) { 3543 health.StatusCode = http.StatusNotFound 3544 if !details { 3545 health.Status = na 3546 health.Error = fmt.Sprintf("JetStream stream %q not found on account %q", opts.Stream, opts.Account) 3547 } else { 3548 health.Errors = []HealthzError{ 3549 { 3550 Type: HealthzErrorStream, 3551 Account: opts.Account, 3552 Stream: opts.Stream, 3553 Error: fmt.Sprintf("JetStream stream %q not found on account %q", opts.Stream, opts.Account), 3554 }, 3555 } 3556 } 3557 js.mu.RUnlock() 3558 return health 3559 } 3560 csa := sa.copyGroup() 3561 csa.consumers = make(map[string]*consumerAssignment) 3562 var consumerFound bool 3563 for consumer, ca := range sa.consumers { 3564 if opts.Consumer != _EMPTY_ { 3565 if consumer != opts.Consumer || !ca.Group.isMember(ourID) { 3566 continue 3567 } 3568 consumerFound = true 3569 } 3570 // If we are a member and we are not being restored, select for check. 3571 if sa.Group.isMember(ourID) && sa.Restore == nil { 3572 csa.consumers[consumer] = ca 3573 } 3574 if consumerFound { 3575 break 3576 } 3577 } 3578 if opts.Consumer != _EMPTY_ && !consumerFound { 3579 health.StatusCode = http.StatusNotFound 3580 if !details { 3581 health.Status = na 3582 health.Error = fmt.Sprintf("JetStream consumer %q not found for stream %q on account %q", opts.Consumer, opts.Stream, opts.Account) 3583 } else { 3584 health.Errors = []HealthzError{ 3585 { 3586 Type: HealthzErrorConsumer, 3587 Account: opts.Account, 3588 Stream: opts.Stream, 3589 Consumer: opts.Consumer, 3590 Error: fmt.Sprintf("JetStream consumer %q not found for stream %q on account %q", opts.Consumer, opts.Stream, opts.Account), 3591 }, 3592 } 3593 } 3594 js.mu.RUnlock() 3595 return health 3596 } 3597 nasa[opts.Stream] = csa 3598 } else { 3599 for stream, sa := range asa { 3600 // If we are a member and we are not being restored, select for check. 3601 if sa.Group.isMember(ourID) && sa.Restore == nil { 3602 csa := sa.copyGroup() 3603 csa.consumers = make(map[string]*consumerAssignment) 3604 for consumer, ca := range sa.consumers { 3605 if ca.Group.isMember(ourID) { 3606 csa.consumers[consumer] = ca 3607 } 3608 } 3609 nasa[stream] = csa 3610 } 3611 } 3612 } 3613 streams[opts.Account] = nasa 3614 } 3615 js.mu.RUnlock() 3616 3617 // Use our copy to traverse so we do not need to hold the js lock. 3618 for accName, asa := range streams { 3619 acc, err := s.LookupAccount(accName) 3620 if err != nil && len(asa) > 0 { 3621 if !details { 3622 health.Status = na 3623 health.Error = fmt.Sprintf("JetStream can not lookup account %q: %v", accName, err) 3624 return health 3625 } 3626 health.Errors = append(health.Errors, HealthzError{ 3627 Type: HealthzErrorAccount, 3628 Account: accName, 3629 Error: fmt.Sprintf("JetStream can not lookup account %q: %v", accName, err), 3630 }) 3631 continue 3632 } 3633 3634 for stream, sa := range asa { 3635 // Make sure we can look up 3636 if !js.isStreamHealthy(acc, sa) { 3637 if !details { 3638 health.Status = na 3639 health.Error = fmt.Sprintf("JetStream stream '%s > %s' is not current", accName, stream) 3640 return health 3641 } 3642 health.Errors = append(health.Errors, HealthzError{ 3643 Type: HealthzErrorStream, 3644 Account: accName, 3645 Stream: stream, 3646 Error: fmt.Sprintf("JetStream stream '%s > %s' is not current", accName, stream), 3647 }) 3648 continue 3649 } 3650 mset, _ := acc.lookupStream(stream) 3651 // Now check consumers. 3652 for consumer, ca := range sa.consumers { 3653 if !js.isConsumerHealthy(mset, consumer, ca) { 3654 if !details { 3655 health.Status = na 3656 health.Error = fmt.Sprintf("JetStream consumer '%s > %s > %s' is not current", acc, stream, consumer) 3657 return health 3658 } 3659 health.Errors = append(health.Errors, HealthzError{ 3660 Type: HealthzErrorConsumer, 3661 Account: accName, 3662 Stream: stream, 3663 Consumer: consumer, 3664 Error: fmt.Sprintf("JetStream consumer '%s > %s > %s' is not current", acc, stream, consumer), 3665 }) 3666 } 3667 } 3668 } 3669 } 3670 // Success. 3671 return health 3672 } 3673 3674 type ProfilezStatus struct { 3675 Profile []byte `json:"profile"` 3676 Error string `json:"error"` 3677 } 3678 3679 func (s *Server) profilez(opts *ProfilezOptions) *ProfilezStatus { 3680 if opts.Name == _EMPTY_ { 3681 return &ProfilezStatus{ 3682 Error: "Profile name not specified", 3683 } 3684 } 3685 profile := pprof.Lookup(opts.Name) 3686 if profile == nil { 3687 return &ProfilezStatus{ 3688 Error: fmt.Sprintf("Profile %q not found", opts.Name), 3689 } 3690 } 3691 var buffer bytes.Buffer 3692 if err := profile.WriteTo(&buffer, opts.Debug); err != nil { 3693 return &ProfilezStatus{ 3694 Error: fmt.Sprintf("Profile %q error: %s", opts.Name, err), 3695 } 3696 } 3697 return &ProfilezStatus{ 3698 Profile: buffer.Bytes(), 3699 } 3700 }