// outputDotFile generates a .dot file describing the current state of // the gossip network. nodes is a map from network address to gossip // node. edgeSet is empty on the first invocation, but // its content is set to encompass the entire set of edges in the // network when this method returns. It should be resupplied with each // successive invocation, as it is used to determine which edges are // new and which have been deleted and show those changes visually in // the output graph. New edges are drawn green; edges which were // removed over the course of the last simulation step(s) are drawn in // a lightly-dashed red. // // The format of the output looks like this: // // digraph G { // node [shape=record]; // node1 [fontsize=12,label="{Node 1|MH=3}"] // node1 -> node3 [color=green] // node1 -> node4 // node1 -> node5 [color=red,style=dotted] // node2 [fontsize=24,label="{Node 2|MH=2}"] // node2 -> node5 // node3 [fontsize=18,label="{Node 3|MH=5}"] // node3 -> node5 // node3 -> node4 // node4 [fontsize=24,label="{Node 4|MH=4}"] // node4 -> node2 // node5 [fontsize=24,label="{Node 5|MH=1}"] // node5 -> node2 // node5 -> node3 // } func outputDotFile(dotFN string, cycle int, nodes map[string]*gossip.Gossip, edgeSet map[string]edge) string { f, err := os.Create(dotFN) if err != nil { log.Fatalf("unable to create temp file: %s", err) } defer f.Close() // Determine maximum number of incoming connections. Create outgoing // edges, keeping track of which are new since last time (added=true). outgoingMap := make(edgeMap) sortedAddresses := util.MapKeys(nodes).([]string) sort.Strings(sortedAddresses) var maxIncoming int // The order the graph file is written influences the arrangement // of nodes in the output image, so it makes sense to eliminate // randomness here. Unfortunately with graphviz it's fairly hard // to get a consistent ordering. for _, addr := range sortedAddresses { node := nodes[addr] incoming := node.Incoming() for _, iAddr := range incoming { e := edge{dest: addr} key := fmt.Sprintf("%s:%s", iAddr.String(), addr) if _, ok := edgeSet[key]; !ok { e.added = true } delete(edgeSet, key) outgoingMap.addEdge(iAddr.String(), e) } if len(incoming) > maxIncoming { maxIncoming = len(incoming) } } // Find all edges which were deleted. for key, e := range edgeSet { e.added = false e.deleted = true outgoingMap.addEdge(strings.Split(key, ":")[0], e) delete(edgeSet, key) } f.WriteString("digraph G {\n") f.WriteString("node [shape=record];\n") for _, addr := range sortedAddresses { node := nodes[addr] var incomplete int var totalAge int64 for infoKey := range nodes { if infoKey == addr { continue // skip the node's own info } if val, err := node.GetInfo(infoKey); err != nil { log.Infof("error getting info for key %q: %s", infoKey, err) incomplete++ } else { totalAge += int64(cycle) - val.(int64) } } var sentinelAge int64 if val, err := node.GetInfo(gossip.KeySentinel); err != nil { log.Infof("error getting info for sentinel gossip key %q: %s", gossip.KeySentinel, err) } else { sentinelAge = int64(cycle) - val.(int64) } var age, nodeColor string if incomplete > 0 { nodeColor = "color=red," age = fmt.Sprintf("missing %d", incomplete) } else { age = strconv.FormatFloat(float64(totalAge)/float64(len(nodes)-1), 'f', 2, 64) } fontSize := minDotFontSize if maxIncoming > 0 { fontSize = minDotFontSize + int(math.Floor(float64(len(node.Incoming())* (maxDotFontSize-minDotFontSize))/float64(maxIncoming))) } f.WriteString(fmt.Sprintf("\t%s [%sfontsize=%d,label=\"{%s|MH=%d, AA=%s, SA=%d}\"]\n", node.Name, nodeColor, fontSize, node.Name, node.MaxHops(), age, sentinelAge)) outgoing := outgoingMap[addr] for _, e := range outgoing { dest := nodes[e.dest] style := "" if e.added { style = " [color=green]" } else if e.deleted { style = " [color=red,style=dotted]" } f.WriteString(fmt.Sprintf("\t%s -> %s%s\n", node.Name, dest.Name, style)) if !e.deleted { edgeSet[fmt.Sprintf("%s:%s", addr, e.dest)] = e } } } f.WriteString("}\n") return f.Name() }
ConditionalPut: struct{}{}, Increment: struct{}{}, Delete: struct{}{}, DeleteRange: struct{}{}, EndTransaction: struct{}{}, AccumulateTS: struct{}{}, ReapQueue: struct{}{}, EnqueueUpdate: struct{}{}, EnqueueMessage: struct{}{}, InternalHeartbeatTxn: struct{}{}, InternalPushTxn: struct{}{}, InternalResolveIntent: struct{}{}, } // ReadMethods lists the read-only methods supported by a range. var ReadMethods = util.MapKeys(readMethods).([]string) // WriteMethods lists the methods supported by a range which write data. var WriteMethods = util.MapKeys(writeMethods).([]string) // Methods lists all the methods supported by a range. var Methods = append(ReadMethods, WriteMethods...) // NeedReadPerm returns true if the specified method requires read permissions. func NeedReadPerm(method string) bool { _, ok := readMethods[method] return ok } // NeedWritePerm returns true if the specified method requires write permissions. func NeedWritePerm(method string) bool {