forked from pneymrl2f/nightingale
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
535 lines
17 KiB
535 lines
17 KiB
// Copyright (c) 2015 Uber Technologies, Inc.
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
package tchannel
|
|
|
|
import (
|
|
"encoding/json"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"time"
|
|
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// IntrospectionOptions are the options used when introspecting the Channel.
|
|
type IntrospectionOptions struct {
|
|
// IncludeExchanges will include all the IDs in the message exchanges.
|
|
IncludeExchanges bool `json:"includeExchanges"`
|
|
|
|
// IncludeEmptyPeers will include peers, even if they have no connections.
|
|
IncludeEmptyPeers bool `json:"includeEmptyPeers"`
|
|
|
|
// IncludeTombstones will include tombstones when introspecting relays.
|
|
IncludeTombstones bool `json:"includeTombstones"`
|
|
|
|
// IncludeOtherChannels will include basic information about other channels
|
|
// created in the same process as this channel.
|
|
IncludeOtherChannels bool `json:"includeOtherChannels"`
|
|
}
|
|
|
|
// RuntimeVersion includes version information about the runtime and
|
|
// the tchannel library.
|
|
type RuntimeVersion struct {
|
|
GoVersion string `json:"goVersion"`
|
|
LibraryVersion string `json:"tchannelVersion"`
|
|
}
|
|
|
|
// RuntimeState is a snapshot of the runtime state for a channel.
|
|
type RuntimeState struct {
|
|
ID uint32 `json:"id"`
|
|
ChannelState string `json:"channelState"`
|
|
|
|
// CreatedStack is the stack for how this channel was created.
|
|
CreatedStack string `json:"createdStack"`
|
|
|
|
// LocalPeer is the local peer information (service name, host-port, etc).
|
|
LocalPeer LocalPeerInfo `json:"localPeer"`
|
|
|
|
// SubChannels contains information about any subchannels.
|
|
SubChannels map[string]SubChannelRuntimeState `json:"subChannels"`
|
|
|
|
// RootPeers contains information about all the peers on this channel and their connections.
|
|
RootPeers map[string]PeerRuntimeState `json:"rootPeers"`
|
|
|
|
// Peers is the list of shared peers for this channel.
|
|
Peers []SubPeerScore `json:"peers"`
|
|
|
|
// NumConnections is the number of connections stored in the channel.
|
|
NumConnections int `json:"numConnections"`
|
|
|
|
// Connections is the list of connection IDs in the channel
|
|
Connections []uint32 ` json:"connections"`
|
|
|
|
// InactiveConnections is the connection state for connections that are not active,
|
|
// and hence are not reported as part of root peers.
|
|
InactiveConnections []ConnectionRuntimeState `json:"inactiveConnections"`
|
|
|
|
// OtherChannels is information about any other channels running in this process.
|
|
OtherChannels map[string][]ChannelInfo `json:"otherChannels,omitEmpty"`
|
|
|
|
// RuntimeVersion is the version information about the runtime and the library.
|
|
RuntimeVersion RuntimeVersion `json:"runtimeVersion"`
|
|
}
|
|
|
|
// GoRuntimeStateOptions are the options used when getting Go runtime state.
|
|
type GoRuntimeStateOptions struct {
|
|
// IncludeGoStacks will include all goroutine stacks.
|
|
IncludeGoStacks bool `json:"includeGoStacks"`
|
|
}
|
|
|
|
// ChannelInfo is the state of other channels in the same process.
|
|
type ChannelInfo struct {
|
|
ID uint32 `json:"id"`
|
|
CreatedStack string `json:"createdStack"`
|
|
LocalPeer LocalPeerInfo `json:"localPeer"`
|
|
}
|
|
|
|
// GoRuntimeState is a snapshot of runtime stats from the runtime.
|
|
type GoRuntimeState struct {
|
|
MemStats runtime.MemStats `json:"memStats"`
|
|
NumGoroutines int `json:"numGoRoutines"`
|
|
NumCPU int `json:"numCPU"`
|
|
NumCGo int64 `json:"numCGo"`
|
|
GoStacks []byte `json:"goStacks,omitempty"`
|
|
}
|
|
|
|
// SubChannelRuntimeState is the runtime state for a subchannel.
|
|
type SubChannelRuntimeState struct {
|
|
Service string `json:"service"`
|
|
Isolated bool `json:"isolated"`
|
|
// IsolatedPeers is the list of all isolated peers for this channel.
|
|
IsolatedPeers []SubPeerScore `json:"isolatedPeers,omitempty"`
|
|
Handler HandlerRuntimeState `json:"handler"`
|
|
}
|
|
|
|
// HandlerRuntimeState TODO
|
|
type HandlerRuntimeState struct {
|
|
Type handlerType `json:"type"`
|
|
Methods []string `json:"methods,omitempty"`
|
|
}
|
|
|
|
type handlerType string
|
|
|
|
func (h handlerType) String() string { return string(h) }
|
|
|
|
const (
|
|
methodHandler handlerType = "methods"
|
|
overrideHandler = "overriden"
|
|
)
|
|
|
|
// SubPeerScore show the runtime state of a peer with score.
|
|
type SubPeerScore struct {
|
|
HostPort string `json:"hostPort"`
|
|
Score uint64 `json:"score"`
|
|
}
|
|
|
|
// ConnectionRuntimeState is the runtime state for a single connection.
|
|
type ConnectionRuntimeState struct {
|
|
ID uint32 `json:"id"`
|
|
ConnectionState string `json:"connectionState"`
|
|
LocalHostPort string `json:"localHostPort"`
|
|
RemoteHostPort string `json:"remoteHostPort"`
|
|
OutboundHostPort string `json:"outboundHostPort"`
|
|
RemotePeer PeerInfo `json:"remotePeer"`
|
|
InboundExchange ExchangeSetRuntimeState `json:"inboundExchange"`
|
|
OutboundExchange ExchangeSetRuntimeState `json:"outboundExchange"`
|
|
Relayer RelayerRuntimeState `json:"relayer"`
|
|
HealthChecks []bool `json:"healthChecks,omitempty"`
|
|
LastActivity int64 `json:"lastActivity"`
|
|
}
|
|
|
|
// RelayerRuntimeState is the runtime state for a single relayer.
|
|
type RelayerRuntimeState struct {
|
|
Count int `json:"count"`
|
|
InboundItems RelayItemSetState `json:"inboundItems"`
|
|
OutboundItems RelayItemSetState `json:"outboundItems"`
|
|
MaxTimeout time.Duration `json:"maxTimeout"`
|
|
}
|
|
|
|
// ExchangeSetRuntimeState is the runtime state for a message exchange set.
|
|
type ExchangeSetRuntimeState struct {
|
|
Name string `json:"name"`
|
|
Count int `json:"count"`
|
|
Exchanges map[string]ExchangeRuntimeState `json:"exchanges,omitempty"`
|
|
}
|
|
|
|
// RelayItemSetState is the runtime state for a list of relay items.
|
|
type RelayItemSetState struct {
|
|
Name string `json:"name"`
|
|
Count int `json:"count"`
|
|
Items map[string]RelayItemState `json:"items,omitempty"`
|
|
}
|
|
|
|
// ExchangeRuntimeState is the runtime state for a single message exchange.
|
|
type ExchangeRuntimeState struct {
|
|
ID uint32 `json:"id"`
|
|
MessageType messageType `json:"messageType"`
|
|
}
|
|
|
|
// RelayItemState is the runtime state for a single relay item.
|
|
type RelayItemState struct {
|
|
ID uint32 `json:"id"`
|
|
RemapID uint32 `json:"remapID"`
|
|
DestinationConnectionID uint32 `json:"destinationConnectionID"`
|
|
Tomb bool `json:"tomb"`
|
|
}
|
|
|
|
// PeerRuntimeState is the runtime state for a single peer.
|
|
type PeerRuntimeState struct {
|
|
HostPort string `json:"hostPort"`
|
|
OutboundConnections []ConnectionRuntimeState `json:"outboundConnections"`
|
|
InboundConnections []ConnectionRuntimeState `json:"inboundConnections"`
|
|
ChosenCount uint64 `json:"chosenCount"`
|
|
SCCount uint32 `json:"scCount"`
|
|
}
|
|
|
|
// IntrospectState returns the RuntimeState for this channel.
|
|
// Note: this is purely for debugging and monitoring, and may slow down your Channel.
|
|
func (ch *Channel) IntrospectState(opts *IntrospectionOptions) *RuntimeState {
|
|
if opts == nil {
|
|
opts = &IntrospectionOptions{}
|
|
}
|
|
|
|
ch.mutable.RLock()
|
|
state := ch.mutable.state
|
|
numConns := len(ch.mutable.conns)
|
|
inactiveConns := make([]*Connection, 0, numConns)
|
|
connIDs := make([]uint32, 0, numConns)
|
|
for id, conn := range ch.mutable.conns {
|
|
connIDs = append(connIDs, id)
|
|
if !conn.IsActive() {
|
|
inactiveConns = append(inactiveConns, conn)
|
|
}
|
|
}
|
|
|
|
ch.mutable.RUnlock()
|
|
|
|
ch.State()
|
|
return &RuntimeState{
|
|
ID: ch.chID,
|
|
ChannelState: state.String(),
|
|
CreatedStack: ch.createdStack,
|
|
LocalPeer: ch.PeerInfo(),
|
|
SubChannels: ch.subChannels.IntrospectState(opts),
|
|
RootPeers: ch.RootPeers().IntrospectState(opts),
|
|
Peers: ch.Peers().IntrospectList(opts),
|
|
NumConnections: numConns,
|
|
Connections: connIDs,
|
|
InactiveConnections: getConnectionRuntimeState(inactiveConns, opts),
|
|
OtherChannels: ch.IntrospectOthers(opts),
|
|
RuntimeVersion: introspectRuntimeVersion(),
|
|
}
|
|
}
|
|
|
|
// IntrospectOthers returns the ChannelInfo for all other channels in this process.
|
|
func (ch *Channel) IntrospectOthers(opts *IntrospectionOptions) map[string][]ChannelInfo {
|
|
if !opts.IncludeOtherChannels {
|
|
return nil
|
|
}
|
|
|
|
channelMap.Lock()
|
|
defer channelMap.Unlock()
|
|
|
|
states := make(map[string][]ChannelInfo)
|
|
for svc, channels := range channelMap.existing {
|
|
channelInfos := make([]ChannelInfo, 0, len(channels))
|
|
for _, otherChan := range channels {
|
|
if ch == otherChan {
|
|
continue
|
|
}
|
|
channelInfos = append(channelInfos, otherChan.ReportInfo(opts))
|
|
}
|
|
states[svc] = channelInfos
|
|
}
|
|
|
|
return states
|
|
}
|
|
|
|
// ReportInfo returns ChannelInfo for a channel.
|
|
func (ch *Channel) ReportInfo(opts *IntrospectionOptions) ChannelInfo {
|
|
return ChannelInfo{
|
|
ID: ch.chID,
|
|
CreatedStack: ch.createdStack,
|
|
LocalPeer: ch.PeerInfo(),
|
|
}
|
|
}
|
|
|
|
type containsPeerList interface {
|
|
Copy() map[string]*Peer
|
|
}
|
|
|
|
func fromPeerList(peers containsPeerList, opts *IntrospectionOptions) map[string]PeerRuntimeState {
|
|
m := make(map[string]PeerRuntimeState)
|
|
for _, peer := range peers.Copy() {
|
|
peerState := peer.IntrospectState(opts)
|
|
if len(peerState.InboundConnections)+len(peerState.OutboundConnections) > 0 || opts.IncludeEmptyPeers {
|
|
m[peer.HostPort()] = peerState
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
// IntrospectState returns the runtime state of the
|
|
func (l *RootPeerList) IntrospectState(opts *IntrospectionOptions) map[string]PeerRuntimeState {
|
|
return fromPeerList(l, opts)
|
|
}
|
|
|
|
// IntrospectState returns the runtime state of the subchannels.
|
|
func (subChMap *subChannelMap) IntrospectState(opts *IntrospectionOptions) map[string]SubChannelRuntimeState {
|
|
m := make(map[string]SubChannelRuntimeState)
|
|
subChMap.RLock()
|
|
for k, sc := range subChMap.subchannels {
|
|
state := SubChannelRuntimeState{
|
|
Service: k,
|
|
Isolated: sc.Isolated(),
|
|
}
|
|
if state.Isolated {
|
|
state.IsolatedPeers = sc.Peers().IntrospectList(opts)
|
|
}
|
|
if hmap, ok := sc.handler.(*handlerMap); ok {
|
|
state.Handler.Type = methodHandler
|
|
methods := make([]string, 0, len(hmap.handlers))
|
|
for k := range hmap.handlers {
|
|
methods = append(methods, k)
|
|
}
|
|
sort.Strings(methods)
|
|
state.Handler.Methods = methods
|
|
} else {
|
|
state.Handler.Type = overrideHandler
|
|
}
|
|
m[k] = state
|
|
}
|
|
subChMap.RUnlock()
|
|
return m
|
|
}
|
|
|
|
func getConnectionRuntimeState(conns []*Connection, opts *IntrospectionOptions) []ConnectionRuntimeState {
|
|
connStates := make([]ConnectionRuntimeState, len(conns))
|
|
|
|
for i, conn := range conns {
|
|
connStates[i] = conn.IntrospectState(opts)
|
|
}
|
|
|
|
return connStates
|
|
}
|
|
|
|
// IntrospectState returns the runtime state for this peer.
|
|
func (p *Peer) IntrospectState(opts *IntrospectionOptions) PeerRuntimeState {
|
|
p.RLock()
|
|
defer p.RUnlock()
|
|
|
|
return PeerRuntimeState{
|
|
HostPort: p.hostPort,
|
|
InboundConnections: getConnectionRuntimeState(p.inboundConnections, opts),
|
|
OutboundConnections: getConnectionRuntimeState(p.outboundConnections, opts),
|
|
ChosenCount: p.chosenCount.Load(),
|
|
SCCount: p.scCount,
|
|
}
|
|
}
|
|
|
|
// IntrospectState returns the runtime state for this connection.
|
|
func (c *Connection) IntrospectState(opts *IntrospectionOptions) ConnectionRuntimeState {
|
|
c.stateMut.RLock()
|
|
defer c.stateMut.RUnlock()
|
|
|
|
// TODO(prashantv): Add total number of health checks, and health check options.
|
|
state := ConnectionRuntimeState{
|
|
ID: c.connID,
|
|
ConnectionState: c.state.String(),
|
|
LocalHostPort: c.conn.LocalAddr().String(),
|
|
RemoteHostPort: c.conn.RemoteAddr().String(),
|
|
OutboundHostPort: c.outboundHP,
|
|
RemotePeer: c.remotePeerInfo,
|
|
InboundExchange: c.inbound.IntrospectState(opts),
|
|
OutboundExchange: c.outbound.IntrospectState(opts),
|
|
HealthChecks: c.healthCheckHistory.asBools(),
|
|
LastActivity: c.lastActivity.Load(),
|
|
}
|
|
if c.relay != nil {
|
|
state.Relayer = c.relay.IntrospectState(opts)
|
|
}
|
|
return state
|
|
}
|
|
|
|
// IntrospectState returns the runtime state for this relayer.
|
|
func (r *Relayer) IntrospectState(opts *IntrospectionOptions) RelayerRuntimeState {
|
|
count := r.inbound.Count() + r.outbound.Count()
|
|
return RelayerRuntimeState{
|
|
Count: count,
|
|
InboundItems: r.inbound.IntrospectState(opts, "inbound"),
|
|
OutboundItems: r.outbound.IntrospectState(opts, "outbound"),
|
|
MaxTimeout: r.maxTimeout,
|
|
}
|
|
}
|
|
|
|
// IntrospectState returns the runtime state for this relayItems.
|
|
func (ri *relayItems) IntrospectState(opts *IntrospectionOptions, name string) RelayItemSetState {
|
|
setState := RelayItemSetState{
|
|
Name: name,
|
|
Count: ri.Count(),
|
|
}
|
|
if opts.IncludeExchanges {
|
|
ri.RLock()
|
|
defer ri.RUnlock()
|
|
|
|
setState.Items = make(map[string]RelayItemState, len(ri.items))
|
|
for k, v := range ri.items {
|
|
if !opts.IncludeTombstones && v.tomb {
|
|
continue
|
|
}
|
|
state := RelayItemState{
|
|
ID: k,
|
|
RemapID: v.remapID,
|
|
DestinationConnectionID: v.destination.conn.connID,
|
|
Tomb: v.tomb,
|
|
}
|
|
setState.Items[strconv.Itoa(int(k))] = state
|
|
}
|
|
}
|
|
|
|
return setState
|
|
}
|
|
|
|
// IntrospectState returns the runtime state for this messsage exchange set.
|
|
func (mexset *messageExchangeSet) IntrospectState(opts *IntrospectionOptions) ExchangeSetRuntimeState {
|
|
mexset.RLock()
|
|
setState := ExchangeSetRuntimeState{
|
|
Name: mexset.name,
|
|
Count: len(mexset.exchanges),
|
|
}
|
|
|
|
if opts.IncludeExchanges {
|
|
setState.Exchanges = make(map[string]ExchangeRuntimeState, len(mexset.exchanges))
|
|
for k, v := range mexset.exchanges {
|
|
state := ExchangeRuntimeState{
|
|
ID: k,
|
|
MessageType: v.msgType,
|
|
}
|
|
setState.Exchanges[strconv.Itoa(int(k))] = state
|
|
}
|
|
}
|
|
|
|
mexset.RUnlock()
|
|
return setState
|
|
}
|
|
|
|
func getStacks(all bool) []byte {
|
|
var buf []byte
|
|
for n := 4096; n < 10*1024*1024; n *= 2 {
|
|
buf = make([]byte, n)
|
|
stackLen := runtime.Stack(buf, all)
|
|
if stackLen < n {
|
|
return buf[:stackLen]
|
|
}
|
|
}
|
|
|
|
// return the first 10MB of stacks if we have more than 10MB.
|
|
return buf
|
|
}
|
|
func (ch *Channel) handleIntrospection(arg3 []byte) interface{} {
|
|
var opts IntrospectionOptions
|
|
json.Unmarshal(arg3, &opts)
|
|
return ch.IntrospectState(&opts)
|
|
}
|
|
|
|
// IntrospectList returns the list of peers (hostport, score) in this peer list.
|
|
func (l *PeerList) IntrospectList(opts *IntrospectionOptions) []SubPeerScore {
|
|
var peers []SubPeerScore
|
|
l.RLock()
|
|
for _, ps := range l.peerHeap.peerScores {
|
|
peers = append(peers, SubPeerScore{
|
|
HostPort: ps.Peer.hostPort,
|
|
Score: ps.score,
|
|
})
|
|
}
|
|
l.RUnlock()
|
|
|
|
return peers
|
|
}
|
|
|
|
// IntrospectNumConnections returns the number of connections returns the number
|
|
// of connections. Note: like other introspection APIs, this is not a stable API.
|
|
func (ch *Channel) IntrospectNumConnections() int {
|
|
ch.mutable.RLock()
|
|
numConns := len(ch.mutable.conns)
|
|
ch.mutable.RUnlock()
|
|
return numConns
|
|
}
|
|
|
|
func handleInternalRuntime(arg3 []byte) interface{} {
|
|
var opts GoRuntimeStateOptions
|
|
json.Unmarshal(arg3, &opts)
|
|
|
|
state := GoRuntimeState{
|
|
NumGoroutines: runtime.NumGoroutine(),
|
|
NumCPU: runtime.NumCPU(),
|
|
NumCGo: runtime.NumCgoCall(),
|
|
}
|
|
runtime.ReadMemStats(&state.MemStats)
|
|
if opts.IncludeGoStacks {
|
|
state.GoStacks = getStacks(true /* all */)
|
|
}
|
|
|
|
return state
|
|
}
|
|
|
|
func introspectRuntimeVersion() RuntimeVersion {
|
|
return RuntimeVersion{
|
|
GoVersion: runtime.Version(),
|
|
LibraryVersion: VersionInfo,
|
|
}
|
|
}
|
|
|
|
// registerInternal registers the following internal handlers which return runtime state:
|
|
// _gometa_introspect: TChannel internal state.
|
|
// _gometa_runtime: Golang runtime stats.
|
|
func (ch *Channel) registerInternal() {
|
|
endpoints := []struct {
|
|
name string
|
|
handler func([]byte) interface{}
|
|
}{
|
|
{"_gometa_introspect", ch.handleIntrospection},
|
|
{"_gometa_runtime", handleInternalRuntime},
|
|
}
|
|
|
|
tchanSC := ch.GetSubChannel("tchannel")
|
|
for _, ep := range endpoints {
|
|
// We need ep in our closure.
|
|
ep := ep
|
|
handler := func(ctx context.Context, call *InboundCall) {
|
|
var arg2, arg3 []byte
|
|
if err := NewArgReader(call.Arg2Reader()).Read(&arg2); err != nil {
|
|
return
|
|
}
|
|
if err := NewArgReader(call.Arg3Reader()).Read(&arg3); err != nil {
|
|
return
|
|
}
|
|
if err := NewArgWriter(call.Response().Arg2Writer()).Write(nil); err != nil {
|
|
return
|
|
}
|
|
NewArgWriter(call.Response().Arg3Writer()).WriteJSON(ep.handler(arg3))
|
|
}
|
|
ch.Register(HandlerFunc(handler), ep.name)
|
|
tchanSC.Register(HandlerFunc(handler), ep.name)
|
|
}
|
|
}
|