Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rudimentary debugger for mircat #58

Merged
merged 2 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions cmd/mircat/debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package main

import (
"bufio"
"context"
"fmt"
"github.com/filecoin-project/mir"
mirCrypto "github.com/filecoin-project/mir/pkg/crypto"
"github.com/filecoin-project/mir/pkg/deploytest"
"github.com/filecoin-project/mir/pkg/eventlog"
"github.com/filecoin-project/mir/pkg/events"
"github.com/filecoin-project/mir/pkg/iss"
"github.com/filecoin-project/mir/pkg/logging"
"github.com/filecoin-project/mir/pkg/modules"
"github.com/filecoin-project/mir/pkg/pb/eventpb"
"github.com/filecoin-project/mir/pkg/pb/recordingpb"
t "github.com/filecoin-project/mir/pkg/types"
"google.golang.org/protobuf/encoding/protojson"
"io"
"os"
)

// debug extracts events from the event log entries and submits them to a node instance
// that it creates for this purpose.
func debug(args *arguments) error {

// Create a reader for the input event log file.
reader, err := eventlog.NewReader(args.srcFile)
if err != nil {
return err
}

// Create a debugger node and a new Context.
node, err := debuggerNode(args.ownID, args.membership)
if err != nil {
return err
}
ctx, stopNode := context.WithCancel(context.Background())
defer stopNode()

// Create channel for node output events and start printing its contents.
nodeOutput := make(chan *events.EventList)
go printNodeOutput(nodeOutput)

// Start the debugger node.
go func() {
if err := node.Debug(ctx, nodeOutput); err != nil {
fmt.Printf("Debugged node stopped with error: %v\n", err)
}
}()

// Keep track of the position of events in the recorded log.
index := uint64(0)

// Process event log.
// Each entry can contain multiple events.
var entry *recordingpb.Entry
for entry, err = reader.ReadEntry(); err == nil; entry, err = reader.ReadEntry() {

// Create event metadata structure and fill in fields that are common for all events in the log entry.
// This structure is modified several times and used as a value parameter.
metadata := eventMetadata{
nodeID: t.NodeID(entry.NodeId),
time: entry.Time,
}

// Process each event in the entry.
for _, event := range entry.Events {

// Set the index of the event in the event log.
metadata.index = index

// If the event was selected by the user for inspection, pause before submitting it to the node.
// The processing continues after the user's interactive confirmation.
if selected(event, args.selectedEvents, args.selectedIssEvents) && index >= uint64(args.offset) {
stopBeforeNext(event, metadata)
}

// Submit the event to the debugger node.
if err := node.Step(ctx, event); err != nil {
return fmt.Errorf("node step failed: %w", err)
}

// Increment position of the event in the log (to be used with the next event).
index++
}
}

if err == io.EOF {
fmt.Println("End of trace, done debugging.")
return nil
} else {
return fmt.Errorf("error reading event log: %w", err)
}
}

// debuggerNode creates a new Mir node instance to be used for debugging.
func debuggerNode(id t.NodeID, membership []t.NodeID) (*mir.Node, error) {

// Logger used by the node.
logger := logging.ConsoleDebugLogger

// Instantiate an ISS protocol module with the default configuration.
protocol, err := iss.New(id, iss.DefaultConfig(membership), logging.Decorate(logger, "ISS: "))
if err != nil {
return nil, fmt.Errorf("could not instantiate protocol module: %w", err)
}

// Instantiate and return a minimal Mir Node.
node, err := mir.NewNode(id, &mir.NodeConfig{Logger: logger}, &modules.Modules{
Net: &nullNet{},
Crypto: &mirCrypto.DummyCrypto{DummySig: []byte{0}},
App: &deploytest.FakeApp{},
Protocol: protocol,
})
if err != nil {
return nil, fmt.Errorf("could not instantiate mir node: %w", err)
} else {
return node, nil
}
}

