2017-08-05 18:17:15 +00:00
|
|
|
// Copyright 2017 Google Inc. All Rights Reserved.
|
|
|
|
//
|
|
|
|
// 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 profiler
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"io"
|
2017-09-13 12:09:48 +00:00
|
|
|
"runtime/pprof"
|
2017-08-05 18:17:15 +00:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2017-09-13 12:09:48 +00:00
|
|
|
gcemd "cloud.google.com/go/compute/metadata"
|
2017-08-05 18:17:15 +00:00
|
|
|
"cloud.google.com/go/internal/testutil"
|
|
|
|
"cloud.google.com/go/profiler/mocks"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
"github.com/golang/protobuf/ptypes"
|
|
|
|
gax "github.com/googleapis/gax-go"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
pb "google.golang.org/genproto/googleapis/devtools/cloudprofiler/v2"
|
|
|
|
edpb "google.golang.org/genproto/googleapis/rpc/errdetails"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
grpcmd "google.golang.org/grpc/metadata"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2017-09-13 12:09:48 +00:00
|
|
|
testProjectID = "test-project-ID"
|
|
|
|
testInstanceName = "test-instance-name"
|
|
|
|
testZoneName = "test-zone-name"
|
|
|
|
testTarget = "test-target"
|
|
|
|
testService = "test-service"
|
|
|
|
testServiceVersion = "test-service-version"
|
2017-08-05 18:17:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func createTestDeployment() *pb.Deployment {
|
2017-09-13 12:09:48 +00:00
|
|
|
labels := map[string]string{
|
|
|
|
zoneNameLabel: testZoneName,
|
|
|
|
versionLabel: testServiceVersion,
|
|
|
|
}
|
2017-08-05 18:17:15 +00:00
|
|
|
return &pb.Deployment{
|
|
|
|
ProjectId: testProjectID,
|
2017-09-13 12:09:48 +00:00
|
|
|
Target: testService,
|
2017-08-05 18:17:15 +00:00
|
|
|
Labels: labels,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func createTestAgent(psc pb.ProfilerServiceClient) *agent {
|
|
|
|
c := &client{client: psc}
|
2017-09-13 12:09:48 +00:00
|
|
|
return &agent{
|
|
|
|
client: c,
|
|
|
|
deployment: createTestDeployment(),
|
|
|
|
profileLabels: map[string]string{instanceLabel: testInstanceName},
|
2017-08-05 18:17:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func createTrailers(dur time.Duration) map[string]string {
|
|
|
|
b, _ := proto.Marshal(&edpb.RetryInfo{
|
|
|
|
RetryDelay: ptypes.DurationProto(dur),
|
|
|
|
})
|
|
|
|
return map[string]string{
|
|
|
|
retryInfoMetadata: string(b),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateProfile(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
defer ctrl.Finish()
|
|
|
|
mpc := mocks.NewMockProfilerServiceClient(ctrl)
|
|
|
|
a := createTestAgent(mpc)
|
|
|
|
p := &pb.Profile{Name: "test_profile"}
|
|
|
|
wantRequest := pb.CreateProfileRequest{
|
|
|
|
Deployment: a.deployment,
|
|
|
|
ProfileType: []pb.ProfileType{pb.ProfileType_CPU, pb.ProfileType_HEAP},
|
|
|
|
}
|
|
|
|
|
|
|
|
mpc.EXPECT().CreateProfile(ctx, gomock.Eq(&wantRequest), gomock.Any()).Times(1).Return(p, nil)
|
|
|
|
|
|
|
|
gotP := a.createProfile(ctx)
|
|
|
|
|
|
|
|
if !testutil.Equal(gotP, p) {
|
|
|
|
t.Errorf("CreateProfile() got wrong profile, got %v, want %v", gotP, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProfileAndUpload(t *testing.T) {
|
2017-09-13 12:09:48 +00:00
|
|
|
defer func() {
|
|
|
|
startCPUProfile = pprof.StartCPUProfile
|
|
|
|
stopCPUProfile = pprof.StopCPUProfile
|
|
|
|
writeHeapProfile = pprof.WriteHeapProfile
|
|
|
|
sleep = gax.Sleep
|
|
|
|
}()
|
|
|
|
|
2017-08-05 18:17:15 +00:00
|
|
|
ctx := context.Background()
|
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
defer ctrl.Finish()
|
|
|
|
|
|
|
|
errFunc := func(io.Writer) error { return errors.New("") }
|
|
|
|
testDuration := time.Second * 5
|
|
|
|
tests := []struct {
|
|
|
|
profileType pb.ProfileType
|
|
|
|
duration *time.Duration
|
|
|
|
startCPUProfileFunc func(io.Writer) error
|
|
|
|
writeHeapProfileFunc func(io.Writer) error
|
|
|
|
wantBytes []byte
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
profileType: pb.ProfileType_CPU,
|
|
|
|
duration: &testDuration,
|
|
|
|
startCPUProfileFunc: func(w io.Writer) error {
|
|
|
|
w.Write([]byte{1})
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
writeHeapProfileFunc: errFunc,
|
|
|
|
wantBytes: []byte{1},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
profileType: pb.ProfileType_CPU,
|
|
|
|
startCPUProfileFunc: errFunc,
|
|
|
|
writeHeapProfileFunc: errFunc,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
profileType: pb.ProfileType_CPU,
|
|
|
|
duration: &testDuration,
|
|
|
|
startCPUProfileFunc: func(w io.Writer) error {
|
|
|
|
w.Write([]byte{2})
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
writeHeapProfileFunc: func(w io.Writer) error {
|
|
|
|
w.Write([]byte{3})
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
wantBytes: []byte{2},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
profileType: pb.ProfileType_HEAP,
|
|
|
|
startCPUProfileFunc: errFunc,
|
|
|
|
writeHeapProfileFunc: func(w io.Writer) error {
|
|
|
|
w.Write([]byte{4})
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
wantBytes: []byte{4},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
profileType: pb.ProfileType_HEAP,
|
|
|
|
startCPUProfileFunc: errFunc,
|
|
|
|
writeHeapProfileFunc: errFunc,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
profileType: pb.ProfileType_HEAP,
|
|
|
|
startCPUProfileFunc: func(w io.Writer) error {
|
|
|
|
w.Write([]byte{5})
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
writeHeapProfileFunc: func(w io.Writer) error {
|
|
|
|
w.Write([]byte{6})
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
wantBytes: []byte{6},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
profileType: pb.ProfileType_PROFILE_TYPE_UNSPECIFIED,
|
|
|
|
startCPUProfileFunc: func(w io.Writer) error {
|
|
|
|
w.Write([]byte{7})
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
writeHeapProfileFunc: func(w io.Writer) error {
|
|
|
|
w.Write([]byte{8})
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
mpc := mocks.NewMockProfilerServiceClient(ctrl)
|
|
|
|
a := createTestAgent(mpc)
|
|
|
|
startCPUProfile = tt.startCPUProfileFunc
|
|
|
|
stopCPUProfile = func() {}
|
|
|
|
writeHeapProfile = tt.writeHeapProfileFunc
|
|
|
|
var gotSleep *time.Duration
|
|
|
|
sleep = func(ctx context.Context, d time.Duration) error {
|
|
|
|
gotSleep = &d
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
p := &pb.Profile{ProfileType: tt.profileType}
|
|
|
|
if tt.duration != nil {
|
|
|
|
p.Duration = ptypes.DurationProto(*tt.duration)
|
|
|
|
}
|
|
|
|
if tt.wantBytes != nil {
|
|
|
|
wantProfile := &pb.Profile{
|
|
|
|
ProfileType: p.ProfileType,
|
|
|
|
Duration: p.Duration,
|
|
|
|
ProfileBytes: tt.wantBytes,
|
2017-09-13 12:09:48 +00:00
|
|
|
Labels: a.profileLabels,
|
2017-08-05 18:17:15 +00:00
|
|
|
}
|
|
|
|
wantRequest := pb.UpdateProfileRequest{
|
|
|
|
Profile: wantProfile,
|
|
|
|
}
|
|
|
|
mpc.EXPECT().UpdateProfile(ctx, gomock.Eq(&wantRequest)).Times(1)
|
|
|
|
} else {
|
|
|
|
mpc.EXPECT().UpdateProfile(gomock.Any(), gomock.Any()).MaxTimes(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
a.profileAndUpload(ctx, p)
|
|
|
|
|
|
|
|
if tt.duration == nil {
|
|
|
|
if gotSleep != nil {
|
|
|
|
t.Errorf("profileAndUpload(%v) slept for: %v, want no sleep", p, gotSleep)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if gotSleep == nil {
|
|
|
|
t.Errorf("profileAndUpload(%v) didn't sleep, want sleep for: %v", p, tt.duration)
|
|
|
|
} else if *gotSleep != *tt.duration {
|
|
|
|
t.Errorf("profileAndUpload(%v) slept for wrong duration, got: %v, want: %v", p, gotSleep, tt.duration)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRetry(t *testing.T) {
|
|
|
|
normalDuration := time.Second * 3
|
|
|
|
negativeDuration := time.Second * -3
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
trailers map[string]string
|
|
|
|
wantPause *time.Duration
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
createTrailers(normalDuration),
|
|
|
|
&normalDuration,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
createTrailers(negativeDuration),
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
map[string]string{retryInfoMetadata: "wrong format"},
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
map[string]string{},
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
md := grpcmd.New(tt.trailers)
|
|
|
|
r := &retryer{
|
|
|
|
backoff: gax.Backoff{
|
|
|
|
Initial: initialBackoff,
|
|
|
|
Max: maxBackoff,
|
|
|
|
Multiplier: backoffMultiplier,
|
|
|
|
},
|
|
|
|
md: md,
|
|
|
|
}
|
|
|
|
|
|
|
|
pause, shouldRetry := r.Retry(status.Error(codes.Aborted, ""))
|
|
|
|
|
|
|
|
if !shouldRetry {
|
|
|
|
t.Error("retryer.Retry() returned shouldRetry false, want true")
|
|
|
|
}
|
|
|
|
|
|
|
|
if tt.wantPause != nil {
|
|
|
|
if pause != *tt.wantPause {
|
|
|
|
t.Errorf("retryer.Retry() returned wrong pause, got: %v, want: %v", pause, tt.wantPause)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if pause > initialBackoff {
|
|
|
|
t.Errorf("retryer.Retry() returned wrong pause, got: %v, want: < %v", pause, initialBackoff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
md := grpcmd.New(map[string]string{})
|
|
|
|
|
|
|
|
r := &retryer{
|
|
|
|
backoff: gax.Backoff{
|
|
|
|
Initial: initialBackoff,
|
|
|
|
Max: maxBackoff,
|
|
|
|
Multiplier: backoffMultiplier,
|
|
|
|
},
|
|
|
|
md: md,
|
|
|
|
}
|
|
|
|
for i := 0; i < 100; i++ {
|
|
|
|
pause, shouldRetry := r.Retry(errors.New(""))
|
|
|
|
if !shouldRetry {
|
|
|
|
t.Errorf("retryer.Retry() called %v times, returned shouldRetry false, want true", i)
|
|
|
|
}
|
|
|
|
if pause > maxBackoff {
|
|
|
|
t.Errorf("retryer.Retry() called %v times, returned wrong pause, got: %v, want: < %v", i, pause, maxBackoff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInitializeResources(t *testing.T) {
|
|
|
|
d := createTestDeployment()
|
2017-09-13 12:09:48 +00:00
|
|
|
l := map[string]string{instanceLabel: testInstanceName}
|
|
|
|
|
2017-08-05 18:17:15 +00:00
|
|
|
ctx := context.Background()
|
|
|
|
|
2017-09-13 12:09:48 +00:00
|
|
|
a, ctx := initializeResources(ctx, nil, d, l)
|
2017-08-05 18:17:15 +00:00
|
|
|
|
|
|
|
if xg := a.client.xGoogHeader; len(xg) == 0 {
|
|
|
|
t.Errorf("initializeResources() sets empty xGoogHeader")
|
|
|
|
} else {
|
|
|
|
if !strings.Contains(xg[0], "gl-go/") {
|
|
|
|
t.Errorf("initializeResources() sets wrong xGoogHeader, got: %v, want gl-go key", xg[0])
|
|
|
|
}
|
|
|
|
if !strings.Contains(xg[0], "gccl/") {
|
|
|
|
t.Errorf("initializeResources() sets wrong xGoogHeader, got: %v, want gccl key", xg[0])
|
|
|
|
}
|
|
|
|
if !strings.Contains(xg[0], "gax/") {
|
|
|
|
t.Errorf("initializeResources() sets wrong xGoogHeader, got: %v, want gax key", xg[0])
|
|
|
|
}
|
|
|
|
if !strings.Contains(xg[0], "grpc/") {
|
|
|
|
t.Errorf("initializeResources() sets wrong xGoogHeader, got: %v, want grpc key", xg[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
md, _ := grpcmd.FromOutgoingContext(ctx)
|
|
|
|
|
|
|
|
if !testutil.Equal(md[xGoogAPIMetadata], a.client.xGoogHeader) {
|
|
|
|
t.Errorf("md[%v] = %v, want equal xGoogHeader = %v", xGoogAPIMetadata, md[xGoogAPIMetadata], a.client.xGoogHeader)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInitializeDeployment(t *testing.T) {
|
2017-09-13 12:09:48 +00:00
|
|
|
defer func() {
|
|
|
|
getProjectID = gcemd.ProjectID
|
|
|
|
getZone = gcemd.Zone
|
|
|
|
config = Config{}
|
|
|
|
}()
|
|
|
|
|
2017-08-05 18:17:15 +00:00
|
|
|
getProjectID = func() (string, error) {
|
|
|
|
return testProjectID, nil
|
|
|
|
}
|
|
|
|
getZone = func() (string, error) {
|
|
|
|
return testZoneName, nil
|
|
|
|
}
|
|
|
|
|
2017-09-13 12:09:48 +00:00
|
|
|
cfg := Config{Service: testService, ServiceVersion: testServiceVersion}
|
|
|
|
initializeConfig(cfg)
|
2017-08-05 18:17:15 +00:00
|
|
|
d, err := initializeDeployment()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("initializeDeployment() got error: %v, want no error", err)
|
|
|
|
}
|
|
|
|
|
2017-09-13 12:09:48 +00:00
|
|
|
if want := createTestDeployment(); !testutil.Equal(d, want) {
|
|
|
|
t.Errorf("initializeDeployment() got: %v, want %v", d, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInitializeConfig(t *testing.T) {
|
|
|
|
oldConfig := config
|
|
|
|
defer func() {
|
|
|
|
config = oldConfig
|
|
|
|
}()
|
|
|
|
|
|
|
|
for _, tt := range []struct {
|
|
|
|
config Config
|
|
|
|
wantTarget string
|
|
|
|
wantErrorString string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
Config{Service: testService},
|
|
|
|
testService,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Config{Target: testTarget},
|
|
|
|
testTarget,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Config{},
|
|
|
|
"",
|
|
|
|
"service name must be specified in the configuration",
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
errorString := ""
|
|
|
|
if err := initializeConfig(tt.config); err != nil {
|
|
|
|
errorString = err.Error()
|
|
|
|
}
|
|
|
|
|
|
|
|
if errorString != tt.wantErrorString {
|
|
|
|
t.Errorf("initializeConfig(%v) got error: %v, want %v", tt.config, errorString, tt.wantErrorString)
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.Target != tt.wantTarget {
|
|
|
|
t.Errorf("initializeConfig(%v) got target: %v, want %v", tt.config, config.Target, tt.wantTarget)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestInitializeProfileLabels(t *testing.T) {
|
|
|
|
defer func() {
|
|
|
|
getInstanceName = gcemd.InstanceName
|
|
|
|
}()
|
|
|
|
|
|
|
|
getInstanceName = func() (string, error) {
|
|
|
|
return testInstanceName, nil
|
|
|
|
}
|
2017-08-05 18:17:15 +00:00
|
|
|
|
2017-09-13 12:09:48 +00:00
|
|
|
l := initializeProfileLabels()
|
|
|
|
want := map[string]string{instanceLabel: testInstanceName}
|
|
|
|
if !testutil.Equal(l, want) {
|
|
|
|
t.Errorf("initializeProfileLabels() got: %v, want %v", l, want)
|
2017-08-05 18:17:15 +00:00
|
|
|
}
|
|
|
|
}
|