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