Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
wxiaoguang committed Dec 25, 2024
1 parent 65e45fd commit 4b7547b
Show file tree
Hide file tree
Showing 19 changed files with 378 additions and 47 deletions.
20 changes: 15 additions & 5 deletions models/db/engine_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,33 @@ import (
"context"
"time"

"code.gitea.io/gitea/modules/gtprof"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"

"xorm.io/xorm/contexts"
)

type SlowQueryHook struct {
type EngineHook struct {
Threshold time.Duration
Logger log.Logger
}

var _ contexts.Hook = (*SlowQueryHook)(nil)
var _ contexts.Hook = (*EngineHook)(nil)

func (*SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
return c.Ctx, nil
func (*EngineHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
ctx, _ := gtprof.GetTracer().Start(c.Ctx, gtprof.TraceSpanDatabase)
return ctx, nil
}

func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
func (h *EngineHook) AfterProcess(c *contexts.ContextHook) error {
span := gtprof.GetContextSpan(c.Ctx)
if span != nil {
span.SetAttributeString(gtprof.TraceAttrDbSQL, c.SQL)
span.End()
} else {
setting.PanicInDevOrTesting("span in database engine hook is nil")
}
if c.ExecuteTime >= h.Threshold {
// 8 is the amount of skips passed to runtime.Caller, so that in the log the correct function
// is being displayed (the function that ultimately wants to execute the query in the code)
Expand Down
2 changes: 1 addition & 1 deletion models/db/engine_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func InitEngine(ctx context.Context) error {
xe.SetDefaultContext(ctx)

if setting.Database.SlowQueryThreshold > 0 {
xe.AddHook(&SlowQueryHook{
xe.AddHook(&EngineHook{
Threshold: setting.Database.SlowQueryThreshold,
Logger: log.GetLogger("xorm"),
})
Expand Down
12 changes: 9 additions & 3 deletions modules/git/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"time"

"code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions
"code.gitea.io/gitea/modules/gtprof"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/util"
Expand Down Expand Up @@ -54,7 +55,7 @@ func logArgSanitize(arg string) string {
} else if filepath.IsAbs(arg) {
base := filepath.Base(arg)
dir := filepath.Dir(arg)
return filepath.Join(filepath.Base(dir), base)
return ".../" + filepath.Join(filepath.Base(dir), base)
}
return arg
}
Expand Down Expand Up @@ -289,15 +290,20 @@ func (c *Command) run(skip int, opts *RunOpts) error {
timeout = defaultCommandExecutionTimeout
}

var desc string
cmdLogString := c.LogString()
callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + skip /* parent */)
if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 {
callerInfo = callerInfo[pos+1:]
}
// these logs are for debugging purposes only, so no guarantee of correctness or stability
desc = fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), c.LogString())
desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), cmdLogString)
log.Debug("git.Command: %s", desc)

_, span := gtprof.GetTracer().Start(c.parentContext, gtprof.TraceSpanGitRun)
defer span.End()
span.SetAttributeString(gtprof.TraceAttrFuncCaller, callerInfo)
span.SetAttributeString(gtprof.TraceAttrGitCommand, cmdLogString)

var ctx context.Context
var cancel context.CancelFunc
var finished context.CancelFunc
Expand Down
2 changes: 1 addition & 1 deletion modules/git/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,5 @@ func TestCommandString(t *testing.T) {
assert.EqualValues(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.LogString())

cmd = NewCommandContextNoGlobals(context.Background(), "url: https://a:b@c/", "/root/dir-a/dir-b")
assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/" dir-a/dir-b`, cmd.LogString())
assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/" .../dir-a/dir-b`, cmd.LogString())
}
119 changes: 119 additions & 0 deletions modules/gtprof/trace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2024 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 {
end()
}

type TraceSpan struct {
// immutable
parent *TraceSpan
internalSpans []traceSpanInternal

// mutable, must be protected by mutex
mu sync.RWMutex
name 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) 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()}
existingCtxSpan, _ := ctx.Value(ContextKeySpan).(*TraceSpan)
if existingCtxSpan != nil {
existingCtxSpan.mu.Lock()
existingCtxSpan.children = append(existingCtxSpan.children, ts)
existingCtxSpan.mu.Unlock()
ts.parent = existingCtxSpan
}
for internalSpanIdx, tsp := range starters {
var internalSpan traceSpanInternal
ctx, internalSpan = tsp.start(ctx, ts, internalSpanIdx)
ts.internalSpans = append(ts.internalSpans, internalSpan)
}
ctx = context.WithValue(ctx, ContextKeySpan, ts)
return ctx, ts
}

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
}
66 changes: 66 additions & 0 deletions modules/gtprof/trace_builtin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package gtprof

