blob: 638b32abb2d27ef2aff4ae976314456bb1849da6 [file] [log] [blame]
// Copyright 2018 Google LLC
//
// 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 spanner
import (
"context"
"fmt"
"math"
"strings"
"testing"
"time"
"cloud.google.com/go/internal/testutil"
"cloud.google.com/go/internal/version"
stestutil "cloud.google.com/go/spanner/internal/testutil"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
)
// Check that stats are being exported.
func TestOCStats(t *testing.T) {
te := testutil.NewTestExporter()
defer te.Unregister()
_, c, teardown := setupMockedTestServer(t)
defer teardown()
c.Single().ReadRow(context.Background(), "Users", Key{"alice"}, []string{"email"})
// Wait until we see data from the view.
select {
case <-te.Stats:
case <-time.After(1 * time.Second):
t.Fatal("no stats were exported before timeout")
}
}
func TestOCStats_SessionPool(t *testing.T) {
for _, test := range []struct {
name string
view *view.View
measure string
value string
}{
{
"OpenSessionCount",
OpenSessionCountView,
"open_session_count",
"25",
},
{
"MaxAllowedSessionsCount",
MaxAllowedSessionsCountView,
"max_allowed_sessions",
"400",
},
{
"MaxInUseSessionsCount",
MaxInUseSessionsCountView,
"max_in_use_sessions",
"1",
},
{
"AcquiredSessionsCount",
AcquiredSessionsCountView,
"num_acquired_sessions",
"1",
},
{
"ReleasedSessionsCount",
ReleasedSessionsCountView,
"num_released_sessions",
"1",
},
} {
t.Run(test.name, func(t *testing.T) {
testSimpleMetric(t, test.view, test.measure, test.value)
})
}
}
func testSimpleMetric(t *testing.T, v *view.View, measure, value string) {
te := testutil.NewTestExporter(v)
defer te.Unregister()
_, client, teardown := setupMockedTestServer(t)
defer teardown()
client.Single().ReadRow(context.Background(), "Users", Key{"alice"}, []string{"email"})
// Wait for a while to see all exported metrics.
waitErr := &Error{}
waitFor(t, func() error {
select {
case stat := <-te.Stats:
if len(stat.Rows) > 0 {
return nil
}
}
return waitErr
})
// Wait until we see data from the view.
select {
case stat := <-te.Stats:
if len(stat.Rows) == 0 {
t.Fatal("No metrics are exported")
}
if got, want := stat.View.Measure.Name(), statsPrefix+measure; got != want {
t.Fatalf("Incorrect measure: got %v, want %v", got, want)
}
row := stat.Rows[0]
m := getTagMap(row.Tags)
checkCommonTags(t, m)
var data string
switch row.Data.(type) {
default:
data = fmt.Sprintf("%v", row.Data)
case *view.CountData:
data = fmt.Sprintf("%v", row.Data.(*view.CountData).Value)
case *view.LastValueData:
data = fmt.Sprintf("%v", row.Data.(*view.LastValueData).Value)
}
if got, want := data, value; got != want {
t.Fatalf("Incorrect data: got %v, want %v", got, want)
}
case <-time.After(1 * time.Second):
t.Fatal("no stats were exported before timeout")
}
}
func TestOCStats_SessionPool_SessionsCount(t *testing.T) {
te := testutil.NewTestExporter(SessionsCountView)
defer te.Unregister()
waitErr := &Error{}
_, client, teardown := setupMockedTestServerWithConfig(t, ClientConfig{SessionPoolConfig: DefaultSessionPoolConfig})
defer teardown()
// Wait for the session pool initialization to finish.
expectedWrites := uint64(math.Floor(float64(DefaultSessionPoolConfig.MinOpened) * DefaultSessionPoolConfig.WriteSessions))
expectedReads := DefaultSessionPoolConfig.MinOpened - expectedWrites
waitFor(t, func() error {
client.idleSessions.mu.Lock()
defer client.idleSessions.mu.Unlock()
if client.idleSessions.numReads == expectedReads && client.idleSessions.numWrites == expectedWrites {
return nil
}
return waitErr
})
client.Single().ReadRow(context.Background(), "Users", Key{"alice"}, []string{"email"})
// Wait for a while to see all exported metrics.
waitFor(t, func() error {
select {
case stat := <-te.Stats:
if len(stat.Rows) >= 4 {
return nil
}
}
return waitErr
})
// Wait until we see data from the view.
select {
case stat := <-te.Stats:
// There are 4 types for this metric, so we should see at least four
// rows.
if len(stat.Rows) < 4 {
t.Fatal("No enough metrics are exported")
}
if got, want := stat.View.Measure.Name(), statsPrefix+"num_sessions_in_pool"; got != want {
t.Fatalf("Incorrect measure: got %v, want %v", got, want)
}
for _, row := range stat.Rows {
m := getTagMap(row.Tags)
checkCommonTags(t, m)
// view.AggregationData does not have a way to extract the value. So
// we have to convert it to a string and then compare with expected
// values.
data := row.Data.(*view.LastValueData)
got := fmt.Sprintf("%v", data.Value)
var want string
switch m[tagKeyType] {
case "num_write_prepared_sessions":
want = "20"
case "num_read_sessions":
want = "80"
case "num_sessions_being_prepared":
want = "0"
case "num_in_use_sessions":
want = "0"
default:
t.Fatalf("Incorrect type: %v", m[tagKeyType])
}
if got != want {
t.Fatalf("Incorrect data: got %v, want %v", got, want)
}
}
case <-time.After(1 * time.Second):
t.Fatal("no stats were exported before timeout")
}
}
func TestOCStats_SessionPool_GetSessionTimeoutsCount(t *testing.T) {
te := testutil.NewTestExporter(GetSessionTimeoutsCountView)
defer te.Unregister()
server, client, teardown := setupMockedTestServer(t)
defer teardown()
server.TestSpanner.PutExecutionTime(stestutil.MethodBatchCreateSession,
stestutil.SimulatedExecutionTime{
MinimumExecutionTime: 2 * time.Millisecond,
})
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond)
defer cancel()
client.Single().ReadRow(ctx, "Users", Key{"alice"}, []string{"email"})
// Wait for a while to see all exported metrics.
waitErr := &Error{}
waitFor(t, func() error {
select {
case stat := <-te.Stats:
if len(stat.Rows) > 0 {
return nil
}
}
return waitErr
})
// Wait until we see data from the view.
select {
case stat := <-te.Stats:
if len(stat.Rows) == 0 {
t.Fatal("No metrics are exported")
}
if got, want := stat.View.Measure.Name(), statsPrefix+"get_session_timeouts"; got != want {
t.Fatalf("Incorrect measure: got %v, want %v", got, want)
}
row := stat.Rows[0]
m := getTagMap(row.Tags)
checkCommonTags(t, m)
data := row.Data.(*view.CountData).Value
if got, want := fmt.Sprintf("%v", data), "1"; got != want {
t.Fatalf("Incorrect data: got %v, want %v", got, want)
}
case <-time.After(1 * time.Second):
t.Fatal("no stats were exported before timeout")
}
}
func getTagMap(tags []tag.Tag) map[tag.Key]string {
m := make(map[tag.Key]string)
for _, t := range tags {
m[t.Key] = t.Value
}
return m
}
func checkCommonTags(t *testing.T, m map[tag.Key]string) {
// We only check prefix because client ID increases if we create
// multiple clients for the same database.
if !strings.HasPrefix(m[tagKeyClientID], "client-") {
t.Fatalf("Incorrect client ID: %v", m[tagKeyClientID])
}
if m[tagKeyInstance] != "[INSTANCE]" {
t.Fatalf("Incorrect instance ID: %v", m[tagKeyInstance])
}
if m[tagKeyDatabase] != "[DATABASE]" {
t.Fatalf("Incorrect database ID: %v", m[tagKeyDatabase])
}
if m[tagKeyLibVersion] != version.Repo {
t.Fatalf("Incorrect library version: %v", m[tagKeyLibVersion])
}
}