// stopBeforeNext waits for two confirmations of the user, a confirmation being a new line on the standard input.
// After the first one, event is displayed and after the second one, the function returns.
func stopBeforeNext(event *eventpb.Event, metadata eventMetadata) {
bufio.NewScanner(os.Stdin).Scan()
fmt.Printf("========================================\n")
fmt.Printf("Next step (%d):\n", metadata.index)
displayEvent(event, metadata)
bufio.NewScanner(os.Stdin).Scan()
fmt.Printf("========================================\n")
}

// printNodeOutput reads all events output by a node from the given eventChan channel
// and prints them to standard output.
func printNodeOutput(eventChan chan *events.EventList) {
for receivedEvents, ok := <-eventChan; ok; receivedEvents, ok = <-eventChan {
fmt.Printf("========================================\n")
fmt.Printf("Node produced the following events:\n\n")
for _, event := range receivedEvents.Slice() {
fmt.Println(protojson.Format(event))
}
}
}
96 changes: 96 additions & 0 deletions cmd/mircat/display.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package main

//handles the processing, display and retrieval of events from a given eventlog file

import (
"fmt"
"github.com/filecoin-project/mir/pkg/eventlog"
"github.com/filecoin-project/mir/pkg/pb/eventpb"
"github.com/filecoin-project/mir/pkg/pb/recordingpb"
t "github.com/filecoin-project/mir/pkg/types"
"github.com/ttacon/chalk"
"google.golang.org/protobuf/encoding/protojson"
"io"
"os"
"strconv"
)

// extracts events from eventlog entries and
// forwards them for display
func displayEvents(srcFile *os.File, events map[string]struct{}, issEvents map[string]struct{}, offset int) error {

//new reader
reader, err := eventlog.NewReader(srcFile)

if err != nil {
return err
}

index := uint64(0) // a counter set to track the log indices

var entry *recordingpb.Entry
for entry, err = reader.ReadEntry(); err == nil; entry, err = reader.ReadEntry() {
metadata := eventMetadata{
nodeID: t.NodeID(entry.NodeId),
time: entry.Time,
}
//getting events from entry
for _, event := range entry.Events {
metadata.index = index

if _, ok := events[eventName(event)]; ok && index >= uint64(offset) {
// If event type has been selected for displaying

switch e := event.Type.(type) {
case *eventpb.Event_Iss:
// Only display selected sub-types of the ISS Event
if _, ok := issEvents[issEventName(e.Iss)]; ok {
displayEvent(event, metadata)
}
default:
displayEvent(event, metadata)
}
}

index++
}
}

if err == io.EOF {
fmt.Println("End of trace.")
return nil
} else {
return fmt.Errorf("error reading event log: %w", err)
}
}

// Displays one event according to its type.
func displayEvent(event *eventpb.Event, metadata eventMetadata) {

switch e := event.Type.(type) {
case *eventpb.Event_Iss:
display(fmt.Sprintf("%s : %s", eventName(event), issEventName(e.Iss)), protojson.Format(event), metadata)
default:
display(eventName(event), protojson.Format(event), metadata)
}
}

//Creates and returns a prefix tag for event display using event metadata
func getMetaTag(eventType string, metadata eventMetadata) string {
boldGreen := chalk.Green.NewStyle().WithTextStyle(chalk.Bold) //setting font color and style
boldCyan := chalk.Cyan.NewStyle().WithTextStyle(chalk.Bold)
return fmt.Sprintf("%s %s",
boldGreen.Style(fmt.Sprintf("[ Event_%s ]", eventType)),
boldCyan.Style(fmt.Sprintf("[ Node #%v ] [ Time _%s ] [ Index #%s ]",
metadata.nodeID,
strconv.FormatInt(metadata.time, 10),
strconv.FormatUint(metadata.index, 10))),
)
}

//displays the event
func display(eventType string, event string, metadata eventMetadata) {
whiteText := chalk.White.NewStyle().WithTextStyle(chalk.Bold)
metaTag := getMetaTag(eventType, metadata)
fmt.Printf("%s\n%s \n", metaTag, whiteText.Style(event))
}
106 changes: 46 additions & 60 deletions cmd/mircat/eventhandler.go → cmd/mircat/eventloader.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package main