import (
"context"
"fmt"
"strings"
"time"

"code.gitea.io/gitea/modules/tailmsg"
)

type traceBuiltinStarter struct{}

type traceBuiltinSpan struct {
ts *TraceSpan

internalSpanIdx int
}

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()))
}
out.WriteString("\n")
for _, a := range t.ts.attributes {
out.WriteString(strings.Repeat(" ", indent+2))
out.WriteString(a.Key)
out.WriteString(": ")
out.WriteString(a.Value.AsString())
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 {
// FIXME: debug purpose only
// FIXME: it should distinguish between http response network lag and actual processing time
if len(t.ts.children) > 3 || t.ts.endTime.Sub(t.ts.startTime) > 100*time.Millisecond {
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{})
}
19 changes: 19 additions & 0 deletions modules/gtprof/trace_const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package gtprof

// Some interesting names could be found in https://github.com/open-telemetry/opentelemetry-go/tree/main/semconv

const (
TraceSpanHTTP = "http"
TraceSpanGitRun = "git-run"
TraceSpanDatabase = "database"
)

const (
TraceAttrFuncCaller = "func.caller"
TraceAttrDbSQL = "db.sql"
TraceAttrGitCommand = "git.command"
TraceAttrHTTPRoute = "http.route"
)
73 changes: 73 additions & 0 deletions modules/tailmsg/taillog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package tailmsg

import (
"sync"
"time"
)

type MsgRecord struct {
Time time.Time
Content string
}

type MsgRecorder interface {
Record(content string)
GetRecords() []*MsgRecord
}

type memoryMsgRecorder struct {
mu sync.RWMutex
msgs []*MsgRecord
limit int
}

// TODO: use redis for a clustered environment

func (m *memoryMsgRecorder) Record(content string) {
m.mu.Lock()
defer m.mu.Unlock()
m.msgs = append(m.msgs, &MsgRecord{
Time: time.Now(),
Content: content,
})
if len(m.msgs) > m.limit {
m.msgs = m.msgs[len(m.msgs)-m.limit:]
}
}

func (m *memoryMsgRecorder) GetRecords() []*MsgRecord {
m.mu.RLock()
defer m.mu.RUnlock()
ret := make([]*MsgRecord, len(m.msgs))
copy(ret, m.msgs)
return ret
}

func NewMsgRecorder(limit int) MsgRecorder {
return &memoryMsgRecorder{
limit: limit,
}
}

type Manager struct {
traceRecorder MsgRecorder
logRecorder MsgRecorder
}

func (m *Manager) GetTraceRecorder() MsgRecorder {
return m.traceRecorder
}

func (m *Manager) GetLogRecorder() MsgRecorder {
return m.logRecorder
}

var GetManager = sync.OnceValue(func() *Manager {
return &Manager{
traceRecorder: NewMsgRecorder(100),
logRecorder: NewMsgRecorder(1000),
}
})
3 changes: 2 additions & 1 deletion options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3355,6 +3355,8 @@ monitor.previous = Previous Time
monitor.execute_times = Executions
monitor.process = Running Processes
monitor.stacktrace = Stacktrace
monitor.trace = Trace
monitor.performance_logs = Performance Logs
monitor.processes_count = %d Processes
monitor.download_diagnosis_report = Download diagnosis report
monitor.desc = Description
Expand All @@ -3363,7 +3365,6 @@ monitor.execute_time = Execution Time
monitor.last_execution_result = Result
monitor.process.cancel = Cancel process
monitor.process.cancel_desc = Cancelling a process may cause data loss
monitor.process.cancel_notices = Cancel: <strong>%s</strong>?
monitor.process.children = Children
monitor.queues = Queues
Expand Down
Loading

0 comments on commit 4b7547b

Please sign in to comment.