| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build goexperiment.exectracer2 |
| |
| // Trace time and clock. |
| |
| package runtime |
| |
| import "internal/goarch" |
| |
| // Timestamps in trace are produced through either nanotime or cputicks |
| // and divided by traceTimeDiv. nanotime is used everywhere except on |
| // platforms where osHasLowResClock is true, because the system clock |
| // isn't granular enough to get useful information out of a trace in |
| // many cases. |
| // |
| // This makes absolute values of timestamp diffs smaller, and so they are |
| // encoded in fewer bytes. |
| // |
| // The target resolution in all cases is 64 nanoseconds. |
| // This is based on the fact that fundamentally the execution tracer won't emit |
| // events more frequently than roughly every 200 ns or so, because that's roughly |
| // how long it takes to call through the scheduler. |
| // We could be more aggressive and bump this up to 128 ns while still getting |
| // useful data, but the extra bit doesn't save us that much and the headroom is |
| // nice to have. |
| // |
| // Hitting this target resolution is easy in the nanotime case: just pick a |
| // division of 64. In the cputicks case it's a bit more complex. |
| // |
| // For x86, on a 3 GHz machine, we'd want to divide by 3*64 to hit our target. |
| // To keep the division operation efficient, we round that up to 4*64, or 256. |
| // Given what cputicks represents, we use this on all other platforms except |
| // for PowerPC. |
| // The suggested increment frequency for PowerPC's time base register is |
| // 512 MHz according to Power ISA v2.07 section 6.2, so we use 32 on ppc64 |
| // and ppc64le. |
| const traceTimeDiv = (1-osHasLowResClockInt)*64 + osHasLowResClockInt*(256-224*(goarch.IsPpc64|goarch.IsPpc64le)) |
| |
| // traceTime represents a timestamp for the trace. |
| type traceTime uint64 |
| |
| // traceClockNow returns a monotonic timestamp. The clock this function gets |
| // the timestamp from is specific to tracing, and shouldn't be mixed with other |
| // clock sources. |
| // |
| // nosplit because it's called from exitsyscall, which is nosplit. |
| // |
| //go:nosplit |
| func traceClockNow() traceTime { |
| if osHasLowResClock { |
| return traceTime(cputicks() / traceTimeDiv) |
| } |
| return traceTime(nanotime() / traceTimeDiv) |
| } |
| |
| // traceClockUnitsPerSecond estimates the number of trace clock units per |
| // second that elapse. |
| func traceClockUnitsPerSecond() uint64 { |
| if osHasLowResClock { |
| // We're using cputicks as our clock, so we need a real estimate. |
| return uint64(ticksPerSecond() / traceTimeDiv) |
| } |
| // Our clock is nanotime, so it's just the constant time division. |
| // (trace clock units / nanoseconds) * (1e9 nanoseconds / 1 second) |
| return uint64(1.0 / float64(traceTimeDiv) * 1e9) |
| } |
| |
| // traceFrequency writes a batch with a single EvFrequency event. |
| // |
| // freq is the number of trace clock units per second. |
| func traceFrequency(gen uintptr) { |
| w := unsafeTraceWriter(gen, nil) |
| |
| // Ensure we have a place to write to. |
| w, _ = w.ensure(1 + traceBytesPerNumber /* traceEvFrequency + frequency */) |
| |
| // Write out the string. |
| w.byte(byte(traceEvFrequency)) |
| w.varint(traceClockUnitsPerSecond()) |
| |
| // Immediately flush the buffer. |
| systemstack(func() { |
| lock(&trace.lock) |
| traceBufFlush(w.traceBuf, gen) |
| unlock(&trace.lock) |
| }) |
| } |