lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/tracing/tracing.go (about)

     1  // Copyright (C) 2017-2021  Nexedi SA and Contributors.
     2  //                          Kirill Smelkov <kirr@nexedi.com>
     3  //
     4  // This program is free software: you can Use, Study, Modify and Redistribute
     5  // it under the terms of the GNU General Public License version 3, or (at your
     6  // option) any later version, as published by the Free Software Foundation.
     7  //
     8  // You can also Link and Combine this program with other software covered by
     9  // the terms of any of the Free Software licenses or any of the Open Source
    10  // Initiative approved licenses and Convey the resulting work. Corresponding
    11  // source of such a combination shall include the source code for all other
    12  // software used.
    13  //
    14  // This program is distributed WITHOUT ANY WARRANTY; without even the implied
    15  // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    16  //
    17  // See COPYING file for full licensing terms.
    18  // See https://www.nexedi.com/licensing for rationale and options.
    19  
    20  /*
    21  Package tracing provides usage and runtime support for Go tracing facilities.
    22  
    23  # Trace events
    24  
    25  A Go package can define several events of interest to trace via special
    26  comments. With such definition a tracing event becomes associated with trace
    27  function that is used to signal when the event happens. For example:
    28  
    29  	package hello
    30  
    31  	//trace:event traceHelloPre(who string)
    32  	//trace:event traceHello(who string)
    33  
    34  	func SayHello(who string) {
    35  		traceHelloPre(who)
    36  		fmt.Println("Hello, %s", who)
    37  		traceHello(who)
    38  	}
    39  
    40  By default using trace function does nothing and has very small overhead(*).
    41  
    42  
    43  # Probes
    44  
    45  However it is possible to attach probing functions to events. A probe, once
    46  attached, is called whenever event is signalled in the context which triggered
    47  the event and pauses original code execution until the probe is finished. It is
    48  possible to attach several probing functions to the same event and dynamically
    49  detach/(re-)attach them at runtime. Attaching/detaching probes must be done
    50  under tracing.Lock. For example:
    51  
    52  	type saidHelloT struct {
    53  		who  string
    54  		when time.Time
    55  	}
    56  	saidHello := make(chan saidHelloT)
    57  
    58  	tracing.Lock()
    59  	p := traceHello_Attach(nil, func(who string) {
    60  		saidHello <- saidHelloT{who, time.Now()}
    61  	})
    62  	tracing.Unlock()
    63  
    64  	go func() {
    65  		for hello := range saidHello {
    66  			fmt.Printf("Said hello to %v @ %v\n", hello.who, hello.when)
    67  		}
    68  	}()
    69  
    70  	SayHello("JP")
    71  	SayHello("Kirr")
    72  	SayHello("Varya")
    73  
    74  	tracing.Lock()
    75  	p.Detach()
    76  	tracing.Unlock()
    77  
    78  	close(saidHello)
    79  
    80  For convenience it is possible to keep group of attached probes and detach them
    81  all at once using ProbeGroup:
    82  
    83  	pg := &tracing.ProbeGroup{}
    84  
    85  	tracing.Lock()
    86  	traceHelloPre_Attach(pg, func(who string) { ... })
    87  	traceHello_Attach(pg, func(who string) { ... })
    88  	tracing.Unlock()
    89  
    90  	// some activity
    91  
    92  	// when probes needs to be detached (no explicit tracing.Lock needed):
    93  	pg.Done()
    94  
    95  Probes is general mechanism which allows various kinds of trace events usage.
    96  Three ways particularly are well-understood and handy:
    97  
    98    - recording events stream
    99    - profiling
   100    - synchronous tracing
   101  
   102  
   103  # Recording events stream
   104  
   105  To get better understanding of what happens when it is possible to record
   106  events into a stream and later either visualize or postprocess them.
   107  This is similar to how Go execution tracer works:
   108  
   109  	https://golang.org/s/go15trace
   110  	https://golang.org/pkg/runtime/trace
   111  	https://golang.org/cmd/trace
   112  
   113  though there it records only predefined set of events related to Go runtime.
   114  
   115  TODO tracing should provide infrastructure to write events out in format
   116  understood by chromium trace-viewer: https://github.com/catapult-project/catapult/tree/master/tracing
   117  
   118  NOTE there is also talk/work to implement user events for runtime/trace: https://golang.org/issues/16619.
   119  
   120  # Profiling
   121  
   122  A profile is aggregate summary of collection of stack traces showing the call sequences that led
   123  to instances of a particular event. One could create runtime/pprof.Profile and
   124  use Profile.Add in a probe attached to particular trace event. The profile can
   125  be later analyzed and visualised with Profile.WriteTo and `go tool pprof`.
   126  
   127  Please refer to runtime/pprof package documentation for details.
   128  
   129  XXX Profile.Add needs unique value for each invocation - how do we do? Provide NaN every time?
   130  
   131  XXX should tracing provide more tight integration with runtime/pprof.Profile?
   132  
   133  
   134  # Synchronous tracing
   135  
   136  For testing purposes it is sometimes practical to leverage the property that
   137  probes pause original code execution until the probe run is finished. That
   138  means while the probe is running original goroutine
   139  
   140    - is paused at well-defined point (where trace function is called), thus
   141    - it cannot mutate any state it is programmed to mutate.
   142  
   143  Using this properties it is possible to attach testing probes and verify that
   144  a set of goroutines in tested code in question
   145  
   146    - produce events in correct order, and
   147    - at every event associated internal state is correct.
   148  
   149  Please see package lab.nexedi.com/kirr/go123/tracing/tracetest for details.
   150  
   151  
   152  # Cross package tracing
   153  
   154  Trace events are not part of exported package API with rationale that package's
   155  regular API and internal trace events usually have different stability
   156  commitments. However with tracing-specific importing mechanism it is possible
   157  to get access to trace events another package provides:
   158  
   159  	package another
   160  
   161  	//trace:import "hello"
   162  
   163  This will make _Attach functions for all tracing events from package hello be
   164  available as regular functions prefixed with imported package name:
   165  
   166  	tracing.Lock()
   167  	hello_traceHello_Attach(nil, func(who string) {
   168  		fmt.Printf("SayHello in package hello: %s", who)
   169  	})
   170  	tracing.Unlock()
   171  
   172  	...
   173  
   174  
   175  # Gotrace
   176  
   177  The way //trace:event and //trace:import work is via additional code being
   178  generated for them. Whenever a package uses any //trace: directive,
   179  it has to organize to run `gotrace gen` on its sources for them to work,
   180  usually with the help of //go:generate. For example:
   181  
   182  	package hello
   183  
   184  	//go:generate gotrace gen .
   185  
   186  	//trace:event ...
   187  
   188  Besides `gotrace gen` gotrace has other subcommands also related to tracing,
   189  for example `gotrace list` lists trace events a package provides.
   190  
   191  Please see `gotrace help` and lab.nexedi.com/kirr/go123/tracing/cmd/gotrace for gotrace documentation.
   192  
   193  --------
   194  
   195  (*) inlined check whether a global pointer != nil.
   196  */
   197  package tracing
   198  
   199  import (
   200  	"sync"
   201  	"sync/atomic"
   202  	"unsafe"
   203  
   204  	"lab.nexedi.com/kirr/go123/tracing/internal/xruntime"
   205  )
   206  
   207  // big tracing lock
   208  var traceMu     sync.Mutex
   209  var traceLocked int32      // for cheap protective checks whether Lock is held
   210  
   211  // Lock serializes modification access to tracepoints.
   212  //
   213  // Under Lock it is safe to attach/detach probes to/from tracepoints:
   214  // - no other goroutine is attaching or detaching probes from tracepoints,
   215  // - a tracepoint readers won't be neither confused nor raced by such adjustments.
   216  //
   217  // Lock returns with the world stopped.
   218  func Lock() {
   219  	traceMu.Lock()
   220  	xruntime.StopTheWorld("tracing lock")
   221  	atomic.StoreInt32(&traceLocked, 1)
   222  	// we synchronized with everyone via stopping the world - there is now
   223  	// no other goroutines running to race with.
   224  	xruntime.RaceIgnoreBegin()
   225  }
   226  
   227  // Unlock is the opposite to Lock and returns with the world resumed.
   228  func Unlock() {
   229  	xruntime.RaceIgnoreEnd()
   230  	atomic.StoreInt32(&traceLocked, 0)
   231  	xruntime.StartTheWorld()
   232  	traceMu.Unlock()
   233  }
   234  
   235  // verifyLocked makes sure tracing is locked and panics otherwise.
   236  func verifyLocked() {
   237  	if atomic.LoadInt32(&traceLocked) == 0 {
   238  		panic("tracing must be locked")
   239  	}
   240  }
   241  
   242  // verifyUnlocked makes sure tracing is not locked and panics otherwise.
   243  func verifyUnlocked() {
   244  	if atomic.LoadInt32(&traceLocked) != 0 {
   245  		panic("tracing must be unlocked")
   246  	}
   247  }
   248  
   249  
   250  // Probe describes one probe attached to a tracepoint.
   251  type Probe struct {
   252  	// NOTE .next must come first as probe list header is only 1 word and
   253  	// is treated as *Probe on probe attach/detach - accessing/modifying its .next
   254  	next, prev *Probe
   255  
   256  	// implicitly:
   257  	// probefunc  func(some arguments)
   258  }
   259  
   260  // Next returns next probe attached to the same tracepoint.
   261  //
   262  // It is safe to iterate Next under any conditions.
   263  func (p *Probe) Next() *Probe {
   264  	return p.next
   265  }
   266  
   267  // AttachProbe attaches newly created Probe to the end of a probe list.
   268  //
   269  // If group is non-nil the probe is also added to the group.
   270  // Must be called under Lock.
   271  // Probe must be newly created.
   272  func AttachProbe(pg *ProbeGroup, listp **Probe, probe *Probe) {
   273  	verifyLocked()
   274  
   275  	if !(probe.prev == nil || probe.next == nil) {
   276  		panic("attach probe: probe is not newly created")
   277  	}
   278  
   279  	last := (*Probe)(unsafe.Pointer(listp))
   280  	for p := *listp; p != nil; last, p = p, p.next {
   281  	}
   282  
   283  	last.next = probe
   284  	probe.prev = last
   285  
   286  	if pg != nil {
   287  		pg.Add(probe)
   288  	}
   289  }
   290  
   291  // Detach detaches probe from a tracepoint.
   292  //
   293  // Must be called under Lock.
   294  func (p *Probe) Detach() {
   295  	verifyLocked()
   296  
   297  	// protection: already detached
   298  	if p.prev == nil {
   299  		return
   300  	}
   301  
   302  	// we can safely change prev.next pointer:
   303  	// - no reader is currently reading it
   304  	// - either a reader already read prev.next, and will proceed with our probe entry, or
   305  	// - it will read updated prev.next and will proceed with p.next probe entry
   306  	p.prev.next = p.next
   307  
   308  	// we can safely change next.prev pointer:
   309  	// - readers only go through list forward
   310  	// - there is no other updater because we are under Lock
   311  	if p.next != nil {
   312  		p.next.prev = p.prev
   313  	}
   314  
   315  	// mark us detached so that if Detach is erroneously called the second
   316  	// time it does not do harm
   317  	p.prev = nil
   318  	p.next = nil
   319  }
   320  
   321  // ProbeGroup is a group of probes attached to tracepoints.
   322  type ProbeGroup struct {
   323  	probev []*Probe
   324  }
   325  
   326  // Add adds a probe to the group.
   327  //
   328  // Must be called under Lock.
   329  func (pg *ProbeGroup) Add(p *Probe) {
   330  	verifyLocked()
   331  	pg.probev = append(pg.probev, p)
   332  }
   333  
   334  // Done detaches all probes registered to the group.
   335  //
   336  // Must be called under normal conditions, not under Lock.
   337  func (pg *ProbeGroup) Done() {
   338  	verifyUnlocked()
   339  	Lock()
   340  	defer Unlock()
   341  
   342  	for _, p := range pg.probev {
   343  		p.Detach()
   344  	}
   345  	pg.probev = nil
   346  }