github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/trace/trace.go (about)

     1  /*
     2   * Copyright (C) 2020 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package trace
    19  
    20  import (
    21  	"fmt"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/mysteriumnetwork/node/eventbus"
    27  	"github.com/rs/zerolog/log"
    28  )
    29  
    30  const (
    31  	// AppTopicTraceEvent represents event topic for Trace events
    32  	AppTopicTraceEvent = "Trace"
    33  )
    34  
    35  // NewTracer returns new tracer instance.
    36  func NewTracer(name string) *Tracer {
    37  	tracer := &Tracer{
    38  		stages: make([]*stage, 0),
    39  	}
    40  	tracer.name = tracer.StartStage(name)
    41  	return tracer
    42  }
    43  
    44  // Tracer represents tracer which records stages durations. It can be used
    45  // to record stages times for tracing how long it took time.
    46  type Tracer struct {
    47  	name     string
    48  	mu       sync.Mutex
    49  	stages   []*stage
    50  	finished bool
    51  }
    52  
    53  // StartStage starts tracing stage for given key.
    54  func (t *Tracer) StartStage(key string) string {
    55  	t.mu.Lock()
    56  	defer t.mu.Unlock()
    57  
    58  	if t.finished {
    59  		log.Error().Msg("Tracer is already finished")
    60  		return ""
    61  	}
    62  	if _, ok := t.findStage(key); ok {
    63  		log.Error().Msgf("Stage %s was already started", key)
    64  		return ""
    65  	}
    66  
    67  	t.stages = append(t.stages, &stage{
    68  		key:   key,
    69  		start: time.Now(),
    70  	})
    71  	return key
    72  }
    73  
    74  // EndStage ends tracing stage for given key.
    75  func (t *Tracer) EndStage(key string) {
    76  	t.mu.Lock()
    77  	defer t.mu.Unlock()
    78  
    79  	if t.finished {
    80  		log.Error().Msg("Tracer is already finished")
    81  		return
    82  	}
    83  	s, ok := t.findStage(key)
    84  	if !ok {
    85  		log.Error().Msgf("Stage %s was not started", key)
    86  		return
    87  	}
    88  
    89  	s.end = time.Now()
    90  }
    91  
    92  // Finish finishes tracing and returns formatted string with stages durations.
    93  func (t *Tracer) Finish(eventPublisher eventbus.Publisher, id string) string {
    94  	t.EndStage(t.name)
    95  
    96  	t.mu.Lock()
    97  	defer t.mu.Unlock()
    98  	t.finished = true
    99  
   100  	var strs []string
   101  	for _, s := range t.stages {
   102  		if s.end.After(time.Time{}) {
   103  			t.publishStageEvent(eventPublisher, id, *s)
   104  			strs = append(strs, fmt.Sprintf("%q took %s", s.key, s.end.Sub(s.start).String()))
   105  		} else {
   106  			strs = append(strs, fmt.Sprintf("%q did not start", s.key))
   107  		}
   108  	}
   109  
   110  	return strings.Join(strs, ", ")
   111  }
   112  
   113  func (t *Tracer) findStage(key string) (*stage, bool) {
   114  	for _, s := range t.stages {
   115  		if s.key == key {
   116  			return s, true
   117  		}
   118  	}
   119  	return nil, false
   120  }
   121  
   122  func (t *Tracer) publishStageEvent(eventPublisher eventbus.Publisher, id string, stage stage) {
   123  	if eventPublisher == nil {
   124  		return
   125  	}
   126  
   127  	eventPublisher.Publish(AppTopicTraceEvent,
   128  		Event{
   129  			ID:       id,
   130  			Key:      stage.key,
   131  			Duration: stage.end.Sub(stage.start),
   132  		},
   133  	)
   134  }
   135  
   136  type stage struct {
   137  	key        string
   138  	start, end time.Time
   139  }
   140  
   141  // Event represents a published Trace event.
   142  type Event struct {
   143  	ID       string
   144  	Key      string
   145  	Duration time.Duration
   146  }