mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			279 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			279 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2016 The Go Authors. All rights reserved.
 | 
						|
// Use of this source code is governed by a BSD-style
 | 
						|
// license that can be found in the LICENSE file.
 | 
						|
 | 
						|
package trace
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"sort"
 | 
						|
)
 | 
						|
 | 
						|
type eventBatch struct {
 | 
						|
	events   []*Event
 | 
						|
	selected bool
 | 
						|
}
 | 
						|
 | 
						|
type orderEvent struct {
 | 
						|
	ev    *Event
 | 
						|
	batch int
 | 
						|
	g     uint64
 | 
						|
	init  gState
 | 
						|
	next  gState
 | 
						|
}
 | 
						|
 | 
						|
type gStatus int
 | 
						|
 | 
						|
type gState struct {
 | 
						|
	seq    uint64
 | 
						|
	status gStatus
 | 
						|
}
 | 
						|
 | 
						|
const (
 | 
						|
	gDead gStatus = iota
 | 
						|
	gRunnable
 | 
						|
	gRunning
 | 
						|
	gWaiting
 | 
						|
 | 
						|
	unordered = ^uint64(0)
 | 
						|
	garbage   = ^uint64(0) - 1
 | 
						|
	noseq     = ^uint64(0)
 | 
						|
	seqinc    = ^uint64(0) - 1
 | 
						|
)
 | 
						|
 | 
						|
// order1007 merges a set of per-P event batches into a single, consistent stream.
 | 
						|
// The high level idea is as follows. Events within an individual batch are in
 | 
						|
// correct order, because they are emitted by a single P. So we need to produce
 | 
						|
// a correct interleaving of the batches. To do this we take first unmerged event
 | 
						|
// from each batch (frontier). Then choose subset that is "ready" to be merged,
 | 
						|
// that is, events for which all dependencies are already merged. Then we choose
 | 
						|
// event with the lowest timestamp from the subset, merge it and repeat.
 | 
						|
// This approach ensures that we form a consistent stream even if timestamps are
 | 
						|
// incorrect (condition observed on some machines).
 | 
						|
