event: use Source instead of just namespace in event

Also add tests for the Source capture functionality.

Change-Id: I0ba8c3f31c2d30247c60b49baec74a65f5f50739
Reviewed-on: https://go-review.googlesource.com/c/exp/+/329795
Trust: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/event/adapter/logfmt/logfmt.go b/event/adapter/logfmt/logfmt.go
index 32eadbe..c3eca4f 100644
--- a/event/adapter/logfmt/logfmt.go
+++ b/event/adapter/logfmt/logfmt.go
@@ -54,8 +54,14 @@
 		io.WriteString(w, `"`)
 	}
 
-	if !p.SuppressNamespace && ev.Namespace != "" {
-		p.Label(w, event.String("in", ev.Namespace))
+	if !p.SuppressNamespace && ev.Source.Space != "" {
+		p.Label(w, event.String("in", ev.Source.Space))
+	}
+	if ev.Source.Owner != "" {
+		p.Label(w, event.String("owner", ev.Source.Owner))
+	}
+	if ev.Source.Name != "" {
+		p.Label(w, event.String("name", ev.Source.Name))
 	}
 
 	if ev.Parent != 0 {
@@ -70,10 +76,10 @@
 		p.Label(w, l)
 	}
 
-	if ev.TraceID != 0 {
+	if ev.ID != 0 {
 		p.separator(w)
 		io.WriteString(w, `trace=`)
-		w.Write(strconv.AppendUint(p.buf[:0], ev.TraceID, 10))
+		w.Write(strconv.AppendUint(p.buf[:0], ev.ID, 10))
 	}
 
 	if ev.Kind == event.EndKind {
diff --git a/event/adapter/logfmt/logfmt_test.go b/event/adapter/logfmt/logfmt_test.go
index 072caaa..0fdb62b 100644
--- a/event/adapter/logfmt/logfmt_test.go
+++ b/event/adapter/logfmt/logfmt_test.go
@@ -28,7 +28,7 @@
 		expect: ``,
 	}, {
 		name:   "span",
-		event:  event.Event{TraceID: 34},
+		event:  event.Event{ID: 34},
 		expect: `trace=34`,
 	}, {
 		name:   "parent",
@@ -36,7 +36,7 @@
 		expect: `parent=14`,
 	}, {
 		name:   "namespace",
-		event:  event.Event{Namespace: "golang.org/x/exp/event"},
+		event:  event.Event{Source: event.Source{Space: "golang.org/x/exp/event"}},
 		expect: `in=golang.org/x/exp/event`,
 	}, {
 		name:   "at",
@@ -174,8 +174,8 @@
 		name:    "suppress namespace",
 		printer: logfmt.Printer{SuppressNamespace: true},
 		event: event.Event{
-			Namespace: "golang.org/x/exp/event",
-			Labels:    []event.Label{event.String("msg", "some text")},
+			Source: event.Source{Space: "golang.org/x/exp/event"},
+			Labels: []event.Label{event.String("msg", "some text")},
 		},
 		before: `in=golang.org/x/exp/event msg="some text"`,
 		after:  `msg="some text"`,
diff --git a/event/common.go b/event/common.go
index 529a79e..68c0008 100644
--- a/event/common.go
+++ b/event/common.go
@@ -62,9 +62,9 @@
 	if ev != nil {
 		ev.Labels = append(ev.Labels, String("name", name))
 		ev.Labels = append(ev.Labels, labels...)
-		ev.TraceID = atomic.AddUint64(&ev.target.exporter.lastEvent, 1)
+		ev.ID = atomic.AddUint64(&ev.target.exporter.lastEvent, 1)
 		ev.target.exporter.prepare(ev)
-		ev.ctx = newContext(ev.ctx, ev.target.exporter, ev.TraceID, ev.At)
+		ev.ctx = newContext(ev.ctx, ev.target.exporter, ev.ID, ev.At)
 		ctx = ev.Deliver()
 	}
 	return ctx
diff --git a/event/event.go b/event/event.go
index 485e706..bfba077 100644
--- a/event/event.go
+++ b/event/event.go
@@ -13,12 +13,12 @@
 // Event holds the information about an event that occurred.
 // It combines the event metadata with the user supplied labels.
 type Event struct {
-	TraceID   uint64
-	Parent    uint64    // id of the parent event for this event
-	Namespace string    // namespace of event; if empty, set by exporter to import path
-	At        time.Time // time at which the event is delivered to the exporter.
-	Kind      Kind
-	Labels    []Label
+	ID     uint64
+	Parent uint64    // id of the parent event for this event
+	Source Source    // source of event; if empty, set by exporter to import path
+	At     time.Time // time at which the event is delivered to the exporter.
+	Kind   Kind
+	Labels []Label
 
 	ctx    context.Context
 	target *target
diff --git a/event/export.go b/event/export.go
index abfe673..9307c44 100644
--- a/event/export.go
+++ b/event/export.go
@@ -101,8 +101,8 @@
 	if e.opts.Now != nil && ev.At.IsZero() {
 		ev.At = e.opts.Now()
 	}
-	if e.opts.EnableNamespaces && ev.Namespace == "" {
-		ev.Namespace = e.sources.scanStack().Space
+	if e.opts.EnableNamespaces && ev.Source.Space == "" {
+		ev.Source = e.sources.scanStack()
 	}
 }
 
diff --git a/event/namespace_test.go b/event/namespace_test.go
deleted file mode 100644
index abab523..0000000
--- a/event/namespace_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2021 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 event_test
-
-import (
-	"context"
-	"runtime"
-	"testing"
-
-	"golang.org/x/exp/event"
-)
-
-const thisImportPath = "golang.org/x/exp/event_test"
-
-func TestNamespace(t *testing.T) {
-	var h nsHandler
-	ctx := event.WithExporter(context.Background(), event.NewExporter(&h, &event.ExporterOptions{EnableNamespaces: true}))
-	event.Log(ctx, "msg")
-	if got, want := h.ns, thisImportPath; got != want {
-		t.Errorf("got namespace %q, want, %q", got, want)
-	}
-}
-
-type nsHandler struct {
-	ns string
-}
-
-func (h *nsHandler) Event(ctx context.Context, ev *event.Event) context.Context {
-	h.ns = ev.Namespace
-	return ctx
-}
-
-func BenchmarkRuntimeCallers(b *testing.B) {
-	for i := 0; i < b.N; i++ {
-		var pcs [1]uintptr
-		_ = runtime.Callers(2, pcs[:])
-	}
-}
-
-func BenchmarkCallersFrames(b *testing.B) {
-	var pcs [1]uintptr
-	n := runtime.Callers(2, pcs[:])
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		frames := runtime.CallersFrames(pcs[:n])
-		frame, _ := frames.Next()
-		_ = frame.Function //namespace(frame.Function)
-	}
-}
-
-func TestStablePCs(t *testing.T) {
-	// The pc is stable regardless of the call stack.
-	pc1 := f()
-	pc2 := g()
-	if pc1 != pc2 {
-		t.Fatal("pcs differ")
-	}
-	// We can recover frame information after the function has returned.
-	frames := runtime.CallersFrames([]uintptr{pc1})
-	frame, _ := frames.Next()
-	want := thisImportPath + ".h"
-	if got := frame.Function; got != want {
-		t.Errorf("got %q, want %q", got, want)
-	}
-}
-
-func f() uintptr {
-	return h()
-}
-
-func g() uintptr {
-	return h()
-}
-
-func h() uintptr {
-	var pcs [1]uintptr
-	runtime.Callers(1, pcs[:])
-	return pcs[0]
-}
diff --git a/event/source.go b/event/source.go
index 183e336..efe3c5d 100644
--- a/event/source.go
+++ b/event/source.go
@@ -200,8 +200,8 @@
 		entry.Space = full[:slash+dot]
 		entry.Name = full[slash+dot+1:]
 		if dot = strings.LastIndexByte(entry.Name, '.'); dot >= 0 {
-			entry.Owner = entry.Name[dot+1:]
-			entry.Name = entry.Name[:dot]
+			entry.Owner = entry.Name[:dot]
+			entry.Name = entry.Name[dot+1:]
 		}
 	}
 	return entry
diff --git a/event/source_test.go b/event/source_test.go
new file mode 100644
index 0000000..603f23c
--- /dev/null
+++ b/event/source_test.go
@@ -0,0 +1,86 @@
+// Copyright 2021 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 event_test
+
+import (
+	"context"
+	"testing"
+
+	"golang.org/x/exp/event"
+	"golang.org/x/exp/event/eventtest"
+)
+
+const thisImportPath = "golang.org/x/exp/event_test"
+
+func TestNamespace(t *testing.T) {
+	event.RegisterHelper(testHelperB)
+	event.RegisterHelper(thisImportPath + ".testHelperC")
+	h := &eventtest.CaptureHandler{}
+	opt := eventtest.ExporterOptions()
+	opt.EnableNamespaces = true
+	ctx := event.WithExporter(context.Background(), event.NewExporter(h, opt))
+	for _, test := range []struct {
+		name   string
+		do     func(context.Context)
+		expect event.Source
+	}{{
+		name:   "simple",
+		do:     testA,
+		expect: event.Source{Space: thisImportPath, Name: "testA"},
+	}, {
+		name:   "pointer helper",
+		do:     testB,
+		expect: event.Source{Space: thisImportPath, Name: "testB"},
+	}, {
+		name:   "named helper",
+		do:     testC,
+		expect: event.Source{Space: thisImportPath, Name: "testC"},
+	}, {
+		name:   "method",
+		do:     testD,
+		expect: event.Source{Space: thisImportPath, Owner: "tester", Name: "D"},
+	}} {
+		t.Run(test.name, func(t *testing.T) {
+			h.Got = h.Got[:0]
+			test.do(ctx)
+			if len(h.Got) != 1 {
+				t.Fatalf("Expected 1 event, got %v", len(h.Got))
+			}
+			got := h.Got[0].Source
+			if got.Space != test.expect.Space {
+				t.Errorf("got namespace %q, want, %q", got.Space, test.expect.Space)
+			}
+			if got.Owner != test.expect.Owner {
+				t.Errorf("got owner %q, want, %q", got.Owner, test.expect.Owner)
+			}
+			if got.Name != test.expect.Name {
+				t.Errorf("got name %q, want, %q", got.Name, test.expect.Name)
+			}
+		})
+	}
+}
+
+type tester struct{}
+
+//go:noinline
+func testA(ctx context.Context) { event.Log(ctx, "test A") }
+
+//go:noinline
+func testB(ctx context.Context) { testHelperB(ctx) }
+
+//go:noinline
+func testHelperB(ctx context.Context) { event.Log(ctx, "test B") }
+
+//go:noinline
+func testC(ctx context.Context) { testHelperC(ctx) }
+
+//go:noinline
+func testHelperC(ctx context.Context) { event.Log(ctx, "test C") }
+
+//go:noinline
+func testD(ctx context.Context) { tester{}.D(ctx) }
+
+//go:noinline
+func (tester) D(ctx context.Context) { event.Log(ctx, "test D") }