nuts-foundation/nuts-node

View on GitHub
network/dag/dotviz.go

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
/*
 * Copyright (C) 2021 Nuts community
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

package dag

import (
    "fmt"
    "strings"
)

// DotGraphVisitor is a graph visitor that outputs the walked path as "dot" diagram.
// The is currently unused, but can be used to debug the DAG to see how transactions relate to each other. The output
// can be viewed using a DOT plugin or when rendering the DOT file to SVG/PNG, etc.
type DotGraphVisitor struct {
    aliases    map[string]string
    labelStyle LabelStyle

    nodes []string
    edges []string
}

// LabelStyle defines node label styles for DotGraphVisitor.
type LabelStyle int

const (
    // ShowAliasLabelStyle is a style that uses integer aliases for node labels.
    ShowAliasLabelStyle LabelStyle = iota
    // ShowRefLabelStyle is a style that uses the references of nodes as label.
    ShowRefLabelStyle LabelStyle = iota
    // ShowShortRefLabelStyle is a style that uses a shorter version of the references of nodes as label.
    ShowShortRefLabelStyle LabelStyle = iota
)

// NewDotGraphVisitor creates a new DotGraphVisitor
func NewDotGraphVisitor(labelStyle LabelStyle) *DotGraphVisitor {
    return &DotGraphVisitor{
        aliases:    map[string]string{},
        labelStyle: labelStyle,
    }
}

// Accept adds a transaction to the dot graph. Should be called by the DAG walker.
func (d *DotGraphVisitor) Accept(transaction Transaction) bool {
    d.nodes = append(d.nodes, fmt.Sprintf("  \"%s\"[label=\"%s (LC=%d)\"]", transaction.Ref().String(), d.label(transaction), transaction.Clock()))
    for _, prev := range transaction.Previous() {
        d.edges = append(d.edges, fmt.Sprintf("  \"%s\" -> \"%s\"", prev.String(), transaction.Ref().String()))
    }
    return true
}

// Render returns the walked DAG visualized as dot graph.
func (d *DotGraphVisitor) Render() string {
    var lines []string
    lines = append(lines, "digraph {")
    lines = append(lines, d.nodes...)
    lines = append(lines, d.edges...)
    lines = append(lines, "}")
    return strings.Join(lines, "\n")
}

func (d *DotGraphVisitor) label(transaction Transaction) string {
    ref := transaction.Ref().String()
    switch d.labelStyle {
    case ShowAliasLabelStyle:
        return d.getAlias(ref, func(ref string) string {
            return fmt.Sprintf("%d", len(d.aliases)+1)
        })
    case ShowShortRefLabelStyle:
        return d.getAlias(ref, func(ref string) string {
            return fmt.Sprintf("%s..%s", ref[:4], ref[len(ref)-4:])
        })
    case ShowRefLabelStyle:
        fallthrough
    default:
        return ref
    }
}

func (d *DotGraphVisitor) getAlias(ref string, aliaser func(ref string) string) string {
    alias := d.aliases[ref]
    if alias == "" {
        alias = aliaser(ref)
        d.aliases[ref] = alias
    }
    return alias
}