Skip to content
quarry Artifact [6806c558f3]
Login

Artifact [6806c558f3]

Artifact 6806c558f39d4682ad2e86967e761ed77d4ad91c6381fc74892d5c5c3792336a:


package obs

import (
	"context"
	"strconv"
	"sync/atomic"
)

type ctxRequest struct{}

var requestSeq atomic.Uint64

// WithRequest tags a context with a fresh request id and returns both.
//
// The id is what lets a store call name the page that caused it. Before
// it, the subprocess panel and the render panel were two independent
// lists: "GET /search took 1132ms" and "zit artifact ran 499 times"
// could not be connected even when one caused the other, so the page
// could find a slow route and never say why.
//
// A counter rather than a UUID: this identifies a request WITHIN one
// process's memory for as long as the rings hold it, and nothing
// compares ids across processes. A cheap monotonic value is the whole
// requirement.
func WithRequest(ctx context.Context) (context.Context, string) {
	id := strconv.FormatUint(requestSeq.Add(1), 36)
	return context.WithValue(ctx, ctxRequest{}, id), id
}

// RequestID is the tag on this context, empty when there is none —
// which is the ordinary case for work with no request behind it: ingest
// rounds, warm passes, the doc indexer. Callers record the empty string
// rather than guessing, because "background" is a true answer and a
// wrong attribution is not.
func RequestID(ctx context.Context) string {
	if ctx == nil {
		return ""
	}
	id, _ := ctx.Value(ctxRequest{}).(string)
	return id
}

// ctxReason tags a context with WHY the work beneath it is happening,
// for calls that have no request behind them.
type ctxReason struct{}

// The reasons quarry names. A closed set on purpose: an operator scans
// this column, and a free-text field would grow one spelling per call
// site until the column could not be grouped.
const (
	ReasonSync     = "sync"      // pulling from an upstream mirror
	ReasonIngest   = "ingest"    // folding new artifacts into the index
	ReasonWarm     = "warm"      // building a cache before anybody asks
	ReasonDocIndex = "doc index" // the documentation sweep
	ReasonCI       = "ci"        // cloning or driving a CI run
)

// WithReason names the background activity a subprocess serves. The
// request path needs none of this — RequestID already says which render
// caused the call, and the route is a better answer than any word here.
// This is for the work that answers to nobody: without it every ingest
// round, warm pass and doc sweep records the same empty string and the
// panel can only say "background".
func WithReason(ctx context.Context, reason string) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, ctxReason{}, reason)
}

// Reason is why this context's work is happening, empty when a request
// is behind it or when nobody said.
func Reason(ctx context.Context) string {
	if ctx == nil {
		return ""
	}
	r, _ := ctx.Value(ctxReason{}).(string)
	return r
}

// ctxSurface tags a context with the door a request came in by.
type ctxSurface struct{}

// The surfaces an op can be called from. A closed set for the same reason
// the reasons are: the ops panels group and filter on it.
const (
	SurfaceWeb = "web" // a page or fragment the web handler served
	SurfaceAPI = "api" // /api/v1, whoever holds the token: an agent, a script, the runner
)

// WithSurface names the surface a request came in by, so the registry
// can say where a slow op was called from. Set once, by the outermost
// timer of each surface.
func WithSurface(ctx context.Context, surface string) context.Context {
	return context.WithValue(ctx, ctxSurface{}, surface)
}

// Surface is this context's surface, empty for work no request asked for.
func Surface(ctx context.Context) string {
	if ctx == nil {
		return ""
	}
	s, _ := ctx.Value(ctxSurface{}).(string)
	return s
}