1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package expvar 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "math" 11 "net" 12 "net/http/httptest" 13 "runtime" 14 "strconv" 15 "sync" 16 "sync/atomic" 17 "testing" 18 ) 19 20 // RemoveAll removes all exported variables. 21 // This is for tests only. 22 func RemoveAll() { 23 mutex.Lock() 24 defer mutex.Unlock() 25 vars = make(map[string]Var) 26 varKeys = nil 27 } 28 29 func TestInt(t *testing.T) { 30 RemoveAll() 31 reqs := NewInt("requests") 32 if reqs.i != 0 { 33 t.Errorf("reqs.i = %v, want 0", reqs.i) 34 } 35 if reqs != Get("requests").(*Int) { 36 t.Errorf("Get() failed.") 37 } 38 39 reqs.Add(1) 40 reqs.Add(3) 41 if reqs.i != 4 { 42 t.Errorf("reqs.i = %v, want 4", reqs.i) 43 } 44 45 if s := reqs.String(); s != "4" { 46 t.Errorf("reqs.String() = %q, want \"4\"", s) 47 } 48 49 reqs.Set(-2) 50 if reqs.i != -2 { 51 t.Errorf("reqs.i = %v, want -2", reqs.i) 52 } 53 } 54 55 func BenchmarkIntAdd(b *testing.B) { 56 var v Int 57 58 b.RunParallel(func(pb *testing.PB) { 59 for pb.Next() { 60 v.Add(1) 61 } 62 }) 63 } 64 65 func BenchmarkIntSet(b *testing.B) { 66 var v Int 67 68 b.RunParallel(func(pb *testing.PB) { 69 for pb.Next() { 70 v.Set(1) 71 } 72 }) 73 } 74 75 func (v *Float) val() float64 { 76 return math.Float64frombits(atomic.LoadUint64(&v.f)) 77 } 78 79 func TestFloat(t *testing.T) { 80 RemoveAll() 81 reqs := NewFloat("requests-float") 82 if reqs.f != 0.0 { 83 t.Errorf("reqs.f = %v, want 0", reqs.f) 84 } 85 if reqs != Get("requests-float").(*Float) { 86 t.Errorf("Get() failed.") 87 } 88 89 reqs.Add(1.5) 90 reqs.Add(1.25) 91 if v := reqs.val(); v != 2.75 { 92 t.Errorf("reqs.val() = %v, want 2.75", v) 93 } 94 95 if s := reqs.String(); s != "2.75" { 96 t.Errorf("reqs.String() = %q, want \"4.64\"", s) 97 } 98 99 reqs.Add(-2) 100 if v := reqs.val(); v != 0.75 { 101 t.Errorf("reqs.val() = %v, want 0.75", v) 102 } 103 } 104 105 func BenchmarkFloatAdd(b *testing.B) { 106 var f Float 107 108 b.RunParallel(func(pb *testing.PB) { 109 for pb.Next() { 110 f.Add(1.0) 111 } 112 }) 113 } 114 115 func BenchmarkFloatSet(b *testing.B) { 116 var f Float 117 118 b.RunParallel(func(pb *testing.PB) { 119 for pb.Next() { 120 f.Set(1.0) 121 } 122 }) 123 } 124 125 func TestString(t *testing.T) { 126 RemoveAll() 127 name := NewString("my-name") 128 if name.s != "" { 129 t.Errorf("name.s = %q, want \"\"", name.s) 130 } 131 132 name.Set("Mike") 133 if name.s != "Mike" { 134 t.Errorf("name.s = %q, want \"Mike\"", name.s) 135 } 136 137 if s := name.String(); s != "\"Mike\"" { 138 t.Errorf("reqs.String() = %q, want \"\"Mike\"\"", s) 139 } 140 } 141 142 func BenchmarkStringSet(b *testing.B) { 143 var s String 144 145 b.RunParallel(func(pb *testing.PB) { 146 for pb.Next() { 147 s.Set("red") 148 } 149 }) 150 } 151 152 func TestMapCounter(t *testing.T) { 153 RemoveAll() 154 colors := NewMap("bike-shed-colors") 155 156 colors.Add("red", 1) 157 colors.Add("red", 2) 158 colors.Add("blue", 4) 159 colors.AddFloat(`green "midori"`, 4.125) 160 if x := colors.m["red"].(*Int).i; x != 3 { 161 t.Errorf("colors.m[\"red\"] = %v, want 3", x) 162 } 163 if x := colors.m["blue"].(*Int).i; x != 4 { 164 t.Errorf("colors.m[\"blue\"] = %v, want 4", x) 165 } 166 if x := colors.m[`green "midori"`].(*Float).val(); x != 4.125 { 167 t.Errorf("colors.m[`green \"midori\"] = %v, want 4.125", x) 168 } 169 170 // colors.String() should be '{"red":3, "blue":4}', 171 // though the order of red and blue could vary. 172 s := colors.String() 173 var j interface{} 174 err := json.Unmarshal([]byte(s), &j) 175 if err != nil { 176 t.Errorf("colors.String() isn't valid JSON: %v", err) 177 } 178 m, ok := j.(map[string]interface{}) 179 if !ok { 180 t.Error("colors.String() didn't produce a map.") 181 } 182 red := m["red"] 183 x, ok := red.(float64) 184 if !ok { 185 t.Error("red.Kind() is not a number.") 186 } 187 if x != 3 { 188 t.Errorf("red = %v, want 3", x) 189 } 190 } 191 192 func BenchmarkMapSet(b *testing.B) { 193 m := new(Map).Init() 194 195 v := new(Int) 196 197 b.RunParallel(func(pb *testing.PB) { 198 for pb.Next() { 199 m.Set("red", v) 200 } 201 }) 202 } 203 204 func BenchmarkMapAddSame(b *testing.B) { 205 for i := 0; i < b.N; i++ { 206 m := new(Map).Init() 207 m.Add("red", 1) 208 m.Add("red", 1) 209 m.Add("red", 1) 210 m.Add("red", 1) 211 } 212 } 213 214 func BenchmarkMapAddDifferent(b *testing.B) { 215 for i := 0; i < b.N; i++ { 216 m := new(Map).Init() 217 m.Add("red", 1) 218 m.Add("blue", 1) 219 m.Add("green", 1) 220 m.Add("yellow", 1) 221 } 222 } 223 224 func TestFunc(t *testing.T) { 225 RemoveAll() 226 var x interface{} = []string{"a", "b"} 227 f := Func(func() interface{} { return x }) 228 if s, exp := f.String(), `["a","b"]`; s != exp { 229 t.Errorf(`f.String() = %q, want %q`, s, exp) 230 } 231 232 x = 17 233 if s, exp := f.String(), `17`; s != exp { 234 t.Errorf(`f.String() = %q, want %q`, s, exp) 235 } 236 } 237 238 func TestHandler(t *testing.T) { 239 RemoveAll() 240 m := NewMap("map1") 241 m.Add("a", 1) 242 m.Add("z", 2) 243 m2 := NewMap("map2") 244 for i := 0; i < 9; i++ { 245 m2.Add(strconv.Itoa(i), int64(i)) 246 } 247 rr := httptest.NewRecorder() 248 rr.Body = new(bytes.Buffer) 249 expvarHandler(rr, nil) 250 want := `{ 251 "map1": {"a": 1, "z": 2}, 252 "map2": {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8} 253 } 254 ` 255 if got := rr.Body.String(); got != want { 256 t.Errorf("HTTP handler wrote:\n%s\nWant:\n%s", got, want) 257 } 258 } 259 260 func BenchmarkRealworldExpvarUsage(b *testing.B) { 261 var ( 262 bytesSent Int 263 bytesRead Int 264 ) 265 266 // The benchmark creates GOMAXPROCS client/server pairs. 267 // Each pair creates 4 goroutines: client reader/writer and server reader/writer. 268 // The benchmark stresses concurrent reading and writing to the same connection. 269 // Such pattern is used in net/http and net/rpc. 270 271 b.StopTimer() 272 273 P := runtime.GOMAXPROCS(0) 274 N := b.N / P 275 W := 1000 276 277 // Setup P client/server connections. 278 clients := make([]net.Conn, P) 279 servers := make([]net.Conn, P) 280 ln, err := net.Listen("tcp", "127.0.0.1:0") 281 if err != nil { 282 b.Fatalf("Listen failed: %v", err) 283 } 284 defer ln.Close() 285 done := make(chan bool) 286 go func() { 287 for p := 0; p < P; p++ { 288 s, err := ln.Accept() 289 if err != nil { 290 b.Errorf("Accept failed: %v", err) 291 return 292 } 293 servers[p] = s 294 } 295 done <- true 296 }() 297 for p := 0; p < P; p++ { 298 c, err := net.Dial("tcp", ln.Addr().String()) 299 if err != nil { 300 b.Fatalf("Dial failed: %v", err) 301 } 302 clients[p] = c 303 } 304 <-done 305 306 b.StartTimer() 307 308 var wg sync.WaitGroup 309 wg.Add(4 * P) 310 for p := 0; p < P; p++ { 311 // Client writer. 312 go func(c net.Conn) { 313 defer wg.Done() 314 var buf [1]byte 315 for i := 0; i < N; i++ { 316 v := byte(i) 317 for w := 0; w < W; w++ { 318 v *= v 319 } 320 buf[0] = v 321 n, err := c.Write(buf[:]) 322 if err != nil { 323 b.Errorf("Write failed: %v", err) 324 return 325 } 326 327 bytesSent.Add(int64(n)) 328 } 329 }(clients[p]) 330 331 // Pipe between server reader and server writer. 332 pipe := make(chan byte, 128) 333 334 // Server reader. 335 go func(s net.Conn) { 336 defer wg.Done() 337 var buf [1]byte 338 for i := 0; i < N; i++ { 339 n, err := s.Read(buf[:]) 340 341 if err != nil { 342 b.Errorf("Read failed: %v", err) 343 return 344 } 345 346 bytesRead.Add(int64(n)) 347 pipe <- buf[0] 348 } 349 }(servers[p]) 350 351 // Server writer. 352 go func(s net.Conn) { 353 defer wg.Done() 354 var buf [1]byte 355 for i := 0; i < N; i++ { 356 v := <-pipe 357 for w := 0; w < W; w++ { 358 v *= v 359 } 360 buf[0] = v 361 n, err := s.Write(buf[:]) 362 if err != nil { 363 b.Errorf("Write failed: %v", err) 364 return 365 } 366 367 bytesSent.Add(int64(n)) 368 } 369 s.Close() 370 }(servers[p]) 371 372 // Client reader. 373 go func(c net.Conn) { 374 defer wg.Done() 375 var buf [1]byte 376 for i := 0; i < N; i++ { 377 n, err := c.Read(buf[:]) 378 379 if err != nil { 380 b.Errorf("Read failed: %v", err) 381 return 382 } 383 384 bytesRead.Add(int64(n)) 385 } 386 c.Close() 387 }(clients[p]) 388 } 389 wg.Wait() 390 } 391