From de489fb38c269aee152025b769276091139e3008 Mon Sep 17 00:00:00 2001 From: David Symonds Date: Mon, 20 Apr 2009 22:38:14 -0700 Subject: [PATCH] Refactor exvar to use interface types, and add mapVar. R=r APPROVED=r DELTA=170 (136 added, 6 deleted, 28 changed) OCL=27628 CL=27652 --- src/lib/exvar.go | 137 ++++++++++++++++++++++++++++++++++++------ src/lib/exvar_test.go | 61 ++++++++++++++----- 2 files changed, 164 insertions(+), 34 deletions(-) diff --git a/src/lib/exvar.go b/src/lib/exvar.go index 319a0977a2..88f36b7bd8 100644 --- a/src/lib/exvar.go +++ b/src/lib/exvar.go @@ -11,44 +11,141 @@ import ( "sync"; ) +// If mismatched names are used (e.g. calling IncrementInt on a mapVar), the +// var name is silently mapped to these. We will consider variables starting +// with reservedPrefix to be reserved by this package, and so we avoid the +// possibility of a user doing IncrementInt("x-mismatched-map", 1). +// TODO(dsymonds): Enforce this. +const ( + reservedPrefix = "x-"; + mismatchedInt = reservedPrefix + "mismatched-int"; + mismatchedMap = reservedPrefix + "mismatched-map"; +) + +// exVar is an abstract type for all exported variables. +type exVar interface { + String() string; +} + +// intVar is an integer variable, and satisfies the exVar interface. +type intVar int; + +func (i intVar) String() string { + return fmt.Sprint(int(i)) +} + +// mapVar is a map variable, and satisfies the exVar interface. +type mapVar map[string] int; + +func (m mapVar) String() string { + s := "map:x"; // TODO(dsymonds): the 'x' should be user-specified! + for k, v := range m { + s += fmt.Sprintf(" %s:%v", k, v) + } + return s +} + +// TODO(dsymonds): +// - string-valued vars +// - dynamic lookup vars (via chan?) + // Global state. var ( mutex sync.Mutex; - intVars = make(map[string] int); - mapVars = make(map[string] map[string] int); - // TODO(dsymonds): - // - string-valued vars - // - docstrings - // - dynamic lookup vars (via chan) + vars = make(map[string] exVar); + // TODO(dsymonds): docstrings ) -// Increment adds inc to the var called name. -func Increment(name string, inc int) { +// getOrInitIntVar either gets or initializes an intVar called name. +// Callers should already be holding the mutex. +func getOrInitIntVar(name string) *intVar { + if v, ok := vars[name]; ok { + // Existing var + if iv, ok := v.(*intVar); ok { + return iv + } + // Type mismatch. + return getOrInitIntVar(mismatchedInt) + } + // New var + iv := new(intVar); + vars[name] = iv; + return iv +} + +// getOrInitMapVar either gets or initializes a mapVar called name. +// Callers should already be holding the mutex. +func getOrInitMapVar(name string) *mapVar { + if v, ok := vars[name]; ok { + // Existing var + if mv, ok := v.(*mapVar); ok { + return mv + } + // Type mismatch. + return getOrInitMapVar(mismatchedMap) + } + // New var + var m mapVar = make(map[string] int); + vars[name] = &m; + return &m +} + +// IncrementInt adds inc to the integer-valued var called name. +func IncrementInt(name string, inc int) { mutex.Lock(); defer mutex.Unlock(); - if x, ok := intVars[name]; ok { - intVars[name] += inc + *getOrInitIntVar(name) += inc +} + +// IncrementMap adds inc to the keyed value in the map-valued var called name. +func IncrementMap(name string, key string, inc int) { + mutex.Lock(); + defer mutex.Unlock(); + + mv := getOrInitMapVar(name); + // TODO(dsymonds): Change this to just mv[key] when bug143 is fixed. + if v, ok := (*mv)[key]; ok { + mv[key] += inc } else { - intVars[name] = inc + mv[key] = inc } } -// Set sets the var called name to value. -func Set(name string, value int) { +// SetInt sets the integer-valued var called name to value. +func SetInt(name string, value int) { mutex.Lock(); defer mutex.Unlock(); - intVars[name] = value + *getOrInitIntVar(name) = value } -// Get retrieves an integer-valued var called name. -func Get(name string) (x int, ok bool) { - x, ok = intVars[name]; - return +// SetMap sets the keyed value in the map-valued var called name. +func SetMap(name string, key string, value int) { + mutex.Lock(); + defer mutex.Unlock(); + + getOrInitMapVar(name)[key] = value } -// TODO(dsymonds): Functions for map-valued vars. +// GetInt retrieves an integer-valued var called name. +func GetInt(name string) int { + mutex.Lock(); + defer mutex.Unlock(); + + return *getOrInitIntVar(name) +} + +// GetMap retrieves the keyed value for a map-valued var called name. +func GetMap(name string, key string) int { + mutex.Lock(); + defer mutex.Unlock(); + + // TODO(dsymonds): Change this to just getOrInitMapVar(name)[key] when + // bug143 is fixed. + x, ok := (*getOrInitMapVar(name))[key]; + return x +} // String produces a string of all the vars in textual format. func String() string { @@ -56,7 +153,7 @@ func String() string { defer mutex.Unlock(); s := ""; - for name, value := range intVars { + for name, value := range vars { s += fmt.Sprintln(name, value) } return s diff --git a/src/lib/exvar_test.go b/src/lib/exvar_test.go index 2948fc6365..6b309f3a49 100644 --- a/src/lib/exvar_test.go +++ b/src/lib/exvar_test.go @@ -11,19 +11,17 @@ import ( ) func TestSimpleCounter(t *testing.T) { - // Unknown exvar should be zero, and return !ok. - x, ok := Get("requests"); - if x != 0 || ok { - t.Errorf("Get(nonexistent) = (%v, %v), want (%v, %v)", - x, ok, 0, false) + // Unknown exvar should be zero. + x := GetInt("requests"); + if x != 0 { + t.Errorf("Get(nonexistent) = %v, want 0", x) } - Increment("requests", 1); - Increment("requests", 3); - x, ok = Get("requests"); - if x != 4 || !ok { - t.Errorf("Get('requests') = (%v, %v), want (%v, %v)", - x, ok, 4, true) + IncrementInt("requests", 1); + IncrementInt("requests", 3); + x = GetInt("requests"); + if x != 4 { + t.Errorf("Get('requests') = %v, want 4", x) } out := String(); @@ -33,22 +31,57 @@ func TestSimpleCounter(t *testing.T) { } } +func TestMismatchedCounters(t *testing.T) { + // Make sure some vars exist. + GetInt("requests"); + GetMap("colours", "red"); + + IncrementInt("colours", 1); + if x := GetInt("x-mismatched-int"); x != 1 { + t.Errorf("GetInt('x-mismatched-int') = %v, want 1", x) + } + + IncrementMap("requests", "orange", 1); + if x := GetMap("x-mismatched-map", "orange"); x != 1 { + t.Errorf("GetMap('x-mismatched-int', 'orange') = %v, want 1", x) + } +} + +func TestMapCounter(t *testing.T) { + // Unknown exvar should be zero. + if x := GetMap("colours", "red"); x != 0 { + t.Errorf("GetMap(non, existent) = %v, want 0", x) + } + + IncrementMap("colours", "red", 1); + IncrementMap("colours", "red", 2); + IncrementMap("colours", "blue", 4); + if x := GetMap("colours", "red"); x != 3 { + t.Errorf("GetMap('colours', 'red') = %v, want 3", x) + } + if x := GetMap("colours", "blue"); x != 4 { + t.Errorf("GetMap('colours', 'blue') = %v, want 4", x) + } + + // TODO(dsymonds): Test String() +} + func hammer(name string, total int, done chan <- int) { for i := 0; i < total; i++ { - Increment(name, 1) + IncrementInt(name, 1) } done <- 1 } func TestHammer(t *testing.T) { - Set("hammer-times", 0); + SetInt("hammer-times", 0); sync := make(chan int); hammer_times := int(1e5); go hammer("hammer-times", hammer_times, sync); go hammer("hammer-times", hammer_times, sync); <-sync; <-sync; - if final, ok := Get("hammer-times"); final != 2 * hammer_times { + if final := GetInt("hammer-times"); final != 2 * hammer_times { t.Errorf("hammer-times = %v, want %v", final, 2 * hammer_times) } }