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 }