Commit 17d1be95 authored by Petar Maymounkov's avatar Petar Maymounkov

Add table health report.

parent e37b9d1e
package kademlia
import (
"github.com/libp2p/go-libp2p-xor/key"
"github.com/libp2p/go-libp2p-xor/trie"
)
// BucketAtDepth returns the bucket in the routing table at a given depth.
// A bucket at depth D holds contacts that share a prefix of exactly D bits with node.
func BucketAtDepth(node key.Key, table *trie.Trie, depth int) *trie.Trie {
dir := node.BitAt(depth)
if table.IsLeaf() {
return nil
} else {
if depth == 0 {
return table.Branch[1-dir]
} else {
return BucketAtDepth(node, table.Branch[dir], depth-1)
}
}
}
package kademlia package kademlia
import ( import (
"sort"
"github.com/libp2p/go-libp2p-xor/key" "github.com/libp2p/go-libp2p-xor/key"
"github.com/libp2p/go-libp2p-xor/trie" "github.com/libp2p/go-libp2p-xor/trie"
) )
// TableHealthReport describes the discrepancy between a node's routing table from the theoretical ideal, // TableHealthReport describes the discrepancy between a node's routing table from the theoretical ideal,
// given knowledge of all nodes present in the network. // given knowledge of all nodes present in the network.
// TODO: Make printable in a way easy to ingest in Python/matplotlib for viewing in a Jupyter notebook.
// E.g. one would like to see a histogram of (IdealDepth - ActualDepth) across all tables.
type TableHealthReport struct { type TableHealthReport struct {
// IdealDepth is the depth that the node's rouing table should have. // IdealDepth is the depth that the node's routing table should have.
IdealDepth int IdealDepth int
// ActualDepth is the depth that the node's routing table has. // ActualDepth is the depth that the node's routing table has.
ActualDepth int ActualDepth int
// Bucket... // Bucket contains the individual health reports for each of the node's routing buckets.
Bucket []*BucketHealthReport Bucket []*BucketHealthReport
} }
// BucketHealth describes the discrepancy between a node's routing bucket and the theoretical ideal, // BucketHealth describes the discrepancy between a node's routing bucket and the theoretical ideal,
// given knowledge of all nodes present in the network (aka the "known" nodes). // given knowledge of all nodes present in the network (aka the "known" nodes).
type BucketHealthReport struct { type BucketHealthReport struct {
// Depth is the bucket depth, starting from zero.
Depth int
// MaxKnownContacts is the number of all known network nodes, // MaxKnownContacts is the number of all known network nodes,
// which are eligible to be in this bucket. // which are eligible to be in this bucket.
MaxKnownContacts int MaxKnownContacts int
...@@ -30,6 +36,40 @@ type BucketHealthReport struct { ...@@ -30,6 +36,40 @@ type BucketHealthReport struct {
ActualUnknownContacts int ActualUnknownContacts int
} }
// sortedBucketHealthReport sorts bucket health reports in ascending order of depth.
type sortedBucketHealthReport []*BucketHealthReport
func (s sortedBucketHealthReport) Less(i, j int) bool {
return s[i].Depth < s[j].Depth
}
func (s sortedBucketHealthReport) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s sortedBucketHealthReport) Len() int {
return len(s)
}
type Table struct {
Node key.Key
Contacts []key.Key
}
// AllTablesHealth ...
func AllTablesHealth(tables []*Table) (report []*TableHealthReport) {
// Construct global network view trie
knownNodes := trie.New()
for _, table := range tables {
knownNodes.Add(table.Node)
}
// Compute individual table health
for _, table := range tables {
report = append(report, TableHealth(table.Node, table.Contacts, knownNodes))
}
return
}
// TableHealth computes the health report for a node, // TableHealth computes the health report for a node,
// given its routing contacts and a list of all known nodes in the network currently. // given its routing contacts and a list of all known nodes in the network currently.
func TableHealth(node key.Key, nodeContacts []key.Key, knownNodes *trie.Trie) *TableHealthReport { func TableHealth(node key.Key, nodeContacts []key.Key, knownNodes *trie.Trie) *TableHealthReport {
...@@ -52,8 +92,57 @@ func TableHealth(node key.Key, nodeContacts []key.Key, knownNodes *trie.Trie) *T ...@@ -52,8 +92,57 @@ func TableHealth(node key.Key, nodeContacts []key.Key, knownNodes *trie.Trie) *T
// BucketHealth computes the health report for each bucket in a node's routing table, // BucketHealth computes the health report for each bucket in a node's routing table,
// given the node's routing table and a list of all known nodes in the network currently. // given the node's routing table and a list of all known nodes in the network currently.
func BucketHealth(node key.Key, nodeTable, knownNodes *trie.Trie) []*BucketHealthReport { func BucketHealth(node key.Key, nodeTable, knownNodes *trie.Trie) []*BucketHealthReport {
panic("u") r := walkBucketHealth(0, node, nodeTable, knownNodes)
// actualDepth, _ := nodeTable.Find(node) sort.Sort(sortedBucketHealthReport(r))
// bucket := makeXXX return r
// return bucket }
func walkBucketHealth(depth int, node key.Key, nodeTable, knownNodes *trie.Trie) []*BucketHealthReport {
if nodeTable.IsLeaf() {
return nil
} else {
dir := node.BitAt(depth)
switch {
case knownNodes == nil || knownNodes.IsEmptyLeaf():
r := walkBucketHealth(depth+1, node, nodeTable.Branch[dir], nil)
return append(r,
&BucketHealthReport{
Depth: depth,
MaxKnownContacts: 0,
ActualKnownContacts: 0,
ActualUnknownContacts: nodeTable.Branch[1-dir].Size(),
})
case knownNodes.IsNonEmptyLeaf():
if knownNodes.Key.BitAt(depth) == dir {
r := walkBucketHealth(depth+1, node, nodeTable.Branch[dir], knownNodes)
return append(r,
&BucketHealthReport{
Depth: depth,
MaxKnownContacts: 0,
ActualKnownContacts: 0,
ActualUnknownContacts: nodeTable.Branch[1-dir].Size(),
})
} else {
r := walkBucketHealth(depth+1, node, nodeTable.Branch[dir], nil)
return append(r, bucketReportFromTries(depth, nodeTable.Branch[1-dir], knownNodes))
}
case !knownNodes.IsLeaf():
r := walkBucketHealth(depth+1, node, nodeTable.Branch[dir], knownNodes.Branch[dir])
return append(r,
bucketReportFromTries(depth, nodeTable.Branch[1-dir], knownNodes.Branch[1-dir]))
default:
panic("unreachable")
}
}
}
func bucketReportFromTries(depth int, actualBucket, maxBucket *trie.Trie) *BucketHealthReport {
actualKnown := trie.Intersect(actualBucket, maxBucket)
actualKnownSize := actualKnown.Size()
return &BucketHealthReport{
Depth: depth,
MaxKnownContacts: maxBucket.Size(),
ActualKnownContacts: actualKnownSize,
ActualUnknownContacts: actualBucket.Size() - actualKnownSize,
}
} }
...@@ -4,6 +4,9 @@ import ( ...@@ -4,6 +4,9 @@ import (
"github.com/libp2p/go-libp2p-xor/key" "github.com/libp2p/go-libp2p-xor/key"
) )
// Find looks for the key q in the trie.
// It returns the depth of the leaf reached along the path of q, regardless of whether q was found in that leaf.
// It also returns a boolean flag indicating whether the key was found.
func (trie *Trie) Find(q key.Key) (reachedDepth int, found bool) { func (trie *Trie) Find(q key.Key) (reachedDepth int, found bool) {
return trie.FindAtDepth(0, q) return trie.FindAtDepth(0, q)
} }
......
...@@ -19,14 +19,14 @@ func New() *Trie { ...@@ -19,14 +19,14 @@ func New() *Trie {
} }
func (trie *Trie) Depth() int { func (trie *Trie) Depth() int {
return trie.depth(0) return trie.DepthAtDepth(0)
} }
func (trie *Trie) depth(depth int) int { func (trie *Trie) DepthAtDepth(depth int) int {
if trie.Branch[0] == nil && trie.Branch[1] == nil { if trie.Branch[0] == nil && trie.Branch[1] == nil {
return depth return depth
} else { } else {
return max(trie.Branch[0].depth(depth+1), trie.Branch[1].depth(depth+1)) return max(trie.Branch[0].DepthAtDepth(depth+1), trie.Branch[1].DepthAtDepth(depth+1))
} }
} }
...@@ -37,6 +37,24 @@ func max(x, y int) int { ...@@ -37,6 +37,24 @@ func max(x, y int) int {
return y return y
} }
// Size returns the number of keys added to the trie.
// In other words, it returns the number of non-empty leaves in the trie.
func (trie *Trie) Size() int {
return trie.SizeAtDepth(0)
}
func (trie *Trie) SizeAtDepth(depth int) int {
if trie.Branch[0] == nil && trie.Branch[1] == nil {
if trie.IsEmpty() {
return 0
} else {
return 1
}
} else {
return trie.Branch[0].SizeAtDepth(depth+1) + trie.Branch[1].SizeAtDepth(depth+1)
}
}
func (trie *Trie) IsEmpty() bool { func (trie *Trie) IsEmpty() bool {
return trie.Key == nil return trie.Key == nil
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment