Merge "veyron/tools/mgmt,veyron/security/agent/agentd: node mgr integration test."
diff --git a/lib/stats/counter/counter.go b/lib/stats/counter/counter.go
index 4aa9db3..0dc4ec9 100644
--- a/lib/stats/counter/counter.go
+++ b/lib/stats/counter/counter.go
@@ -20,7 +20,7 @@
var (
// Used for testing.
- Now func() time.Time = time.Now
+ TimeNow func() time.Time = time.Now
)
const (
@@ -39,7 +39,7 @@
// New returns a new Counter.
func New() *Counter {
- now := Now()
+ now := TimeNow()
c := &Counter{}
c.ts[hour] = newTimeSeries(now, time.Hour, time.Minute)
c.ts[tenminutes] = newTimeSeries(now, 10*time.Minute, 10*time.Second)
@@ -48,7 +48,7 @@
}
func (c *Counter) advance() time.Time {
- now := Now()
+ now := TimeNow()
for _, ts := range c.ts {
ts.advanceTime(now)
}
@@ -141,7 +141,7 @@
func (c *Counter) Reset() {
c.mu.Lock()
defer c.mu.Unlock()
- now := Now()
+ now := TimeNow()
for _, ts := range c.ts {
ts.reset(now)
}
diff --git a/lib/stats/counter/counter_test.go b/lib/stats/counter/counter_test.go
index 03a3951..fc943b3 100644
--- a/lib/stats/counter/counter_test.go
+++ b/lib/stats/counter/counter_test.go
@@ -10,7 +10,7 @@
func TestCounter(t *testing.T) {
now := time.Unix(1, 0)
- counter.Now = func() time.Time { return now }
+ counter.TimeNow = func() time.Time { return now }
c := counter.New()
// Time 1, value=1
@@ -107,7 +107,7 @@
func TestCounterRate(t *testing.T) {
now := time.Unix(1, 0)
- counter.Now = func() time.Time { return now }
+ counter.TimeNow = func() time.Time { return now }
c := counter.New()
// No data, rate is 0.
diff --git a/lib/stats/counter/timeseries.go b/lib/stats/counter/timeseries.go
index 06b5981..3573d57 100644
--- a/lib/stats/counter/timeseries.go
+++ b/lib/stats/counter/timeseries.go
@@ -29,10 +29,10 @@
}
}
-// advanceTime moves the timeseries forward to time t and fills in any slots
-// that get skipped in the process. Values older than the timeseries period are
-// lost.
-func (ts *timeseries) advanceTime(t time.Time) {
+// advanceTimeWithFill moves the timeseries forward to time t and fills in any
+// slots that get skipped in the process with the given value. Values older than
+// the timeseries period are lost.
+func (ts *timeseries) advanceTimeWithFill(t time.Time, value int64) {
advanceTo := t.Truncate(ts.resolution)
if !advanceTo.After(ts.time) {
// This is shortcut for the most common case of a busy counter
@@ -45,7 +45,6 @@
if steps > ts.size {
steps = ts.size
}
- value := ts.slots[ts.head]
for steps > 0 {
ts.head = (ts.head + 1) % ts.size
ts.slots[ts.head] = value
@@ -54,6 +53,13 @@
ts.time = advanceTo
}
+// advanceTime moves the timeseries forward to time t and fills in any slots
+// that get skipped in the process with the head value. Values older than the
+// timeseries period are lost.
+func (ts *timeseries) advanceTime(t time.Time) {
+ ts.advanceTimeWithFill(t, ts.slots[ts.head])
+}
+
// set sets the current value of the timeseries.
func (ts *timeseries) set(value int64) {
ts.slots[ts.head] = value
@@ -113,9 +119,10 @@
if ts.stepCount < int64(ts.size) {
to = ts.head + 1
}
+ tail := (ts.head + 1) % ts.size
min := int64(math.MaxInt64)
for b := 0; b < to; b++ {
- if ts.slots[b] < min {
+ if b != tail && ts.slots[b] < min {
min = ts.slots[b]
}
}
@@ -128,9 +135,10 @@
if ts.stepCount < int64(ts.size) {
to = ts.head + 1
}
+ tail := (ts.head + 1) % ts.size
max := int64(math.MinInt64)
for b := 0; b < to; b++ {
- if ts.slots[b] > max {
+ if b != tail && ts.slots[b] > max {
max = ts.slots[b]
}
}
diff --git a/lib/stats/counter/timeseries_test.go b/lib/stats/counter/timeseries_test.go
index a05191e..9a3700d 100644
--- a/lib/stats/counter/timeseries_test.go
+++ b/lib/stats/counter/timeseries_test.go
@@ -69,6 +69,28 @@
if expected, got := int64(345), ts.tailValue(); got != expected {
t.Errorf("unexpected value. Got %v, want %v", got, expected)
}
+ if expected, got := int64(111), ts.min(); got != expected {
+ t.Errorf("unexpected value. Got %v, want %v", got, expected)
+ }
+ if expected, got := int64(345), ts.max(); got != expected {
+ t.Errorf("unexpected value. Got %v, want %v", got, expected)
+ }
+
+ // Time 8
+ now = now.Add(time.Second)
+ ts.advanceTime(now)
+ if expected, got := int64(111), ts.headValue(); got != expected {
+ t.Errorf("unexpected value. Got %v, want %v", got, expected)
+ }
+ if expected, got := int64(345), ts.tailValue(); got != expected {
+ t.Errorf("unexpected value. Got %v, want %v", got, expected)
+ }
+ if expected, got := int64(111), ts.min(); got != expected {
+ t.Errorf("unexpected value. Got %v, want %v", got, expected)
+ }
+ if expected, got := int64(111), ts.max(); got != expected {
+ t.Errorf("unexpected value. Got %v, want %v", got, expected)
+ }
// Time 27
now = now.Add(20 * time.Second)
diff --git a/lib/stats/counter/tracker.go b/lib/stats/counter/tracker.go
new file mode 100644
index 0000000..30cc114
--- /dev/null
+++ b/lib/stats/counter/tracker.go
@@ -0,0 +1,159 @@
+package counter
+
+import (
+ "math"
+ "sync"
+ "time"
+)
+
+// Tracker is a min/max value tracker that keeps track of its min/max values
+// over a given period of time, and with a given resolution. The initial min
+// and max values are math.MaxInt64 and math.MinInt64 respectively.
+type Tracker struct {
+ mu sync.RWMutex
+ min, max int64 // All time min/max.
+ minTS, maxTS [3]*timeseries
+ lastUpdate time.Time
+}
+
+// NewTracker returns a new Tracker.
+func NewTracker() *Tracker {
+ now := TimeNow()
+ t := &Tracker{}
+ t.minTS[hour] = newTimeSeries(now, time.Hour, time.Minute)
+ t.minTS[tenminutes] = newTimeSeries(now, 10*time.Minute, 10*time.Second)
+ t.minTS[minute] = newTimeSeries(now, time.Minute, time.Second)
+ t.maxTS[hour] = newTimeSeries(now, time.Hour, time.Minute)
+ t.maxTS[tenminutes] = newTimeSeries(now, 10*time.Minute, 10*time.Second)
+ t.maxTS[minute] = newTimeSeries(now, time.Minute, time.Second)
+ t.init()
+ return t
+}
+
+func (t *Tracker) init() {
+ t.min = math.MaxInt64
+ t.max = math.MinInt64
+ for _, ts := range t.minTS {
+ ts.set(math.MaxInt64)
+ }
+ for _, ts := range t.maxTS {
+ ts.set(math.MinInt64)
+ }
+}
+
+func (t *Tracker) advance() time.Time {
+ now := TimeNow()
+ for _, ts := range t.minTS {
+ ts.advanceTimeWithFill(now, math.MaxInt64)
+ }
+ for _, ts := range t.maxTS {
+ ts.advanceTimeWithFill(now, math.MinInt64)
+ }
+ return now
+}
+
+// LastUpdate returns the last update time of the range.
+func (t *Tracker) LastUpdate() time.Time {
+ t.mu.RLock()
+ defer t.mu.RUnlock()
+ return t.lastUpdate
+}
+
+// Push adds a new value if it is a new minimum or maximum.
+func (t *Tracker) Push(value int64) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ t.lastUpdate = t.advance()
+ if t.min > value {
+ t.min = value
+ }
+ if t.max < value {
+ t.max = value
+ }
+ for _, ts := range t.minTS {
+ if ts.headValue() > value {
+ ts.set(value)
+ }
+ }
+ for _, ts := range t.maxTS {
+ if ts.headValue() < value {
+ ts.set(value)
+ }
+ }
+}
+
+// Min returns the minimum value of the tracker
+func (t *Tracker) Min() int64 {
+ t.mu.RLock()
+ defer t.mu.RUnlock()
+ return t.min
+}
+
+// Max returns the maximum value of the tracker.
+func (t *Tracker) Max() int64 {
+ t.mu.RLock()
+ defer t.mu.RUnlock()
+ return t.max
+}
+
+// Min1h returns the minimum value for the last hour.
+func (t *Tracker) Min1h() int64 {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ t.advance()
+ return t.minTS[hour].min()
+}
+
+// Max1h returns the maximum value for the last hour.
+func (t *Tracker) Max1h() int64 {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ t.advance()
+ return t.maxTS[hour].max()
+}
+
+// Min10m returns the minimum value for the last 10 minutes.
+func (t *Tracker) Min10m() int64 {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ t.advance()
+ return t.minTS[tenminutes].min()
+}
+
+// Max10m returns the maximum value for the last 10 minutes.
+func (t *Tracker) Max10m() int64 {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ t.advance()
+ return t.maxTS[tenminutes].max()
+}
+
+// Min1m returns the minimum value for the last 1 minute.
+func (t *Tracker) Min1m() int64 {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ t.advance()
+ return t.minTS[minute].min()
+}
+
+// Max1m returns the maximum value for the last 1 minute.
+func (t *Tracker) Max1m() int64 {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ t.advance()
+ return t.maxTS[minute].max()
+}
+
+// Reset resets the range to an empty state.
+func (t *Tracker) Reset() {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ now := TimeNow()
+ for _, ts := range t.minTS {
+ ts.reset(now)
+ }
+ for _, ts := range t.maxTS {
+ ts.reset(now)
+ }
+ t.init()
+}
diff --git a/lib/stats/counter/tracker_test.go b/lib/stats/counter/tracker_test.go
new file mode 100644
index 0000000..c2e152c
--- /dev/null
+++ b/lib/stats/counter/tracker_test.go
@@ -0,0 +1,210 @@
+package counter_test
+
+import (
+ "fmt"
+ "math"
+ "math/rand"
+ "sync"
+ "testing"
+ "time"
+
+ "veyron.io/veyron/veyron/lib/stats/counter"
+)
+
+var trackerTests = []struct {
+ after time.Duration
+ push int64 // 0 = none, -1 = reset
+ mins, maxs []int64 // min, 10min, hour, all time
+}{
+ { // T0
+ after: 0,
+ push: 0,
+ mins: []int64{math.MaxInt64, math.MaxInt64, math.MaxInt64, math.MaxInt64},
+ maxs: []int64{math.MinInt64, math.MinInt64, math.MinInt64, math.MinInt64},
+ },
+ { // T1
+ after: 1 * time.Second,
+ push: 5,
+ mins: []int64{5, 5, 5, 5},
+ maxs: []int64{5, 5, 5, 5},
+ },
+ { // T2
+ after: 1 * time.Second,
+ push: 10,
+ mins: []int64{5, 5, 5, 5},
+ maxs: []int64{10, 10, 10, 10},
+ },
+ { // T3
+ after: 1 * time.Second,
+ push: 1,
+ mins: []int64{1, 1, 1, 1},
+ maxs: []int64{10, 10, 10, 10},
+ },
+ { // T4
+ after: 1 * time.Minute,
+ push: 0,
+ mins: []int64{math.MaxInt64, 1, 1, 1},
+ maxs: []int64{math.MinInt64, 10, 10, 10},
+ },
+ { // T5
+ after: 10 * time.Minute,
+ push: 0,
+ mins: []int64{math.MaxInt64, math.MaxInt64, 1, 1},
+ maxs: []int64{math.MinInt64, math.MinInt64, 10, 10},
+ },
+ { // T6
+ after: 1 * time.Hour,
+ push: 0,
+ mins: []int64{math.MaxInt64, math.MaxInt64, math.MaxInt64, 1},
+ maxs: []int64{math.MinInt64, math.MinInt64, math.MinInt64, 10},
+ },
+ { // T7
+ after: 1 * time.Second,
+ push: 5,
+ mins: []int64{5, 5, 5, 1},
+ maxs: []int64{5, 5, 5, 10},
+ },
+ { // T8
+ after: 1 * time.Minute,
+ push: 20,
+ mins: []int64{20, 5, 5, 1},
+ maxs: []int64{20, 20, 20, 20},
+ },
+ { // T9
+ after: 10 * time.Minute,
+ push: 15,
+ mins: []int64{15, 15, 5, 1},
+ maxs: []int64{15, 15, 20, 20},
+ },
+ { // T10
+ after: 1 * time.Hour,
+ push: 10,
+ mins: []int64{10, 10, 10, 1},
+ maxs: []int64{10, 10, 10, 20},
+ },
+ { // T11
+ after: 1 * time.Second,
+ push: -1,
+ mins: []int64{math.MaxInt64, math.MaxInt64, math.MaxInt64, math.MaxInt64},
+ maxs: []int64{math.MinInt64, math.MinInt64, math.MinInt64, math.MinInt64},
+ },
+ { // T12
+ after: 1 * time.Second,
+ push: 5,
+ mins: []int64{5, 5, 5, 5},
+ maxs: []int64{5, 5, 5, 5},
+ },
+}
+
+func TestTracker(t *testing.T) {
+ now := time.Unix(1, 0)
+ counter.TimeNow = func() time.Time { return now }
+
+ tracker := counter.NewTracker()
+ for i, tt := range trackerTests {
+ now = now.Add(tt.after)
+ name := fmt.Sprintf("[T%d] %s:", i, now.Format("15:04:05"))
+ if tt.push > 0 {
+ tracker.Push(tt.push)
+ t.Logf("%s pushed %d\n", name, tt.push)
+ } else if tt.push < 0 {
+ tracker.Reset()
+ t.Log(name, "reset")
+ } else {
+ t.Log(name, "none")
+ }
+
+ if expected, got := tt.mins[0], tracker.Min1m(); got != expected {
+ t.Errorf("%s Min1m returned %d, want %v", name, got, expected)
+ }
+ if expected, got := tt.maxs[0], tracker.Max1m(); got != expected {
+ t.Errorf("%s Max1m returned %d, want %v", name, got, expected)
+ }
+ if expected, got := tt.mins[1], tracker.Min10m(); got != expected {
+ t.Errorf("%s Min10m returned %d, want %v", name, got, expected)
+ }
+ if expected, got := tt.maxs[1], tracker.Max10m(); got != expected {
+ t.Errorf("%s Max10m returned %d, want %v", name, got, expected)
+ }
+ if expected, got := tt.mins[2], tracker.Min1h(); got != expected {
+ t.Errorf("%s Min1h returned %d, want %v", name, got, expected)
+ }
+ if expected, got := tt.maxs[2], tracker.Max1h(); got != expected {
+ t.Errorf("%s Max1h returned %d, want %v", name, got, expected)
+ }
+ if expected, got := tt.mins[3], tracker.Min(); got != expected {
+ t.Errorf("%s Min returned %d, want %v", name, got, expected)
+ }
+ if expected, got := tt.maxs[3], tracker.Max(); got != expected {
+ t.Errorf("%s Max returned %d, want %v", name, got, expected)
+ }
+ }
+}
+
+func min(a, b int64) int64 {
+ if a > b {
+ return b
+ }
+ return a
+}
+
+func max(a, b int64) int64 {
+ if a < b {
+ return b
+ }
+ return a
+}
+
+func TestTrackerConcurrent(t *testing.T) {
+ rand.Seed(time.Now().UnixNano())
+
+ const numGoRoutines = 100
+ const numPushPerGoRoutine = 100000
+ tracker := counter.NewTracker()
+
+ var mins, maxs [numGoRoutines]int64
+ var wg sync.WaitGroup
+ wg.Add(numGoRoutines)
+ for i := 0; i < numGoRoutines; i++ {
+ go func(i int) {
+ var localMin, localMax int64 = math.MaxInt64, math.MinInt64
+ for j := 0; j < numPushPerGoRoutine; j++ {
+ v := rand.Int63()
+ tracker.Push(v)
+ localMin, localMax = min(localMin, v), max(localMax, v)
+ }
+
+ mins[i], maxs[i] = localMin, localMax
+ wg.Done()
+ }(i)
+ }
+ wg.Wait()
+
+ var expectedMin, expectedMax int64 = math.MaxInt64, math.MinInt64
+ for _, v := range mins {
+ expectedMin = min(expectedMin, v)
+ }
+ for _, v := range maxs {
+ expectedMax = max(expectedMax, v)
+ }
+
+ if got := tracker.Min(); got != expectedMin {
+ t.Errorf("Min returned %d, want %v", got, expectedMin)
+ }
+ if got := tracker.Max(); got != expectedMax {
+ t.Errorf("Max returned %d, want %v", got, expectedMax)
+ }
+}
+
+func BenchmarkTrackerPush(b *testing.B) {
+ const numVals = 10000
+ vals := rand.Perm(numVals)
+ tracker := counter.NewTracker()
+
+ b.SetParallelism(100)
+ b.RunParallel(func(pb *testing.PB) {
+ for i := 0; pb.Next(); i++ {
+ tracker.Push(int64(vals[i%numVals]))
+ }
+ })
+}
diff --git a/lib/stats/histogram/histogram.go b/lib/stats/histogram/histogram.go
index 44c3b07..2c0ccef 100644
--- a/lib/stats/histogram/histogram.go
+++ b/lib/stats/histogram/histogram.go
@@ -17,8 +17,9 @@
type Histogram struct {
opts Options
buckets []bucketInternal
- sum *counter.Counter
count *counter.Counter
+ sum *counter.Counter
+ tracker *counter.Tracker
}
// Options contains the parameters that define the histogram's buckets.
@@ -54,8 +55,9 @@
h := Histogram{
opts: opts,
buckets: make([]bucketInternal, opts.NumBuckets),
- sum: counter.New(),
count: counter.New(),
+ sum: counter.New(),
+ tracker: counter.NewTracker(),
}
low := opts.MinValue
delta := opts.SmallestBucketSize
@@ -82,6 +84,7 @@
h.buckets[bucket].count.Incr(1)
h.count.Incr(1)
h.sum.Incr(value)
+ h.tracker.Push(value)
return nil
}
@@ -103,6 +106,8 @@
v := stats.HistogramValue{
Count: h.count.Value(),
Sum: h.sum.Value(),
+ Min: h.tracker.Min(),
+ Max: h.tracker.Max(),
Buckets: b,
}
return v
@@ -121,6 +126,8 @@
v := stats.HistogramValue{
Count: h.count.Delta1h(),
Sum: h.sum.Delta1h(),
+ Min: h.tracker.Min1h(),
+ Max: h.tracker.Max1h(),
Buckets: b,
}
return v
@@ -139,6 +146,8 @@
v := stats.HistogramValue{
Count: h.count.Delta10m(),
Sum: h.sum.Delta10m(),
+ Min: h.tracker.Min10m(),
+ Max: h.tracker.Max10m(),
Buckets: b,
}
return v
@@ -157,6 +166,8 @@
v := stats.HistogramValue{
Count: h.count.Delta1m(),
Sum: h.sum.Delta1m(),
+ Min: h.tracker.Min1m(),
+ Max: h.tracker.Max1m(),
Buckets: b,
}
return v
diff --git a/lib/stats/histogram/histogram_test.go b/lib/stats/histogram/histogram_test.go
index e184142..538e619 100644
--- a/lib/stats/histogram/histogram_test.go
+++ b/lib/stats/histogram/histogram_test.go
@@ -8,11 +8,11 @@
func TestHistogram(t *testing.T) {
// This creates a histogram with the following buckets:
- // [1, 2[
- // [2, 4[
- // [4, 8[
- // [8, 16[
- // [16, Inf
+ // [1, 2)
+ // [2, 4)
+ // [4, 8)
+ // [8, 16)
+ // [16, Inf)
opts := histogram.Options{
NumBuckets: 5,
GrowthFactor: 1.0,
@@ -22,21 +22,35 @@
h := histogram.New(opts)
// Trying to add a value that's less than MinValue, should return an error.
if err := h.Add(0); err == nil {
- t.Errorf("unexpected return value for Add(0.0). Want != nil, Got nil")
+ t.Errorf("unexpected return value for Add(0.0). Want != nil, got nil")
}
// Adding good values. Expect no errors.
for i := 1; i <= 50; i++ {
if err := h.Add(int64(i)); err != nil {
- t.Errorf("unexpected return value for Add(%d). Want nil, Got %v", i, err)
+ t.Errorf("unexpected return value for Add(%d). Want nil, got %v", i, err)
}
}
expectedCount := []int64{1, 2, 4, 8, 35}
buckets := h.Value().Buckets
for i := 0; i < opts.NumBuckets; i++ {
if buckets[i].Count != expectedCount[i] {
- t.Errorf("unexpected count for bucket[%d]. Want %d, Got %v", i, expectedCount[i], buckets[i].Count)
+ t.Errorf("unexpected count for bucket[%d]. Want %d, got %v", i, expectedCount[i], buckets[i].Count)
}
}
+
+ v := h.Value()
+ if expected, got := int64(50), v.Count; got != expected {
+ t.Errorf("unexpected count in histogram value. Want %d, got %v", expected, got)
+ }
+ if expected, got := int64(50*(1+50)/2), v.Sum; got != expected {
+ t.Errorf("unexpected sum in histogram value. Want %d, got %v", expected, got)
+ }
+ if expected, got := int64(1), v.Min; got != expected {
+ t.Errorf("unexpected min in histogram value. Want %d, got %v", expected, got)
+ }
+ if expected, got := int64(50), v.Max; got != expected {
+ t.Errorf("unexpected max in histogram value. Want %d, got %v", expected, got)
+ }
}
func BenchmarkHistogram(b *testing.B) {
diff --git a/lib/stats/stats_test.go b/lib/stats/stats_test.go
index 0773280..e7b28a4 100644
--- a/lib/stats/stats_test.go
+++ b/lib/stats/stats_test.go
@@ -25,7 +25,7 @@
func TestStats(t *testing.T) {
now := time.Unix(1, 0)
- counter.Now = func() time.Time { return now }
+ counter.TimeNow = func() time.Time { return now }
a := libstats.NewInteger("ipc/test/aaa")
b := libstats.NewFloat("ipc/test/bbb")
@@ -224,6 +224,8 @@
Value: istats.HistogramValue{
Count: 2,
Sum: 3,
+ Min: 1,
+ Max: 2,
Buckets: []istats.HistogramBucket{
istats.HistogramBucket{LowBound: 0, Count: 0},
istats.HistogramBucket{LowBound: 1, Count: 1},
@@ -253,6 +255,8 @@
Value: istats.HistogramValue{
Count: 2,
Sum: 5,
+ Min: 2,
+ Max: 3,
Buckets: []istats.HistogramBucket{
istats.HistogramBucket{LowBound: 0, Count: 0},
istats.HistogramBucket{LowBound: 1, Count: 0},
diff --git a/services/identity/googleoauth/template.go b/services/identity/googleoauth/template.go
index 7a06956..a3dc4ee 100644
--- a/services/identity/googleoauth/template.go
+++ b/services/identity/googleoauth/template.go
@@ -185,18 +185,17 @@
<label class="col-sm-2" for="required-caveat">Expiration</label>
<div class="col-sm-10" class="input-group" name="required-caveat">
<div class="radio">
- <div class="input-group">
- <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Expiry" checked>
- <input type="datetime-local" id="expiry" name="expiry">
- </div>
- </div>
- <div class="radio">
<label>
- <!-- TODO(suharshs): Enable this after ThirdPartyCaveats are fixed. -->
- <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Revocation" disabled>
+ <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Revocation" checked>
When explicitly revoked
</label>
</div>
+ <div class="radio">
+ <div class="input-group">
+ <input type="radio" name="requiredCaveat" id="requiredCaveat" value="Expiry">
+ <input type="datetime-local" id="expiry" name="expiry">
+ </div>
+ </div>
</div>
</div>
<h4 class="form-signin-heading">Additional caveats</h4>
diff --git a/services/mgmt/stats/impl/stats_test.go b/services/mgmt/stats/impl/stats_test.go
index 5555d2c..fe502c4 100644
--- a/services/mgmt/stats/impl/stats_test.go
+++ b/services/mgmt/stats/impl/stats_test.go
@@ -179,6 +179,8 @@
want := istats.HistogramValue{
Count: 10,
Sum: 45,
+ Min: 0,
+ Max: 9,
Buckets: []istats.HistogramBucket{
istats.HistogramBucket{LowBound: 0, Count: 1},
istats.HistogramBucket{LowBound: 1, Count: 2},
diff --git a/services/mgmt/stats/types.go b/services/mgmt/stats/types.go
new file mode 100644
index 0000000..1791465
--- /dev/null
+++ b/services/mgmt/stats/types.go
@@ -0,0 +1,51 @@
+package stats
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+)
+
+// Print writes textual output of the histogram values.
+func (v HistogramValue) Print(w io.Writer) {
+ avg := float64(v.Sum) / float64(v.Count)
+ fmt.Fprintf(w, "Count: %d Min: %d Max: %d Avg: %.2f\n", v.Count, v.Min, v.Max, avg)
+ fmt.Fprintf(w, "%s\n", strings.Repeat("-", 60))
+ if v.Count <= 0 {
+ return
+ }
+
+ maxBucketDigitLen := len(strconv.FormatInt(v.Buckets[len(v.Buckets)-1].LowBound, 10))
+ if maxBucketDigitLen < 3 {
+ // For "inf".
+ maxBucketDigitLen = 3
+ }
+ maxCountDigitLen := len(strconv.FormatInt(v.Count, 10))
+ percentMulti := 100 / float64(v.Count)
+
+ accCount := int64(0)
+ for i, b := range v.Buckets {
+ fmt.Fprintf(w, "[%*d, ", maxBucketDigitLen, b.LowBound)
+ if i+1 < len(v.Buckets) {
+ fmt.Fprintf(w, "%*d)", maxBucketDigitLen, v.Buckets[i+1].LowBound)
+ } else {
+ fmt.Fprintf(w, "%*s)", maxBucketDigitLen, "inf")
+ }
+
+ accCount += b.Count
+ fmt.Fprintf(w, " %*d %5.1f%% %5.1f%%", maxCountDigitLen, b.Count, float64(b.Count)*percentMulti, float64(accCount)*percentMulti)
+
+ const barScale = 0.1
+ barLength := int(float64(b.Count)*percentMulti*barScale + 0.5)
+ fmt.Fprintf(w, " %s\n", strings.Repeat("#", barLength))
+ }
+}
+
+// String returns the textual output of the histogram values as string.
+func (v HistogramValue) String() string {
+ var b bytes.Buffer
+ v.Print(&b)
+ return b.String()
+}
diff --git a/services/mgmt/stats/types.vdl b/services/mgmt/stats/types.vdl
index 8aa3ec9..d11aa13 100644
--- a/services/mgmt/stats/types.vdl
+++ b/services/mgmt/stats/types.vdl
@@ -7,6 +7,10 @@
Count int64
// Sum is the sum of all the values added to the histogram.
Sum int64
+ // Min is the minimum of all the values added to the histogram.
+ Min int64
+ // Max is the maximum of all the values added to the histogram.
+ Max int64
// Buckets contains all the buckets of the histogram.
Buckets []HistogramBucket
}
diff --git a/services/mgmt/stats/types.vdl.go b/services/mgmt/stats/types.vdl.go
index cc41c75..4884f69 100644
--- a/services/mgmt/stats/types.vdl.go
+++ b/services/mgmt/stats/types.vdl.go
@@ -15,6 +15,10 @@
Count int64
// Sum is the sum of all the values added to the histogram.
Sum int64
+ // Min is the minimum of all the values added to the histogram.
+ Min int64
+ // Max is the maximum of all the values added to the histogram.
+ Max int64
// Buckets contains all the buckets of the histogram.
Buckets []HistogramBucket
}
diff --git a/tools/debug/impl.go b/tools/debug/impl.go
index 9c5effc..6a5a1fa 100644
--- a/tools/debug/impl.go
+++ b/tools/debug/impl.go
@@ -3,7 +3,6 @@
import (
"bytes"
"fmt"
- "io"
"os"
"os/exec"
"regexp"
@@ -423,40 +422,13 @@
}
switch v := value.(type) {
case istats.HistogramValue:
- writeASCIIHistogram(&buf, v)
+ v.Print(&buf)
default:
fmt.Fprintf(&buf, "%v", v)
}
return buf.String()
}
-func writeASCIIHistogram(w io.Writer, h istats.HistogramValue) {
- scale := h.Count
- if scale > 100 {
- scale = 100
- }
- var avg float64
- if h.Count > 0 {
- avg = float64(h.Sum) / float64(h.Count)
- }
- fmt.Fprintf(w, "Count: %d Sum: %d Avg: %f\n", h.Count, h.Sum, avg)
- for i, b := range h.Buckets {
- var r string
- if i+1 < len(h.Buckets) {
- r = fmt.Sprintf("[%d,%d[", b.LowBound, h.Buckets[i+1].LowBound)
- } else {
- r = fmt.Sprintf("[%d,Inf", b.LowBound)
- }
- fmt.Fprintf(w, "%-18s: ", r)
- if b.Count > 0 && h.Count > 0 {
- fmt.Fprintf(w, "%s %d (%.1f%%)", strings.Repeat("*", int(b.Count*scale/h.Count)), b.Count, 100*float64(b.Count)/float64(h.Count))
- }
- if i+1 < len(h.Buckets) {
- fmt.Fprintln(w)
- }
- }
-}
-
var cmdPProfRun = &cmdline.Command{
Run: runPProf,
Name: "run",
diff --git a/tools/debug/test.sh b/tools/debug/test.sh
index c460c21..17a016b 100755
--- a/tools/debug/test.sh
+++ b/tools/debug/test.sh
@@ -85,7 +85,7 @@
"${DEBUG_BIN}" stats watch -raw "${EP}/__debug/stats/ipc/server/routing-id/*/methods/ReadLog/latency-ms")
shell::timed_wait_for "${shell_test_DEFAULT_MESSAGE_TIMEOUT}" "${TMP}" "ReadLog/latency-ms"
kill "${DEBUG_PID}"
- grep -q "Count:1 " "${TMP}" || (dumplogs "${TMP}"; shell_test::fail "line ${LINENO}: failed to find expected output")
+ grep -q "Count: 1 " "${TMP}" || (dumplogs "${TMP}"; shell_test::fail "line ${LINENO}: failed to find expected output")
# Test pprof.
if ! "${DEBUG_BIN}" pprof run "${EP}/__debug/pprof" heap --text &> "${DBGLOG}"; then