mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-30 15:50:07 +01:00
49ebc48809
This should simplify further debugging, since the first thing to start the debugging by query trace is to know the version of VictoriaMetrics, which produced this trace.
268 lines
6.8 KiB
Go
268 lines
6.8 KiB
Go
package querytracer
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
|
)
|
|
|
|
var denyQueryTracing = flag.Bool("denyQueryTracing", false, "Whether to disable the ability to trace queries. See https://docs.victoriametrics.com/#query-tracing")
|
|
|
|
// Tracer represents query tracer.
|
|
//
|
|
// It must be created via New call.
|
|
// Each created tracer must be finalized via Done or Donef call.
|
|
//
|
|
// Tracer may contain sub-tracers (branches) in order to build tree-like execution order.
|
|
// Call Tracer.NewChild func for adding sub-tracer.
|
|
type Tracer struct {
|
|
// startTime is the time when Tracer was created
|
|
startTime time.Time
|
|
// doneTime is the time when Done or Donef was called
|
|
doneTime time.Time
|
|
// message is the message generated by NewChild, Printf or Donef call.
|
|
message string
|
|
// children is a list of children Tracer objects
|
|
children []*Tracer
|
|
// span contains span for the given Tracer. It is added via Tracer.AddSpan().
|
|
// If span is non-nil, then the remaining fields aren't used.
|
|
span *span
|
|
}
|
|
|
|
// New creates a new instance of the tracer with the given fmt.Sprintf(format, args...) message.
|
|
//
|
|
// If enabled isn't set, then all function calls to the returned object will be no-op.
|
|
//
|
|
// Done or Donef must be called when the tracer should be finished.
|
|
func New(enabled bool, format string, args ...interface{}) *Tracer {
|
|
if *denyQueryTracing || !enabled {
|
|
return nil
|
|
}
|
|
message := fmt.Sprintf(format, args...)
|
|
message = buildinfo.Version + ": " + message
|
|
return &Tracer{
|
|
message: message,
|
|
startTime: time.Now(),
|
|
}
|
|
}
|
|
|
|
// Enabled returns true if the t is enabled.
|
|
func (t *Tracer) Enabled() bool {
|
|
return t != nil
|
|
}
|
|
|
|
// NewChild adds a new child Tracer to t with the given fmt.Sprintf(format, args...) message.
|
|
//
|
|
// The returned child must be closed via Done or Donef calls.
|
|
//
|
|
// NewChild cannot be called from concurrent goroutines.
|
|
// Create children tracers from a single goroutine and then pass them
|
|
// to concurrent goroutines.
|
|
func (t *Tracer) NewChild(format string, args ...interface{}) *Tracer {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
if !t.doneTime.IsZero() {
|
|
panic(fmt.Errorf("BUG: NewChild() cannot be called after Donef(%q) call", t.message))
|
|
}
|
|
child := &Tracer{
|
|
message: fmt.Sprintf(format, args...),
|
|
startTime: time.Now(),
|
|
}
|
|
t.children = append(t.children, child)
|
|
return child
|
|
}
|
|
|
|
// Done finishes t.
|
|
//
|
|
// Done cannot be called multiple times.
|
|
// Other Tracer functions cannot be called after Done call.
|
|
func (t *Tracer) Done() {
|
|
if t == nil {
|
|
return
|
|
}
|
|
if !t.doneTime.IsZero() {
|
|
panic(fmt.Errorf("BUG: Donef(%q) already called", t.message))
|
|
}
|
|
t.doneTime = time.Now()
|
|
}
|
|
|
|
// Donef appends the given fmt.Sprintf(format, args..) message to t and finished it.
|
|
//
|
|
// Donef cannot be called multiple times.
|
|
// Other Tracer functions cannot be called after Donef call.
|
|
func (t *Tracer) Donef(format string, args ...interface{}) {
|
|
if t == nil {
|
|
return
|
|
}
|
|
if !t.doneTime.IsZero() {
|
|
panic(fmt.Errorf("BUG: Donef(%q) already called", t.message))
|
|
}
|
|
t.message += ": " + fmt.Sprintf(format, args...)
|
|
t.doneTime = time.Now()
|
|
}
|
|
|
|
// Printf adds new fmt.Sprintf(format, args...) message to t.
|
|
//
|
|
// Printf cannot be called from concurrent goroutines.
|
|
func (t *Tracer) Printf(format string, args ...interface{}) {
|
|
if t == nil {
|
|
return
|
|
}
|
|
if !t.doneTime.IsZero() {
|
|
panic(fmt.Errorf("BUG: Printf() cannot be called after Done(%q) call", t.message))
|
|
}
|
|
now := time.Now()
|
|
child := &Tracer{
|
|
startTime: now,
|
|
doneTime: now,
|
|
message: fmt.Sprintf(format, args...),
|
|
}
|
|
t.children = append(t.children, child)
|
|
}
|
|
|
|
// AddJSON adds a sub-trace to t.
|
|
//
|
|
// The jsonTrace must be encoded with ToJSON.
|
|
//
|
|
// AddJSON cannot be called from concurrent goroutines.
|
|
func (t *Tracer) AddJSON(jsonTrace []byte) error {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
if len(jsonTrace) == 0 {
|
|
return nil
|
|
}
|
|
var s *span
|
|
if err := json.Unmarshal(jsonTrace, &s); err != nil {
|
|
return fmt.Errorf("cannot unmarshal json trace: %s", err)
|
|
}
|
|
child := &Tracer{
|
|
span: s,
|
|
}
|
|
t.children = append(t.children, child)
|
|
return nil
|
|
}
|
|
|
|
// String returns string representation of t.
|
|
//
|
|
// String must be called when t methods aren't called by other goroutines.
|
|
func (t *Tracer) String() string {
|
|
if t == nil {
|
|
return ""
|
|
}
|
|
s := t.toSpan()
|
|
var bb bytes.Buffer
|
|
s.writePlaintextWithIndent(&bb, 0)
|
|
return bb.String()
|
|
}
|
|
|
|
// ToJSON returns JSON representation of t.
|
|
//
|
|
// ToJSON must be called when t methods aren't called by other goroutines.
|
|
func (t *Tracer) ToJSON() string {
|
|
if t == nil {
|
|
return ""
|
|
}
|
|
s := t.toSpan()
|
|
data, err := json.Marshal(s)
|
|
if err != nil {
|
|
panic(fmt.Errorf("BUG: unexpected error from json.Marshal: %w", err))
|
|
}
|
|
return string(data)
|
|
}
|
|
|
|
func (t *Tracer) toSpan() *span {
|
|
s, _ := t.toSpanInternal(time.Now())
|
|
return s
|
|
}
|
|
|
|
func (t *Tracer) toSpanInternal(prevTime time.Time) (*span, time.Time) {
|
|
if t.span != nil {
|
|
return t.span, prevTime
|
|
}
|
|
if t.doneTime == t.startTime {
|
|
// a single-line trace
|
|
d := t.startTime.Sub(prevTime)
|
|
s := &span{
|
|
DurationMsec: float64(d.Microseconds()) / 1000,
|
|
Message: t.message,
|
|
}
|
|
return s, t.doneTime
|
|
}
|
|
// tracer with children
|
|
msg := t.message
|
|
doneTime := t.doneTime
|
|
if doneTime.IsZero() {
|
|
msg += ": missing Tracer.Done() call"
|
|
doneTime = t.getLastChildDoneTime(t.startTime)
|
|
}
|
|
d := doneTime.Sub(t.startTime)
|
|
var children []*span
|
|
var sChild *span
|
|
prevChildTime := t.startTime
|
|
for _, child := range t.children {
|
|
sChild, prevChildTime = child.toSpanInternal(prevChildTime)
|
|
children = append(children, sChild)
|
|
}
|
|
s := &span{
|
|
DurationMsec: float64(d.Microseconds()) / 1000,
|
|
Message: msg,
|
|
Children: children,
|
|
}
|
|
return s, doneTime
|
|
}
|
|
|
|
func (t *Tracer) getLastChildDoneTime(defaultTime time.Time) time.Time {
|
|
if len(t.children) == 0 {
|
|
return defaultTime
|
|
}
|
|
lastChild := t.children[len(t.children)-1]
|
|
return lastChild.getLastChildDoneTime(lastChild.startTime)
|
|
}
|
|
|
|
// span represents a single trace span
|
|
type span struct {
|
|
// DurationMsec is the duration for the current trace span in milliseconds.
|
|
DurationMsec float64 `json:"duration_msec"`
|
|
// Message is a trace message
|
|
Message string `json:"message"`
|
|
// Children contains children spans
|
|
Children []*span `json:"children,omitempty"`
|
|
}
|
|
|
|
func (s *span) writePlaintextWithIndent(w io.Writer, indent int) {
|
|
prefix := ""
|
|
for i := 0; i < indent; i++ {
|
|
prefix += "| "
|
|
}
|
|
prefix += "- "
|
|
msg := s.messageWithPrefix(prefix)
|
|
fmt.Fprintf(w, "%s%.03fms: %s\n", prefix, s.DurationMsec, msg)
|
|
childIndent := indent + 1
|
|
for _, sChild := range s.Children {
|
|
sChild.writePlaintextWithIndent(w, childIndent)
|
|
}
|
|
}
|
|
|
|
func (s *span) messageWithPrefix(prefix string) string {
|
|
prefix = strings.Replace(prefix, "-", "|", 1)
|
|
lines := strings.Split(s.Message, "\n")
|
|
result := lines[:1]
|
|
for i := range lines[1:] {
|
|
ln := lines[i+1]
|
|
if ln == "" {
|
|
continue
|
|
}
|
|
ln = prefix + ln
|
|
result = append(result, ln)
|
|
}
|
|
return strings.Join(result, "\n")
|
|
}
|