| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- // Copyright 2025 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package gtprof
-
- import (
- "context"
- "fmt"
- "sync"
- "time"
-
- "code.gitea.io/gitea/modules/util"
- )
-
- type contextKey struct {
- name string
- }
-
- var contextKeySpan = &contextKey{"span"}
-
- type traceStarter interface {
- start(ctx context.Context, traceSpan *TraceSpan, internalSpanIdx int) (context.Context, traceSpanInternal)
- }
-
- type traceSpanInternal interface {
- addEvent(name string, cfg *EventConfig)
- recordError(err error, cfg *EventConfig)
- end()
- }
-
- type TraceSpan struct {
- // immutable
- parent *TraceSpan
- internalSpans []traceSpanInternal
- internalContexts []context.Context
-
- // mutable, must be protected by mutex
- mu sync.RWMutex
- name string
- statusCode uint32
- statusDesc string
- startTime time.Time
- endTime time.Time
- attributes []*TraceAttribute
- children []*TraceSpan
- }
-
- type TraceAttribute struct {
- Key string
- Value TraceValue
- }
-
- type TraceValue struct {
- v any
- }
-
- func (t *TraceValue) AsString() string {
- return fmt.Sprint(t.v)
- }
-
- func (t *TraceValue) AsInt64() int64 {
- v, _ := util.ToInt64(t.v)
- return v
- }
-
- func (t *TraceValue) AsFloat64() float64 {
- v, _ := util.ToFloat64(t.v)
- return v
- }
-
- var globalTraceStarters []traceStarter
-
- type Tracer struct {
- starters []traceStarter
- }
-
- func (s *TraceSpan) SetName(name string) {
- s.mu.Lock()
- defer s.mu.Unlock()
- s.name = name
- }
-
- func (s *TraceSpan) SetStatus(code uint32, desc string) {
- s.mu.Lock()
- defer s.mu.Unlock()
- s.statusCode, s.statusDesc = code, desc
- }
-
- func (s *TraceSpan) AddEvent(name string, options ...EventOption) {
- cfg := eventConfigFromOptions(options...)
- for _, tsp := range s.internalSpans {
- tsp.addEvent(name, cfg)
- }
- }
-
- func (s *TraceSpan) RecordError(err error, options ...EventOption) {
- cfg := eventConfigFromOptions(options...)
- for _, tsp := range s.internalSpans {
- tsp.recordError(err, cfg)
- }
- }
-
- func (s *TraceSpan) SetAttributeString(key, value string) *TraceSpan {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- s.attributes = append(s.attributes, &TraceAttribute{Key: key, Value: TraceValue{v: value}})
- return s
- }
-
- func (t *Tracer) Start(ctx context.Context, spanName string) (context.Context, *TraceSpan) {
- starters := t.starters
- if starters == nil {
- starters = globalTraceStarters
- }
- ts := &TraceSpan{name: spanName, startTime: time.Now()}
- parentSpan := GetContextSpan(ctx)
- if parentSpan != nil {
- parentSpan.mu.Lock()
- parentSpan.children = append(parentSpan.children, ts)
- parentSpan.mu.Unlock()
- ts.parent = parentSpan
- }
-
- parentCtx := ctx
- for internalSpanIdx, tsp := range starters {
- var internalSpan traceSpanInternal
- if parentSpan != nil {
- parentCtx = parentSpan.internalContexts[internalSpanIdx]
- }
- ctx, internalSpan = tsp.start(parentCtx, ts, internalSpanIdx)
- ts.internalContexts = append(ts.internalContexts, ctx)
- ts.internalSpans = append(ts.internalSpans, internalSpan)
- }
- ctx = context.WithValue(ctx, contextKeySpan, ts)
- return ctx, ts
- }
-
- type mutableContext interface {
- context.Context
- SetContextValue(key, value any)
- GetContextValue(key any) any
- }
-
- // StartInContext starts a trace span in Gitea's mutable context (usually the web request context).
- // Due to the design limitation of Gitea's web framework, it can't use `context.WithValue` to bind a new span into a new context.
- // So here we use our "reqctx" framework to achieve the same result: web request context could always see the latest "span".
- func (t *Tracer) StartInContext(ctx mutableContext, spanName string) (*TraceSpan, func()) {
- curTraceSpan := GetContextSpan(ctx)
- _, newTraceSpan := GetTracer().Start(ctx, spanName)
- ctx.SetContextValue(contextKeySpan, newTraceSpan)
- return newTraceSpan, func() {
- newTraceSpan.End()
- ctx.SetContextValue(contextKeySpan, curTraceSpan)
- }
- }
-
- func (s *TraceSpan) End() {
- s.mu.Lock()
- s.endTime = time.Now()
- s.mu.Unlock()
-
- for _, tsp := range s.internalSpans {
- tsp.end()
- }
- }
-
- func GetTracer() *Tracer {
- return &Tracer{}
- }
-
- func GetContextSpan(ctx context.Context) *TraceSpan {
- ts, _ := ctx.Value(contextKeySpan).(*TraceSpan)
- return ts
- }
|