1// Copyright 2018 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
15package spanner
16
17import (
18	"context"
19	"fmt"
20	"math"
21	"strings"
22	"testing"
23	"time"
24
25	"cloud.google.com/go/internal/testutil"
26	"cloud.google.com/go/internal/version"
27	stestutil "cloud.google.com/go/spanner/internal/testutil"
28	"go.opencensus.io/stats/view"
29	"go.opencensus.io/tag"
30)
31
32// Check that stats are being exported.
33func TestOCStats(t *testing.T) {
34	te := testutil.NewTestExporter()
35	defer te.Unregister()
36
37	_, c, teardown := setupMockedTestServer(t)
38	defer teardown()
39
40	c.Single().ReadRow(context.Background(), "Users", Key{"alice"}, []string{"email"})
41	// Wait until we see data from the view.
42	select {
43	case <-te.Stats:
44	case <-time.After(1 * time.Second):
45		t.Fatal("no stats were exported before timeout")
46	}
47}
48
49func TestOCStats_SessionPool(t *testing.T) {
50	for _, test := range []struct {
51		name    string
52		view    *view.View
53		measure string
54		value   string
55	}{
56		{
57			"OpenSessionCount",
58			OpenSessionCountView,
59			"open_session_count",
60			"&{25}",
61		},
62		{
63			"MaxAllowedSessionsCount",
64			MaxAllowedSessionsCountView,
65			"max_allowed_sessions",
66			"&{400}",
67		},
68		{
69			"MaxInUseSessionsCount",
70			MaxInUseSessionsCountView,
71			"max_in_use_sessions",
72			"&{1}",
73		},
74		{
75			"AcquiredSessionsCount",
76			AcquiredSessionsCountView,
77			"num_acquired_sessions",
78			"&{1}",
79		},
80		{
81			"ReleasedSessionsCount",
82			ReleasedSessionsCountView,
83			"num_released_sessions",
84			"&{1}",
85		},
86	} {
87		t.Run(test.name, func(t *testing.T) {
88			testSimpleMetric(t, test.view, test.measure, test.value)
89		})
90	}
91}
92
93func testSimpleMetric(t *testing.T, v *view.View, measure, value string) {
94	te := testutil.NewTestExporter(v)
95	defer te.Unregister()
96
97	_, client, teardown := setupMockedTestServer(t)
98	defer teardown()
99
100	client.Single().ReadRow(context.Background(), "Users", Key{"alice"}, []string{"email"})
101
102	// Wait for a while to see all exported metrics.
103	waitErr := &Error{}
104	waitFor(t, func() error {
105		select {
106		case stat := <-te.Stats:
107			if len(stat.Rows) > 0 {
108				return nil
109			}
110		}
111		return waitErr
112	})
113
114	// Wait until we see data from the view.
115	select {
116	case stat := <-te.Stats:
117		if len(stat.Rows) == 0 {
118			t.Fatal("No metrics are exported")
119		}
120		if got, want := stat.View.Measure.Name(), statsPrefix+measure; got != want {
121			t.Fatalf("Incorrect measure: got %v, want %v", got, want)
122		}
123		row := stat.Rows[0]
124		m := getTagMap(row.Tags)
125		checkCommonTags(t, m)
126		if got, want := fmt.Sprintf("%v", row.Data), value; got != want {
127			t.Fatalf("Incorrect data: got %v, want %v", got, want)
128		}
129	case <-time.After(1 * time.Second):
130		t.Fatal("no stats were exported before timeout")
131	}
132}
133
134func TestOCStats_SessionPool_SessionsCount(t *testing.T) {
135	te := testutil.NewTestExporter(SessionsCountView)
136	defer te.Unregister()
137
138	waitErr := &Error{}
139	_, client, teardown := setupMockedTestServerWithConfig(t, ClientConfig{SessionPoolConfig: DefaultSessionPoolConfig})
140	defer teardown()
141	// Wait for the session pool initialization to finish.
142	expectedWrites := uint64(math.Floor(float64(DefaultSessionPoolConfig.MinOpened) * DefaultSessionPoolConfig.WriteSessions))
143	expectedReads := DefaultSessionPoolConfig.MinOpened - expectedWrites
144	waitFor(t, func() error {
145		client.idleSessions.mu.Lock()
146		defer client.idleSessions.mu.Unlock()
147		if client.idleSessions.numReads == expectedReads && client.idleSessions.numWrites == expectedWrites {
148			return nil
149		}
150		return waitErr
151	})
152	client.Single().ReadRow(context.Background(), "Users", Key{"alice"}, []string{"email"})
153
154	// Wait for a while to see all exported metrics.
155	waitFor(t, func() error {
156		select {
157		case stat := <-te.Stats:
158			if len(stat.Rows) >= 4 {
159				return nil
160			}
161		}
162		return waitErr
163	})
164
165	// Wait until we see data from the view.
166	select {
167	case stat := <-te.Stats:
168		// There are 4 types for this metric, so we should see at least four
169		// rows.
170		if len(stat.Rows) < 4 {
171			t.Fatal("No enough metrics are exported")
172		}
173		if got, want := stat.View.Measure.Name(), statsPrefix+"num_sessions_in_pool"; got != want {
174			t.Fatalf("Incorrect measure: got %v, want %v", got, want)
175		}
176		for _, row := range stat.Rows {
177			m := getTagMap(row.Tags)
178			checkCommonTags(t, m)
179			// view.AggregationData does not have a way to extract the value. So
180			// we have to convert it to a string and then compare with expected
181			// values.
182			got := fmt.Sprintf("%v", row.Data)
183			var want string
184			switch m[tagKeyType] {
185			case "num_write_prepared_sessions":
186				want = "&{20}"
187			case "num_read_sessions":
188				want = "&{80}"
189			case "num_sessions_being_prepared":
190				want = "&{0}"
191			case "num_in_use_sessions":
192				want = "&{0}"
193			default:
194				t.Fatalf("Incorrect type: %v", m[tagKeyType])
195			}
196			if got != want {
197				t.Fatalf("Incorrect data: got %v, want %v", got, want)
198			}
199		}
200	case <-time.After(1 * time.Second):
201		t.Fatal("no stats were exported before timeout")
202	}
203}
204
205func TestOCStats_SessionPool_GetSessionTimeoutsCount(t *testing.T) {
206	te := testutil.NewTestExporter(GetSessionTimeoutsCountView)
207	defer te.Unregister()
208
209	server, client, teardown := setupMockedTestServer(t)
210	defer teardown()
211
212	server.TestSpanner.PutExecutionTime(stestutil.MethodBatchCreateSession,
213		stestutil.SimulatedExecutionTime{
214			MinimumExecutionTime: 2 * time.Millisecond,
215		})
216
217	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
218	defer cancel()
219	client.Single().ReadRow(ctx, "Users", Key{"alice"}, []string{"email"})
220
221	// Wait for a while to see all exported metrics.
222	waitErr := &Error{}
223	waitFor(t, func() error {
224		select {
225		case stat := <-te.Stats:
226			if len(stat.Rows) > 0 {
227				return nil
228			}
229		}
230		return waitErr
231	})
232
233	// Wait until we see data from the view.
234	select {
235	case stat := <-te.Stats:
236		if len(stat.Rows) == 0 {
237			t.Fatal("No metrics are exported")
238		}
239		if got, want := stat.View.Measure.Name(), statsPrefix+"get_session_timeouts"; got != want {
240			t.Fatalf("Incorrect measure: got %v, want %v", got, want)
241		}
242		row := stat.Rows[0]
243		m := getTagMap(row.Tags)
244		checkCommonTags(t, m)
245		if got, want := fmt.Sprintf("%v", row.Data), "&{1}"; got != want {
246			t.Fatalf("Incorrect data: got %v, want %v", got, want)
247		}
248	case <-time.After(1 * time.Second):
249		t.Fatal("no stats were exported before timeout")
250	}
251}
252
253func getTagMap(tags []tag.Tag) map[tag.Key]string {
254	m := make(map[tag.Key]string)
255	for _, t := range tags {
256		m[t.Key] = t.Value
257	}
258	return m
259}
260
261func checkCommonTags(t *testing.T, m map[tag.Key]string) {
262	// We only check prefix because client ID increases if we create
263	// multiple clients for the same database.
264	if !strings.HasPrefix(m[tagKeyClientID], "client-") {
265		t.Fatalf("Incorrect client ID: %v", m[tagKeyClientID])
266	}
267	if m[tagKeyInstance] != "[INSTANCE]" {
268		t.Fatalf("Incorrect instance ID: %v", m[tagKeyInstance])
269	}
270	if m[tagKeyDatabase] != "[DATABASE]" {
271		t.Fatalf("Incorrect database ID: %v", m[tagKeyDatabase])
272	}
273	if m[tagKeyLibVersion] != version.Repo {
274		t.Fatalf("Incorrect library version: %v", m[tagKeyLibVersion])
275	}
276}
277