Skip to content

Commit 4a72ca4

Browse files
committed
fix
1 parent 1ea5216 commit 4a72ca4

File tree

3 files changed

+163
-2
lines changed

3 files changed

+163
-2
lines changed

modules/gtprof/trace.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ package gtprof
55

66
import (
77
"context"
8+
"crypto/rand"
9+
"encoding/hex"
810
"fmt"
11+
mathRand "math/rand/v2"
912
"sync"
1013
"time"
1114

@@ -36,6 +39,7 @@ type TraceSpan struct {
3639

3740
// mutable, must be protected by mutex
3841
mu sync.RWMutex
42+
id string
3943
name string
4044
statusCode uint32
4145
statusDesc string
@@ -54,6 +58,11 @@ type TraceValue struct {
5458
v any
5559
}
5660

61+
func (t *TraceValue) IsString() bool {
62+
_, ok := t.v.(string)
63+
return ok
64+
}
65+
5766
func (t *TraceValue) AsString() string {
5867
return fmt.Sprint(t.v)
5968
}
@@ -71,6 +80,7 @@ func (t *TraceValue) AsFloat64() float64 {
7180
var globalTraceStarters []traceStarter
7281

7382
type Tracer struct {
83+
chacha8 *mathRand.ChaCha8
7484
starters []traceStarter
7585
}
7686

@@ -113,7 +123,7 @@ func (t *Tracer) Start(ctx context.Context, spanName string) (context.Context, *
113123
if starters == nil {
114124
starters = globalTraceStarters
115125
}
116-
ts := &TraceSpan{name: spanName, startTime: time.Now()}
126+
ts := &TraceSpan{id: t.randomHexForBytes(8), name: spanName, startTime: time.Now()}
117127
parentSpan := GetContextSpan(ctx)
118128
if parentSpan != nil {
119129
parentSpan.mu.Lock()
@@ -165,8 +175,19 @@ func (s *TraceSpan) End() {
165175
}
166176
}
167177

178+
func (t *Tracer) randomHexForBytes(n int) string {
179+
b := make([]byte, n)
180+
_, _ = t.chacha8.Read(b) // it never fails
181+
return hex.EncodeToString(b)
182+
}
183+
168184
func GetTracer() *Tracer {
169-
return &Tracer{}
185+
var seed [32]byte
186+
_, err := rand.Read(seed[:])
187+
if err != nil {
188+
panic(fmt.Sprintf("rand.Read: %v", err))
189+
}
190+
return &Tracer{chacha8: mathRand.NewChaCha8(seed)}
170191
}
171192

172193
func GetContextSpan(ctx context.Context) *TraceSpan {

modules/gtprof/trace_builtin.go

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func (t *traceBuiltinSpan) end() {
7878
t.toString(sb, 0)
7979
tailmsg.GetManager().GetTraceRecorder().Record(sb.String())
8080
}
81+
GetDefaultOtelExporter().recordTrace(t)
8182
}
8283
}
8384

modules/gtprof/trace_otel.go

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package gtprof
5+
6+
import (
7+
"bytes"
8+
"net/http"
9+
"strconv"
10+
11+
"code.gitea.io/gitea/modules/json"
12+
)
13+
14+
type OtelAttributeStringValue struct {
15+
StringValue string `json:"stringValue"`
16+
}
17+
18+
type OtelAttributeIntValue struct {
19+
IntValue string `json:"intValue"`
20+
}
21+
22+
type OtelAttribute struct {
23+
Key string `json:"key"`
24+
Value any `json:"value"`
25+
}
26+
27+
type OtelResource struct {
28+
Attributes []*OtelAttribute `json:"attributes,omitempty"`
29+
}
30+
31+
type OtelScope struct {
32+
Name string `json:"name"`
33+
Version string `json:"version"`
34+
Attributes []*OtelAttribute `json:"attributes,omitempty"`
35+
}
36+
37+
type OtelSpan struct {
38+
TraceID string `json:"traceId"`
39+
SpanID string `json:"spanId"`
40+
ParentSpanID string `json:"parentSpanId,omitempty"`
41+
Name string `json:"name"`
42+
StartTimeUnixNano string `json:"startTimeUnixNano"`
43+
EndTimeUnixNano string `json:"endTimeUnixNano"`
44+
Kind int `json:"kind"`
45+
Attributes []*OtelAttribute `json:"attributes,omitempty"`
46+
}
47+
48+
type OtelScopeSpan struct {
49+
Scope *OtelScope `json:"scope"`
50+
Spans []*OtelSpan `json:"spans"`
51+
}
52+
53+
type OtelResourceSpan struct {
54+
Resource *OtelResource `json:"resource"`
55+
ScopeSpans []*OtelScopeSpan `json:"scopeSpans"`
56+
}
57+
58+
type OtelTrace struct {
59+
ResourceSpans []*OtelResourceSpan `json:"resourceSpans"`
60+
}
61+
62+
type OtelExporter struct{}
63+
64+
func (e *OtelExporter) toOtelSpan(traceID string, t *traceBuiltinSpan, scopeSpan *OtelScopeSpan) {
65+
t.ts.mu.RLock()
66+
defer t.ts.mu.RUnlock()
67+
68+
span := &OtelSpan{
69+
TraceID: traceID,
70+
SpanID: t.ts.id,
71+
Name: t.ts.name,
72+
StartTimeUnixNano: strconv.FormatInt(t.ts.startTime.UnixNano(), 10),
73+
EndTimeUnixNano: strconv.FormatInt(t.ts.endTime.UnixNano(), 10),
74+
Kind: 2,
75+
}
76+
77+
if t.ts.parent != nil {
78+
span.ParentSpanID = t.ts.parent.id
79+
}
80+
81+
scopeSpan.Spans = append(scopeSpan.Spans, span)
82+
83+
for _, a := range t.ts.attributes {
84+
var otelVal any
85+
if a.Value.IsString() {
86+
otelVal = OtelAttributeStringValue{a.Value.AsString()}
87+
} else {
88+
otelVal = OtelAttributeIntValue{strconv.FormatInt(a.Value.AsInt64(), 10)}
89+
}
90+
span.Attributes = append(span.Attributes, &OtelAttribute{Key: a.Key, Value: otelVal})
91+
}
92+
93+
for _, c := range t.ts.children {
94+
child := c.internalSpans[t.internalSpanIdx].(*traceBuiltinSpan)
95+
e.toOtelSpan(traceID, child, scopeSpan)
96+
}
97+
}
98+
99+
func (e *OtelExporter) recordTrace(t *traceBuiltinSpan) {
100+
var spans []*OtelSpan
101+
102+
scopeSpan := &OtelScopeSpan{
103+
Scope: &OtelScope{
104+
Name: "gitea-web",
105+
Version: "1.0", // FIXME: get version from somewhere
106+
},
107+
Spans: spans,
108+
}
109+
110+
traceID := GetTracer().randomHexForBytes(16)
111+
e.toOtelSpan(traceID, t, scopeSpan)
112+
113+
resSpans := OtelResourceSpan{
114+
Resource: &OtelResource{
115+
Attributes: []*OtelAttribute{
116+
{Key: "service.name", Value: OtelAttributeStringValue{"gitea"}},
117+
},
118+
},
119+
ScopeSpans: []*OtelScopeSpan{scopeSpan},
120+
}
121+
122+
otelTrace := &OtelTrace{ResourceSpans: []*OtelResourceSpan{&resSpans}}
123+
124+
// TODO: use a async queue
125+
otelTraceJSON, err := json.Marshal(otelTrace)
126+
if err == nil {
127+
_, _ = http.Post("http://localhost:4318/v1/traces", "application/json", bytes.NewReader(otelTraceJSON))
128+
}
129+
}
130+
131+
var defaultOtelExporter = NewOtelExporter()
132+
133+
func GetDefaultOtelExporter() *OtelExporter {
134+
return defaultOtelExporter
135+
}
136+
137+
func NewOtelExporter() *OtelExporter {
138+
return &OtelExporter{}
139+
}

0 commit comments

Comments
 (0)