Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 

857 рядки
24 KiB

  1. // Copyright 2017 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package profiler
  15. import (
  16. "bytes"
  17. "compress/gzip"
  18. "errors"
  19. "fmt"
  20. "io"
  21. "log"
  22. "math/rand"
  23. "os"
  24. "runtime"
  25. "strings"
  26. "sync"
  27. "testing"
  28. "time"
  29. "cloud.google.com/go/internal/testutil"
  30. "cloud.google.com/go/profiler/mocks"
  31. "github.com/golang/mock/gomock"
  32. "github.com/golang/protobuf/proto"
  33. "github.com/golang/protobuf/ptypes"
  34. "github.com/google/pprof/profile"
  35. gax "github.com/googleapis/gax-go"
  36. "golang.org/x/net/context"
  37. gtransport "google.golang.org/api/transport/grpc"
  38. pb "google.golang.org/genproto/googleapis/devtools/cloudprofiler/v2"
  39. edpb "google.golang.org/genproto/googleapis/rpc/errdetails"
  40. "google.golang.org/grpc/codes"
  41. grpcmd "google.golang.org/grpc/metadata"
  42. "google.golang.org/grpc/status"
  43. )
  44. const (
  45. testProjectID = "test-project-ID"
  46. testInstance = "test-instance"
  47. testZone = "test-zone"
  48. testService = "test-service"
  49. testSvcVersion = "test-service-version"
  50. testProfileDuration = time.Second * 10
  51. testServerTimeout = time.Second * 15
  52. )
  53. func createTestDeployment() *pb.Deployment {
  54. labels := map[string]string{
  55. zoneNameLabel: testZone,
  56. versionLabel: testSvcVersion,
  57. }
  58. return &pb.Deployment{
  59. ProjectId: testProjectID,
  60. Target: testService,
  61. Labels: labels,
  62. }
  63. }
  64. func createTestAgent(psc pb.ProfilerServiceClient) *agent {
  65. return &agent{
  66. client: psc,
  67. deployment: createTestDeployment(),
  68. profileLabels: map[string]string{instanceLabel: testInstance},
  69. profileTypes: []pb.ProfileType{pb.ProfileType_CPU, pb.ProfileType_HEAP, pb.ProfileType_THREADS},
  70. }
  71. }
  72. func createTrailers(dur time.Duration) map[string]string {
  73. b, _ := proto.Marshal(&edpb.RetryInfo{
  74. RetryDelay: ptypes.DurationProto(dur),
  75. })
  76. return map[string]string{
  77. retryInfoMetadata: string(b),
  78. }
  79. }
  80. func TestCreateProfile(t *testing.T) {
  81. ctx := context.Background()
  82. ctrl := gomock.NewController(t)
  83. defer ctrl.Finish()
  84. mpc := mocks.NewMockProfilerServiceClient(ctrl)
  85. a := createTestAgent(mpc)
  86. p := &pb.Profile{Name: "test_profile"}
  87. wantRequest := pb.CreateProfileRequest{
  88. Deployment: a.deployment,
  89. ProfileType: a.profileTypes,
  90. }
  91. mpc.EXPECT().CreateProfile(ctx, gomock.Eq(&wantRequest), gomock.Any()).Times(1).Return(p, nil)
  92. gotP := a.createProfile(ctx)
  93. if !testutil.Equal(gotP, p) {
  94. t.Errorf("CreateProfile() got wrong profile, got %v, want %v", gotP, p)
  95. }
  96. }
  97. func TestProfileAndUpload(t *testing.T) {
  98. oldStartCPUProfile, oldStopCPUProfile, oldWriteHeapProfile, oldSleep := startCPUProfile, stopCPUProfile, writeHeapProfile, sleep
  99. defer func() {
  100. startCPUProfile, stopCPUProfile, writeHeapProfile, sleep = oldStartCPUProfile, oldStopCPUProfile, oldWriteHeapProfile, oldSleep
  101. }()
  102. ctx := context.Background()
  103. ctrl := gomock.NewController(t)
  104. defer ctrl.Finish()
  105. errFunc := func(io.Writer) error { return errors.New("") }
  106. testDuration := time.Second * 5
  107. tests := []struct {
  108. profileType pb.ProfileType
  109. duration *time.Duration
  110. startCPUProfileFunc func(io.Writer) error
  111. writeHeapProfileFunc func(io.Writer) error
  112. wantBytes []byte
  113. }{
  114. {
  115. profileType: pb.ProfileType_CPU,
  116. duration: &testDuration,
  117. startCPUProfileFunc: func(w io.Writer) error {
  118. w.Write([]byte{1})
  119. return nil
  120. },
  121. writeHeapProfileFunc: errFunc,
  122. wantBytes: []byte{1},
  123. },
  124. {
  125. profileType: pb.ProfileType_CPU,
  126. startCPUProfileFunc: errFunc,
  127. writeHeapProfileFunc: errFunc,
  128. },
  129. {
  130. profileType: pb.ProfileType_CPU,
  131. duration: &testDuration,
  132. startCPUProfileFunc: func(w io.Writer) error {
  133. w.Write([]byte{2})
  134. return nil
  135. },
  136. writeHeapProfileFunc: func(w io.Writer) error {
  137. w.Write([]byte{3})
  138. return nil
  139. },
  140. wantBytes: []byte{2},
  141. },
  142. {
  143. profileType: pb.ProfileType_HEAP,
  144. startCPUProfileFunc: errFunc,
  145. writeHeapProfileFunc: func(w io.Writer) error {
  146. w.Write([]byte{4})
  147. return nil
  148. },
  149. wantBytes: []byte{4},
  150. },
  151. {
  152. profileType: pb.ProfileType_HEAP,
  153. startCPUProfileFunc: errFunc,
  154. writeHeapProfileFunc: errFunc,
  155. },
  156. {
  157. profileType: pb.ProfileType_HEAP,
  158. startCPUProfileFunc: func(w io.Writer) error {
  159. w.Write([]byte{5})
  160. return nil
  161. },
  162. writeHeapProfileFunc: func(w io.Writer) error {
  163. w.Write([]byte{6})
  164. return nil
  165. },
  166. wantBytes: []byte{6},
  167. },
  168. {
  169. profileType: pb.ProfileType_PROFILE_TYPE_UNSPECIFIED,
  170. startCPUProfileFunc: func(w io.Writer) error {
  171. w.Write([]byte{7})
  172. return nil
  173. },
  174. writeHeapProfileFunc: func(w io.Writer) error {
  175. w.Write([]byte{8})
  176. return nil
  177. },
  178. },
  179. }
  180. for _, tt := range tests {
  181. mpc := mocks.NewMockProfilerServiceClient(ctrl)
  182. a := createTestAgent(mpc)
  183. startCPUProfile = tt.startCPUProfileFunc
  184. stopCPUProfile = func() {}
  185. writeHeapProfile = tt.writeHeapProfileFunc
  186. var gotSleep *time.Duration
  187. sleep = func(ctx context.Context, d time.Duration) error {
  188. gotSleep = &d
  189. return nil
  190. }
  191. p := &pb.Profile{ProfileType: tt.profileType}
  192. if tt.duration != nil {
  193. p.Duration = ptypes.DurationProto(*tt.duration)
  194. }
  195. if tt.wantBytes != nil {
  196. wantProfile := &pb.Profile{
  197. ProfileType: p.ProfileType,
  198. Duration: p.Duration,
  199. ProfileBytes: tt.wantBytes,
  200. Labels: a.profileLabels,
  201. }
  202. wantRequest := pb.UpdateProfileRequest{
  203. Profile: wantProfile,
  204. }
  205. mpc.EXPECT().UpdateProfile(ctx, gomock.Eq(&wantRequest)).Times(1)
  206. } else {
  207. mpc.EXPECT().UpdateProfile(gomock.Any(), gomock.Any()).MaxTimes(0)
  208. }
  209. a.profileAndUpload(ctx, p)
  210. if tt.duration == nil {
  211. if gotSleep != nil {
  212. t.Errorf("profileAndUpload(%v) slept for: %v, want no sleep", p, gotSleep)
  213. }
  214. } else {
  215. if gotSleep == nil {
  216. t.Errorf("profileAndUpload(%v) didn't sleep, want sleep for: %v", p, tt.duration)
  217. } else if *gotSleep != *tt.duration {
  218. t.Errorf("profileAndUpload(%v) slept for wrong duration, got: %v, want: %v", p, gotSleep, tt.duration)
  219. }
  220. }
  221. }
  222. }
  223. func TestRetry(t *testing.T) {
  224. normalDuration := time.Second * 3
  225. negativeDuration := time.Second * -3
  226. tests := []struct {
  227. trailers map[string]string
  228. wantPause *time.Duration
  229. }{
  230. {
  231. createTrailers(normalDuration),
  232. &normalDuration,
  233. },
  234. {
  235. createTrailers(negativeDuration),
  236. nil,
  237. },
  238. {
  239. map[string]string{retryInfoMetadata: "wrong format"},
  240. nil,
  241. },
  242. {
  243. map[string]string{},
  244. nil,
  245. },
  246. }
  247. for _, tt := range tests {
  248. md := grpcmd.New(tt.trailers)
  249. r := &retryer{
  250. backoff: gax.Backoff{
  251. Initial: initialBackoff,
  252. Max: maxBackoff,
  253. Multiplier: backoffMultiplier,
  254. },
  255. md: md,
  256. }
  257. pause, shouldRetry := r.Retry(status.Error(codes.Aborted, ""))
  258. if !shouldRetry {
  259. t.Error("retryer.Retry() returned shouldRetry false, want true")
  260. }
  261. if tt.wantPause != nil {
  262. if pause != *tt.wantPause {
  263. t.Errorf("retryer.Retry() returned wrong pause, got: %v, want: %v", pause, tt.wantPause)
  264. }
  265. } else {
  266. if pause > initialBackoff {
  267. t.Errorf("retryer.Retry() returned wrong pause, got: %v, want: < %v", pause, initialBackoff)
  268. }
  269. }
  270. }
  271. md := grpcmd.New(map[string]string{})
  272. r := &retryer{
  273. backoff: gax.Backoff{
  274. Initial: initialBackoff,
  275. Max: maxBackoff,
  276. Multiplier: backoffMultiplier,
  277. },
  278. md: md,
  279. }
  280. for i := 0; i < 100; i++ {
  281. pause, shouldRetry := r.Retry(errors.New(""))
  282. if !shouldRetry {
  283. t.Errorf("retryer.Retry() called %v times, returned shouldRetry false, want true", i)
  284. }
  285. if pause > maxBackoff {
  286. t.Errorf("retryer.Retry() called %v times, returned wrong pause, got: %v, want: < %v", i, pause, maxBackoff)
  287. }
  288. }
  289. }
  290. func TestWithXGoogHeader(t *testing.T) {
  291. ctx := withXGoogHeader(context.Background())
  292. md, _ := grpcmd.FromOutgoingContext(ctx)
  293. if xg := md[xGoogAPIMetadata]; len(xg) == 0 {
  294. t.Errorf("withXGoogHeader() sets empty xGoogHeader")
  295. } else {
  296. if !strings.Contains(xg[0], "gl-go/") {
  297. t.Errorf("withXGoogHeader() got: %v, want gl-go key", xg[0])
  298. }
  299. if !strings.Contains(xg[0], "gccl/") {
  300. t.Errorf("withXGoogHeader() got: %v, want gccl key", xg[0])
  301. }
  302. if !strings.Contains(xg[0], "gax/") {
  303. t.Errorf("withXGoogHeader() got: %v, want gax key", xg[0])
  304. }
  305. if !strings.Contains(xg[0], "grpc/") {
  306. t.Errorf("withXGoogHeader() got: %v, want grpc key", xg[0])
  307. }
  308. }
  309. }
  310. func TestInitializeAgent(t *testing.T) {
  311. oldConfig, oldMutexEnabled := config, mutexEnabled
  312. defer func() {
  313. config, mutexEnabled = oldConfig, oldMutexEnabled
  314. }()
  315. for _, tt := range []struct {
  316. config Config
  317. enableMutex bool
  318. wantProfileTypes []pb.ProfileType
  319. wantDeploymentLabels map[string]string
  320. wantProfileLabels map[string]string
  321. }{
  322. {
  323. config: Config{ServiceVersion: testSvcVersion, zone: testZone},
  324. wantProfileTypes: []pb.ProfileType{pb.ProfileType_CPU, pb.ProfileType_HEAP, pb.ProfileType_THREADS},
  325. wantDeploymentLabels: map[string]string{zoneNameLabel: testZone, versionLabel: testSvcVersion, languageLabel: "go"},
  326. wantProfileLabels: map[string]string{},
  327. },
  328. {
  329. config: Config{zone: testZone},
  330. wantProfileTypes: []pb.ProfileType{pb.ProfileType_CPU, pb.ProfileType_HEAP, pb.ProfileType_THREADS},
  331. wantDeploymentLabels: map[string]string{zoneNameLabel: testZone, languageLabel: "go"},
  332. wantProfileLabels: map[string]string{},
  333. },
  334. {
  335. config: Config{ServiceVersion: testSvcVersion},
  336. wantProfileTypes: []pb.ProfileType{pb.ProfileType_CPU, pb.ProfileType_HEAP, pb.ProfileType_THREADS},
  337. wantDeploymentLabels: map[string]string{versionLabel: testSvcVersion, languageLabel: "go"},
  338. wantProfileLabels: map[string]string{},
  339. },
  340. {
  341. config: Config{instance: testInstance},
  342. wantProfileTypes: []pb.ProfileType{pb.ProfileType_CPU, pb.ProfileType_HEAP, pb.ProfileType_THREADS},
  343. wantDeploymentLabels: map[string]string{languageLabel: "go"},
  344. wantProfileLabels: map[string]string{instanceLabel: testInstance},
  345. },
  346. {
  347. config: Config{instance: testInstance},
  348. enableMutex: true,
  349. wantProfileTypes: []pb.ProfileType{pb.ProfileType_CPU, pb.ProfileType_HEAP, pb.ProfileType_THREADS, pb.ProfileType_CONTENTION},
  350. wantDeploymentLabels: map[string]string{languageLabel: "go"},
  351. wantProfileLabels: map[string]string{instanceLabel: testInstance},
  352. },
  353. {
  354. config: Config{NoHeapProfiling: true},
  355. wantProfileTypes: []pb.ProfileType{pb.ProfileType_CPU, pb.ProfileType_THREADS},
  356. wantDeploymentLabels: map[string]string{languageLabel: "go"},
  357. wantProfileLabels: map[string]string{},
  358. },
  359. {
  360. config: Config{NoHeapProfiling: true, NoGoroutineProfiling: true},
  361. wantProfileTypes: []pb.ProfileType{pb.ProfileType_CPU},
  362. wantDeploymentLabels: map[string]string{languageLabel: "go"},
  363. wantProfileLabels: map[string]string{},
  364. },
  365. } {
  366. config = tt.config
  367. config.ProjectID = testProjectID
  368. config.Service = testService
  369. mutexEnabled = tt.enableMutex
  370. a := initializeAgent(nil)
  371. wantDeployment := &pb.Deployment{
  372. ProjectId: testProjectID,
  373. Target: testService,
  374. Labels: tt.wantDeploymentLabels,
  375. }
  376. if !testutil.Equal(a.deployment, wantDeployment) {
  377. t.Errorf("initializeAgent() got deployment: %v, want %v", a.deployment, wantDeployment)
  378. }
  379. if !testutil.Equal(a.profileLabels, tt.wantProfileLabels) {
  380. t.Errorf("initializeAgent() got profile labels: %v, want %v", a.profileLabels, tt.wantProfileLabels)
  381. }
  382. if !testutil.Equal(a.profileTypes, tt.wantProfileTypes) {
  383. t.Errorf("initializeAgent() got profile types: %v, want %v", a.profileTypes, tt.wantProfileTypes)
  384. }
  385. }
  386. }
  387. func TestInitializeConfig(t *testing.T) {
  388. oldConfig, oldService, oldVersion, oldEnvProjectID, oldGetProjectID, oldGetInstanceName, oldGetZone, oldOnGCE := config, os.Getenv("GAE_SERVICE"), os.Getenv("GAE_VERSION"), os.Getenv("GOOGLE_CLOUD_PROJECT"), getProjectID, getInstanceName, getZone, onGCE
  389. defer func() {
  390. config, getProjectID, getInstanceName, getZone, onGCE = oldConfig, oldGetProjectID, oldGetInstanceName, oldGetZone, oldOnGCE
  391. if err := os.Setenv("GAE_SERVICE", oldService); err != nil {
  392. t.Fatal(err)
  393. }
  394. if err := os.Setenv("GAE_VERSION", oldVersion); err != nil {
  395. t.Fatal(err)
  396. }
  397. if err := os.Setenv("GOOGLE_CLOUD_PROJECT", oldEnvProjectID); err != nil {
  398. t.Fatal(err)
  399. }
  400. }()
  401. const (
  402. testGAEService = "test-gae-service"
  403. testGAEVersion = "test-gae-version"
  404. testGCEProjectID = "test-gce-project-id"
  405. testEnvProjectID = "test-env-project-id"
  406. )
  407. for _, tt := range []struct {
  408. desc string
  409. config Config
  410. wantConfig Config
  411. wantErrorString string
  412. onGAE bool
  413. onGCE bool
  414. envProjectID bool
  415. }{
  416. {
  417. "accepts service name",
  418. Config{Service: testService},
  419. Config{Service: testService, ProjectID: testGCEProjectID, zone: testZone, instance: testInstance},
  420. "",
  421. false,
  422. true,
  423. false,
  424. },
  425. {
  426. "env project overrides GCE project",
  427. Config{Service: testService},
  428. Config{Service: testService, ProjectID: testEnvProjectID, zone: testZone, instance: testInstance},
  429. "",
  430. false,
  431. true,
  432. true,
  433. },
  434. {
  435. "requires service name",
  436. Config{},
  437. Config{},
  438. "service name must be configured",
  439. false,
  440. true,
  441. false,
  442. },
  443. {
  444. "accepts service name from config and service version from GAE",
  445. Config{Service: testService},
  446. Config{Service: testService, ServiceVersion: testGAEVersion, ProjectID: testGCEProjectID, zone: testZone, instance: testInstance},
  447. "",
  448. true,
  449. true,
  450. false,
  451. },
  452. {
  453. "reads both service name and version from GAE env vars",
  454. Config{},
  455. Config{Service: testGAEService, ServiceVersion: testGAEVersion, ProjectID: testGCEProjectID, zone: testZone, instance: testInstance},
  456. "",
  457. true,
  458. true,
  459. false,
  460. },
  461. {
  462. "accepts service version from config",
  463. Config{Service: testService, ServiceVersion: testSvcVersion},
  464. Config{Service: testService, ServiceVersion: testSvcVersion, ProjectID: testGCEProjectID, zone: testZone, instance: testInstance},
  465. "",
  466. false,
  467. true,
  468. false,
  469. },
  470. {
  471. "configured version has priority over GAE-provided version",
  472. Config{Service: testService, ServiceVersion: testSvcVersion},
  473. Config{Service: testService, ServiceVersion: testSvcVersion, ProjectID: testGCEProjectID, zone: testZone, instance: testInstance},
  474. "",
  475. true,
  476. true,
  477. false,
  478. },
  479. {
  480. "configured project ID has priority over metadata-provided project ID",
  481. Config{Service: testService, ProjectID: testProjectID},
  482. Config{Service: testService, ProjectID: testProjectID, zone: testZone, instance: testInstance},
  483. "",
  484. false,
  485. true,
  486. false,
  487. },
  488. {
  489. "configured project ID has priority over environment project ID",
  490. Config{Service: testService, ProjectID: testProjectID},
  491. Config{Service: testService, ProjectID: testProjectID},
  492. "",
  493. false,
  494. false,
  495. true,
  496. },
  497. {
  498. "requires project ID if not on GCE",
  499. Config{Service: testService},
  500. Config{Service: testService},
  501. "project ID must be specified in the configuration if running outside of GCP",
  502. false,
  503. false,
  504. false,
  505. },
  506. } {
  507. t.Logf("Running test: %s", tt.desc)
  508. envService, envVersion := "", ""
  509. if tt.onGAE {
  510. envService, envVersion = testGAEService, testGAEVersion
  511. }
  512. if err := os.Setenv("GAE_SERVICE", envService); err != nil {
  513. t.Fatal(err)
  514. }
  515. if err := os.Setenv("GAE_VERSION", envVersion); err != nil {
  516. t.Fatal(err)
  517. }
  518. if tt.onGCE {
  519. onGCE = func() bool { return true }
  520. getProjectID = func() (string, error) { return testGCEProjectID, nil }
  521. getZone = func() (string, error) { return testZone, nil }
  522. getInstanceName = func() (string, error) { return testInstance, nil }
  523. } else {
  524. onGCE = func() bool { return false }
  525. getProjectID = func() (string, error) { return "", fmt.Errorf("test get project id error") }
  526. getZone = func() (string, error) { return "", fmt.Errorf("test get zone error") }
  527. getInstanceName = func() (string, error) { return "", fmt.Errorf("test get instance error") }
  528. }
  529. envProjectID := ""
  530. if tt.envProjectID {
  531. envProjectID = testEnvProjectID
  532. }
  533. if err := os.Setenv("GOOGLE_CLOUD_PROJECT", envProjectID); err != nil {
  534. t.Fatal(err)
  535. }
  536. errorString := ""
  537. if err := initializeConfig(tt.config); err != nil {
  538. errorString = err.Error()
  539. }
  540. if !strings.Contains(errorString, tt.wantErrorString) {
  541. t.Errorf("initializeConfig(%v) got error: %v, want contain %v", tt.config, errorString, tt.wantErrorString)
  542. }
  543. if tt.wantErrorString == "" {
  544. tt.wantConfig.APIAddr = apiAddress
  545. }
  546. if config != tt.wantConfig {
  547. t.Errorf("initializeConfig(%v) got: %v, want %v", tt.config, config, tt.wantConfig)
  548. }
  549. }
  550. for _, tt := range []struct {
  551. wantErrorString string
  552. getProjectIDError bool
  553. getZoneError bool
  554. getInstanceError bool
  555. }{
  556. {
  557. wantErrorString: "failed to get the project ID from Compute Engine:",
  558. getProjectIDError: true,
  559. },
  560. {
  561. wantErrorString: "failed to get zone from Compute Engine:",
  562. getZoneError: true,
  563. },
  564. {
  565. wantErrorString: "failed to get instance from Compute Engine:",
  566. getInstanceError: true,
  567. },
  568. } {
  569. onGCE = func() bool { return true }
  570. if tt.getProjectIDError {
  571. getProjectID = func() (string, error) { return "", fmt.Errorf("test get project ID error") }
  572. } else {
  573. getProjectID = func() (string, error) { return testGCEProjectID, nil }
  574. }
  575. if tt.getZoneError {
  576. getZone = func() (string, error) { return "", fmt.Errorf("test get zone error") }
  577. } else {
  578. getZone = func() (string, error) { return testZone, nil }
  579. }
  580. if tt.getInstanceError {
  581. getInstanceName = func() (string, error) { return "", fmt.Errorf("test get instance error") }
  582. } else {
  583. getInstanceName = func() (string, error) { return testInstance, nil }
  584. }
  585. errorString := ""
  586. if err := initializeConfig(Config{Service: testService}); err != nil {
  587. errorString = err.Error()
  588. }
  589. if !strings.Contains(errorString, tt.wantErrorString) {
  590. t.Errorf("initializeConfig() got error: %v, want contain %v", errorString, tt.wantErrorString)
  591. }
  592. }
  593. }
  594. type fakeProfilerServer struct {
  595. pb.ProfilerServiceServer
  596. count int
  597. gotProfiles map[string][]byte
  598. done chan bool
  599. }
  600. func (fs *fakeProfilerServer) CreateProfile(ctx context.Context, in *pb.CreateProfileRequest) (*pb.Profile, error) {
  601. fs.count++
  602. switch fs.count {
  603. case 1:
  604. return &pb.Profile{Name: "testCPU", ProfileType: pb.ProfileType_CPU, Duration: ptypes.DurationProto(testProfileDuration)}, nil
  605. case 2:
  606. return &pb.Profile{Name: "testHeap", ProfileType: pb.ProfileType_HEAP}, nil
  607. default:
  608. select {}
  609. }
  610. }
  611. func (fs *fakeProfilerServer) UpdateProfile(ctx context.Context, in *pb.UpdateProfileRequest) (*pb.Profile, error) {
  612. switch in.Profile.ProfileType {
  613. case pb.ProfileType_CPU:
  614. fs.gotProfiles["CPU"] = in.Profile.ProfileBytes
  615. case pb.ProfileType_HEAP:
  616. fs.gotProfiles["HEAP"] = in.Profile.ProfileBytes
  617. fs.done <- true
  618. }
  619. return in.Profile, nil
  620. }
  621. func profileeLoop(quit chan bool) {
  622. for {
  623. select {
  624. case <-quit:
  625. return
  626. default:
  627. profileeWork()
  628. }
  629. }
  630. }
  631. func profileeWork() {
  632. data := make([]byte, 1024*1024)
  633. rand.Read(data)
  634. var b bytes.Buffer
  635. gz := gzip.NewWriter(&b)
  636. if _, err := gz.Write(data); err != nil {
  637. log.Println("failed to write to gzip stream", err)
  638. return
  639. }
  640. if err := gz.Flush(); err != nil {
  641. log.Println("failed to flush to gzip stream", err)
  642. return
  643. }
  644. if err := gz.Close(); err != nil {
  645. log.Println("failed to close gzip stream", err)
  646. }
  647. }
  648. func validateProfile(rawData []byte, wantFunctionName string) error {
  649. p, err := profile.ParseData(rawData)
  650. if err != nil {
  651. return fmt.Errorf("ParseData failed: %v", err)
  652. }
  653. if len(p.Sample) == 0 {
  654. return fmt.Errorf("profile contains zero samples: %v", p)
  655. }
  656. if len(p.Location) == 0 {
  657. return fmt.Errorf("profile contains zero locations: %v", p)
  658. }
  659. if len(p.Function) == 0 {
  660. return fmt.Errorf("profile contains zero functions: %v", p)
  661. }
  662. for _, l := range p.Location {
  663. if len(l.Line) > 0 && l.Line[0].Function != nil && strings.Contains(l.Line[0].Function.Name, wantFunctionName) {
  664. return nil
  665. }
  666. }
  667. return fmt.Errorf("wanted function name %s not found in the profile", wantFunctionName)
  668. }
  669. func TestDeltaMutexProfile(t *testing.T) {
  670. oldMutexEnabled, oldMaxProcs := mutexEnabled, runtime.GOMAXPROCS(10)
  671. defer func() {
  672. mutexEnabled = oldMutexEnabled
  673. runtime.GOMAXPROCS(oldMaxProcs)
  674. }()
  675. if mutexEnabled = enableMutexProfiling(); !mutexEnabled {
  676. t.Skip("Go too old - mutex profiling not supported.")
  677. }
  678. hog(time.Second, mutexHog)
  679. go func() {
  680. hog(2*time.Second, backgroundHog)
  681. }()
  682. var prof bytes.Buffer
  683. if err := deltaMutexProfile(context.Background(), time.Second, &prof); err != nil {
  684. t.Fatalf("deltaMutexProfile() got error: %v", err)
  685. }
  686. p, err := profile.Parse(&prof)
  687. if err != nil {
  688. t.Fatalf("profile.Parse() got error: %v", err)
  689. }
  690. if s := sum(p, "mutexHog"); s != 0 {
  691. t.Errorf("mutexHog found in the delta mutex profile (sum=%d):\n%s", s, p)
  692. }
  693. if s := sum(p, "backgroundHog"); s <= 0 {
  694. t.Errorf("backgroundHog not in the delta mutex profile (sum=%d):\n%s", s, p)
  695. }
  696. }
  697. // sum returns the sum of all mutex counts from the samples whose
  698. // stacks include the specified function name.
  699. func sum(p *profile.Profile, fname string) int64 {
  700. locIDs := map[*profile.Location]bool{}
  701. for _, loc := range p.Location {
  702. for _, l := range loc.Line {
  703. if strings.Contains(l.Function.Name, fname) {
  704. locIDs[loc] = true
  705. break
  706. }
  707. }
  708. }
  709. var s int64
  710. for _, sample := range p.Sample {
  711. for _, loc := range sample.Location {
  712. if locIDs[loc] {
  713. s += sample.Value[0]
  714. break
  715. }
  716. }
  717. }
  718. return s
  719. }
  720. func mutexHog(mu1, mu2 *sync.Mutex, start time.Time, dt time.Duration) {
  721. for time.Since(start) < dt {
  722. mu1.Lock()
  723. runtime.Gosched()
  724. mu2.Lock()
  725. mu1.Unlock()
  726. mu2.Unlock()
  727. }
  728. }
  729. // backgroundHog is identical to mutexHog. We keep them separate
  730. // in order to distinguish them with function names in the stack trace.
  731. func backgroundHog(mu1, mu2 *sync.Mutex, start time.Time, dt time.Duration) {
  732. for time.Since(start) < dt {
  733. mu1.Lock()
  734. runtime.Gosched()
  735. mu2.Lock()
  736. mu1.Unlock()
  737. mu2.Unlock()
  738. }
  739. }
  740. func hog(dt time.Duration, hogger func(mu1, mu2 *sync.Mutex, start time.Time, dt time.Duration)) {
  741. start := time.Now()
  742. mu1 := new(sync.Mutex)
  743. mu2 := new(sync.Mutex)
  744. var wg sync.WaitGroup
  745. wg.Add(10)
  746. for i := 0; i < 10; i++ {
  747. go func() {
  748. defer wg.Done()
  749. hogger(mu1, mu2, start, dt)
  750. }()
  751. }
  752. wg.Wait()
  753. }
  754. func TestAgentWithServer(t *testing.T) {
  755. oldDialGRPC, oldConfig := dialGRPC, config
  756. defer func() {
  757. dialGRPC, config = oldDialGRPC, oldConfig
  758. }()
  759. srv, err := testutil.NewServer()
  760. if err != nil {
  761. t.Fatalf("testutil.NewServer(): %v", err)
  762. }
  763. fakeServer := &fakeProfilerServer{gotProfiles: map[string][]byte{}, done: make(chan bool)}
  764. pb.RegisterProfilerServiceServer(srv.Gsrv, fakeServer)
  765. srv.Start()
  766. dialGRPC = gtransport.DialInsecure
  767. if err := Start(Config{
  768. Service: testService,
  769. ProjectID: testProjectID,
  770. APIAddr: srv.Addr,
  771. instance: testInstance,
  772. zone: testZone,
  773. }); err != nil {
  774. t.Fatalf("Start(): %v", err)
  775. }
  776. quitProfilee := make(chan bool)
  777. go profileeLoop(quitProfilee)
  778. select {
  779. case <-fakeServer.done:
  780. case <-time.After(testServerTimeout):
  781. t.Errorf("got timeout after %v, want fake server done", testServerTimeout)
  782. }
  783. quitProfilee <- true
  784. for _, pType := range []string{"CPU", "HEAP"} {
  785. if profile, ok := fakeServer.gotProfiles[pType]; !ok {
  786. t.Errorf("fakeServer.gotProfiles[%s] got no profile, want profile", pType)
  787. } else if err := validateProfile(profile, "profilee"); err != nil {
  788. t.Errorf("validateProfile(%s) got error: %v", pType, err)
  789. }
  790. }
  791. }