This commit introduces a new tracing library, that replaces golang.org/x/net/trace, and supports (amongts other thing) nested traces. This is a minimal change, future patches will make use of the new functionality.
256 lines
5.8 KiB
Go
256 lines
5.8 KiB
Go
package nettrace
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func getValues(t *testing.T, vs url.Values, code int) string {
|
|
t.Helper()
|
|
|
|
req := httptest.NewRequest("GET", "/debug/traces?"+vs.Encode(), nil)
|
|
w := httptest.NewRecorder()
|
|
RenderTraces(w, req)
|
|
|
|
resp := w.Result()
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
if resp.StatusCode != code {
|
|
t.Errorf("expected %d, got %v", code, resp)
|
|
}
|
|
|
|
return string(body)
|
|
}
|
|
|
|
type v struct {
|
|
fam, b, lat, trace, ref, all string
|
|
}
|
|
|
|
func getCode(t *testing.T, vs v, code int) string {
|
|
t.Helper()
|
|
|
|
u := url.Values{}
|
|
if vs.fam != "" {
|
|
u.Add("fam", vs.fam)
|
|
}
|
|
if vs.b != "" {
|
|
u.Add("b", vs.b)
|
|
}
|
|
if vs.lat != "" {
|
|
u.Add("lat", vs.lat)
|
|
}
|
|
if vs.trace != "" {
|
|
u.Add("trace", vs.trace)
|
|
}
|
|
if vs.ref != "" {
|
|
u.Add("ref", vs.ref)
|
|
}
|
|
if vs.all != "" {
|
|
u.Add("all", vs.all)
|
|
}
|
|
|
|
return getValues(t, u, code)
|
|
}
|
|
|
|
func get(t *testing.T, fam, b, lat, trace, ref, all string) string {
|
|
t.Helper()
|
|
return getCode(t, v{fam, b, lat, trace, ref, all}, 200)
|
|
}
|
|
|
|
func getErr(t *testing.T, fam, b, lat, trace, ref, all string, code int, err string) string {
|
|
t.Helper()
|
|
body := getCode(t, v{fam, b, lat, trace, ref, all}, code)
|
|
if !strings.Contains(body, err) {
|
|
t.Errorf("Body does not contain error message %q", err)
|
|
t.Logf("Body: %v", body)
|
|
}
|
|
|
|
return body
|
|
}
|
|
|
|
func checkContains(t *testing.T, body, s string) {
|
|
t.Helper()
|
|
if !strings.Contains(body, s) {
|
|
t.Errorf("Body does not contain %q", s)
|
|
t.Logf("Body: %v", body)
|
|
}
|
|
}
|
|
|
|
func TestHTTP(t *testing.T) {
|
|
tr := New("TestHTTP", "http")
|
|
tr.Printf("entry #1")
|
|
tr.Finish()
|
|
|
|
tr = New("TestHTTP", "http")
|
|
tr.Printf("entry #2")
|
|
tr.Finish()
|
|
|
|
tr = New("TestHTTP", "http")
|
|
tr.Errorf("entry #3 (error)")
|
|
tr.Finish()
|
|
|
|
tr = New("TestHTTP", "http")
|
|
tr.Printf("hola marola")
|
|
tr.Printf("entry #4")
|
|
// This one is active until the end.
|
|
defer tr.Finish()
|
|
|
|
// Get the plain index.
|
|
body := get(t, "", "", "", "", "", "")
|
|
checkContains(t, body, "TestHTTP")
|
|
|
|
// Get a specific family, but no bucket.
|
|
body = get(t, "TestHTTP", "", "", "", "", "")
|
|
checkContains(t, body, "TestHTTP")
|
|
|
|
// Get a family and active bucket.
|
|
body = get(t, "TestHTTP", "-1", "", "", "", "")
|
|
checkContains(t, body, "hola marola")
|
|
|
|
// Get a family and error bucket.
|
|
body = get(t, "TestHTTP", "-2", "", "", "", "")
|
|
checkContains(t, body, "entry #3 (error)")
|
|
|
|
// Get a family and first bucket.
|
|
body = get(t, "TestHTTP", "0", "", "", "", "")
|
|
checkContains(t, body, "entry #2")
|
|
|
|
// Latency view. There are 3 events because the 4th is active.
|
|
body = get(t, "TestHTTP", "", "lat", "", "", "")
|
|
checkContains(t, body, "Count: 3")
|
|
|
|
// Get a specific trace. No family given, since it shouldn't be needed (we
|
|
// take it from the id).
|
|
body = get(t, "", "", "", string(tr.(*trace).ID), "", "")
|
|
checkContains(t, body, "hola marola")
|
|
|
|
// Check the "all=true" views.
|
|
body = get(t, "TestHTTP", "0", "", "", "", "true")
|
|
checkContains(t, body, "entry #2")
|
|
checkContains(t, body, "?fam=TestHTTP&b=-2&all=true")
|
|
|
|
tr.Finish()
|
|
}
|
|
|
|
func TestHTTPLong(t *testing.T) {
|
|
// Test a long trace.
|
|
tr := New("TestHTTPLong", "verbose")
|
|
for i := 0; i < 1000; i++ {
|
|
tr.Printf("entry #%d", i)
|
|
}
|
|
tr.Finish()
|
|
get(t, "TestHTTPLong", "", "", string(tr.(*trace).ID), "", "")
|
|
}
|
|
|
|
func TestHTTPErrors(t *testing.T) {
|
|
tr := New("TestHTTPErrors", "http")
|
|
tr.Printf("entry #1")
|
|
tr.Finish()
|
|
|
|
// Unknown family.
|
|
getErr(t, "unkfamily", "", "", "", "", "",
|
|
404, "Unknown family")
|
|
|
|
// Invalid bucket.
|
|
getErr(t, "TestHTTPErrors", "abc", "", "", "", "",
|
|
400, "Invalid bucket")
|
|
getErr(t, "TestHTTPErrors", "-3", "", "", "", "",
|
|
400, "Invalid bucket")
|
|
getErr(t, "TestHTTPErrors", "9", "", "", "", "",
|
|
400, "Invalid bucket")
|
|
|
|
// Unknown trace id (malformed).
|
|
getErr(t, "TestHTTPErrors", "", "", "unktrace", "", "",
|
|
404, "Trace not found")
|
|
|
|
// Unknown trace id.
|
|
getErr(t, "TestHTTPErrors", "", "", string(tr.(*trace).ID)+"xxx", "", "",
|
|
404, "Trace not found")
|
|
|
|
// Check that the trace is actually there.
|
|
get(t, "", "", "", string(tr.(*trace).ID), "", "")
|
|
}
|
|
|
|
func TestHTTPUroboro(t *testing.T) {
|
|
trA := New("TestHTTPUroboro", "trA")
|
|
defer trA.Finish()
|
|
trA.Printf("this is trace A")
|
|
|
|
trB := New("TestHTTPUroboro", "trB")
|
|
defer trB.Finish()
|
|
trB.Printf("this is trace B")
|
|
|
|
trA.Link(trB, "B is my friend")
|
|
trB.Link(trA, "A is my friend")
|
|
|
|
// Check that we handle cross-linked events well.
|
|
get(t, "TestHTTPUroboro", "", "", "", "", "")
|
|
get(t, "TestHTTPUroboro", "-1", "", "", "", "")
|
|
get(t, "", "", "", string(trA.(*trace).ID), "", "")
|
|
get(t, "", "", "", string(trB.(*trace).ID), "", "")
|
|
}
|
|
|
|
func TestHTTPDeep(t *testing.T) {
|
|
tr := New("TestHTTPDeep", "level-0")
|
|
defer tr.Finish()
|
|
ts := []Trace{tr}
|
|
for i := 1; i <= 9; i++ {
|
|
tr = tr.NewChild("TestHTTPDeep", fmt.Sprintf("level-%d", i))
|
|
defer tr.Finish()
|
|
ts = append(ts, tr)
|
|
}
|
|
|
|
// Active view.
|
|
body := get(t, "TestHTTPDeep", "-1", "", "", "", "")
|
|
checkContains(t, body, "level-9")
|
|
|
|
// Recursive view.
|
|
body = get(t, "TestHTTPDeep", "", "", string(ts[0].(*trace).ID), "", "")
|
|
checkContains(t, body, "level-9")
|
|
}
|
|
|
|
func TestStripZeros(t *testing.T) {
|
|
cases := []struct {
|
|
d time.Duration
|
|
exp string
|
|
}{
|
|
{0 * time.Second, " . 0"},
|
|
{1 * time.Millisecond, " . 1000"},
|
|
{5 * time.Millisecond, " . 5000"},
|
|
{1 * time.Second, "1.000000"},
|
|
{1*time.Second + 8*time.Millisecond, "1.008000"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := stripZeros(c.d); got != c.exp {
|
|
t.Errorf("stripZeros(%s) got %q, expected %q",
|
|
c.d, got, c.exp)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRegisterHandler(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
RegisterHandler(mux)
|
|
|
|
req := httptest.NewRequest("GET", "/debug/traces", nil)
|
|
w := httptest.NewRecorder()
|
|
mux.ServeHTTP(w, req)
|
|
|
|
resp := w.Result()
|
|
|
|
if resp.StatusCode != 200 {
|
|
t.Errorf("expected 200, got %v", resp)
|
|
}
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
if !strings.Contains(string(body), "<h1>Traces</h1>") {
|
|
t.Errorf("unexpected body: %s", body)
|
|
}
|
|
}
|