Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: runtime/trace: add Helper() to hide helpers from callers stack for region API #60887

Open
dolmen opened this issue Jun 20, 2023 · 3 comments
Labels
Milestone

Comments

@dolmen
Copy link
Contributor

dolmen commented Jun 20, 2023

Add an Helper() function to allow clients of the runtime/trace region API to create helpers but hide them from the stack traces.
The API would be similar to testing.Helper().

Use case

The region API (trace.WithRegion, trace.StartRegion) is quite intrusive: to enhance existing code with regions, one must wrap the existing block in a func(). It is even more verbose if the existing block returned values to use after the block (ex: error).

To limit the verbosity, I used generics to write wrappers around the trace.StartRegion API (see below).

Unfortunately, with Go 1.20 the go tool trace output shows the helper function as the encapsulated code instead of the real user's code. This makes the tracing annotations much less useful.

It would be helpful to have a way to declare a caller of the trace.StartRegion as an helper. The testing package has Helper() which would be a good inspiration (but a different API might be needed as runtime/trace performance is critical).

Example helpers

Example wrappers in user code that would be declared as helpers:

// traceWithRegionErr wraps [trace.WithRegion] to ease handling of failure cases.
func traceWithRegionErr(ctx context.Context, regionType string, fn func() error) error {
	defer trace.StartRegion(ctx, regionType).End()
	err := fn()
	if err != nil {
		return fmt.Errorf("%s: %w", regionType, err)
	}
	return nil
}

// traceWithRegion1Err1 wraps [trace.WithRegion] to ease defining a region for a function
// with one input parameter and returning a value and an error.
func traceWithRegion1Err1[I1 any, O1 any](ctx context.Context, regionType string, fn func(I1) (O1, error)) func(I1) (O1, error) {
	return func(i1 I1) (O1, error) {
		defer trace.StartRegion(ctx, regionType).End()
		o1, err := fn(i1)
		if err != nil {
			return o1, fmt.Errorf("%s: %w", regionType, err)
		}
		return o1, nil
	}
}

// traceWithRegionCtx1Err1 wraps [trace.WithRegion] to ease defining a region for a function
// with a context, another input parameter and returning a value and an error.
func traceWithRegionCtx1Err1[I1 any, O1 any](ctx context.Context, regionType string, fn func(context.Context, I1) (O1, error)) func(I1) (O1, error) {
	return func(i1 I1) (O1, error) {
		defer trace.StartRegion(ctx, regionType).End()
		o1, err := fn(ctx, i1)
		if err != nil {
			return o1, fmt.Errorf("%s: %w", regionType, err)
		}
		return o1, nil
	}
}

// traceWithRegionCtx31 wraps [trace.WithRegion] to ease defining a region for a function
// with a context, 3 other input parameters and returning one value.
func traceWithRegionCtx31[I1 any, I2 any, I3 any, O1 any](ctx context.Context, regionType string, fn func(context.Context, I1, I2, I3) O1) func(I1, I2, I3) O1 {
	return func(i1 I1, i2 I2, i3 I3) O1 {
		defer trace.StartRegion(ctx, regionType).End()
		return fn(ctx, i1, i2, i3)
	}
}
@gopherbot gopherbot added this to the Proposal milestone Jun 20, 2023
@dolmen
Copy link
Contributor Author

dolmen commented Jun 20, 2023

Cc: @mknyszek

@earthboundkid
Copy link
Contributor

What about Helper(int) to specify how many levels to skip?

@mknyszek
Copy link
Contributor

To keep traceback costs (the dominating cost) low, Helper would probably be implemented as a trace event that marks a function by name, and the filtering could be applied lazily in the trace parser.

But I also wonder if we could just do something to make the runtime/trace region API more ergonomic to begin with. I'm not certain generics are powerful enough to cover every case. One possible approach could be to annotate a function (e.g. //go:traceRegion <regionType>) and have the compiler generate a wrapper (which is a fairly straightforward transformation) assuming the first argument is a context.Context. Though, I'm not sure we'd want to add more special annotations of this form.

Just thinking out loud; this needs more thought.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

4 participants