// Copyright 2025 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package gtprof import ( "context" "fmt" "strings" "sync/atomic" "time" "code.gitea.io/gitea/modules/tailmsg" ) type traceBuiltinStarter struct{} type traceBuiltinSpan struct { ts *TraceSpan internalSpanIdx int } func (t *traceBuiltinSpan) addEvent(name string, cfg *EventConfig) { // No-op because builtin tracer doesn't need it. // In the future we might use it to mark the time point between backend logic and network response. } func (t *traceBuiltinSpan) recordError(err error, cfg *EventConfig) { // No-op because builtin tracer doesn't need it. // Actually Gitea doesn't handle err this way in most cases } func (t *traceBuiltinSpan) toString(out *strings.Builder, indent int) { t.ts.mu.RLock() defer t.ts.mu.RUnlock() out.WriteString(strings.Repeat(" ", indent)) out.WriteString(t.ts.name) if t.ts.endTime.IsZero() { out.WriteString(" duration: (not ended)") } else { out.WriteString(fmt.Sprintf(" duration=%.4fs", t.ts.endTime.Sub(t.ts.startTime).Seconds())) } for _, a := range t.ts.attributes { out.WriteString(" ") out.WriteString(a.Key) out.WriteString("=") value := a.Value.AsString() if strings.ContainsAny(value, " \t\r\n") { quoted := false for _, c := range "\"'`" { if quoted = !strings.Contains(value, string(c)); quoted { value = string(c) + value + string(c) break } } if !quoted { value = fmt.Sprintf("%q", value) } } out.WriteString(value) } out.WriteString("\n") for _, c := range t.ts.children { span := c.internalSpans[t.internalSpanIdx].(*traceBuiltinSpan) span.toString(out, indent+2) } } func (t *traceBuiltinSpan) end() { if t.ts.parent == nil { // TODO: debug purpose only // TODO: it should distinguish between http response network lag and actual processing time threshold := time.Duration(traceBuiltinThreshold.Load()) if threshold != 0 && t.ts.endTime.Sub(t.ts.startTime) > threshold { sb := &strings.Builder{} t.toString(sb, 0) tailmsg.GetManager().GetTraceRecorder().Record(sb.String()) } } } func (t *traceBuiltinStarter) start(ctx context.Context, traceSpan *TraceSpan, internalSpanIdx int) (context.Context, traceSpanInternal) { return ctx, &traceBuiltinSpan{ts: traceSpan, internalSpanIdx: internalSpanIdx} } func init() { globalTraceStarters = append(globalTraceStarters, &traceBuiltinStarter{}) } var traceBuiltinThreshold atomic.Int64 func EnableBuiltinTracer(threshold time.Duration) { traceBuiltinThreshold.Store(int64(threshold)) }