mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-25 01:44:43 +01:00
10a14a8c50
package trace: - introduce the concept of tasks and spans, tracked as linked list within ctx - see package-level docs for an overview of the concepts - **main feature 1**: unique stack of task and span IDs - makes it easy to follow a series of log entries in concurrent code - **main feature 2**: ability to produce a chrome://tracing-compatible trace file - either via an env variable or a `zrepl pprof` subcommand - this is not a CPU profile, we already have go pprof for that - but it is very useful to visually inspect where the replication / snapshotter / pruner spends its time ( fixes #307 ) usage in package daemon/logging: - goal: every log entry should have a trace field with the ID stack from package trace - make `logging.GetLogger(ctx, Subsys)` the authoritative `logger.Logger` factory function - the context carries a linked list of injected fields which `logging.GetLogger` adds to the logger it returns - `logging.GetLogger` also uses package `trace` to get the task-and-span-stack and injects it into the returned logger's fields
206 lines
5.6 KiB
Go
206 lines
5.6 KiB
Go
package trace
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/gitchander/permutation"
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestRegularSpanUsage(t *testing.T) {
|
|
root, endRoot := WithTask(context.Background(), "root")
|
|
defer endRoot()
|
|
|
|
s1, endS1 := WithSpan(root, "parent")
|
|
s2, endS2 := WithSpan(s1, "child")
|
|
_, endS3 := WithSpan(s2, "grand-child")
|
|
require.NotPanics(t, func() { endS3() })
|
|
require.NotPanics(t, func() { endS2() })
|
|
|
|
// reuse
|
|
_, endS4 := WithSpan(s1, "child-2")
|
|
require.NotPanics(t, func() { endS4() })
|
|
|
|
// close parent
|
|
require.NotPanics(t, func() { endS1() })
|
|
}
|
|
|
|
func TestMultipleActiveChildSpansNotAllowed(t *testing.T) {
|
|
root, endRoot := WithTask(context.Background(), "root")
|
|
defer endRoot()
|
|
|
|
s1, _ := WithSpan(root, "s1")
|
|
_, endS2 := WithSpan(s1, "s1-child1")
|
|
|
|
require.PanicsWithValue(t, ErrAlreadyActiveChildSpan, func() {
|
|
_, _ = WithSpan(s1, "s1-child2")
|
|
})
|
|
|
|
endS2()
|
|
|
|
require.NotPanics(t, func() {
|
|
_, _ = WithSpan(s1, "s1-child2")
|
|
})
|
|
}
|
|
|
|
func TestForkingChildSpansNotAllowed(t *testing.T) {
|
|
root, endRoot := WithTask(context.Background(), "root")
|
|
defer endRoot()
|
|
|
|
s1, _ := WithSpan(root, "s1")
|
|
sc, endSC := WithSpan(s1, "s1-child")
|
|
_, _ = WithSpan(sc, "s1-child-child")
|
|
|
|
require.PanicsWithValue(t, ErrSpanStillHasActiveChildSpan, func() {
|
|
endSC()
|
|
})
|
|
}
|
|
|
|
func TestRegularTaskUsage(t *testing.T) {
|
|
// assert concurrent activities on different tasks can end in any order
|
|
closeOrder := []int{0, 1, 2}
|
|
closeOrders := permutation.New(permutation.IntSlice(closeOrder))
|
|
for closeOrders.Next() {
|
|
t.Run(fmt.Sprintf("%v", closeOrder), func(t *testing.T) {
|
|
root, endRoot := WithTask(context.Background(), "root")
|
|
defer endRoot()
|
|
|
|
c1, endC1 := WithTask(root, "c1")
|
|
defer endC1()
|
|
c2, endC2 := WithTask(root, "c2")
|
|
defer endC2()
|
|
|
|
// begin 3 concurrent activities
|
|
_, endAR := WithSpan(root, "aR")
|
|
_, endAC1 := WithSpan(c1, "aC1")
|
|
_, endAC2 := WithSpan(c2, "aC2")
|
|
|
|
endFuncs := []DoneFunc{endAR, endAC1, endAC2}
|
|
for _, i := range closeOrder {
|
|
require.NotPanics(t, func() {
|
|
endFuncs[i]()
|
|
}, "%v", i)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTaskEndWithActiveChildTaskNotAllowed(t *testing.T) {
|
|
root, _ := WithTask(context.Background(), "root")
|
|
c, endC := WithTask(root, "child")
|
|
_, _ = WithTask(c, "grand-child")
|
|
func() {
|
|
defer func() {
|
|
r := recover()
|
|
require.NotNil(t, r)
|
|
err, ok := r.(error)
|
|
require.True(t, ok)
|
|
require.Equal(t, ErrTaskStillHasActiveChildTasks, errors.Cause(err))
|
|
}()
|
|
endC()
|
|
}()
|
|
|
|
}
|
|
|
|
func TestIdempotentEndTask(t *testing.T) {
|
|
_, end := WithTask(context.Background(), "root")
|
|
end()
|
|
require.NotPanics(t, func() { end() })
|
|
}
|
|
|
|
func TestSpansPanicIfNoParentTask(t *testing.T) {
|
|
require.Panics(t, func() { WithSpan(context.Background(), "taskless-span") })
|
|
}
|
|
|
|
func TestIdempotentEndSpan(t *testing.T) {
|
|
root, _ := WithTask(context.Background(), "root")
|
|
_, end := WithSpan(root, "span")
|
|
end()
|
|
require.NotPanics(t, func() { end() })
|
|
}
|
|
|
|
func logAndGetTraceNode(t *testing.T, descr string, ctx context.Context) *traceNode {
|
|
n, ok := ctx.Value(contextKeyTraceNode).(*traceNode)
|
|
require.True(t, ok)
|
|
t.Logf("% 20s %p %#v", descr, n, n)
|
|
return n
|
|
}
|
|
|
|
func TestWhiteboxHierachy(t *testing.T) {
|
|
root, e1 := WithTask(context.Background(), "root")
|
|
rootN := logAndGetTraceNode(t, "root", root)
|
|
assert.Nil(t, rootN.parentTask)
|
|
assert.Nil(t, rootN.parentSpan)
|
|
|
|
child, e2 := WithSpan(root, "child")
|
|
childN := logAndGetTraceNode(t, "child", child)
|
|
assert.Equal(t, rootN, childN.parentTask)
|
|
assert.Equal(t, rootN, childN.parentSpan)
|
|
|
|
grandchild, e3 := WithSpan(child, "grandchild")
|
|
grandchildN := logAndGetTraceNode(t, "grandchild", grandchild)
|
|
assert.Equal(t, rootN, grandchildN.parentTask)
|
|
assert.Equal(t, childN, grandchildN.parentSpan)
|
|
|
|
gcTask, e4 := WithTask(grandchild, "grandchild-task")
|
|
gcTaskN := logAndGetTraceNode(t, "grandchild-task", gcTask)
|
|
assert.Equal(t, rootN, gcTaskN.parentTask)
|
|
assert.Nil(t, gcTaskN.parentSpan)
|
|
|
|
// it is allowed that a child task outlives the _span_ in which it was created
|
|
// (albeit not its parent task)
|
|
e3()
|
|
e2()
|
|
gcTaskSpan, e5 := WithSpan(gcTask, "granschild-task-span")
|
|
gcTaskSpanN := logAndGetTraceNode(t, "granschild-task-span", gcTaskSpan)
|
|
assert.Equal(t, gcTaskN, gcTaskSpanN.parentTask)
|
|
assert.Equal(t, gcTaskN, gcTaskSpanN.parentSpan)
|
|
e5()
|
|
|
|
e4()
|
|
e1()
|
|
}
|
|
|
|
func TestOrphanTasksBecomeNewRootWhenAllAncestorsAreDead(t *testing.T) {
|
|
parent, e1 := WithTask(context.Background(), "parent")
|
|
_ = logAndGetTraceNode(t, "parent-task", parent)
|
|
e1()
|
|
|
|
var child context.Context
|
|
var e2 DoneFunc
|
|
require.NotPanics(t, func() {
|
|
child, e2 = WithTask(parent, "child")
|
|
})
|
|
childN := logAndGetTraceNode(t, "child-task", child)
|
|
assert.Nil(t, childN.parentTask)
|
|
|
|
grandchild, _ := WithTask(child, "grandchild")
|
|
grandchildN := logAndGetTraceNode(t, "grandchild-task", grandchild)
|
|
assert.Equal(t, childN, grandchildN.parentTask)
|
|
|
|
require.Panics(t, func() { e2() }, "if the parent was alive at creation of child task, wrong termination order remains a panicable offense though")
|
|
}
|
|
|
|
func TestOrphanTaskBecomesChildToNearestLiveAncestor(t *testing.T) {
|
|
parent, e1 := WithTask(context.Background(), "parent")
|
|
parentN := logAndGetTraceNode(t, "parent-task", parent)
|
|
|
|
child, e2 := WithTask(parent, "child")
|
|
childN := logAndGetTraceNode(t, "child-task", child)
|
|
assert.Equal(t, parentN, childN.parentTask)
|
|
e2()
|
|
|
|
grandchild, e3 := WithTask(child, "grandchild")
|
|
grandchildN := logAndGetTraceNode(t, "grandchild-task", grandchild)
|
|
assert.Equal(t, parentN, grandchildN.parentTask) // child is already dead
|
|
|
|
require.NotPanics(t, func() {
|
|
e3()
|
|
e1()
|
|
})
|
|
}
|