diff --git a/cmd/simulator/.gitignore b/cmd/propagation_simulator/.gitignore similarity index 57% rename from cmd/simulator/.gitignore rename to cmd/propagation_simulator/.gitignore index ece16d0..f692259 100644 --- a/cmd/simulator/.gitignore +++ b/cmd/propagation_simulator/.gitignore @@ -1,3 +1,3 @@ -simulator +propagation_simulator network.json propagation.json diff --git a/cmd/simulator/README.md b/cmd/propagation_simulator/README.md similarity index 100% rename from cmd/simulator/README.md rename to cmd/propagation_simulator/README.md diff --git a/cmd/simulator/main.go b/cmd/propagation_simulator/main.go similarity index 100% rename from cmd/simulator/main.go rename to cmd/propagation_simulator/main.go diff --git a/simulation/naivep2p/links.go b/propagation/naivep2p/links.go similarity index 100% rename from simulation/naivep2p/links.go rename to propagation/naivep2p/links.go diff --git a/simulation/naivep2p/logentry.go b/propagation/naivep2p/logentry.go similarity index 100% rename from simulation/naivep2p/logentry.go rename to propagation/naivep2p/logentry.go diff --git a/simulation/naivep2p/simulator.go b/propagation/naivep2p/simulator.go similarity index 100% rename from simulation/naivep2p/simulator.go rename to propagation/naivep2p/simulator.go diff --git a/simulation/simulation.go b/propagation/simulation.go similarity index 100% rename from simulation/simulation.go rename to propagation/simulation.go diff --git a/simulation/whisperv6/logentry.go b/propagation/whisperv6/logentry.go similarity index 100% rename from simulation/whisperv6/logentry.go rename to propagation/whisperv6/logentry.go diff --git a/simulation/whisperv6/message.go b/propagation/whisperv6/message.go similarity index 100% rename from simulation/whisperv6/message.go rename to propagation/whisperv6/message.go diff --git a/simulation/whisperv6/simulator.go b/propagation/whisperv6/simulator.go similarity index 100% rename from simulation/whisperv6/simulator.go rename to propagation/whisperv6/simulator.go diff --git a/stats/histogram.go b/stats/histogram.go new file mode 100644 index 0000000..cc0b1f7 --- /dev/null +++ b/stats/histogram.go @@ -0,0 +1,196 @@ +package stats + +import ( + "bytes" + "fmt" + "math" + "strconv" + "sync" +) + +// Histogram implements simple histogram for node counters. +type Histogram struct { + ranges []int + counts []int + + totalCount int + + TotalDataPoint int + MinDataPoint int + MaxDataPoint int + + m sync.Mutex +} + +// NewHistogram creates a new, ready to use Histogram. The numBins +// must be >= 2. The binFirst is the width of the first bin. The +// binGrowthFactor must be > 1.0 or 0.0. +// +// A special case of binGrowthFactor of 0.0 means the the allocated +// bins will have constant, non-growing size or "width". +func NewHistogram( + numBins int, + binFirst int, + binGrowthFactor float64) *Histogram { + gh := &Histogram{ + ranges: make([]int, numBins), + counts: make([]int, numBins), + totalCount: 0, + MinDataPoint: math.MaxInt64, + MaxDataPoint: 0, + } + + gh.ranges[0] = 0 + gh.ranges[1] = binFirst + + for i := 2; i < len(gh.ranges); i++ { + if binGrowthFactor == 0.0 { + gh.ranges[i] = gh.ranges[i-1] + binFirst + } else { + gh.ranges[i] = + int(math.Ceil(binGrowthFactor * float64(gh.ranges[i-1]))) + } + } + + return gh +} + +// Add increases the count in the bin for the given dataPoint. +func (gh *Histogram) Add(dataPoint int, count int) { + gh.m.Lock() + + idx := search(gh.ranges, dataPoint) + if idx >= 0 { + gh.counts[idx] += count + gh.totalCount += count + + gh.TotalDataPoint += dataPoint + if gh.MinDataPoint > dataPoint { + gh.MinDataPoint = dataPoint + } + if gh.MaxDataPoint < dataPoint { + gh.MaxDataPoint = dataPoint + } + } + + gh.m.Unlock() +} + +// Finds the last arr index where the arr entry <= dataPoint. +func search(arr []int, dataPoint int) int { + i, j := 0, len(arr) + + for i < j { + h := i + (j-i)/2 // Avoids h overflow, where i <= h < j. + if dataPoint >= arr[h] { + i = h + 1 + } else { + j = h + } + } + + return i - 1 +} + +// AddAll adds all the counts from the src histogram into this +// histogram. The src and this histogram must either have the same +// exact creation parameters. +func (gh *Histogram) AddAll(src *Histogram) { + src.m.Lock() + gh.m.Lock() + + for i := 0; i < len(src.counts); i++ { + gh.counts[i] += src.counts[i] + } + gh.totalCount += src.totalCount + + gh.TotalDataPoint += src.TotalDataPoint + if gh.MinDataPoint > src.MinDataPoint { + gh.MinDataPoint = src.MinDataPoint + } + if gh.MaxDataPoint < src.MaxDataPoint { + gh.MaxDataPoint = src.MaxDataPoint + } + + gh.m.Unlock() + src.m.Unlock() +} + +// Graph emits an ascii graph to the optional out buffer, allocating a +// out buffer if none was supplied. Returns the out buffer. Each +// line emitted may have an optional prefix. +// +// For example: +// 0+ 10=2 10.00% ******** +// 10+ 10=1 10.00% **** +// 20+ 10=3 10.00% ************ +func (gh *Histogram) EmitGraph(prefix []byte, + out *bytes.Buffer) *bytes.Buffer { + gh.m.Lock() + + ranges := gh.ranges + rangesN := len(ranges) + counts := gh.counts + countsN := len(counts) + + if out == nil { + out = bytes.NewBuffer(make([]byte, 0, 80*countsN)) + } + + var maxCount int + for _, c := range counts { + if maxCount < c { + maxCount = c + } + } + maxCountF := float64(maxCount) + totCountF := float64(gh.totalCount) + + widthRange := len(strconv.Itoa(int(ranges[rangesN-1]))) + widthWidth := len(strconv.Itoa(int(ranges[rangesN-1] - ranges[rangesN-2]))) + widthCount := len(strconv.Itoa(int(maxCount))) + + // Each line looks like: "[prefix]START+WIDTH=COUNT PCT% BAR\n" + f := fmt.Sprintf("%%%dd+%%%dd=%%%dd%% 7.2f%%%%", + widthRange, widthWidth, widthCount) + + var runCount int // Running total while emitting lines. + + barLen := float64(len(bar)) + + for i, c := range counts { + if prefix != nil { + out.Write(prefix) + } + + var width int + if i < countsN-1 { + width = int(ranges[i+1] - ranges[i]) + } + + runCount += c + fmt.Fprintf(out, f, ranges[i], width, c, + 100.0*(float64(runCount)/totCountF)) + + if c > 0 { + out.Write([]byte(" ")) + barWant := int(math.Floor(barLen * (float64(c) / maxCountF))) + out.Write(bar[0:barWant]) + } + + out.Write([]byte("\n")) + } + + gh.m.Unlock() + + return out +} + +var bar = []byte("******************************") + +// CallSync invokes the callback func while the histogram is locked. +func (gh *Histogram) CallSync(f func()) { + gh.m.Lock() + f() + gh.m.Unlock() +} diff --git a/stats/stats.go b/stats/stats.go index e97f9d9..0544457 100644 --- a/stats/stats.go +++ b/stats/stats.go @@ -4,6 +4,8 @@ import ( "fmt" "log" + "v.io/x/ref/lib/stats/histogram" + "github.com/divan/graph-experiments/graph" "github.com/status-im/simulator/simulation" ) @@ -13,6 +15,7 @@ type Stats struct { NodeHits map[string]int NodeCoverage Coverage LinkCoverage Coverage + Hist *histogram.Histogram } // PrintVerbose prints detailed terminal-friendly stats to @@ -21,18 +24,12 @@ func (s *Stats) PrintVerbose() { fmt.Println("Stats:") fmt.Println("Nodes coverage:", s.NodeCoverage) fmt.Println("Links coverage:", s.LinkCoverage) - for k, v := range s.NodeHits { - fmt.Printf("%s: ", k) - for i := 0; i < v; i++ { - fmt.Printf(".") - } - fmt.Println() - } + fmt.Println("Node hits:", s.Hist.Value()) } // Analyze analyzes given propagation log and returns filled Stats object. func Analyze(g *graph.Graph, plog *simulation.Log) *Stats { - nodeHits := analyzeNodeHits(g, plog) + nodeHits, hist := analyzeNodeHits(g, plog) nodeCoverage := analyzeNodeCoverage(g, nodeHits) linkCoverage := analyzeLinkCoverage(g, plog) @@ -40,10 +37,11 @@ func Analyze(g *graph.Graph, plog *simulation.Log) *Stats { NodeHits: nodeHits, NodeCoverage: nodeCoverage, LinkCoverage: linkCoverage, + Hist: hist, } } -func analyzeNodeHits(g *graph.Graph, plog *simulation.Log) map[string]int { +func analyzeNodeHits(g *graph.Graph, plog *simulation.Log) (map[string]int, *histogram.Histogram) { nodeHits := make(map[string]int) for _, nodes := range plog.Nodes { @@ -56,7 +54,12 @@ func analyzeNodeHits(g *graph.Graph, plog *simulation.Log) map[string]int { } } - return nodeHits + hist := NewHistogram(1, 1, 1) + for _, v := range nodeHits { + hist.Add(v) + } + + return nodeHits, hist } func analyzeNodeCoverage(g *graph.Graph, nodeHits map[string]int) Coverage {