github.com/aavshr/aws-sdk-go@v1.41.3/aws/csm/reporter_test.go (about)

     1  //go:build go1.7
     2  // +build go1.7
     3  
     4  package csm_test
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"sort"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/aavshr/aws-sdk-go/aws"
    16  	"github.com/aavshr/aws-sdk-go/aws/awserr"
    17  	"github.com/aavshr/aws-sdk-go/aws/client"
    18  	"github.com/aavshr/aws-sdk-go/aws/client/metadata"
    19  	"github.com/aavshr/aws-sdk-go/aws/csm"
    20  	"github.com/aavshr/aws-sdk-go/aws/request"
    21  	v4 "github.com/aavshr/aws-sdk-go/aws/signer/v4"
    22  	"github.com/aavshr/aws-sdk-go/awstesting/unit"
    23  	"github.com/aavshr/aws-sdk-go/private/protocol/jsonrpc"
    24  )
    25  
    26  func TestReportingMetrics(t *testing.T) {
    27  	sess := unit.Session.Copy(&aws.Config{
    28  		SleepDelay: func(time.Duration) {},
    29  	})
    30  	sess.Handlers.Validate.Clear()
    31  	sess.Handlers.Sign.Clear()
    32  	sess.Handlers.Send.Clear()
    33  
    34  	reporter := csm.Get()
    35  	if reporter == nil {
    36  		t.Errorf("expected non-nil reporter")
    37  	}
    38  	reporter.InjectHandlers(&sess.Handlers)
    39  
    40  	cases := map[string]struct {
    41  		Request       *request.Request
    42  		ExpectMetrics []map[string]interface{}
    43  	}{
    44  		"successful request": {
    45  			Request: func() *request.Request {
    46  				md := metadata.ClientInfo{}
    47  				op := &request.Operation{Name: "OperationName"}
    48  				req := request.New(*sess.Config, md, sess.Handlers, client.DefaultRetryer{NumMaxRetries: 3}, op, nil, nil)
    49  				req.Handlers.Send.PushBack(func(r *request.Request) {
    50  					req.HTTPResponse = &http.Response{
    51  						StatusCode: 200,
    52  						Header:     http.Header{},
    53  					}
    54  				})
    55  				return req
    56  			}(),
    57  			ExpectMetrics: []map[string]interface{}{
    58  				{
    59  					"Type":           "ApiCallAttempt",
    60  					"HttpStatusCode": float64(200),
    61  				},
    62  				{
    63  					"Type":                "ApiCall",
    64  					"FinalHttpStatusCode": float64(200),
    65  				},
    66  			},
    67  		},
    68  		"failed request, no retry": {
    69  			Request: func() *request.Request {
    70  				md := metadata.ClientInfo{}
    71  				op := &request.Operation{Name: "OperationName"}
    72  				req := request.New(*sess.Config, md, sess.Handlers, client.DefaultRetryer{NumMaxRetries: 3}, op, nil, nil)
    73  				req.Handlers.Send.PushBack(func(r *request.Request) {
    74  					req.HTTPResponse = &http.Response{
    75  						StatusCode: 400,
    76  						Header:     http.Header{},
    77  					}
    78  					req.Retryable = aws.Bool(false)
    79  					req.Error = awserr.New("Error", "Message", nil)
    80  				})
    81  
    82  				return req
    83  			}(),
    84  			ExpectMetrics: []map[string]interface{}{
    85  				{
    86  					"Type":                "ApiCallAttempt",
    87  					"HttpStatusCode":      float64(400),
    88  					"AwsException":        "Error",
    89  					"AwsExceptionMessage": "Error: Message",
    90  				},
    91  				{
    92  					"Type":                     "ApiCall",
    93  					"FinalHttpStatusCode":      float64(400),
    94  					"FinalAwsException":        "Error",
    95  					"FinalAwsExceptionMessage": "Error: Message",
    96  					"AttemptCount":             float64(1),
    97  				},
    98  			},
    99  		},
   100  		"failed request, with retry": {
   101  			Request: func() *request.Request {
   102  				md := metadata.ClientInfo{}
   103  				op := &request.Operation{Name: "OperationName"}
   104  				req := request.New(*sess.Config, md, sess.Handlers, client.DefaultRetryer{NumMaxRetries: 1}, op, nil, nil)
   105  				resps := []*http.Response{
   106  					{
   107  						StatusCode: 500,
   108  						Header:     http.Header{},
   109  					},
   110  					{
   111  						StatusCode: 500,
   112  						Header:     http.Header{},
   113  					},
   114  				}
   115  				req.Handlers.Send.PushBack(func(r *request.Request) {
   116  					req.HTTPResponse = resps[0]
   117  					resps = resps[1:]
   118  				})
   119  
   120  				return req
   121  			}(),
   122  			ExpectMetrics: []map[string]interface{}{
   123  				{
   124  					"Type":                "ApiCallAttempt",
   125  					"HttpStatusCode":      float64(500),
   126  					"AwsException":        "UnknownError",
   127  					"AwsExceptionMessage": "UnknownError: unknown error",
   128  				},
   129  				{
   130  					"Type":                "ApiCallAttempt",
   131  					"HttpStatusCode":      float64(500),
   132  					"AwsException":        "UnknownError",
   133  					"AwsExceptionMessage": "UnknownError: unknown error",
   134  				},
   135  				{
   136  					"Type":                     "ApiCall",
   137  					"FinalHttpStatusCode":      float64(500),
   138  					"FinalAwsException":        "UnknownError",
   139  					"FinalAwsExceptionMessage": "UnknownError: unknown error",
   140  					"AttemptCount":             float64(2),
   141  				},
   142  			},
   143  		},
   144  		"success request, with retry": {
   145  			Request: func() *request.Request {
   146  				md := metadata.ClientInfo{}
   147  				op := &request.Operation{Name: "OperationName"}
   148  				req := request.New(*sess.Config, md, sess.Handlers, client.DefaultRetryer{NumMaxRetries: 3}, op, nil, nil)
   149  				errs := []error{
   150  					awserr.New("AWSError", "aws error", nil),
   151  					awserr.New(request.ErrCodeRequestError, "sdk error", nil),
   152  					nil,
   153  				}
   154  				resps := []*http.Response{
   155  					{
   156  						StatusCode: 500,
   157  						Header:     http.Header{},
   158  					},
   159  					{
   160  						StatusCode: 500,
   161  						Header:     http.Header{},
   162  					},
   163  					{
   164  						StatusCode: 200,
   165  						Header:     http.Header{},
   166  					},
   167  				}
   168  				req.Handlers.Send.PushBack(func(r *request.Request) {
   169  					req.HTTPResponse = resps[0]
   170  					resps = resps[1:]
   171  					req.Error = errs[0]
   172  					errs = errs[1:]
   173  				})
   174  
   175  				return req
   176  			}(),
   177  			ExpectMetrics: []map[string]interface{}{
   178  				{
   179  					"Type":                "ApiCallAttempt",
   180  					"AwsException":        "AWSError",
   181  					"AwsExceptionMessage": "AWSError: aws error",
   182  					"HttpStatusCode":      float64(500),
   183  				},
   184  				{
   185  					"Type":                "ApiCallAttempt",
   186  					"SdkException":        request.ErrCodeRequestError,
   187  					"SdkExceptionMessage": request.ErrCodeRequestError + ": sdk error",
   188  					"HttpStatusCode":      float64(500),
   189  				},
   190  				{
   191  					"Type":                "ApiCallAttempt",
   192  					"AwsException":        nil,
   193  					"AwsExceptionMessage": nil,
   194  					"SdkException":        nil,
   195  					"SdkExceptionMessage": nil,
   196  					"HttpStatusCode":      float64(200),
   197  				},
   198  				{
   199  					"Type":                     "ApiCall",
   200  					"FinalHttpStatusCode":      float64(200),
   201  					"FinalAwsException":        nil,
   202  					"FinalAwsExceptionMessage": nil,
   203  					"FinalSdkException":        nil,
   204  					"FinalSdkExceptionMessage": nil,
   205  					"AttemptCount":             float64(3),
   206  				},
   207  			},
   208  		},
   209  	}
   210  
   211  	for name, c := range cases {
   212  		t.Run(name, func(t *testing.T) {
   213  			ctx, cancelFn := context.WithTimeout(context.Background(), time.Second)
   214  			defer cancelFn()
   215  
   216  			c.Request.Send()
   217  			for i := 0; i < len(c.ExpectMetrics); i++ {
   218  				select {
   219  				case m := <-csm.MetricsCh:
   220  					for ek, ev := range c.ExpectMetrics[i] {
   221  						if ev == nil {
   222  							// must not be set
   223  							if _, ok := m[ek]; ok {
   224  								t.Errorf("%d, expect %v metric member, not to be set, %v", i, ek, m[ek])
   225  							}
   226  							continue
   227  						}
   228  						if _, ok := m[ek]; !ok {
   229  							t.Errorf("%d, expect %v metric member, keys: %v", i, ek, keys(m))
   230  						}
   231  						if e, a := ev, m[ek]; e != a {
   232  							t.Errorf("%d, expect %v:%v(%T), metric value, got %v(%T)", i, ek, e, e, a, a)
   233  						}
   234  					}
   235  				case <-ctx.Done():
   236  					t.Errorf("timeout waiting for metrics")
   237  					return
   238  				}
   239  			}
   240  
   241  			var extraMetrics []map[string]interface{}
   242  		Loop:
   243  			for {
   244  				select {
   245  				case m := <-csm.MetricsCh:
   246  					extraMetrics = append(extraMetrics, m)
   247  				default:
   248  					break Loop
   249  				}
   250  			}
   251  			if len(extraMetrics) != 0 {
   252  				t.Fatalf("unexpected metrics, %#v", extraMetrics)
   253  			}
   254  		})
   255  	}
   256  }
   257  
   258  type mockService struct {
   259  	*client.Client
   260  }
   261  
   262  type input struct{}
   263  type output struct{}
   264  
   265  func (s *mockService) Request(i input) *request.Request {
   266  	op := &request.Operation{
   267  		Name:       "foo",
   268  		HTTPMethod: "POST",
   269  		HTTPPath:   "/",
   270  	}
   271  
   272  	o := output{}
   273  	req := s.NewRequest(op, &i, &o)
   274  	return req
   275  }
   276  
   277  func BenchmarkWithCSM(b *testing.B) {
   278  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   279  		w.Write([]byte(fmt.Sprintf("{}")))
   280  	}))
   281  	defer server.Close()
   282  
   283  	cfg := aws.Config{
   284  		Endpoint: aws.String(server.URL),
   285  	}
   286  
   287  	sess := unit.Session.Copy(&cfg)
   288  	r := csm.Get()
   289  
   290  	r.InjectHandlers(&sess.Handlers)
   291  
   292  	c := sess.ClientConfig("id", &cfg)
   293  
   294  	svc := mockService{
   295  		client.New(
   296  			*c.Config,
   297  			metadata.ClientInfo{
   298  				ServiceName:   "service",
   299  				ServiceID:     "id",
   300  				SigningName:   "signing",
   301  				SigningRegion: "region",
   302  				Endpoint:      server.URL,
   303  				APIVersion:    "0",
   304  				JSONVersion:   "1.1",
   305  				TargetPrefix:  "prefix",
   306  			},
   307  			c.Handlers,
   308  		),
   309  	}
   310  
   311  	svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
   312  	svc.Handlers.Build.PushBackNamed(jsonrpc.BuildHandler)
   313  	svc.Handlers.Unmarshal.PushBackNamed(jsonrpc.UnmarshalHandler)
   314  	svc.Handlers.UnmarshalMeta.PushBackNamed(jsonrpc.UnmarshalMetaHandler)
   315  	svc.Handlers.UnmarshalError.PushBackNamed(jsonrpc.UnmarshalErrorHandler)
   316  
   317  	for i := 0; i < b.N; i++ {
   318  		req := svc.Request(input{})
   319  		req.Send()
   320  	}
   321  }
   322  
   323  func BenchmarkWithCSMNoUDPConnection(b *testing.B) {
   324  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   325  		w.Write([]byte(fmt.Sprintf("{}")))
   326  	}))
   327  	defer server.Close()
   328  
   329  	cfg := aws.Config{
   330  		Endpoint: aws.String(server.URL),
   331  	}
   332  
   333  	sess := unit.Session.Copy(&cfg)
   334  	r := csm.Get()
   335  	r.Pause()
   336  	r.InjectHandlers(&sess.Handlers)
   337  	defer r.Pause()
   338  
   339  	c := sess.ClientConfig("id", &cfg)
   340  
   341  	svc := mockService{
   342  		client.New(
   343  			*c.Config,
   344  			metadata.ClientInfo{
   345  				ServiceName:   "service",
   346  				ServiceID:     "id",
   347  				SigningName:   "signing",
   348  				SigningRegion: "region",
   349  				Endpoint:      server.URL,
   350  				APIVersion:    "0",
   351  				JSONVersion:   "1.1",
   352  				TargetPrefix:  "prefix",
   353  			},
   354  			c.Handlers,
   355  		),
   356  	}
   357  
   358  	svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
   359  	svc.Handlers.Build.PushBackNamed(jsonrpc.BuildHandler)
   360  	svc.Handlers.Unmarshal.PushBackNamed(jsonrpc.UnmarshalHandler)
   361  	svc.Handlers.UnmarshalMeta.PushBackNamed(jsonrpc.UnmarshalMetaHandler)
   362  	svc.Handlers.UnmarshalError.PushBackNamed(jsonrpc.UnmarshalErrorHandler)
   363  
   364  	for i := 0; i < b.N; i++ {
   365  		req := svc.Request(input{})
   366  		req.Send()
   367  	}
   368  }
   369  
   370  func BenchmarkWithoutCSM(b *testing.B) {
   371  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   372  		w.Write([]byte(fmt.Sprintf("{}")))
   373  	}))
   374  	defer server.Close()
   375  
   376  	cfg := aws.Config{
   377  		Endpoint: aws.String(server.URL),
   378  	}
   379  	sess := unit.Session.Copy(&cfg)
   380  	c := sess.ClientConfig("id", &cfg)
   381  
   382  	svc := mockService{
   383  		client.New(
   384  			*c.Config,
   385  			metadata.ClientInfo{
   386  				ServiceName:   "service",
   387  				ServiceID:     "id",
   388  				SigningName:   "signing",
   389  				SigningRegion: "region",
   390  				Endpoint:      server.URL,
   391  				APIVersion:    "0",
   392  				JSONVersion:   "1.1",
   393  				TargetPrefix:  "prefix",
   394  			},
   395  			c.Handlers,
   396  		),
   397  	}
   398  
   399  	svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
   400  	svc.Handlers.Build.PushBackNamed(jsonrpc.BuildHandler)
   401  	svc.Handlers.Unmarshal.PushBackNamed(jsonrpc.UnmarshalHandler)
   402  	svc.Handlers.UnmarshalMeta.PushBackNamed(jsonrpc.UnmarshalMetaHandler)
   403  	svc.Handlers.UnmarshalError.PushBackNamed(jsonrpc.UnmarshalErrorHandler)
   404  
   405  	for i := 0; i < b.N; i++ {
   406  		req := svc.Request(input{})
   407  		req.Send()
   408  	}
   409  }
   410  
   411  func keys(m map[string]interface{}) []string {
   412  	ks := make([]string, 0, len(m))
   413  	for k := range m {
   414  		ks = append(ks, k)
   415  	}
   416  	sort.Strings(ks)
   417  	return ks
   418  }