package debugger
import (
"fmt"
"math"
"regexp"
"slices"
"strconv"
"strings"
"time"
"github.com/pancsta/cview"
"golang.org/x/exp/maps"
"github.com/pancsta/asyncmachine-go/tools/debugger/types"
"github.com/pancsta/asyncmachine-go/internal/utils"
amhelp "github.com/pancsta/asyncmachine-go/pkg/helpers"
am "github.com/pancsta/asyncmachine-go/pkg/machine"
"github.com/pancsta/asyncmachine-go/pkg/telemetry"
ss "github.com/pancsta/asyncmachine-go/tools/debugger/states"
)
func (d *Debugger ) BuildingLogEnter (e *am .Event ) bool {
_ , ok := e .Args ["logRebuildEnd" ].(int )
return ok && d .C != nil
}
func (d *Debugger ) BuildingLogState (e *am .Event ) {
endIndex := e .Args ["logRebuildEnd" ].(int )
cursorTx1 := d .C .CursorTx1
level := d .Opts .Filters .LogLevel
ctx := d .Mach .NewStateCtx (ss .BuildingLog )
var buf string
_ , _ , _ , maxVisible := d .log .GetRect ()
maxDiff := float64 (maxVisible * 1 )
if d .lastResize == d .logLastResize && d .C .logRenderedLevel == level &&
d .logRenderedClient == d .C .MsgStruct .ID &&
d .C .logRenderedFilters .Equal (d .filtersFromStates ()) &&
d .C .logRenderedTimestamps == d .Mach .Is1 (ss .LogTimestamps ) &&
math .Abs (float64 (d .C .logRenderedCursor1 )-float64 (cursorTx1 )) < maxDiff {
d .Mach .EvAdd1 (e , ss .LogBuilt , nil )
return
}
d .logRebuildEnd = endIndex
d .logLastResize = d .lastResize
start := max (0 , cursorTx1 -1 -maxVisible *4 )
end := min (endIndex , start +maxVisible *5 )
for i := start ; i < end && ctx .Err () == nil ; i ++ {
entry , empty := d .hGetLogEntryTxt (i )
if entry == "" {
continue
}
if i > start && !empty {
entry = "\n" + entry
}
buf += entry
}
go func () {
if ctx .Err () != nil {
return
}
d .log .Clear ()
_ , err := d .log .Write ([]byte (buf ))
if ctx .Err () != nil {
return
}
if err != nil {
d .Mach .EvAddErr (e , err , nil )
return
}
d .Mach .Eval ("BuildingLog" , func () {
d .C .logRenderedCursor1 = cursorTx1
d .C .logRenderedLevel = level
d .logRenderedClient = d .C .MsgStruct .ID
d .C .logRenderedFilters = d .filtersFromStates ()
d .C .logRenderedTimestamps = d .Mach .Is1 (ss .LogTimestamps )
d .logAppends = 0
}, ctx )
d .Mach .EvAdd1 (e , ss .LogBuilt , nil )
}()
}
func (d *Debugger ) LogBuiltState (e *am .Event ) {
d .handleLogScroll ()
}
func (d *Debugger ) UpdateLogScheduledState (e *am .Event ) {
if d .Mach .Is1 (ss .UpdatingLog ) {
return
}
go func () {
d .Mach .EvRemove1 (e , ss .UpdateLogScheduled , nil )
d .Mach .EvAdd1 (e , ss .UpdatingLog , nil )
}()
}
func (d *Debugger ) UpdatingLogState (e *am .Event ) {
ctx := d .Mach .NewStateCtx (ss .UpdatingLog )
d .Mach .EvAdd1 (e , ss .BuildingLog , am .A {"logRebuildEnd" : len (d .C .MsgTxs )})
go func () {
res := amhelp .Add1Async (ctx , d .Mach , ss .LogBuilt , ss .BuildingLog , am .A {
"logRebuildEnd" : len (d .C .MsgTxs ),
})
if am .Canceled == res {
return
}
d .Mach .EvAdd1 (e , ss .LogUpdated , nil )
}()
}
func (d *Debugger ) LogUpdatedState (e *am .Event ) {
tx := d .hCurrentTx ()
c := d .C
defer func () {
if d .Mach .Is1 (ss .UpdateLogScheduled ) {
d .Mach .EvRemove1 (e , ss .UpdateLogScheduled , nil )
}
}()
d .hUpdateLogReader (e )
if c .MsgStruct != nil {
title := " Log:" + d .Opts .Filters .LogLevel .String () + " "
if tx != nil && c .CursorTx1 != 0 {
t := strconv .Itoa (int (c .MsgTxsParsed [c .CursorTx1 -1 ].TimeSum ))
title += "[gray]([-]t" + t + "[gray])[-] "
}
d .log .SetTitle (title )
}
bySteps := d .Mach .Is1 (ss .TimelineStepsScrolled )
if bySteps {
tx = d .hNextTx ()
}
if tx == nil {
d .log .Highlight ("" )
if bySteps {
d .log .ScrollToEnd ()
} else {
d .log .ScrollToBeginning ()
}
return
}
last := d .hPrevTx ()
if len (tx .LogEntries ) == 0 && last != nil {
for i := d .C .CursorTx1 - 1 ; i > 0 ; i -- {
if len (last .LogEntries ) > 0 {
tx = last
break
}
last = d .C .MsgTxs [i -1 ]
}
d .log .Highlight (last .ID )
} else {
d .log .Highlight (tx .ID )
}
if d .Mach .Not1 (ss .LogUserScrolled ) {
d .log .ScrollToHighlight ()
}
}
func (d *Debugger ) handleLogScroll () {
if d .Mach .Is1 (ss .LogUserScrolled ) {
return
}
d .log .ScrollToHighlight ()
row , _ := d .log .GetScrollOffset ()
d .log .ScrollTo (row , 0 )
}
func (d *Debugger ) hParseMsgLog (c *Client , msgTx *telemetry .DbgMsgTx , idx int ) {
logEntries := make ([]*am .LogEntry , 0 )
tx := c .MsgTxs [idx ]
txParsed := c .MsgTxsParsed [idx ]
var readerEntries []*types .LogReaderEntryPtr
if idx > 0 {
readerEntries = slices .Clone (c .MsgTxsParsed [idx -1 ].ReaderEntries )
}
if tx .IsQueued || !tx .Accepted || txParsed .TimeDiff == 0 {
names := c .MsgStruct .StatesIndex
entry := &am .LogEntry {Level : am .LogChanges }
switch tx .Type {
case am .MutationAdd :
entry .Text = "+" + strings .Join (tx .CalledStateNames (names ), " +" )
case am .MutationRemove :
entry .Text = "-" + strings .Join (tx .CalledStateNames (names ), " -" )
case am .MutationSet :
}
switch {
case tx .IsQueued :
entry .Text = "[queue] " + entry .Text
case !tx .Accepted :
entry .Text = "[cance] " + entry .Text
case txParsed .TimeDiff == 0 :
entry .Text = "[empty] " + entry .Text
}
entry .Text += am .MutationFormatArgs (tx .Args )
tx .LogEntries = []*am .LogEntry {entry }
}
for _ , entry := range msgTx .PreLogEntries {
readerEntries = d .parseMsgReader (c , entry , readerEntries , tx )
if pe := d .hParseMsgLogEntry (c , tx , entry ); pe != nil {
logEntries = append (logEntries , pe )
}
}
for _ , entry := range msgTx .LogEntries {
readerEntries = d .parseMsgReader (c , entry , readerEntries , tx )
if pe := d .hParseMsgLogEntry (c , tx , entry ); pe != nil {
logEntries = append (logEntries , pe )
}
}
c .LogMsgs = append (c .LogMsgs , logEntries )
c .MsgTxsParsed [idx ].ReaderEntries = readerEntries
}
func (d *Debugger ) hParseMsgLogEntry (
c *Client , tx *telemetry .DbgMsgTx , entry *am .LogEntry ,
) *am .LogEntry {
lvl := entry .Level
t := fmtLogEntry (entry .Text , tx .CalledStateNames (c .MsgStruct .StatesIndex ),
c .MsgStruct .States )
return &am .LogEntry {Level : lvl , Text : t }
}
func (d *Debugger ) hAppendLogEntry (index int ) error {
if d .hIsTxSkipped (d .C , index ) {
return nil
}
entry , empty := d .hGetLogEntryTxt (index )
if entry == "" {
return nil
}
if !empty {
entry = "\n" + entry
}
_ , err := d .log .Write ([]byte (entry ))
if err != nil {
return err
}
if d .Mach .Not1 (ss .LogUserScrolled ) {
d .log .ScrollToHighlight ()
}
d .logAppends ++
if d .logAppends > 100 {
d .Mach .Add1 (ss .BuildingLog , am .A {"logRebuildEnd" : index })
d .logAppends = 0
}
return nil
}
func (d *Debugger ) hGetLogEntryTxt (index int ) (entry string , empty bool ) {
empty = true
if index < 0 || index >= len (d .C .MsgTxs ) || index >= len (d .C .MsgTxsParsed ) ||
index >= len (d .C .LogMsgs ) {
d .Mach .AddErr (fmt .Errorf ("invalid log index %d" , index ), nil )
return "" , true
}
c := d .C
ret := ""
tx := c .MsgTxs [index ]
txParsed := c .MsgTxsParsed [index ]
entries := c .LogMsgs [index ]
if d .hIsTxSkipped (c , index ) {
return "" , true
}
for _ , le := range entries {
logStr := le .Text
logLvl := le .Level
if logStr == "" || logLvl > d .Opts .Filters .LogLevel {
continue
}
isErr := strings .HasPrefix (logStr , `[yellow][error[]` )
isBreakpoint := strings .HasPrefix (logStr , `[yellow][breakpoint[]` )
if (isErr || isBreakpoint ) && strings .Contains (logStr , "\n" ) &&
d .Mach .Is1 (ss .FilterTraces ) {
ret += strings .Split (logStr , "\n" )[0 ] + "\n"
continue
}
ret += logStr
}
if ret != "" {
empty = false
if d .Opts .Filters .LogLevel == am .LogExternal {
ret = strings .ReplaceAll (ret , "[yellow][extern[][white] [darkgrey]" , "" )
}
if (d .Mach .Not1 (ss .FilterCanceledTx ) || d .Mach .Not1 (ss .FilterQueuedTx )) &&
d .Opts .Filters .LogLevel >= am .LogChanges {
p := ""
switch {
case tx .IsQueued :
p = "[-:yellow] [-:-]"
case !tx .Accepted :
p = "[-:red] [-:-]"
case txParsed .TimeDiff == 0 :
p = "[-:grey] [-:-]"
default :
p = "[-:green] [-:-]"
}
ret = strings .TrimRight (ret , "\n" )
ret = p + strings .Join (strings .Split (ret , "\n" ), "\n" +p ) + "\n"
}
if d .Mach .Not1 (ss .LogTimestamps ) && index > 0 {
msgTime := tx .Time
prevMsg := c .MsgTxs [index -1 ]
if prevMsg .Time .Second () != msgTime .Second () ||
msgTime .Sub (*prevMsg .Time ) > time .Second {
ret += `[grey]` + msgTime .Format (timeFormat ) + "[-]\n"
}
}
ret = strings .TrimRight (ret , "\n" )
}
txId := tx .ID
ret = `["` + txId + `"]` + ret + `[""]`
return ret , empty
}
var logPrefixState = regexp .MustCompile (
`^\[yellow\]\[(state|queue|cance|empty)\[\]\[white\] .+\)\n$` )
var logPrefixExtern = regexp .MustCompile (
`^\[yellow\]\[exter.+\n$` )
var (
filenamePattern = regexp .MustCompile (`/[a-z_]+\.go:\d+ \+(?i)` )
methodPattern = regexp .MustCompile (`/[^/]+?$` )
)
func fmtLogEntry(
entry string , calledStates []string , machStruct am .Schema ,
) string {
if entry == "" {
return entry
}
prefixEnd := "[][white]"
ret := ""
for i , s := range strings .Split (entry , "\n" ) {
if i == 0 {
s = strings .Replace (strings .Replace (s ,
"]" , prefixEnd , 1 ),
"[" , "[yellow][" , 1 )
start := strings .Index (s , prefixEnd ) + len (prefixEnd )
left , right := s [:start ], s [start :]
ret += left + cview .Escape (right ) + "\n"
} else {
ret += cview .Escape (s ) + "\n"
}
}
ret = logPrefixState .ReplaceAllStringFunc (ret , func (m string ) string {
line := strings .Split (strings .TrimRight (m , ")\n" ), "(" )
args := ""
for _ , arg := range strings .Split (line [1 ], " " ) {
a := strings .Split (arg , "=" )
if len (a ) == 1 {
args += a [0 ] + " "
} else {
args += "[grey]" + a [0 ] + "=[" + colorInactive .String () + "]" + a [1 ] +
"[:] "
}
}
return line [0 ] + args + "\n"
})
ret = logPrefixExtern .ReplaceAllStringFunc (ret , func (m string ) string {
prefix , content , _ := strings .Cut (m , " " )
return prefix + " [darkgrey]" + content
})
idx := strings .Index (ret , prefixEnd )
prefix := ret [0 : idx +len (prefixEnd )]
toReplace := maps .Keys (machStruct )
slices .Sort (toReplace )
slices .Reverse (toReplace )
for _ , name := range toReplace {
body := ret [idx +len (prefixEnd ):]
if slices .Contains (calledStates , name ) {
body = strings .ReplaceAll (body , " " +name , " [::bu]" +name +"[::-]" )
body = strings .ReplaceAll (body , "+" +name , "+[::bu]" +name +"[::-]" )
body = strings .ReplaceAll (body , "-" +name , "-[darkgrey::u]" +name +"[::-]" )
body = strings .ReplaceAll (body , "," +name , ",[::bu]" +name +"[::-]" )
ret = prefix + strings .ReplaceAll (body , "(" +name , "([::b]" +name +"[::-]" )
} else {
body = strings .ReplaceAll (body , " " +name , " [::b]" +name +"[::-]" )
body = strings .ReplaceAll (body , "+" +name , "+[::b]" +name +"[::-]" )
body = strings .ReplaceAll (body , "-" +name , "-[darkgrey]" +name +"[::-]" )
body = strings .ReplaceAll (body , "," +name , ",[::b]" +name +"[::-]" )
ret = prefix + strings .ReplaceAll (body , "(" +name , "([::b]" +name +"[::-]" )
}
}
ret = strings .Trim (ret , " \n " )
isErr := strings .HasPrefix (ret , `[yellow][error[]` )
isBreakpoint := strings .HasPrefix (ret , `[yellow][breakpoint[]` )
if (isErr || isBreakpoint ) && strings .Contains (ret , "\n" ) {
lines := strings .Split (ret , "\n" )
linesNew := lines [0 :1 ]
skipNext := false
for i , line := range lines {
if i == 0 || skipNext {
skipNext = false
continue
}
if strings .Contains (line , "machine.(*Machine).handlerLoop(" ) {
linesNew = linesNew [0 :max (0 , len (linesNew )-4 )]
skipNext = true
continue
}
if i %2 == 1 {
linesNew = append (linesNew , "[grey]" +
methodPattern .ReplaceAllStringFunc (line ,
func (m string ) string {
return "[white]" + m + "[grey]"
}))
} else {
linesNew = append (linesNew , "[grey]" +
filenamePattern .ReplaceAllStringFunc (line ,
func (m string ) string {
mspace := strings .Split (m , " " )
ret := "[white]" + mspace [0 ]
if len (mspace ) > 1 {
ret += " [grey]" + mspace [1 ]
}
return ret
}))
}
}
ret = strings .Join (linesNew , "\n" )
}
return ret + "\n"
}
type logReaderTreeRef struct {
stateNames am .S
machId string
txId string
machTime uint64
entry *types .LogReaderEntryPtr
addr *types .MachAddress
isQueueRoot bool
}
var removeStyleBracketsRe = regexp .MustCompile (`\[[^\]]*\]` )
func (d *Debugger ) initLogReader () *cview .TreeView {
root := cview .NewTreeNode ("Extracted" )
root .SetIndent (0 )
root .SetReference (&logReaderTreeRef {})
tree := cview .NewTreeView ()
tree .SetRoot (root )
tree .SetCurrentNode (root )
tree .SetSelectedBackgroundColor (colorHighlight2 )
tree .SetSelectedTextColor (colorDefault )
tree .SetHighlightColor (colorHighlight )
tree .SetTopLevel (1 )
tree .SetChangedFunc (func (node *cview .TreeNode ) {
ref , ok := node .GetReference ().(*logReaderTreeRef )
if !ok || ref == nil {
return
}
d .Mach .Eval ("initLogReader" , func () {
if d .C != nil {
d .C .SelectedReaderEntry = ref .entry
}
}, nil )
if len (ref .stateNames ) < 1 {
d .Mach .Remove1 (ss .StateNameSelected , nil )
} else {
d .Mach .Add1 (ss .StateNameSelected , am .A {
"state" : ref .stateNames [0 ],
})
}
})
tree .SetSelectedFunc (func (node *cview .TreeNode ) {
ref , ok := node .GetReference ().(*logReaderTreeRef )
if !ok || ref == nil {
return
}
node .SetExpanded (!node .IsExpanded ())
isTop := node .GetParent () == tree .GetRoot ()
if isTop || ref .isQueueRoot {
name := strings .Split (node .GetText (), " " )[0 ]
name = removeStyleBracketsRe .ReplaceAllString (name , "" )
d .readerExpanded [name ] = node .IsExpanded ()
}
addr := ref .addr
if addr == nil {
addr = &types .MachAddress {
MachId : ref .machId ,
TxId : ref .txId ,
MachTime : ref .machTime ,
}
}
tick := d .Mach .Tick (ss .UpdateLogReader )
text := node .GetText ()
node .SetText ("[" + colorActive .String () + "::u]" +
removeStyleBracketsRe .ReplaceAllString (text , "" ))
d .draw (d .logReader )
go func () {
time .Sleep (time .Millisecond * 200 )
d .hGoToMachAddress (addr , false )
time .Sleep (time .Millisecond * 50 )
if tick != d .Mach .Tick (ss .UpdateLogReader ) {
return
}
node .SetText (text )
d .draw (d .logReader )
}()
})
return tree
}
func (d *Debugger ) hUpdateLogReader (e *am .Event ) {
if d .Mach .Not1 (ss .LogReaderVisible ) {
return
}
d .Mach .EvAdd1 (e , ss .UpdateLogReader , nil )
d .Mach .EvRemove1 (e , ss .UpdateLogReader , nil )
root := d .logReader .GetRoot ()
root .ClearChildren ()
c := d .C
if c == nil || c .CursorTx1 < 1 {
return
}
tx := c .MsgTxs [c .CursorTx1 -1 ]
txParsed := c .MsgTxsParsed [c .CursorTx1 -1 ]
statesIndex := c .MsgStruct .StatesIndex
var (
parentCtx *cview .TreeNode
parentWhen *cview .TreeNode
parentWhenNot *cview .TreeNode
parentWhenTime *cview .TreeNode
parentWhenArgs *cview .TreeNode
parentWhenQueue *cview .TreeNode
parentPipeIn *cview .TreeNode
parentPipeOut *cview .TreeNode
parentHandlers *cview .TreeNode
parentArgs *cview .TreeNode
parentSource *cview .TreeNode
parentQueue *cview .TreeNode
parentForks *cview .TreeNode
parentSiblings *cview .TreeNode
)
selState := d .C .SelectedState
for _ , ptr := range txParsed .ReaderEntries {
entries , ok := c .LogReader [ptr .TxId ]
var node *cview .TreeNode
if !ok || ptr .EntryIdx >= len (entries ) {
node = cview .NewTreeNode ("err:GCed entry" )
node .SetIndent (1 )
} else {
entry := entries [ptr .EntryIdx ]
nodeRef := &logReaderTreeRef {
stateNames : c .IndexesToStates (entry .States ),
machId : entry .Mach ,
entry : ptr ,
}
switch entry .Kind {
case types .LogReaderCtx :
if parentCtx == nil {
parentCtx = cview .NewTreeNode ("StateCtx" )
}
states := amhelp .IndexesToStates (statesIndex , entry .States )
node = cview .NewTreeNode (d .P .Sprintf ("[::b]%s[::-] [grey]t%d[-]" ,
utils .J (states ), entry .CreatedAt ))
parentCtx .AddChild (node )
if slices .Contains (states , selState ) {
node .SetHighlighted (true )
}
case types .LogReaderWhen :
if parentWhen == nil {
parentWhen = cview .NewTreeNode ("When" )
}
states := amhelp .IndexesToStates (statesIndex , entry .States )
node = cview .NewTreeNode (d .P .Sprintf ("[::b]%s[::-] [grey]t%d[-]" ,
utils .J (states ), entry .CreatedAt ))
parentWhen .AddChild (node )
if slices .Contains (states , selState ) {
node .SetHighlighted (true )
}
case types .LogReaderWhenNot :
if parentWhenNot == nil {
parentWhenNot = cview .NewTreeNode ("WhenNot" )
}
states := amhelp .IndexesToStates (statesIndex , entry .States )
node = cview .NewTreeNode (d .P .Sprintf ("[::b]%s[::-] [grey]t%d[-]" ,
utils .J (states ), entry .CreatedAt ))
parentWhenNot .AddChild (node )
if slices .Contains (states , selState ) {
node .SetHighlighted (true )
}
case types .LogReaderWhenTime :
if parentWhenTime == nil {
parentWhenTime = cview .NewTreeNode ("WhenTime" )
}
txt := ""
highlight := false
for i , idx := range entry .States {
txt += fmt .Sprintf ("[::b]%s[::-]:%d " , statesIndex [idx ],
entry .Ticks [i ])
if statesIndex [idx ] == selState {
highlight = true
}
}
node = cview .NewTreeNode (d .P .Sprintf ("%s[grey]t%d[-]" , txt ,
entry .CreatedAt ))
parentWhenTime .AddChild (node )
if highlight {
node .SetHighlighted (true )
}
case types .LogReaderWhenArgs :
if parentWhenArgs == nil {
parentWhenArgs = cview .NewTreeNode ("WhenArgs" )
}
states := amhelp .IndexesToStates (statesIndex , entry .States )
node = cview .NewTreeNode (d .P .Sprintf ("[::b]%s[::-] [grey]t%d[-]" ,
utils .J (states ), entry .CreatedAt ))
node2 := cview .NewTreeNode (d .P .Sprintf ("%s" , entry .Args ))
node2 .SetIndent (1 )
node2 .SetReference (&logReaderTreeRef {entry : ptr })
node .AddChild (node2 )
parentWhenArgs .AddChild (node )
if slices .Contains (states , selState ) {
node .SetHighlighted (true )
}
case types .LogReaderWhenQueue :
if parentWhenQueue == nil {
parentWhenQueue = cview .NewTreeNode ("WhenQueue" )
}
node = cview .NewTreeNode (d .P .Sprintf ("%v" , entry .QueueTick ))
parentWhenQueue .AddChild (node )
case types .LogReaderPipeIn :
states := amhelp .IndexesToStates (statesIndex , entry .States )
nodeRef .machId = c .Id
nodeRef .machTime = entry .CreatedAt
if parentPipeIn == nil {
parentPipeIn = cview .NewTreeNode ("Pipe-in" )
} else {
for _ , child := range parentPipeIn .GetChildren () {
if child .GetText () == states [0 ] {
node = child
break
}
}
}
txIdx := c .TxByMachTime (entry .CreatedAt )
pipeTime := *c .Tx (txIdx ).Time
if node == nil {
node = cview .NewTreeNode (states [0 ])
node .SetBold (true )
parentPipeIn .AddChild (node )
}
node2 := cview .NewTreeNode (d .P .Sprintf ("%-6s | [grey]t%d[-] %s" ,
capitalizeFirst (entry .Pipe .String ()), entry .CreatedAt , entry .Mach ))
node2 .SetIndent (1 )
node2 .SetReference (&logReaderTreeRef {
stateNames : c .IndexesToStates (entry .States ),
entry : ptr ,
machId : entry .Mach ,
addr : &types .MachAddress {
MachId : entry .Mach ,
HumanTime : pipeTime ,
},
})
node .AddChild (node2 )
if slices .Contains (states , selState ) {
node .SetHighlighted (true )
node2 .SetHighlighted (true )
}
case types .LogReaderPipeOut :
states := amhelp .IndexesToStates (statesIndex , entry .States )
nodeRef .machId = c .Id
nodeRef .machTime = entry .CreatedAt
if parentPipeOut == nil {
parentPipeOut = cview .NewTreeNode ("Pipe-out" )
} else {
for _ , child := range parentPipeOut .GetChildren () {
if child .GetText () == states [0 ] {
node = child
break
}
}
}
if node == nil {
node = cview .NewTreeNode (states [0 ])
node .SetBold (true )
parentPipeOut .AddChild (node )
}
txIdx := c .TxByMachTime (entry .CreatedAt )
pipeTime := *c .Tx (txIdx ).Time
node2 := cview .NewTreeNode (d .P .Sprintf ("%-6s | [grey]t%d[-] %s" ,
capitalizeFirst (entry .Pipe .String ()), entry .CreatedAt , entry .Mach ))
node2 .SetIndent (1 )
node2 .SetReference (&logReaderTreeRef {
stateNames : c .IndexesToStates (entry .States ),
entry : ptr ,
machId : entry .Mach ,
addr : &types .MachAddress {
MachId : entry .Mach ,
HumanTime : pipeTime ,
},
})
node .AddChild (node2 )
if slices .Contains (states , selState ) {
node .SetHighlighted (true )
node2 .SetHighlighted (true )
}
default :
continue
}
node .SetIndent (1 )
node .SetSelectable (true )
node .SetReference (nodeRef )
sel := c .SelectedReaderEntry
if sel != nil && ptr .TxId == sel .TxId && ptr .EntryIdx == sel .EntryIdx {
d .logReader .SetCurrentNode (node )
}
}
}
parentSource = cview .NewTreeNode ("Source" )
sourceKnown := false
for _ , entry := range tx .LogEntries {
if !strings .HasPrefix (entry .Text , "[source] " ) {
continue
}
sourceKnown = true
source := strings .Split (entry .Text [len ("[source] " ):], "/" )
machTime , _ := strconv .ParseUint (source [2 ], 10 , 64 )
node := cview .NewTreeNode ("self" )
node .SetIndent (1 )
if source [0 ] != c .Id {
node = cview .NewTreeNode (d .P .Sprintf ("%s [grey]t%v[-]" , source [0 ],
source [2 ]))
node .SetReference (&logReaderTreeRef {
machId : source [0 ],
txId : source [1 ],
machTime : machTime ,
})
}
parentSource .AddChild (node )
if sC , sTx := d .hGetClientTx (source [0 ], source [1 ]); sTx != nil {
stateNames := sTx .CalledStateNames (sC .MsgStruct .StatesIndex )
label := capitalizeFirst (sTx .Type .String ()) + " [::b]" +
utils .J (stateNames )
node2 := cview .NewTreeNode (label )
node2 .SetIndent (1 )
node2 .SetReference (&logReaderTreeRef {
stateNames : stateNames ,
machId : source [0 ],
txId : source [1 ],
machTime : machTime ,
})
parentSource .AddChild (node2 )
if slices .Contains (stateNames , selState ) {
if node != nil {
node .SetHighlighted (true )
}
node2 .SetHighlighted (true )
}
}
if sourceMach := d .hGetClient (source [0 ]); sourceMach != nil &&
source [0 ] != c .Id {
if tags := sourceMach .MsgStruct .Tags ; len (tags ) > 0 {
node2 := cview .NewTreeNode ("[grey]#" + strings .Join (tags , " #" ))
node2 .SetIndent (1 )
parentSource .AddChild (node2 )
}
}
}
if !sourceKnown {
node := cview .NewTreeNode ("mach unknown" )
node .SetIndent (1 )
parentSource .AddChild (node )
node = cview .NewTreeNode ("tx unknown" )
node .SetIndent (1 )
parentSource .AddChild (node )
}
parentQueue = cview .NewTreeNode ("Queue" )
{
tickNode := cview .NewTreeNode (d .P .Sprintf ("current [::b]q%v" ,
tx .QueueTick ))
tickNode .SetIndent (1 )
parentQueue .AddChild (tickNode )
if tx .IsQueued {
var executed *telemetry .DbgMsgTx
for iii := c .CursorTx1 ; iii < len (c .MsgTxs ); iii ++ {
check := c .MsgTxs [iii ]
if check .IsQueued {
continue
}
if check .QueueTick == tx .MutQueueTick ||
(check .MutQueueToken > 0 && check .MutQueueToken == tx .MutQueueToken ) {
executed = check
break
}
}
label := "???"
var ref *logReaderTreeRef
if executed != nil {
label = d .P .Sprintf ("t%v" , executed .TimeSum ())
ref = &logReaderTreeRef {
stateNames : tx .CalledStateNames (statesIndex ),
machId : c .Id ,
txId : executed .ID ,
}
}
executedNode := cview .NewTreeNode ("executed at [::b]" + label )
executedNode .SetReference (ref )
executedNode .SetIndent (1 )
parentQueue .AddChild (executedNode )
queuedNode := cview .NewTreeNode (fmt .Sprintf ("queued at [::b]t%d" ,
tx .TimeSum ()))
queuedNode .SetIndent (1 )
parentQueue .AddChild (queuedNode )
queuedForLabel := "..."
if tx .MutQueueTick > 0 {
queuedForLabel = d .P .Sprintf ("q%v" , tx .MutQueueTick )
}
queuedForNode := cview .NewTreeNode ("queued for [::b]" + queuedForLabel )
queuedForNode .SetIndent (1 )
queuedForNode .SetReference (ref )
parentQueue .AddChild (queuedForNode )
} else {
var queued *telemetry .DbgMsgTx
for iii := c .CursorTx1 - 2 ; iii >= 0 ; iii -- {
check := c .MsgTxs [iii ]
if !check .IsQueued {
continue
}
if check .MutQueueTick == tx .QueueTick ||
(check .MutQueueToken > 0 && check .MutQueueToken == tx .MutQueueToken ) {
queued = check
break
}
}
label := "???"
var ref *logReaderTreeRef
if queued != nil {
label = d .P .Sprintf ("t%v" , queued .TimeSum ())
ref = &logReaderTreeRef {
stateNames : tx .CalledStateNames (statesIndex ),
machId : c .Id ,
txId : queued .ID ,
}
}
executedNode := cview .NewTreeNode ("executed at [::b]..." )
executedNode .SetIndent (1 )
parentQueue .AddChild (executedNode )
queuedNode := cview .NewTreeNode ("queued at [::b]" + label )
queuedNode .SetReference (ref )
queuedNode .SetIndent (1 )
parentQueue .AddChild (queuedNode )
queuedForNode := cview .NewTreeNode ("queued for [::b]..." )
queuedForNode .SetIndent (1 )
parentQueue .AddChild (queuedForNode )
}
lenNodeLabel := d .P .Sprintf ("length [::b]%v[::-]" , tx .Queue )
if tx .IsQueued {
lenNodeLabel += " (inferred)"
}
lenNode := cview .NewTreeNode (lenNodeLabel )
lenNode .SetIndent (1 )
lenNode .SetReference (&logReaderTreeRef {
isQueueRoot : true ,
})
if expanded , ok := d .readerExpanded ["length" ]; ok {
lenNode .SetExpanded (expanded )
}
parentQueue .AddChild (lenNode )
executed := []uint64 {tx .MutQueueToken }
queue := []*cview .TreeNode {}
queuePrio := []*cview .TreeNode {}
listLen := 0
checked := 0
for ii := max (0 , c .CursorTx1 -1 ); ii >= 0 ; ii -- {
past := c .Tx (ii )
if past == nil {
d .Mach .AddErr (fmt .Errorf ("tx missing: %d" , ii ), nil )
break
}
if listLen >= tx .Queue {
break
}
if !past .IsQueued && past .MutQueueToken > 0 {
executed = append (executed , past .MutQueueToken )
}
if !past .IsQueued {
continue
}
if tx .ID != past .ID && past .MutQueueToken > 0 &&
slices .Contains (executed , past .MutQueueToken ) {
continue
}
if tx .QueueTick == past .MutQueueTick {
continue
}
if past .MutQueueTick > 0 && past .MutQueueTick <= tx .QueueTick {
continue
}
checked ++
if checked > 5000 {
break
}
label := past .MutString (statesIndex )
before := label [0 :1 ]
after := label [1 :]
if past .MutQueueTick > 0 {
after = after + " " + d .P .Sprintf ("[gray]q%v" , past .MutQueueTick )
}
switch before {
case "+" :
before = " +"
case "-" :
before = "- "
}
mutNode := cview .NewTreeNode ("[::b]" + before + "[::-]" + after )
mutNode .SetIndent (2 )
mutNode .SetReference (&logReaderTreeRef {
machId : c .Id ,
txId : past .ID ,
stateNames : past .CalledStateNames (statesIndex ),
})
if past .MutQueueToken > 0 {
queuePrio = slices .Concat ([]*cview .TreeNode {mutNode }, queuePrio )
} else {
queue = append (queue , mutNode )
}
listLen = len (queue ) + len (queuePrio )
if listLen > 50 {
break
}
}
slices .Reverse (queuePrio )
slices .Reverse (queue )
for _ , mutNode := range queuePrio {
lenNode .AddChild (mutNode )
}
for _ , mutNode := range queue {
lenNode .AddChild (mutNode )
}
if len (queue )+len (queuePrio ) > 50 || checked > 5000 {
mutNode := cview .NewTreeNode ("..." )
mutNode .SetIndent (2 )
lenNode .AddChild (mutNode )
}
}
parentForks = cview .NewTreeNode ("Forked" )
parentForks .SetExpanded (false )
for _ , link := range txParsed .Forks {
targetMach := d .hGetClient (link .MachId )
targetTxIdx := targetMach .TxIndex (link .TxId )
highlight := false
label := d .P .Sprintf ("%s#%s" , link .MachId , link .TxId )
if link .MachId == c .Id {
label = d .P .Sprintf ("#%s" , link .TxId )
}
if targetTxIdx != -1 {
targetTx := targetMach .Tx (targetTxIdx )
calledStates := targetTx .CalledStateNames (
targetMach .MsgStruct .StatesIndex )
label = capitalizeFirst (tx .Type .String ()) + " [::b]" +
utils .J (calledStates )
if slices .Contains (calledStates , selState ) {
highlight = true
}
}
node := cview .NewTreeNode (label )
node .SetIndent (1 )
node .SetReference (&logReaderTreeRef {
machId : link .MachId ,
txId : link .TxId ,
})
parentForks .AddChild (node )
if highlight {
node .SetHighlighted (true )
}
if link .MachId != c .Id && targetMach != nil {
label2 := d .P .Sprintf ("%s#%s" , link .MachId ,
link .TxId )
if targetTxIdx != -1 {
targetTx := targetMach .Tx (targetTxIdx )
label2 = d .P .Sprintf ("%s [grey]t%v[-]" , link .MachId ,
targetTx .TimeSum ())
}
node2 := cview .NewTreeNode (label2 )
node2 .SetIndent (1 )
node2 .SetReference (&logReaderTreeRef {
machId : link .MachId ,
txId : link .TxId ,
})
node .AddChild (node2 )
if highlight {
node2 .SetHighlighted (true )
}
if tags := targetMach .MsgStruct .Tags ; len (tags ) > 0 {
node3 := cview .NewTreeNode ("[grey]#" + strings .Join (tags , " #" ))
node3 .SetIndent (1 )
node .AddChild (node3 )
if highlight {
node3 .SetHighlighted (true )
}
}
}
}
parentSiblings = cview .NewTreeNode ("Siblings" )
parentSiblings .SetExpanded (false )
for _ , entry := range tx .LogEntries {
if !strings .HasPrefix (entry .Text , "[source] " ) {
continue
}
source := strings .Split (entry .Text [len ("[source] " ):], "/" )
sC , sTx := d .hGetClientTx (source [0 ], source [1 ])
if sTx == nil {
break
}
sTxParsed := sC .TxParsed (sC .TxIndex (sTx .ID ))
for _ , link := range sTxParsed .Forks {
if link .MachId == c .Id && link .TxId == tx .ID {
continue
}
targetMach := d .hGetClient (link .MachId )
targetTxIdx := targetMach .TxIndex (link .TxId )
highlight := false
label := d .P .Sprintf ("%s#%s" , link .MachId , link .TxId )
if link .MachId == c .Id {
label = d .P .Sprintf ("#%s" , link .TxId )
}
if targetTxIdx != -1 {
targetTx := targetMach .Tx (targetTxIdx )
calledStates := targetTx .CalledStateNames (
targetMach .MsgStruct .StatesIndex )
label = capitalizeFirst (tx .Type .String ()) + " [::b]" +
utils .J (calledStates )
if slices .Contains (calledStates , selState ) {
highlight = true
}
}
node := cview .NewTreeNode (label )
node .SetIndent (1 )
node .SetReference (&logReaderTreeRef {
machId : link .MachId ,
txId : link .TxId ,
})
parentSiblings .AddChild (node )
if highlight {
node .SetHighlighted (true )
}
if link .MachId != c .Id && targetMach != nil {
label2 := d .P .Sprintf ("%s#%s" , link .MachId ,
link .TxId )
if targetTxIdx != -1 {
targetTx := targetMach .Tx (targetTxIdx )
label2 = d .P .Sprintf ("%s [grey]t%v[-]" , link .MachId ,
targetTx .TimeSum ())
}
node2 := cview .NewTreeNode (label2 )
node2 .SetIndent (1 )
node2 .SetReference (&logReaderTreeRef {
machId : link .MachId ,
txId : link .TxId ,
})
node .AddChild (node2 )
if highlight {
node2 .SetHighlighted (true )
}
if tags := targetMach .MsgStruct .Tags ; len (tags ) > 0 {
node3 := cview .NewTreeNode ("[grey]#" + strings .Join (tags , " #" ))
node3 .SetIndent (1 )
node .AddChild (node3 )
if highlight {
node3 .SetHighlighted (true )
}
}
}
}
}
parentHandlers = cview .NewTreeNode ("Executed" )
parentHandlers .SetExpanded (false )
for _ , entry := range tx .LogEntries {
if !strings .HasPrefix (entry .Text , "[handler:" ) {
continue
}
idx := strings .Index (entry .Text , "]" )
if idx == -1 {
continue
}
handlerIdx := entry .Text [len ("[handler:" ):idx ]
name := entry .Text [idx +2 :]
node := cview .NewTreeNode (d .P .Sprintf ("%s[grey]:%s[-]" , name ,
handlerIdx ))
node .SetIndent (1 )
parentHandlers .AddChild (node )
if selState != "" && strings .HasPrefix (name , selState ) {
node .SetHighlighted (true )
}
}
parentArgs = cview .NewTreeNode ("Arguments" )
parentArgs .SetExpanded (false )
{
labels := []string {}
for k , v := range tx .Args {
labels = append (labels , d .P .Sprintf (
"[::b]%s[::-] [%s]%s" , k , colorInactive , v ))
}
slices .Sort (labels )
for _ , label := range labels {
node := cview .NewTreeNode (label )
node .SetIndent (1 )
parentArgs .AddChild (node )
}
}
addParent := func (parent *cview .TreeNode ) {
name := parent .GetText ()
if expanded , ok := d .readerExpanded [name ]; ok {
parent .SetExpanded (expanded )
}
count := len (parent .GetChildren ())
if count > 0 && parent != parentSource && parent != parentQueue {
parent .SetText (fmt .Sprintf ("[%s]%s[-] (%d)" , colorActive ,
parent .GetText (), count ))
} else {
parent .SetText (fmt .Sprintf ("[%s]%s[-]" , colorActive , parent .GetText ()))
}
parent .SetIndent (0 )
parent .SetReference (&logReaderTreeRef {})
root .AddChild (parent )
if d .C .ReaderCollapsed {
parent .SetExpanded (false )
}
}
if parentSource != nil {
addParent (parentSource )
d .logReader .SetCurrentNode (parentSource )
}
if parentQueue != nil {
addParent (parentQueue )
}
if parentForks != nil {
addParent (parentForks )
}
if parentSiblings != nil {
addParent (parentSiblings )
}
if parentHandlers != nil {
addParent (parentHandlers )
}
if parentArgs != nil {
addParent (parentArgs )
}
if parentCtx != nil {
addParent (parentCtx )
}
if parentWhen != nil {
addParent (parentWhen )
}
if parentWhenNot != nil {
addParent (parentWhenNot )
}
if parentWhenTime != nil {
addParent (parentWhenTime )
}
if parentWhenArgs != nil {
addParent (parentWhenArgs )
}
if parentWhenQueue != nil {
addParent (parentWhenQueue )
}
if parentPipeIn != nil {
addParent (parentPipeIn )
}
if parentPipeOut != nil {
addParent (parentPipeOut )
}
go d .App .QueueUpdateDraw (func () {
root .CollapseAll ()
root .Expand ()
})
}
func (d *Debugger ) parseMsgReader (
c *Client , log *am .LogEntry , txEntries []*types .LogReaderEntryPtr ,
tx *telemetry .DbgMsgTx ,
) []*types .LogReaderEntryPtr {
if strings .HasPrefix (log .Text , "[source] " ) {
source := strings .Split (log .Text [len ("[source] " ):], "/" )
if sourceMach := d .hGetClient (source [0 ]); sourceMach != nil {
txIdx := sourceMach .TxIndex (source [1 ])
if txIdx != -1 {
srcTxParsed := sourceMach .TxParsed (txIdx )
srcTxParsed .Forks = append (srcTxParsed .Forks , types .MachAddress {
MachId : c .Id ,
TxId : tx .ID ,
})
}
}
} else if strings .HasPrefix (log .Text , "[when:new] " ) {
states := strings .Split (log .Text [len ("[when:new] " ):], " " )
idx := c .AddReaderEntry (tx .ID , &types .LogReaderEntry {
Kind : types .LogReaderWhen ,
States : c .StatesToIndexes (states ),
CreatedAt : c .MTimeSum ,
})
txEntries = append (txEntries , &types .LogReaderEntryPtr {
TxId : tx .ID ,
EntryIdx : idx ,
})
} else if strings .HasPrefix (log .Text , "[whenNot:new] " ) {
states := strings .Split (log .Text [len ("[whenNot:new] " ):], " " )
idx := c .AddReaderEntry (tx .ID , &types .LogReaderEntry {
Kind : types .LogReaderWhenNot ,
States : c .StatesToIndexes (states ),
CreatedAt : c .MTimeSum ,
})
txEntries = append (txEntries , &types .LogReaderEntryPtr {
TxId : tx .ID ,
EntryIdx : idx ,
})
} else if strings .HasPrefix (log .Text , "[whenTime:new] " ) {
msg := strings .Split (log .Text [len ("[whenTime:new] " ):], " " )
states := strings .Split (msg [0 ], "," )
ticksStr := strings .Split (msg [1 ], "," )
idx := c .AddReaderEntry (tx .ID , &types .LogReaderEntry {
Kind : types .LogReaderWhenTime ,
States : c .StatesToIndexes (states ),
Ticks : tickStrToTime (d .Mach , ticksStr ),
CreatedAt : c .MTimeSum ,
})
txEntries = append (txEntries , &types .LogReaderEntryPtr {
TxId : tx .ID ,
EntryIdx : idx ,
})
} else if strings .HasPrefix (log .Text , "[ctx:new] " ) {
state := log .Text [len ("[ctx:new] " ):]
idx := c .AddReaderEntry (tx .ID , &types .LogReaderEntry {
Kind : types .LogReaderCtx ,
States : c .StatesToIndexes (am .S {state }),
CreatedAt : c .MTimeSum ,
})
txEntries = append (txEntries , &types .LogReaderEntryPtr {
TxId : tx .ID ,
EntryIdx : idx ,
})
} else if strings .HasPrefix (log .Text , "[whenArgs:new] " ) {
msg := strings .Split (log .Text [len ("[whenArgs:new] " ):], " " )
args := strings .Trim (msg [1 ], "()" )
idx := c .AddReaderEntry (tx .ID , &types .LogReaderEntry {
Kind : types .LogReaderWhenArgs ,
States : c .StatesToIndexes (am .S {msg [0 ]}),
CreatedAt : c .MTimeSum ,
Args : args ,
})
txEntries = append (txEntries , &types .LogReaderEntryPtr {
TxId : tx .ID ,
EntryIdx : idx ,
})
} else if strings .HasPrefix (log .Text , "[pipe-in:add] " ) ||
strings .HasPrefix (log .Text , "[pipe-in:remove] " ) ||
strings .HasPrefix (log .Text , "[pipe-out:add] " ) ||
strings .HasPrefix (log .Text , "[pipe-out:remove] " ) {
isAdd := strings .HasPrefix (log .Text , "[pipe-in:add] " ) ||
strings .HasPrefix (log .Text , "[pipe-out:add] " )
isOut := strings .HasPrefix (log .Text , "[pipe-out" )
var msg []string
if isOut && isAdd {
msg = strings .Split (log .Text [len ("[pipe-out:add] " ):], " to " )
} else if !isOut && isAdd {
msg = strings .Split (log .Text [len ("[pipe-in:add] " ):], " from " )
} else if isOut && !isAdd {
msg = strings .Split (log .Text [len ("[pipe-out:remove] " ):], " to " )
} else if !isOut && !isAdd {
msg = strings .Split (log .Text [len ("[pipe-in:remove] " ):], " from " )
}
kind := types .LogReaderPipeIn
if isOut {
kind = types .LogReaderPipeOut
}
mut := am .MutationRemove
if isAdd {
mut = am .MutationAdd
}
states := strings .Split (strings .Trim (msg [0 ], "[]" ), " " )
idx := c .AddReaderEntry (tx .ID , &types .LogReaderEntry {
Kind : kind ,
Pipe : mut ,
States : c .StatesToIndexes (states ),
CreatedAt : c .MTimeSum ,
Mach : msg [1 ],
})
txEntries = append (txEntries , &types .LogReaderEntryPtr {
TxId : tx .ID ,
EntryIdx : idx ,
})
} else if strings .HasPrefix (log .Text , "[pipe:gc] " ) {
l := strings .Split (log .Text , " " )
id := l [1 ]
var entries2 []*types .LogReaderEntryPtr
for _ , ptr := range txEntries {
e := c .GetReaderEntry (ptr .TxId , ptr .EntryIdx )
isPipe := e .Kind == types .LogReaderPipeIn ||
e .Kind == types .LogReaderPipeOut
if isPipe && e .Mach == id {
continue
}
entries2 = append (entries2 , ptr )
}
txEntries = entries2
} else if strings .HasPrefix (log .Text , "[whenQueue:new] " ) {
msg := strings .Split (log .Text [len ("[whenQueue:new] " ):], " " )
tick , err := strconv .Atoi (msg [0 ])
if err != nil {
d .Mach .Log ("err: match missing: " + log .Text )
return txEntries
}
idx := c .AddReaderEntry (tx .ID , &types .LogReaderEntry {
Kind : types .LogReaderWhenQueue ,
CreatedAt : c .MTimeSum ,
QueueTick : tick ,
})
txEntries = append (txEntries , &types .LogReaderEntryPtr {
TxId : tx .ID ,
EntryIdx : idx ,
})
} else
if strings .HasPrefix (log .Text , "[when:match] " ) {
states := strings .Split (log .Text [len ("[when:match] " ):], "," )
found := false
for i , ptr := range txEntries {
entry := c .GetReaderEntry (ptr .TxId , ptr .EntryIdx )
if entry != nil && entry .Kind == types .LogReaderWhen &&
am .StatesEqual (states , c .IndexesToStates (entry .States )) {
txEntries = slices .Delete (txEntries , i , i +1 )
entry .ClosedAt = *tx .Time
found = true
break
}
}
if !found {
d .Mach .Log ("err: match missing: " + log .Text )
}
} else if strings .HasPrefix (log .Text , "[whenNot:match] " ) {
states := strings .Split (log .Text [len ("[whenNot:match] " ):], "," )
found := false
for i , ptr := range txEntries {
entry := c .GetReaderEntry (ptr .TxId , ptr .EntryIdx )
if entry != nil && entry .Kind == types .LogReaderWhenNot &&
am .StatesEqual (states , c .IndexesToStates (entry .States )) {
txEntries = slices .Delete (txEntries , i , i +1 )
entry .ClosedAt = *tx .Time
found = true
break
}
}
if !found {
d .Mach .Log ("err: match missing: " + log .Text )
}
} else if strings .HasPrefix (log .Text , "[whenTime:match] " ) {
msg := strings .Split (log .Text [len ("[whenTime:match] " ):], " " )
states := strings .Split (msg [0 ], "," )
ticksStr := strings .Split (strings .Trim (msg [1 ], "[]" ), " " )
ticks := tickStrToTime (d .Mach , ticksStr )
found := false
for i , ptr := range txEntries {
entry := c .GetReaderEntry (ptr .TxId , ptr .EntryIdx )
if entry != nil && entry .Kind == types .LogReaderWhenTime &&
am .StatesEqual (states , c .IndexesToStates (entry .States )) &&
slices .Equal (ticks , entry .Ticks ) {
txEntries = slices .Delete (txEntries , i , i +1 )
entry .ClosedAt = *tx .Time
found = true
break
}
}
if !found {
d .Mach .Log ("err: match missing: " + log .Text )
}
} else if strings .HasPrefix (log .Text , "[ctx:match] " ) {
state := log .Text [len ("[ctx:match] " ):]
found := false
for i , ptr := range txEntries {
entry := c .GetReaderEntry (ptr .TxId , ptr .EntryIdx )
if entry != nil && entry .Kind == types .LogReaderCtx &&
am .StatesEqual (am .S {state }, c .IndexesToStates (entry .States )) {
txEntries = slices .Delete (txEntries , i , i +1 )
entry .ClosedAt = *tx .Time
found = true
break
}
}
if !found {
d .Mach .Log ("err: match missing: " + log .Text )
}
} else if strings .HasPrefix (log .Text , "[whenArgs:match] " ) {
msg := strings .Split (log .Text [len ("[whenArgs:match] " ):], " " )
args := strings .Trim (msg [1 ], "()" )
found := false
for i , ptr := range txEntries {
entry := c .GetReaderEntry (ptr .TxId , ptr .EntryIdx )
if entry != nil && entry .Kind == types .LogReaderWhenArgs &&
am .StatesEqual (am .S {msg [0 ]}, c .IndexesToStates (entry .States )) &&
entry .Args == args {
txEntries = slices .Delete (txEntries , i , i +1 )
entry .ClosedAt = *tx .Time
found = true
break
}
}
if !found {
d .Mach .Log ("err: match missing: " + log .Text )
}
} else if strings .HasPrefix (log .Text , "[whenQueue:match] " ) {
msg := strings .Split (log .Text , " " )
tick , err := strconv .Atoi (msg [1 ])
if err != nil {
d .Mach .Log ("err: match missing: " + log .Text )
return txEntries
}
found := false
for i , ptr := range txEntries {
entry := c .GetReaderEntry (ptr .TxId , ptr .EntryIdx )
if entry != nil && entry .Kind == types .LogReaderWhenQueue &&
entry .QueueTick == tick {
txEntries = slices .Delete (txEntries , i , i +1 )
entry .ClosedAt = *tx .Time
found = true
break
}
}
if !found {
d .Mach .Log ("err: match missing: " + log .Text )
}
}
return txEntries
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .