| /* |
| * |
| * Copyright 2024 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package test |
| |
| import ( |
| "context" |
| "fmt" |
| "net" |
| "sync" |
| "testing" |
| |
| "google.golang.org/grpc" |
| "google.golang.org/grpc/credentials/insecure" |
| "google.golang.org/grpc/internal/stubserver" |
| "google.golang.org/grpc/interop" |
| testgrpc "google.golang.org/grpc/interop/grpc_testing" |
| testpb "google.golang.org/grpc/interop/grpc_testing" |
| "google.golang.org/grpc/peer" |
| "google.golang.org/grpc/stats" |
| ) |
| |
| // TestPeerForClientStatsHandler configures a stats handler that |
| // verifies that peer is sent all stats handler callouts instead |
| // of Begin and PickerUpdated. |
| func (s) TestPeerForClientStatsHandler(t *testing.T) { |
| psh := &peerStatsHandler{} |
| |
| // Stats callouts & peer object population. |
| // Note: |
| // * Begin stats lack peer info (RPC starts pre-resolution). |
| // * PickerUpdated: no peer info (picker lacks transport details). |
| expectedCallouts := map[stats.RPCStats]bool{ |
| &stats.OutPayload{}: true, |
| &stats.InHeader{}: true, |
| &stats.OutHeader{}: true, |
| &stats.InTrailer{}: true, |
| &stats.OutTrailer{}: true, |
| &stats.End{}: true, |
| &stats.Begin{}: false, |
| &stats.PickerUpdated{}: false, |
| } |
| |
| // Start server. |
| l, err := net.Listen("tcp", "localhost:0") |
| if err != nil { |
| t.Fatal(err) |
| } |
| ss := &stubserver.StubServer{ |
| Listener: l, |
| EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) { |
| return &testpb.Empty{}, nil |
| }, |
| S: grpc.NewServer(), |
| } |
| stubserver.StartTestService(t, ss) |
| defer ss.S.Stop() |
| |
| // Create client with stats handler and do some calls. |
| cc, err := grpc.NewClient( |
| l.Addr().String(), |
| grpc.WithTransportCredentials(insecure.NewCredentials()), |
| grpc.WithStatsHandler(psh)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer cc.Close() |
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
| defer cancel() |
| client := testgrpc.NewTestServiceClient(cc) |
| interop.DoEmptyUnaryCall(ctx, client) |
| |
| psh.mu.Lock() |
| pshArgs := psh.args |
| psh.mu.Unlock() |
| |
| // Fetch the total unique stats handlers with peer != nil |
| uniqueStatsTypes := make(map[string]struct{}) |
| for _, callbackArgs := range pshArgs { |
| key := fmt.Sprintf("%T", callbackArgs.rpcStats) |
| if _, exists := uniqueStatsTypes[key]; exists { |
| continue |
| } |
| uniqueStatsTypes[fmt.Sprintf("%T", callbackArgs.rpcStats)] = struct{}{} |
| } |
| if len(uniqueStatsTypes) != len(expectedCallouts) { |
| t.Errorf("Unexpected number of stats handler callouts. Got %v, want %v", len(uniqueStatsTypes), len(expectedCallouts)) |
| } |
| |
| for _, callbackArgs := range pshArgs { |
| expectedPeer, found := expectedCallouts[callbackArgs.rpcStats] |
| // In case expectation is set to false and still we got the peer, |
| // then it's good to have it. So no need to assert those conditions. |
| if found && expectedPeer && callbackArgs.peer != nil { |
| continue |
| } else if expectedPeer && callbackArgs.peer == nil { |
| t.Errorf("peer not populated for: %T", callbackArgs.rpcStats) |
| } |
| } |
| } |
| |
| type peerStats struct { |
| rpcStats stats.RPCStats |
| peer *peer.Peer |
| } |
| |
| type peerStatsHandler struct { |
| args []peerStats |
| mu sync.Mutex |
| } |
| |
| func (h *peerStatsHandler) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context { |
| return ctx |
| } |
| |
| func (h *peerStatsHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { |
| p, _ := peer.FromContext(ctx) |
| h.mu.Lock() |
| defer h.mu.Unlock() |
| h.args = append(h.args, peerStats{rs, p}) |
| } |
| |
| func (h *peerStatsHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { |
| return ctx |
| } |
| |
| func (h *peerStatsHandler) HandleConn(context.Context, stats.ConnStats) {} |