Source file src/runtime/tracecpu.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // CPU profile -> trace
     6  
     7  package runtime
     8  
     9  // traceInitReadCPU initializes CPU profile -> tracer state for tracing.
    10  //
    11  // Returns a profBuf for reading from.
    12  func traceInitReadCPU() {
    13  	if traceEnabled() {
    14  		throw("traceInitReadCPU called with trace enabled")
    15  	}
    16  	// Create new profBuf for CPU samples that will be emitted as events.
    17  	// Format: after the timestamp, header is [pp.id, gp.goid, mp.procid].
    18  	trace.cpuLogRead[0] = newProfBuf(3, profBufWordCount, profBufTagCount)
    19  	trace.cpuLogRead[1] = newProfBuf(3, profBufWordCount, profBufTagCount)
    20  	// We must not acquire trace.signalLock outside of a signal handler: a
    21  	// profiling signal may arrive at any time and try to acquire it, leading to
    22  	// deadlock. Because we can't use that lock to protect updates to
    23  	// trace.cpuLogWrite (only use of the structure it references), reads and
    24  	// writes of the pointer must be atomic. (And although this field is never
    25  	// the sole pointer to the profBuf value, it's best to allow a write barrier
    26  	// here.)
    27  	trace.cpuLogWrite[0].Store(trace.cpuLogRead[0])
    28  	trace.cpuLogWrite[1].Store(trace.cpuLogRead[1])
    29  }
    30  
    31  // traceStartReadCPU creates a goroutine to start reading CPU profile
    32  // data into an active trace.
    33  //
    34  // traceAdvanceSema must be held.
    35  func traceStartReadCPU() {
    36  	if !traceEnabled() {
    37  		throw("traceStartReadCPU called with trace disabled")
    38  	}
    39  	// Spin up the logger goroutine.
    40  	trace.cpuSleep = newWakeableSleep()
    41  	done := make(chan struct{}, 1)
    42  	go func() {
    43  		for traceEnabled() {
    44  			// Sleep here because traceReadCPU is non-blocking. This mirrors
    45  			// how the runtime/pprof package obtains CPU profile data.
    46  			//
    47  			// We can't do a blocking read here because Darwin can't do a
    48  			// wakeup from a signal handler, so all CPU profiling is just
    49  			// non-blocking. See #61768 for more details.
    50  			//
    51  			// Like the runtime/pprof package, even if that bug didn't exist
    52  			// we would still want to do a goroutine-level sleep in between
    53  			// reads to avoid frequent wakeups.
    54  			trace.cpuSleep.sleep(100_000_000)
    55  
    56  			tl := traceAcquire()
    57  			if !tl.ok() {
    58  				// Tracing disabled.
    59  				break
    60  			}
    61  			keepGoing := traceReadCPU(tl.gen)
    62  			traceRelease(tl)
    63  			if !keepGoing {
    64  				break
    65  			}
    66  		}
    67  		done <- struct{}{}
    68  	}()
    69  	trace.cpuLogDone = done
    70  }
    71  
    72  // traceStopReadCPU blocks until the trace CPU reading goroutine exits.
    73  //
    74  // traceAdvanceSema must be held, and tracing must be disabled.
    75  func traceStopReadCPU() {
    76  	if traceEnabled() {
    77  		throw("traceStopReadCPU called with trace enabled")
    78  	}
    79  
    80  	// Once we close the profbuf, we'll be in one of two situations:
    81  	// - The logger goroutine has already exited because it observed
    82  	//   that the trace is disabled.
    83  	// - The logger goroutine is asleep.
    84  	//
    85  	// Wake the goroutine so it can observe that their the buffer is
    86  	// closed an exit.
    87  	trace.cpuLogWrite[0].Store(nil)
    88  	trace.cpuLogWrite[1].Store(nil)
    89  	trace.cpuLogRead[0].close()
    90  	trace.cpuLogRead[1].close()
    91  	trace.cpuSleep.wake()
    92  
    93  	// Wait until the logger goroutine exits.
    94  	<-trace.cpuLogDone
    95  
    96  	// Clear state for the next trace.
    97  	trace.cpuLogDone = nil
    98  	trace.cpuLogRead[0] = nil
    99  	trace.cpuLogRead[1] = nil
   100  	trace.cpuSleep.close()
   101  }
   102  
   103  // traceReadCPU attempts to read from the provided profBuf[gen%2] and write
   104  // into the trace. Returns true if there might be more to read or false
   105  // if the profBuf is closed or the caller should otherwise stop reading.
   106  //
   107  // The caller is responsible for ensuring that gen does not change. Either
   108  // the caller must be in a traceAcquire/traceRelease block, or must be calling
   109  // with traceAdvanceSema held.
   110  //
   111  // No more than one goroutine may be in traceReadCPU for the same
   112  // profBuf at a time.
   113  //
   114  // Must not run on the system stack because profBuf.read performs race
   115  // operations.
   116  func traceReadCPU(gen uintptr) bool {
   117  	var pcBuf [traceStackSize]uintptr
   118  
   119  	data, tags, eof := trace.cpuLogRead[gen%2].read(profBufNonBlocking)
   120  	for len(data) > 0 {
   121  		if len(data) < 4 || data[0] > uint64(len(data)) {
   122  			break // truncated profile
   123  		}
   124  		if data[0] < 4 || tags != nil && len(tags) < 1 {
   125  			break // malformed profile
   126  		}
   127  		if len(tags) < 1 {
   128  			break // mismatched profile records and tags
   129  		}
   130  
   131  		// Deserialize the data in the profile buffer.
   132  		recordLen := data[0]
   133  		timestamp := data[1]
   134  		ppid := data[2] >> 1
   135  		if hasP := (data[2] & 0b1) != 0; !hasP {
   136  			ppid = ^uint64(0)
   137  		}
   138  		goid := data[3]
   139  		mpid := data[4]
   140  		stk := data[5:recordLen]
   141  
   142  		// Overflow records always have their headers contain
   143  		// all zeroes.
   144  		isOverflowRecord := len(stk) == 1 && data[2] == 0 && data[3] == 0 && data[4] == 0
   145  
   146  		// Move the data iterator forward.
   147  		data = data[recordLen:]
   148  		// No support here for reporting goroutine tags at the moment; if
   149  		// that information is to be part of the execution trace, we'd
   150  		// probably want to see when the tags are applied and when they
   151  		// change, instead of only seeing them when we get a CPU sample.
   152  		tags = tags[1:]
   153  
   154  		if isOverflowRecord {
   155  			// Looks like an overflow record from the profBuf. Not much to
   156  			// do here, we only want to report full records.
   157  			continue
   158  		}
   159  
   160  		// Construct the stack for insertion to the stack table.
   161  		nstk := 1
   162  		pcBuf[0] = logicalStackSentinel
   163  		for ; nstk < len(pcBuf) && nstk-1 < len(stk); nstk++ {
   164  			pcBuf[nstk] = uintptr(stk[nstk-1])
   165  		}
   166  
   167  		// Write out a trace event.
   168  		w := unsafeTraceWriter(gen, trace.cpuBuf[gen%2])
   169  
   170  		// Ensure we have a place to write to.
   171  		var flushed bool
   172  		w, flushed = w.ensure(2 + 5*traceBytesPerNumber /* traceEvCPUSamples + traceEvCPUSample + timestamp + g + m + p + stack ID */)
   173  		if flushed {
   174  			// Annotate the batch as containing strings.
   175  			w.byte(byte(traceEvCPUSamples))
   176  		}
   177  
   178  		// Add the stack to the table.
   179  		stackID := trace.stackTab[gen%2].put(pcBuf[:nstk])
   180  
   181  		// Write out the CPU sample.
   182  		w.byte(byte(traceEvCPUSample))
   183  		w.varint(timestamp)
   184  		w.varint(mpid)
   185  		w.varint(ppid)
   186  		w.varint(goid)
   187  		w.varint(stackID)
   188  
   189  		trace.cpuBuf[gen%2] = w.traceBuf
   190  	}
   191  	return !eof
   192  }
   193  
   194  // traceCPUFlush flushes trace.cpuBuf[gen%2]. The caller must be certain that gen
   195  // has completed and that there are no more writers to it.
   196  func traceCPUFlush(gen uintptr) {
   197  	// Flush any remaining trace buffers containing CPU samples.
   198  	if buf := trace.cpuBuf[gen%2]; buf != nil {
   199  		systemstack(func() {
   200  			lock(&trace.lock)
   201  			traceBufFlush(buf, gen)
   202  			unlock(&trace.lock)
   203  			trace.cpuBuf[gen%2] = nil
   204  		})
   205  	}
   206  }
   207  
   208  // traceCPUSample writes a CPU profile sample stack to the execution tracer's
   209  // profiling buffer. It is called from a signal handler, so is limited in what
   210  // it can do. mp must be the thread that is currently stopped in a signal.
   211  func traceCPUSample(gp *g, mp *m, pp *p, stk []uintptr) {
   212  	if !traceEnabled() {
   213  		// Tracing is usually turned off; don't spend time acquiring the signal
   214  		// lock unless it's active.
   215  		return
   216  	}
   217  	if mp == nil {
   218  		// Drop samples that don't have an identifiable thread. We can't render
   219  		// this in any useful way anyway.
   220  		return
   221  	}
   222  
   223  	// We're going to conditionally write to one of two buffers based on the
   224  	// generation. To make sure we write to the correct one, we need to make
   225  	// sure this thread's trace seqlock is held. If it already is, then we're
   226  	// in the tracer and we can just take advantage of that. If it isn't, then
   227  	// we need to acquire it and read the generation.
   228  	locked := false
   229  	if mp.trace.seqlock.Load()%2 == 0 {
   230  		mp.trace.seqlock.Add(1)
   231  		locked = true
   232  	}
   233  	gen := trace.gen.Load()
   234  	if gen == 0 {
   235  		// Tracing is disabled, as it turns out. Release the seqlock if necessary
   236  		// and exit.
   237  		if locked {
   238  			mp.trace.seqlock.Add(1)
   239  		}
   240  		return
   241  	}
   242  
   243  	now := traceClockNow()
   244  	// The "header" here is the ID of the M that was running the profiled code,
   245  	// followed by the IDs of the P and goroutine. (For normal CPU profiling, it's
   246  	// usually the number of samples with the given stack.) Near syscalls, pp
   247  	// may be nil. Reporting goid of 0 is fine for either g0 or a nil gp.
   248  	var hdr [3]uint64
   249  	if pp != nil {
   250  		// Overflow records in profBuf have all header values set to zero. Make
   251  		// sure that real headers have at least one bit set.
   252  		hdr[0] = uint64(pp.id)<<1 | 0b1
   253  	} else {
   254  		hdr[0] = 0b10
   255  	}
   256  	if gp != nil {
   257  		hdr[1] = gp.goid
   258  	}
   259  	hdr[2] = uint64(mp.procid)
   260  
   261  	// Allow only one writer at a time
   262  	for !trace.signalLock.CompareAndSwap(0, 1) {
   263  		// TODO: Is it safe to osyield here? https://go.dev/issue/52672
   264  		osyield()
   265  	}
   266  
   267  	if log := trace.cpuLogWrite[gen%2].Load(); log != nil {
   268  		// Note: we don't pass a tag pointer here (how should profiling tags
   269  		// interact with the execution tracer?), but if we did we'd need to be
   270  		// careful about write barriers. See the long comment in profBuf.write.
   271  		log.write(nil, int64(now), hdr[:], stk)
   272  	}
   273  
   274  	trace.signalLock.Store(0)
   275  
   276  	// Release the seqlock if we acquired it earlier.
   277  	if locked {
   278  		mp.trace.seqlock.Add(1)
   279  	}
   280  }
   281  

View as plain text