func order1007(m map[int][]*Event) (events []*Event, err error) {
 | 
						|
	pending := 0
 | 
						|
	var batches []*eventBatch
 | 
						|
	for _, v := range m {
 | 
						|
		pending += len(v)
 | 
						|
		batches = append(batches, &eventBatch{v, false})
 | 
						|
	}
 | 
						|
	gs := make(map[uint64]gState)
 | 
						|
	var frontier []orderEvent
 | 
						|
	for ; pending != 0; pending-- {
 | 
						|
		for i, b := range batches {
 | 
						|
			if b.selected || len(b.events) == 0 {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			ev := b.events[0]
 | 
						|
			g, init, next := stateTransition(ev)
 | 
						|
			if !transitionReady(g, gs[g], init) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			frontier = append(frontier, orderEvent{ev, i, g, init, next})
 | 
						|
			b.events = b.events[1:]
 | 
						|
			b.selected = true
 | 
						|
			// Get rid of "Local" events, they are intended merely for ordering.
 | 
						|
			switch ev.Type {
 | 
						|
			case EvGoStartLocal:
 | 
						|
				ev.Type = EvGoStart
 | 
						|
			case EvGoUnblockLocal:
 | 
						|
				ev.Type = EvGoUnblock
 | 
						|
			case EvGoSysExitLocal:
 | 
						|
				ev.Type = EvGoSysExit
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if len(frontier) == 0 {
 | 
						|
			return nil, fmt.Errorf("no consistent ordering of events possible")
 | 
						|
		}
 | 
						|
		sort.Sort(orderEventList(frontier))
 | 
						|
		f := frontier[0]
 | 
						|
		frontier[0] = frontier[len(frontier)-1]
 | 
						|
		frontier = frontier[:len(frontier)-1]
 | 
						|
		events = append(events, f.ev)
 | 
						|
		transition(gs, f.g, f.init, f.next)
 | 
						|
		if !batches[f.batch].selected {
 | 
						|
			panic("frontier batch is not selected")
 | 
						|
		}
 | 
						|
		batches[f.batch].selected = false
 | 
						|
	}
 | 
						|
 | 
						|
	// At this point we have a consistent stream of events.
 | 
						|
	// Make sure time stamps respect the ordering.
 | 
						|
	// The tests will skip (not fail) the test case if they see this error.
 | 
						|
	if !sort.IsSorted(eventList(events)) {
 | 
						|
		return nil, ErrTimeOrder
 | 
						|
	}
 | 
						|
 | 
						|
	// The last part is giving correct timestamps to EvGoSysExit events.
 | 
						|
	// The problem with EvGoSysExit is that actual syscall exit timestamp (ev.Args[2])
 | 
						|
	// is potentially acquired long before event emission. So far we've used
 | 
						|
	// timestamp of event emission (ev.Ts).
 | 
						|
	// We could not set ev.Ts = ev.Args[2] earlier, because it would produce
 | 
						|
	// seemingly broken timestamps (misplaced event).
 | 
						|
	// We also can't simply update the timestamp and resort events, because
 | 
						|
	// if timestamps are broken we will misplace the event and later report
 | 
						|
	// logically broken trace (instead of reporting broken timestamps).
 | 
						|
	lastSysBlock := make(map[uint64]int64)
 | 
						|
	for _, ev := range events {
 | 
						|
		switch ev.Type {
 | 
						|
		case EvGoSysBlock, EvGoInSyscall:
 | 
						|
			lastSysBlock[ev.G] = ev.Ts
 | 
						|
		case EvGoSysExit:
 | 
						|
			ts := int64(ev.Args[2])
 | 
						|
			if ts == 0 {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			block := lastSysBlock[ev.G]
 | 
						|
			if block == 0 {
 | 
						|
				return nil, fmt.Errorf("stray syscall exit")
 | 
						|
			}
 | 
						|
			if ts < block {
 | 
						|
				return nil, ErrTimeOrder
 | 
						|
			}
 | 
						|
			ev.Ts = ts
 | 
						|
		}
 | 
						|
	}
 | 
						|
	sort.Stable(eventList(events))
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// stateTransition returns goroutine state (sequence and status) when the event
 | 
						|
// becomes ready for merging (init) and the goroutine state after the event (next).
 | 
						|
func stateTransition(ev *Event) (g uint64, init, next gState) {
 | 
						|
	switch ev.Type {
 | 
						|
	case EvGoCreate:
 | 
						|
		g = ev.Args[0]
 | 
						|
		init = gState{0, gDead}
 | 
						|
		next = gState{1, gRunnable}
 | 
						|
	case EvGoWaiting, EvGoInSyscall:
 | 
						|
		g = ev.G
 | 
						|
		init = gState{1, gRunnable}
 | 
						|
		next = gState{2, gWaiting}
 | 
						|
	case EvGoStart:
 | 
						|
		g = ev.G
 | 
						|
		init = gState{ev.Args[1], gRunnable}
 | 
						|
		next = gState{ev.Args[1] + 1, gRunning}
 | 
						|
	case EvGoStartLocal:
 | 
						|
		// noseq means that this event is ready for merging as soon as
 | 
						|
		// frontier reaches it (EvGoStartLocal is emitted on the same P
 | 
						|
		// as the corresponding EvGoCreate/EvGoUnblock, and thus the latter
 | 
						|
		// is already merged).
 | 
						|
		// seqinc is a stub for cases when event increments g sequence,
 | 
						|
		// but since we don't know current seq we also don't know next seq.
 | 
						|
		g = ev.G
 | 
						|
		init = gState{noseq, gRunnable}
 | 
						|
		next = gState{seqinc, gRunning}
 | 
						|
	case EvGoBlock, EvGoBlockSend, EvGoBlockRecv, EvGoBlockSelect,
 | 
						|
		EvGoBlockSync, EvGoBlockCond, EvGoBlockNet, EvGoSleep, EvGoSysBlock:
 | 
						|
		g = ev.G
 | 
						|
		init = gState{noseq, gRunning}
 | 
						|
		next = gState{noseq, gWaiting}
 | 
						|
	case EvGoSched, EvGoPreempt:
 | 
						|
		g = ev.G
 | 
						|
		init = gState{noseq, gRunning}
 | 
						|
		next = gState{noseq, gRunnable}
 | 
						|
	case EvGoUnblock, EvGoSysExit:
 | 
						|
		g = ev.Args[0]
 | 
						|
		init = gState{ev.Args[1], gWaiting}
 | 
						|
		next = gState{ev.Args[1] + 1, gRunnable}
 | 
						|
	case EvGoUnblockLocal, EvGoSysExitLocal:
 | 
						|
		g = ev.Args[0]
 | 
						|
		init = gState{noseq, gWaiting}
 | 
						|
		next = gState{seqinc, gRunnable}
 | 
						|
	case EvGCStart:
 | 
						|
		g = garbage
 | 
						|
		init = gState{ev.Args[0], gDead}
 | 
						|
		next = gState{ev.Args[0] + 1, gDead}
 | 
						|
	default:
 | 
						|
		// no ordering requirements
 | 
						|
		g = unordered
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func transitionReady(g uint64, curr, init gState) bool {
 | 
						|
	return g == unordered || (init.seq == noseq || init.seq == curr.seq) && init.status == curr.status
 | 
						|
}
 | 
						|
 | 
						|
func transition(gs map[uint64]gState, g uint64, init, next gState) {
 | 
						|
	if g == unordered {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	curr := gs[g]
 | 
						|
	if !transitionReady(g, curr, init) {
 | 
						|
		panic("event sequences are broken")
 | 
						|
	}
 | 
						|
	switch next.seq {
 | 
						|
	case noseq:
 | 
						|
		next.seq = curr.seq
 | 
						|
	case seqinc:
 | 
						|
		next.seq = curr.seq + 1
 | 
						|
	}
 | 
						|
	gs[g] = next
 | 
						|
}
 | 
						|
 | 
						|
// order1005 merges a set of per-P event batches into a single, consistent stream.
 | 
						|
func order1005(m map[int][]*Event) (events []*Event, err error) {
 | 
						|
	for _, batch := range m {
 | 
						|
		events = append(events, batch...)
 | 
						|
	}
 | 
						|
	for _, ev := range events {
 | 
						|
		if ev.Type == EvGoSysExit {
 | 
						|
			// EvGoSysExit emission is delayed until the thread has a P.
 | 
						|
			// Give it the real sequence number and time stamp.
 | 
						|
			ev.seq = int64(ev.Args[1])
 | 
						|
			if ev.Args[2] != 0 {
 | 
						|
				ev.Ts = int64(ev.Args[2])
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	sort.Sort(eventSeqList(events))
 | 
						|
	if !sort.IsSorted(eventList(events)) {
 | 
						|
		return nil, ErrTimeOrder
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
type orderEventList []orderEvent
 | 
						|
 | 
						|
func (l orderEventList) Len() int {
 | 
						|
	return len(l)
 | 
						|
}
 | 
						|
 | 
						|
func (l orderEventList) Less(i, j int) bool {
 | 
						|
	return l[i].ev.Ts < l[j].ev.Ts
 | 
						|
}
 | 
						|
 | 
						|
func (l orderEventList) Swap(i, j int) {
 | 
						|
	l[i], l[j] = l[j], l[i]
 | 
						|
}
 | 
						|
 | 
						|
type eventList []*Event
 | 
						|
 | 
						|
func (l eventList) Len() int {
 | 
						|
	return len(l)
 | 
						|
}
 | 
						|
 | 
						|
func (l eventList) Less(i, j int) bool {
 | 
						|
	return l[i].Ts < l[j].Ts
 | 
						|
}
 | 
						|
 | 
						|
func (l eventList) Swap(i, j int) {
 | 
						|
	l[i], l[j] = l[j], l[i]
 | 
						|
}
 | 
						|
 | 
						|
type eventSeqList []*Event
 | 
						|
 | 
						|
func (l eventSeqList) Len() int {
 | 
						|
	return len(l)
 | 
						|
}
 | 
						|
 | 
						|
func (l eventSeqList) Less(i, j int) bool {
 | 
						|
	return l[i].seq < l[j].seq
 | 
						|
}
 | 
						|
 | 
						|
func (l eventSeqList) Swap(i, j int) {
 | 
						|
	l[i], l[j] = l[j], l[i]
 | 
						|
}
 |