tracing.go 9.48 KB
Newer Older
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
1 2
// Copyright (C) 2017  Nexedi SA and Contributors.
//                     Kirill Smelkov <kirr@nexedi.com>
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
3 4 5 6 7 8
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
9 10 11 12
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
13 14 15 16 17
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
18
// See https://www.nexedi.com/licensing for rationale and options.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
19

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
20
/*
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
21
Package tracing provides usage and runtime support for Go tracing facilities.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
22

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
Trace events

A Go package can define several events of interest to trace via special
comments. With such definition a tracing event becomes associated with trace
function that is used to signal when the event happens. For example:

	package hello

	//trace:event traceHelloPre(who string)
	//trace:event traceHello(who string)

	func SayHello(who string) {
		traceHelloPre(who)
		fmt.Println("Hello, %s", who)
		traceHello(who)
	}

By default trace function does nothing and has very small overhead(*).


Probes

However it is possible to attach probing functions to events. A probe, once
attached, is called whenever event is signalled in the context which triggered
the event and pauses original code execution until the probe is finished. It is
possible to attach several probing functions to the same event and dynamically
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
49 50
detach/(re-)attach them at runtime. Attaching/detaching probes must be done
under tracing.Lock. For example:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94

	type saidHelloT struct {
		who  string
		when time.Time
	}
	saidHello := make(chan saidHelloT)

	tracing.Lock()
	p := traceHello_Attach(nil, func(who string) {
		saidHello <- saidHelloT{who, time.Now()}
	})
	tracing.Unlock()

	go func() {
		for hello := range saidHello {
			fmt.Printf("Said hello to %v @ %v\n", hello.who, hello.when)
		}
	}()

	SayHello("JP")
	SayHello("Kirr")
	SayHello("Varya")

	tracing.Lock()
	p.Detach()
	tracing.Unlock()

	close(saidHello)

For convenience it is possible to keep group of attached probes and detach them
all at once using ProbeGroup:

	pg := &tracing.ProbeGroup{}

	tracing.Lock()
	traceHelloPre_Attach(pg, func(who string) { ... })
	traceHello_Attach(pg, func(who string) { ... })
	tracing.Unlock()

	// some activity

	// when probes needs to be detached (no explicit tracing.Lock needed):
	pg.Done()

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
95
Probes is general mechanism which allows various kinds of trace events usage.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
96
Three ways particularly are well-understood and handy:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
97

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
98
	- recording events stream
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
99 100 101
	- profiling
	- synchronous tracing

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
102

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
103 104
Recording events stream

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
105 106 107 108 109 110 111 112 113 114 115 116
To get better understanding of what happens when it is possible to record
events into a stream and later either visualize or postprocess them.
This is similar to how Go execution tracer works:

https://golang.org/s/go15trace
https://golang.org/pkg/runtime/trace
https://golang.org/cmd/trace

though there it records only predefined set of events related to Go runtime.

TODO tracing should provide infrastructure to write events out in format
understood by chromium trace-viewer: https://github.com/catapult-project/catapult/tree/master/tracing
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
117

118
NOTE there is also talk/work to implement user events for runtime/trace: https://golang.org/issues/16619.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
119

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
120 121
Profiling

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
122
A profile is aggregate summary of collection of stack traces showing the call sequences that led
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
123
to instances of a particular event. One could create runtime/pprof.Profile and
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
124
use Profile.Add in a probe attached to particular trace event. The profile can
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
125
be later analyzed and visualised with Profile.WriteTo and `go tool pprof`.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
126

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
127 128
Please refer to runtime/pprof package documentation for details.

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
129
XXX Profile.Add needs unique value for each invocation - how do we do? Provide NaN every time?
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
130

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
131
XXX should tracing provide more tight integration with runtime/pprof.Profile?
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
132

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
133

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
134 135
Synchronous tracing

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
136 137 138 139 140 141 142 143 144 145 146 147 148 149
For testing purposes it is sometimes practical to leverage the property that
probes pause original code execution until the probe run is finished. That
means while the probe is running original goroutine

- is paused at well-defined point (where trace function is called), thus
- it cannot mutate any state it is programmed to mutate.

Using this properties it is possible to attach testing probes and verify that
a set of goroutines in tested code in question

- produce events in correct order, and
- at every event associated internal state is correct.

TODO example
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175


Cross package tracing

Trace events are not part of exported package API with rationale that package's
regular API and internal trace events usually have different stability
commitments. However with tracing-specific importing mechanism it is possible
to get access to trace events another package provides:

	package another

	//trace:import "hello"

This will make _Attach functions for all tracing events from package hello be
available as regular functions prefixed with imported package name:

	tracing.Lock()
	hello_traceHello_Attach(nil, func(who string) {
		fmt.Printf("SayHello in package hello: %s", who)
	tracing.Unlock()

	...


Gotrace

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
The way //trace:event and //trace:import works is via additional code being
generated for them. Whenever a package uses any //trace: directive,
it has to organize to run `gotrace gen` on its sources for them to work,
usually with the help of //go:generate. For example:

	package hello

	//go:generate gotrace gen .

	//trace:event ...

Besides `gotrace gen` gotrace has other subcommands also related to tracing,
for example `gotrace list` lists trace events a package provides.

Please see TODO link for gotrace documentation.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
191 192 193 194 195 196

--------

(*) conditionally checking whether a pointer != nil. After
https://golang.org/issues/19348 is implemented the call/return overhead will be
also gone.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
197
*/
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
198 199 200 201 202
package tracing

