1/*
2 *
3 * Copyright 2019 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19package profiling
20
21import (
22	"fmt"
23	"strconv"
24	"sync"
25	"testing"
26	"time"
27
28	"google.golang.org/grpc/internal/grpctest"
29	"google.golang.org/grpc/internal/profiling/buffer"
30)
31
32type s struct {
33	grpctest.Tester
34}
35
36func Test(t *testing.T) {
37	grpctest.RunSubTests(t, s{})
38}
39
40func (s) TestProfiling(t *testing.T) {
41	cb, err := buffer.NewCircularBuffer(128)
42	if err != nil {
43		t.Fatalf("error creating circular buffer: %v", err)
44	}
45
46	stat := NewStat("foo")
47	cb.Push(stat)
48	bar := func(n int) {
49		if n%2 == 0 {
50			defer stat.NewTimer(strconv.Itoa(n)).Egress()
51		} else {
52			timer := NewTimer(strconv.Itoa(n))
53			stat.AppendTimer(timer)
54			defer timer.Egress()
55		}
56		time.Sleep(1 * time.Microsecond)
57	}
58
59	numTimers := int(8 * defaultStatAllocatedTimers)
60	for i := 0; i < numTimers; i++ {
61		bar(i)
62	}
63
64	results := cb.Drain()
65	if len(results) != 1 {
66		t.Fatalf("len(results) = %d; want 1", len(results))
67	}
68
69	statReturned := results[0].(*Stat)
70	if stat.Tags != "foo" {
71		t.Fatalf("stat.Tags = %s; want foo", stat.Tags)
72	}
73
74	if len(stat.Timers) != numTimers {
75		t.Fatalf("len(stat.Timers) = %d; want %d", len(stat.Timers), numTimers)
76	}
77
78	lastIdx := 0
79	for i, timer := range statReturned.Timers {
80		// Check that they're in the order of append.
81		if n, err := strconv.Atoi(timer.Tags); err != nil && n != lastIdx {
82			t.Fatalf("stat.Timers[%d].Tags = %s; wanted %d", i, timer.Tags, lastIdx)
83		}
84
85		// Check that the timestamps are consistent.
86		if diff := timer.End.Sub(timer.Begin); diff.Nanoseconds() < 1000 {
87			t.Fatalf("stat.Timers[%d].End - stat.Timers[%d].Begin = %v; want >= 1000ns", i, i, diff)
88		}
89
90		lastIdx++
91	}
92}
93
94func (s) TestProfilingRace(t *testing.T) {
95	stat := NewStat("foo")
96
97	var wg sync.WaitGroup
98	numTimers := int(8 * defaultStatAllocatedTimers) // also tests the slice growth code path
99	wg.Add(numTimers)
100	for i := 0; i < numTimers; i++ {
101		go func(n int) {
102			defer wg.Done()
103			if n%2 == 0 {
104				defer stat.NewTimer(strconv.Itoa(n)).Egress()
105			} else {
106				timer := NewTimer(strconv.Itoa(n))
107				stat.AppendTimer(timer)
108				defer timer.Egress()
109			}
110		}(i)
111	}
112	wg.Wait()
113
114	if len(stat.Timers) != numTimers {
115		t.Fatalf("len(stat.Timers) = %d; want %d", len(stat.Timers), numTimers)
116	}
117
118	// The timers need not be ordered, so we can't expect them to be consecutive
119	// like above.
120	seen := make(map[int]bool)
121	for i, timer := range stat.Timers {
122		n, err := strconv.Atoi(timer.Tags)
123		if err != nil {
124			t.Fatalf("stat.Timers[%d].Tags = %s; wanted integer", i, timer.Tags)
125		}
126		seen[n] = true
127	}
128
129	for i := 0; i < numTimers; i++ {
130		if _, ok := seen[i]; !ok {
131			t.Fatalf("seen[%d] = false or does not exist; want it to be true", i)
132		}
133	}
134}
135
136func BenchmarkProfiling(b *testing.B) {
137	for routines := 1; routines <= 1<<8; routines <<= 1 {
138		b.Run(fmt.Sprintf("goroutines:%d", routines), func(b *testing.B) {
139			perRoutine := b.N / routines
140			stat := NewStat("foo")
141			var wg sync.WaitGroup
142			wg.Add(routines)
143			for r := 0; r < routines; r++ {
144				go func() {
145					for i := 0; i < perRoutine; i++ {
146						stat.NewTimer("bar").Egress()
147					}
148					wg.Done()
149				}()
150			}
151			wg.Wait()
152		})
153	}
154}
155