//handles the processing, display and retrieval of events from a given eventlog file

import (
"fmt"
"io"
"os"
"reflect"
"strings"

"github.com/filecoin-project/mir/pkg/eventlog"
"github.com/filecoin-project/mir/pkg/pb/eventpb"
"github.com/filecoin-project/mir/pkg/pb/isspb"
"github.com/filecoin-project/mir/pkg/pb/recordingpb"
t "github.com/filecoin-project/mir/pkg/types"
"io"
"os"
"reflect"
"sort"
"strings"
)

type eventMetadata struct {
Expand All @@ -22,59 +20,6 @@ type eventMetadata struct {
index uint64
}

// extracts events from eventlog entries and
// forwards them for display
func processEvents(args *arguments) error {

//new reader
reader, err := eventlog.NewReader(args.srcFile)

if err != nil {
return err
}

index := uint64(0) // a counter set to track the log indices

for entry, err := reader.ReadEntry(); err == nil; entry, err = reader.ReadEntry() {
metadata := eventMetadata{
nodeID: t.NodeID(entry.NodeId),
time: entry.Time,
}
//getting events from entry
for _, event := range entry.Events {
metadata.index = index

if _, ok := args.includedEvents[eventName(event)]; ok {
// If event type has been selected for displaying

switch e := event.Type.(type) {
case *eventpb.Event_Iss:
// Only display selected sub-types of the ISS Event
if _, ok := args.includedIssEvents[issEventName(e.Iss)]; ok {
displayEvent(event, metadata)
}
default:
displayEvent(event, metadata)
}
}

index++
}
}
return nil
}

// Displays one event according to its type.
func displayEvent(event *eventpb.Event, metadata eventMetadata) {

switch e := event.Type.(type) {
case *eventpb.Event_Iss:
display(fmt.Sprintf("%s : %s", eventName(event), issEventName(e.Iss)), event.String(), metadata)
default:
display(eventName(event), event.String(), metadata)
}
}

// Returns the list of event names present in the given eventlog file,
// along with the total number of events present in the file.
func getEventList(file *os.File) (map[string]struct{}, map[string]struct{}, int, error) {
Expand Down Expand Up @@ -128,3 +73,44 @@ func issEventName(issEvent *isspb.ISSEvent) string {
reflect.TypeOf(issEvent.Type).Elem().Name(), //gets the type's name i.e. ISSEvent_sb , ISSEvent_PersistCheckpoint,etc
"ISSEvent_", "") //replaces the given substring from the name
}

// selected returns true if the given event has been selected by the user according to the given criteria.
func selected(event *eventpb.Event, selectedEvents map[string]struct{}, selectedIssEvents map[string]struct{}) bool {
if _, ok := selectedEvents[eventName(event)]; !ok {
// If the basic type of the event has not been selected, return false.
return false
} else {
// If the basic type of the event has been selected,
// check whether the sub-type has been selected as well for ISS events.
switch e := event.Type.(type) {
case *eventpb.Event_Iss:
_, ok := selectedIssEvents[issEventName(e.Iss)]
return ok
default:
return true
}
}
}

// Converts a set of strings (represented as a map) to a list.
// Returns a slice containing all the keys present in the given set.
// toList is used to convert sets to a format used by the survey library.
func toList(set map[string]struct{}) []string {
list := make([]string, 0, len(set))
for item := range set {
list = append(list, item)
}
sort.Strings(list)
return list
}

// Converts a list of strings to a set (represented as a map).
// Returns a map of empty structs with one entry for each unique item of the given list (the item being the map key).
// toSet is used to convert lists produced by the survey library to sets for easier lookup.
func toSet(list []string) map[string]struct{} {
set := make(map[string]struct{})
for _, item := range list {
set[item] = struct{}{}
}
return set
}
Loading