import (
	"sync"
	"sync/atomic"
203
	"unsafe"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
204 205

	"fmt"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
206 207 208 209 210 211
)

// big tracing lock
var traceMu     sync.Mutex
var traceLocked int32      // for cheap protective checks whether Lock is held

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
212
// Lock serializes modification access to tracepoints.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
//
// Under Lock it is safe to attach/detach probes to/from tracepoints:
// - no other goroutine is attaching or detaching probes from tracepoints,
// - a tracepoint readers won't be neither confused nor raced by such adjustments.
//
// Lock returns with the world stopped.
func Lock() {
	traceMu.Lock()
	runtime_stopTheWorld("tracing lock")
	atomic.StoreInt32(&traceLocked, 1)
}

// Unlock is the opposite to Lock and returns with the world resumed
func Unlock() {
	atomic.StoreInt32(&traceLocked, 0)
	runtime_startTheWorld()
	traceMu.Unlock()
}

// verifyLocked makes sure tracing is locked and panics otherwise
func verifyLocked() {
	if atomic.LoadInt32(&traceLocked) == 0 {
		panic("tracing must be locked")
	}
}

239 240 241 242 243 244 245
// verifyUnlocked makes sure tracing is not locked and panics otherwise
func verifyUnlocked() {
	if atomic.LoadInt32(&traceLocked) != 0 {
		panic("tracing must be unlocked")
	}
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
246

247
// Probe describes one probe attached to a tracepoint
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
248
type Probe struct {
249 250 251
	// NOTE .next must come first as probe list header is only 1 word and
	// is treated as *Probe on probe attach/detach - accessing/modifying its .next
	next, prev *Probe
252 253 254

	// implicitly:
	// probefunc  func(some arguments)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
255 256
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
257 258
// Next returns next probe attached to the same tracepoint.
//
259
// It is safe to iterate Next under any conditions.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
260 261 262 263
func (p *Probe) Next() *Probe {
	return p.next
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
264 265
// AttachProbe attaches newly created Probe to the end of a probe list.
//
266 267 268
// If group is non-nil the probe is also added to the group.
// Must be called under Lock.
// Probe must be newly created.
Kirill Smelkov's avatar
Kirill Smelkov committed
269
func AttachProbe(pg *ProbeGroup, listp **Probe, probe *Probe) {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
270 271
	verifyLocked()

272 273 274 275
	if !(probe.prev == nil || probe.next == nil) {
		panic("attach probe: probe is not newly created")
	}

276 277
	last := (*Probe)(unsafe.Pointer(listp))
	for p := *listp; p != nil; last, p = p, p.next {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
278 279
	}

280 281
	last.next = probe
	probe.prev = last
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
282

Kirill Smelkov's avatar
Kirill Smelkov committed
283 284
	if pg != nil {
		pg.Add(probe)
285
	}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
286 287
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
288 289
// Detach detaches probe from a tracepoint.
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
290 291 292 293 294
// Must be called under Lock
func (p *Probe) Detach() {
	verifyLocked()

	// protection: already detached
295
	if p.prev == nil {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
296 297 298 299 300 301 302
		return
	}

	// we can safely change prev.next pointer:
	// - no reader is currently reading it
	// - either a reader already read prev.next, and will proceed with our probe entry, or
	// - it will read updated prev.next and will proceed with p.next probe entry
303
	p.prev.next = p.next
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
304 305 306 307 308 309 310 311 312 313

	// we can safely change next.prev pointer:
	// - readers only go through list forward
	// - there is no other updater because we are under Lock
	if p.next != nil {
		p.next.prev = p.prev
	}

	// mark us detached so that if Detach is erroneously called the second
	// time it does not do harm
314 315
	p.prev = nil
	p.next = nil
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
316
}
317

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
318
// ProbeGroup is a group of probes attached to tracepoints.
Kirill Smelkov's avatar
Kirill Smelkov committed
319
type ProbeGroup struct {
320 321 322
	probev []*Probe
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
323 324
// Add adds a probe to the group.
//
325
// Must be called under Lock
Kirill Smelkov's avatar
Kirill Smelkov committed
326
func (pg *ProbeGroup) Add(p *Probe) {
327
	verifyLocked()
Kirill Smelkov's avatar
Kirill Smelkov committed
328
	pg.probev = append(pg.probev, p)
329 330
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
331 332
// Done detaches all probes registered to the group.
//
333
// Must be called under normal conditions, not under Lock
Kirill Smelkov's avatar
Kirill Smelkov committed
334
func (pg *ProbeGroup) Done() {
335 336 337 338
	verifyUnlocked()
	Lock()
	defer Unlock()

Kirill Smelkov's avatar
Kirill Smelkov committed
339
	for _, p := range pg.probev {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
340
		fmt.Printf("detaching %#v\n", p)
341 342
		p.Detach()
	}
Kirill Smelkov's avatar
Kirill Smelkov committed
343
	pg.probev = nil
344
}