yarpc / yarpc-go

@@ -24,6 +24,7 @@
Loading
24 24
	"crypto/tls"
25 25
26 26
	"go.uber.org/net/metrics"
27 +
	yarpctls "go.uber.org/yarpc/api/transport/tls"
27 28
	"go.uber.org/zap"
28 29
)
29 30
@@ -39,11 +40,12 @@
Loading
39 40
	tlsFailuresCounter          *metrics.Counter
40 41
}
41 42
42 -
func newObserver(meter *metrics.Scope, logger *zap.Logger, serviceName, transportName string) *observer {
43 +
func newObserver(meter *metrics.Scope, logger *zap.Logger, serviceName, transportName string, mode yarpctls.Mode) *observer {
43 44
	tags := metrics.Tags{
44 45
		"service":   serviceName,
45 46
		"transport": transportName,
46 47
		"component": "yarpc",
48 +
		"mode":      mode.String(),
47 49
	}
48 50
49 51
	plaintextConns, err := meter.Counter(metrics.Spec{

@@ -29,15 +29,18 @@
Loading
29 29
	"time"
30 30
31 31
	"go.uber.org/net/metrics"
32 +
	yarpctls "go.uber.org/yarpc/api/transport/tls"
32 33
	"go.uber.org/zap"
33 34
)
34 35
35 36
var (
36 37
	errListenerClosed = errors.New("listener closed")
37 38
38 -
	// TODO(jronak): below timeouts are experimental, will be tuned after testing.
39 -
	_sniffReadTimeout    = time.Second * 10
40 -
	_tlsHandshakeTimeout = time.Second * 10
39 +
	// Connection has 15s to transmit first 5 bytes for sniffing.
40 +
	_sniffReadTimeout = time.Second * 15
41 +
	// Handshake timeout set to 120s, see gRPC-go:
42 +
	// https://github.com/grpc/grpc-go/blob/fdc5d2f3da856f3cdd3483280ae33da5bee17a93/server.go#L467
43 +
	_tlsHandshakeTimeout = time.Second * 120
41 44
)
42 45
43 46
// Config describes how listener should be configured.
@@ -49,6 +52,7 @@
Loading
49 52
	TransportName string
50 53
	Meter         *metrics.Scope
51 54
	Logger        *zap.Logger
55 +
	Mode          yarpctls.Mode
52 56
}
53 57
54 58
// listener wraps original net listener and it accepts both TLS and non-TLS connections.
@@ -58,6 +62,7 @@
Loading
58 62
	tlsConfig *tls.Config
59 63
	observer  *observer
60 64
	logger    *zap.Logger
65 +
	mode      yarpctls.Mode
61 66
62 67
	closeOnce   sync.Once
63 68
	connChan    chan net.Conn
@@ -68,14 +73,19 @@
Loading
68 73
// NewListener returns a multiplexed listener which accepts both TLS and
69 74
// plaintext connections.
70 75
func NewListener(c Config) net.Listener {
76 +
	if c.Mode == yarpctls.Disabled {
77 +
		return c.Listener
78 +
	}
79 +
71 80
	lis := &listener{
72 81
		Listener:    c.Listener,
73 82
		tlsConfig:   c.TLSConfig,
74 -
		observer:    newObserver(c.Meter, c.Logger, c.ServiceName, c.TransportName),
83 +
		observer:    newObserver(c.Meter, c.Logger, c.ServiceName, c.TransportName, c.Mode),
75 84
		logger:      c.Logger,
76 85
		connChan:    make(chan net.Conn),
77 86
		stoppedChan: make(chan struct{}),
78 87
		stopChan:    make(chan struct{}),
88 +
		mode:        c.Mode,
79 89
	}
80 90
81 91
	// Starts go routine for the connection server
@@ -114,8 +124,9 @@
Loading
114 124
// go routine for each connection for async muxing.
115 125
func (l *listener) serve() {
116 126
	var wg sync.WaitGroup
117 -
127 +
	ctx, cancel := context.WithCancel(context.Background())
118 128
	defer func() {
129 +
		cancel()
119 130
		wg.Wait()
120 131
		close(l.stoppedChan)
121 132
		close(l.connChan)
@@ -128,16 +139,16 @@
Loading
128 139
		}
129 140
130 141
		wg.Add(1)
131 -
		go l.serveConnection(conn, &wg)
142 +
		go l.serveConnection(ctx, conn, &wg)
132 143
	}
133 144
}
134 145
135 146
// serveConnection muxes the given connection and sends muxed connection to the
136 147
// connection channel.
137 -
func (l *listener) serveConnection(conn net.Conn, wg *sync.WaitGroup) {
148 +
func (l *listener) serveConnection(ctx context.Context, conn net.Conn, wg *sync.WaitGroup) {
138 149
	defer wg.Done()
139 150
140 -
	c, err := l.mux(conn)
151 +
	c, err := l.mux(ctx, conn)
141 152
	if err != nil {
142 153
		conn.Close()
143 154
		return
@@ -152,7 +163,11 @@
Loading
152 163
153 164
// mux accepts both plaintext and tls connection, and returns a plaintext
154 165
// connection.
155 -
func (l *listener) mux(conn net.Conn) (net.Conn, error) {
166 +
func (l *listener) mux(ctx context.Context, conn net.Conn) (net.Conn, error) {
167 +
	if l.mode == yarpctls.Enforced {
168 +
		return l.handleTLSConn(ctx, conn)
169 +
	}
170 +
156 171
	c := newConnectionSniffer(conn)
157 172
	isTLS, err := matchTLSConnection(c)
158 173
	if err != nil {
@@ -161,16 +176,16 @@
Loading
161 176
	}
162 177
163 178
	if isTLS {
164 -
		return l.handleTLSConn(c)
179 +
		return l.handleTLSConn(ctx, c)
165 180
	}
166 181
167 -
	return l.handlePlaintextConn(c), nil
182 +
	return l.handlePlaintextConn(c)
168 183
}
169 184
170 185
// handleTLSConn completes the TLS handshake for the given connection and
171 186
// returns a TLS server wrapped plaintext connection.
172 -
func (l *listener) handleTLSConn(conn net.Conn) (net.Conn, error) {
173 -
	ctx, cancel := context.WithTimeout(context.Background(), _tlsHandshakeTimeout)
187 +
func (l *listener) handleTLSConn(ctx context.Context, conn net.Conn) (net.Conn, error) {
188 +
	ctx, cancel := context.WithTimeout(ctx, _tlsHandshakeTimeout)
174 189
	defer cancel()
175 190
176 191
	tlsConn := tls.Server(conn, l.tlsConfig)
@@ -184,9 +199,9 @@
Loading
184 199
	return tlsConn, nil
185 200
}
186 201
187 -
func (l *listener) handlePlaintextConn(conn net.Conn) net.Conn {
202 +
func (l *listener) handlePlaintextConn(conn net.Conn) (net.Conn, error) {
188 203
	l.observer.incPlaintextConnections()
189 -
	return conn
204 +
	return conn, nil
190 205
}
191 206
192 207
func matchTLSConnection(cs *connSniffer) (bool, error) {

@@ -22,10 +22,13 @@
Loading
22 22
23 23
import (
24 24
	"fmt"
25 +
	"reflect"
25 26
27 +
	"github.com/uber-go/mapdecode"
26 28
	"go.uber.org/multierr"
27 29
	"go.uber.org/yarpc"
28 30
	"go.uber.org/yarpc/api/transport"
31 +
	yarpctls "go.uber.org/yarpc/api/transport/tls"
29 32
	"go.uber.org/yarpc/internal/config"
30 33
)
31 34
@@ -209,7 +212,7 @@
Loading
209 212
	}
210 213
211 214
	b.needTransport(spec)
212 -
	cv, err := spec.Inbound.Decode(attrs, config.InterpolateWith(b.kit.resolver))
215 +
	cv, err := spec.Inbound.Decode(attrs, config.InterpolateWith(b.kit.resolver), mapdecode.DecodeHook(tlsModeDecodeHook))
213 216
	if err != nil {
214 217
		return fmt.Errorf("failed to decode inbound configuration: %v", err)
215 218
	}
@@ -326,3 +329,13 @@
Loading
326 329
func (b *builder) needTransport(spec *compiledTransportSpec) {
327 330
	b.needTransports[spec.Name] = spec
328 331
}
332 +
333 +
func tlsModeDecodeHook(from, to reflect.Type, data reflect.Value) (reflect.Value, error) {
334 +
	var mode yarpctls.Mode
335 +
	if from.Kind() != reflect.String || to != reflect.TypeOf(mode) {
336 +
		return data, nil
337 +
	}
338 +
339 +
	err := mode.UnmarshalText([]byte(data.String()))
340 +
	return reflect.ValueOf(mode), err
341 +
}

@@ -27,6 +27,7 @@
Loading
27 27
28 28
	"go.uber.org/yarpc/api/peer"
29 29
	"go.uber.org/yarpc/api/transport"
30 +
	yarpctls "go.uber.org/yarpc/api/transport/tls"
30 31
	peerchooser "go.uber.org/yarpc/peer"
31 32
	"go.uber.org/yarpc/peer/hostport"
32 33
	"go.uber.org/yarpc/yarpcconfig"
@@ -113,9 +114,18 @@
Loading
113 114
	Enabled  bool   `config:"enabled"` // disabled by default
114 115
	CertFile string `config:"certFile,interpolate"`
115 116
	KeyFile  string `config:"keyFile,interpolate"`
117 +
118 +
	// Mode when set to Permissive or Enforced enables TLS inbound and
119 +
	// TLS configuration must be passed as an inbound option.
120 +
	// Note: enable, cert and key fields are ignored when mode is set.
121 +
	Mode yarpctls.Mode `config:"mode,interpolate"`
116 122
}
117 123
118 124
func (c InboundTLSConfig) inboundOptions() ([]InboundOption, error) {
125 +
	if c.Mode != yarpctls.Disabled {
126 +
		return []InboundOption{InboundTLSMode(c.Mode)}, nil
127 +
	}
128 +
119 129
	if !c.Enabled {
120 130
		return nil, nil
121 131
	}
@@ -272,7 +282,7 @@
Loading
272 282
	return transportSpec, nil
273 283
}
274 284
275 -
func (t *transportSpec) buildTransport(transportConfig *TransportConfig, _ *yarpcconfig.Kit) (transport.Transport, error) {
285 +
func (t *transportSpec) buildTransport(transportConfig *TransportConfig, kit *yarpcconfig.Kit) (transport.Transport, error) {
276 286
	options := t.TransportOptions
277 287
	if transportConfig.ServerMaxRecvMsgSize > 0 {
278 288
		options = append(options, ServerMaxRecvMsgSize(transportConfig.ServerMaxRecvMsgSize))
@@ -296,7 +306,7 @@
Loading
296 306
	if err != nil {
297 307
		return nil, err
298 308
	}
299 -
	options = append(options, BackoffStrategy(backoffStrategy))
309 +
	options = append(options, BackoffStrategy(backoffStrategy), ServiceName(kit.ServiceName()))
300 310
	return newTransport(newTransportOptions(options)), nil
301 311
}
302 312

@@ -21,12 +21,15 @@
Loading
21 21
package grpc
22 22
23 23
import (
24 +
	"errors"
24 25
	"net"
25 26
	"sync"
26 27
27 28
	"go.uber.org/yarpc/api/transport"
29 +
	yarpctls "go.uber.org/yarpc/api/transport/tls"
28 30
	"go.uber.org/yarpc/api/x/introspection"
29 31
	"go.uber.org/yarpc/pkg/lifecycle"
32 +
	"go.uber.org/yarpc/transport/internal/tlsmux"
30 33
	"go.uber.org/yarpc/yarpcerrors"
31 34
	"go.uber.org/zap"
32 35
	"google.golang.org/grpc"
@@ -118,8 +121,24 @@
Loading
118 121
		grpc.MaxSendMsgSize(i.t.options.serverMaxSendMsgSize),
119 122
	}
120 123
124 +
	listener := i.listener
125 +
121 126
	if i.options.creds != nil {
122 127
		serverOptions = append(serverOptions, grpc.Creds(i.options.creds))
128 +
	} else if i.options.tlsMode != yarpctls.Disabled {
129 +
		if i.options.tlsConfig == nil {
130 +
			return errors.New("gRPC TLS enabled but configuration not provided")
131 +
		}
132 +
133 +
		listener = tlsmux.NewListener(tlsmux.Config{
134 +
			Listener:      listener,
135 +
			TLSConfig:     i.options.tlsConfig.Clone(),
136 +
			Logger:        i.t.options.logger,
137 +
			Meter:         i.t.options.meter,
138 +
			ServiceName:   i.t.options.serviceName,
139 +
			TransportName: TransportName,
140 +
			Mode:          i.options.tlsMode,
141 +
		})
123 142
	}
124 143
125 144
	if i.t.options.serverMaxHeaderListSize != nil {
@@ -143,7 +162,7 @@
Loading
143 162
		//
144 163
		// TODO Server always returns a non-nil error but should
145 164
		// we do something with some or all errors?
146 -
		_ = server.Serve(i.listener)
165 +
		_ = server.Serve(listener)
147 166
	}()
148 167
	i.server = server
149 168
	return nil

@@ -22,12 +22,15 @@
Loading
22 22
23 23
import (
24 24
	"context"
25 +
	"crypto/tls"
25 26
	"math"
26 27
	"net"
27 28
28 29
	opentracing "github.com/opentracing/opentracing-go"
30 +
	"go.uber.org/net/metrics"
29 31
	"go.uber.org/yarpc/api/backoff"
30 32
	"go.uber.org/yarpc/api/transport"
33 +
	yarpctls "go.uber.org/yarpc/api/transport/tls"
31 34
	intbackoff "go.uber.org/yarpc/internal/backoff"
32 35
	"go.uber.org/zap"
33 36
	"google.golang.org/grpc"
@@ -61,6 +64,14 @@
Loading
61 64
62 65
func (TransportOption) grpcOption() {}
63 66
67 +
// ServiceName specifices the name of the service used in transport logging
68 +
// and metrics.
69 +
func ServiceName(name string) TransportOption {
70 +
	return func(transportOptions *transportOptions) {
71 +
		transportOptions.serviceName = name
72 +
	}
73 +
}
74 +
64 75
// BackoffStrategy specifies the backoff strategy for delays between
65 76
// connection attempts for each peer.
66 77
//
@@ -90,6 +101,15 @@
Loading
90 101
	}
91 102
}
92 103
104 +
// Meter sets a meter to use for transport metrics.
105 +
//
106 +
// The default is to not emit any metrics.
107 +
func Meter(meter *metrics.Scope) TransportOption {
108 +
	return func(transportOptions *transportOptions) {
109 +
		transportOptions.meter = meter
110 +
	}
111 +
}
112 +
93 113
// ServerMaxRecvMsgSize is the maximum message size the server can receive.
94 114
//
95 115
// The default is 4MB.
@@ -159,6 +179,23 @@
Loading
159 179
	}
160 180
}
161 181
182 +
// InboundTLSConfiguration returns an InboundOption that provides the TLS confiugration
183 +
// used for setting up TLS inbound.
184 +
func InboundTLSConfiguration(config *tls.Config) InboundOption {
185 +
	return func(inboundOptions *inboundOptions) {
186 +
		inboundOptions.tlsConfig = config
187 +
	}
188 +
}
189 +
190 +
// InboundTLSMode returns an InboundOption that sets inbound TLS mode.
191 +
// It must be noted that TLS configuration must be passed separately using inbound
192 +
// option InboundTLSConfiguration.
193 +
func InboundTLSMode(mode yarpctls.Mode) InboundOption {
194 +
	return func(inboundOptions *inboundOptions) {
195 +
		inboundOptions.tlsMode = mode
196 +
	}
197 +
}
198 +
162 199
// OutboundOption is an option for an outbound.
163 200
type OutboundOption func(*outboundOptions)
164 201
@@ -213,6 +250,8 @@
Loading
213 250
	backoffStrategy         backoff.Strategy
214 251
	tracer                  opentracing.Tracer
215 252
	logger                  *zap.Logger
253 +
	meter                   *metrics.Scope
254 +
	serviceName             string
216 255
	serverMaxRecvMsgSize    int
217 256
	serverMaxSendMsgSize    int
218 257
	clientMaxRecvMsgSize    int
@@ -243,6 +282,9 @@
Loading
243 282
244 283
type inboundOptions struct {
245 284
	creds credentials.TransportCredentials
285 +
286 +
	tlsConfig *tls.Config
287 +
	tlsMode   yarpctls.Mode
246 288
}
247 289
248 290
func newInboundOptions(options []InboundOption) *inboundOptions {
Files Coverage
api 97.20%
compressor 92.86%
encoding 80.36%
internal 84.84%
peer 87.41%
pkg 95.27%
transport 90.05%
x/debug 92.31%
yarpcconfig 92.76%
yarpcerrors 98.96%
call.go 100.00%
config.go 80.56%
dispatcher.go 96.23%
dispatcher_introspection.go 85.71%
dispatcher_startup.go 91.07%
errors.go 100.00%
header.go 100.00%
inject.go 96.55%
middleware.go 100.00%
router.go 96.00%
serialize/serialize.go 76.47%
Project Totals (254 files) 87.76%
1
codecov:
2
  branch: dev
3
coverage:
4
  range: 50...90
5
  status:
6
    project:
7
      default:
8
        enabled: yes
9
        target: 80%
10
    patch:
11
      default:
12
        enabled: yes
13
        target: 80%
14
# This list is generated using scripts/generate-cover-ignore.sh
15
# All packages with .nocover files in them MUST be listed here
16
ignore:
17
 - /api/transport/tls/
18
 - /encoding/protobuf/internal/testpb/
19
 - /encoding/thrift/internal/
20
 - /encoding/thrift/internal/observabilitytest/test/
21
 - /encoding/thrift/internal/observabilitytest/test/testserviceclient/
22
 - /encoding/thrift/internal/observabilitytest/test/testservicefx/
23
 - /encoding/thrift/internal/observabilitytest/test/testserviceserver/
24
 - /encoding/thrift/internal/observabilitytest/test/testservicetest/
25
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/NOSERVICES/
26
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/
27
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/readonlystoreclient/
28
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/readonlystorefx/
29
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/readonlystoreserver/
30
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/readonlystoretest/
31
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/storeclient/
32
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/storefx/
33
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/storeserver/
34
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/storetest/
35
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/
36
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/baseserviceclient/
37
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/baseservicefx/
38
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/baseserviceserver/
39
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/baseservicetest/
40
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/emptyserviceclient/
41
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/emptyservicefx/
42
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/emptyserviceserver/
43
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/emptyservicetest/
44
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/extendemptyclient/
45
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/extendemptyfx/
46
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/extendemptyserver/
47
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/extendemptytest/
48
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/extendonlyclient/
49
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/extendonlyfx/
50
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/extendonlyserver/
51
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/extendonlytest/
52
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/
53
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/barclient/
54
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/barfx/
55
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/barserver/
56
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/bartest/
57
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/fooclient/
58
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/foofx/
59
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/fooserver/
60
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/footest/
61
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/nameclient/
62
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/namefx/
63
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/nameserver/
64
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/extends/nametest/
65
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/weather/
66
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/weather/weatherclient/
67
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/weather/weatherfx/
68
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/weather/weatherserver/
69
 - /encoding/thrift/thriftrw-plugin-yarpc/internal/tests/weather/weathertest/
70
 - /internal/cover/
71
 - /internal/crossdock/crossdockpb/
72
 - /internal/crossdock/thrift/echo/
73
 - /internal/crossdock/thrift/echo/echoclient/
74
 - /internal/crossdock/thrift/echo/echofx/
75
 - /internal/crossdock/thrift/echo/echoserver/
76
 - /internal/crossdock/thrift/echo/echotest/
77
 - /internal/crossdock/thrift/gauntlet/
78
 - /internal/crossdock/thrift/gauntlet/secondserviceclient/
79
 - /internal/crossdock/thrift/gauntlet/secondservicefx/
80
 - /internal/crossdock/thrift/gauntlet/secondserviceserver/
81
 - /internal/crossdock/thrift/gauntlet/secondservicetest/
82
 - /internal/crossdock/thrift/gauntlet/thrifttestclient/
83
 - /internal/crossdock/thrift/gauntlet/thrifttestfx/
84
 - /internal/crossdock/thrift/gauntlet/thrifttestserver/
85
 - /internal/crossdock/thrift/gauntlet/thrifttesttest/
86
 - /internal/crossdock/thrift/gen-go/echo/
87
 - /internal/crossdock/thrift/gen-go/gauntlet_apache/
88
 - /internal/crossdock/thrift/gen-go/gauntlet_tchannel/
89
 - /internal/crossdock/thrift/oneway/
90
 - /internal/crossdock/thrift/oneway/onewayclient/
91
 - /internal/crossdock/thrift/oneway/onewayfx/
92
 - /internal/crossdock/thrift/oneway/onewayserver/
93
 - /internal/crossdock/thrift/oneway/onewaytest/
94
 - /internal/examples/json-keyvalue/client/
95
 - /internal/examples/json-keyvalue/server/
96
 - /internal/examples/protobuf/
97
 - /internal/examples/protobuf/example/
98
 - /internal/examples/protobuf/examplepb/
99
 - /internal/examples/protobuf/exampleutil/
100
 - /internal/examples/streaming/
101
 - /internal/examples/streaming/client/
102
 - /internal/examples/streaming/server/
103
 - /internal/examples/thrift-hello/hello/
104
 - /internal/examples/thrift-hello/hello/echo/
105
 - /internal/examples/thrift-hello/hello/echo/helloclient/
106
 - /internal/examples/thrift-hello/hello/echo/hellofx/
107
 - /internal/examples/thrift-hello/hello/echo/helloserver/
108
 - /internal/examples/thrift-hello/hello/echo/hellotest/
109
 - /internal/examples/thrift-keyvalue/keyvalue/client/
110
 - /internal/examples/thrift-keyvalue/keyvalue/kv/
111
 - /internal/examples/thrift-keyvalue/keyvalue/kv/keyvalueclient/
112
 - /internal/examples/thrift-keyvalue/keyvalue/kv/keyvaluefx/
113
 - /internal/examples/thrift-keyvalue/keyvalue/kv/keyvalueserver/
114
 - /internal/examples/thrift-keyvalue/keyvalue/kv/keyvaluetest/
115
 - /internal/examples/thrift-keyvalue/keyvalue/server/
116
 - /internal/examples/thrift-oneway/
117
 - /internal/examples/thrift-oneway/sink/
118
 - /internal/examples/thrift-oneway/sink/helloclient/
119
 - /internal/examples/thrift-oneway/sink/hellofx/
120
 - /internal/examples/thrift-oneway/sink/helloserver/
121
 - /internal/examples/thrift-oneway/sink/hellotest/
122
 - /internal/prototest/example/
123
 - /internal/prototest/examplepb/
124
 - /internal/prototest/exampleutil/
125
 - /internal/service-test/
126
 - /internal/testutils/
127
 - /serialize/internal/
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading