2022-05-31 23:29:19 +00:00
package querytracer
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"strings"
"time"
2022-12-07 17:43:57 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
2022-05-31 23:29:19 +00:00
)
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.
2022-06-08 18:05:17 +00:00
// Each created tracer must be finalized via Done or Donef call.
2022-05-31 23:29:19 +00:00
//
// 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
2022-06-08 18:05:17 +00:00
// doneTime is the time when Done or Donef was called
2022-05-31 23:29:19 +00:00
doneTime time . Time
2022-06-08 18:05:17 +00:00
// message is the message generated by NewChild, Printf or Donef call.
2022-05-31 23:29:19 +00:00
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
}
2022-06-08 18:05:17 +00:00
// New creates a new instance of the tracer with the given fmt.Sprintf(format, args...) message.
2022-05-31 23:29:19 +00:00
//
// If enabled isn't set, then all function calls to the returned object will be no-op.
//
2022-06-08 18:05:17 +00:00
// Done or Donef must be called when the tracer should be finished.
func New ( enabled bool , format string , args ... interface { } ) * Tracer {
2022-05-31 23:29:19 +00:00
if * denyQueryTracing || ! enabled {
return nil
}
2022-12-07 17:43:57 +00:00
message := fmt . Sprintf ( format , args ... )
message = buildinfo . Version + ": " + message
2022-05-31 23:29:19 +00:00
return & Tracer {
2022-12-07 17:43:57 +00:00
message : message ,
2022-05-31 23:29:19 +00:00
startTime : time . Now ( ) ,
}
}
// Enabled returns true if the t is enabled.
func ( t * Tracer ) Enabled ( ) bool {
return t != nil
}
2022-06-08 18:05:17 +00:00
// 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.
2022-05-31 23:29:19 +00:00
//
// NewChild cannot be called from concurrent goroutines.
// Create children tracers from a single goroutine and then pass them
// to concurrent goroutines.
2022-06-08 18:05:17 +00:00
func ( t * Tracer ) NewChild ( format string , args ... interface { } ) * Tracer {
2022-05-31 23:29:19 +00:00
if t == nil {
return nil
}
2022-06-08 18:05:17 +00:00
if ! t . doneTime . IsZero ( ) {
2022-05-31 23:29:19 +00:00
panic ( fmt . Errorf ( "BUG: NewChild() cannot be called after Donef(%q) call" , t . message ) )
}
child := & Tracer {
2022-06-08 18:05:17 +00:00
message : fmt . Sprintf ( format , args ... ) ,
2022-05-31 23:29:19 +00:00
startTime : time . Now ( ) ,
}
t . children = append ( t . children , child )
return child
}
2022-06-08 18:05:17 +00:00
// 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.
2022-05-31 23:29:19 +00:00
//
// 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
}
2022-06-08 18:05:17 +00:00
if ! t . doneTime . IsZero ( ) {
panic ( fmt . Errorf ( "BUG: Donef(%q) already called" , t . message ) )
2022-05-31 23:29:19 +00:00
}
2022-06-08 18:05:17 +00:00
t . message += ": " + fmt . Sprintf ( format , args ... )
2022-05-31 23:29:19 +00:00
t . doneTime = time . Now ( )
}
2022-06-08 18:05:17 +00:00
// Printf adds new fmt.Sprintf(format, args...) message to t.
2022-05-31 23:29:19 +00:00
//
// Printf cannot be called from concurrent goroutines.
func ( t * Tracer ) Printf ( format string , args ... interface { } ) {
if t == nil {
return
}
2022-06-08 18:05:17 +00:00
if ! t . doneTime . IsZero ( ) {
2022-05-31 23:29:19 +00:00
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
2022-06-08 18:05:17 +00:00
if doneTime . IsZero ( ) {
msg += ": missing Tracer.Done() call"
2022-05-31 23:29:19 +00:00
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 {
2022-09-22 11:59:17 +00:00
// DurationMsec is the duration for the current trace span in milliseconds.
2022-05-31 23:29:19 +00:00
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" )
}