blob: 652612178ea76dd5d010dd86db4335c629f2ccb1 [file] [log] [blame]
// Copyright 2021 The Fuchsia 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 tracing
// This file implements a Go equivalent of:
// https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/system/ulib/trace-engine/context_api.cc
package trace
import (
"fmt"
"syscall/zx"
"unsafe"
)
// #include <lib/trace-engine/types.h>
// #include <zircon/types.h>
import "C"
// currentProcessKoid is assigned once by init() for efficiency.
var currentProcessKoid uint64
func GetCurrentProcessKoid() uint64 {
return currentProcessKoid
}
type stringEntryFlags uint32
const (
allocIndexAttempted stringEntryFlags = 1 << iota
allocIndexSucceeded
categoryChecked
categoryEnabled
)
type stringEntry struct {
stringLiteral string
index uint64
flags stringEntryFlags
}
type threadEntry struct {
threadKoid uint64
index uint64
}
// Fuchsia trace format.
// The binary format of trace records are described in
// https://fuchsia.googlesource.com/fuchsia/+/HEAD/docs/reference/tracing/trace-format.md
type ticksT uint64
type CounterID uint64
type AsyncID uint64
type FlowID uint64
const (
wordSize uint64 = 8 // bytes
wordSizeShift = 3 // bits
)
func roundUpToWords(nbytes uint64) uint64 {
return nbytes + ((wordSize - (nbytes % wordSize)) % wordSize)
}
type payload struct {
ptr *[MaxBufferSize]byte
offset uint64
}
func (c *context) newPayload(isDurable bool, nbytes uint64) (payload, bool) {
ptr, offset, ok := func() (*[MaxBufferSize]byte, uint64, bool) {
if isDurable && c.usingDurableBuffer() {
return c.allocDurableRecord(nbytes)
} else {
return c.allocRecord(nbytes)
}
}()
if !ok {
return payload{}, false
}
return payload{
ptr: ptr,
offset: offset,
}, true
}
func (p *payload) writeUint64(value uint64) {
nativeEndian.PutUint64((*p.ptr)[p.offset:], value)
p.offset += wordSize
}
var wordWithZeros [wordSize]byte
func (p *payload) writeBytes(value []byte) {
written := uint64(copy((*p.ptr)[p.offset:], value))
if l := uint64(len(value)); written != l {
panic(fmt.Sprintf("partial copy (%d out of %d)", written, l))
}
p.offset += written
if remainder := uint64(written) % wordSize; remainder != 0 {
padding := wordSize - remainder
written := uint64(copy((*p.ptr)[p.offset:], wordWithZeros[:padding]))
if written != padding {
panic(fmt.Sprintf("partial copy (%d out of %d)", written, padding))
}
p.offset += written
}
}
// Record Header.
type recordType int
const (
metaDataT recordType = 0
initializationT = 1
stringT = 2
threadT = 3
eventT = 4
blobT = 5
kernelObjectT = 6
contextSwitchT = 7
logT = 8
// LargeRecord uses a 32-bit size field.
largeRecordT = 15
)
const (
recordHeaderSize = wordSize
maxRecordSizeWords = 0xfff
maxRecordSizeBytes = maxRecordSizeWords << wordSizeShift
recordHeaderSizeShift = 4
)
func makeRecordHeader(typ recordType, nbytes uint64) uint64 {
// recordSize is i
return uint64(typ) | (nbytes>>wordSizeShift)<<recordHeaderSizeShift
}
// Arguments.
const (
argumentHeaderSize = wordSize
argumentHeaderSizeShift = 4
argumentHeaderNameShift = 16
argumentHeaderValueShift = 32
)
func makeArgumentHeader(typ argumentType, nbytes uint64, nameRef *stringRef) uint64 {
return uint64(typ) | (nbytes>>wordSizeShift)<<argumentHeaderSizeShift | nameRef.encodedValue<<argumentHeaderNameShift
}
type argumentType int
const (
nullArgT argumentType = C.TRACE_ARG_NULL
int32ArgT = C.TRACE_ARG_INT32
uint32ArgT = C.TRACE_ARG_UINT32
int64ArgT = C.TRACE_ARG_INT64
uint64ArgT = C.TRACE_ARG_UINT64
doubleArgT = C.TRACE_ARG_DOUBLE
stringArgT = C.TRACE_ARG_STRING
pointerArgT = C.TRACE_ARG_POINTER
koidArgT = C.TRACE_ARG_KOID
boolArgT = C.TRACE_ARG_BOOL
)
type argValue interface {
writeArgumentHeaderAndName(p *payload, nameRef *stringRef)
encodedSize() uint64
}
type argValueNull struct{}
func (argValueNull) writeArgumentHeaderAndName(p *payload, nameRef *stringRef) {
p.writeArgumentHeaderAndName(nullArgT, nameRef, 0, 0)
}
func (argValueNull) encodedSize() uint64 {
return 0
}
func makeArgValueNull() argValue {
return argValueNull{}
}
type argValueInt32 struct {
v int32
}
func (i32 argValueInt32) writeArgumentHeaderAndName(p *payload, nameRef *stringRef) {
p.writeArgumentHeaderAndName(int32ArgT, nameRef, 0, uint64(i32.v)<<argumentHeaderValueShift)
}
func (i32 argValueInt32) encodedSize() uint64 {
return 0
}
func makeArgValueInt32(val int32) argValue {
return argValueInt32{v: val}
}
type argValueUint32 struct {
v uint32
}
func (u32 argValueUint32) writeArgumentHeaderAndName(p *payload, nameRef *stringRef) {
p.writeArgumentHeaderAndName(uint32ArgT, nameRef, 0, uint64(u32.v)<<argumentHeaderValueShift)
}
func (u32 argValueUint32) encodedSize() uint64 {
return 0
}
func makeArgValueUint32(val uint32) argValue {
return argValueUint32{v: val}
}
type argValueInt64 struct {
v int64
}
func (i64 argValueInt64) writeArgumentHeaderAndName(p *payload, nameRef *stringRef) {
p.writeArgumentHeaderAndName(int64ArgT, nameRef, wordSize, 0)
p.writeUint64(uint64(i64.v))
}
func (i64 argValueInt64) encodedSize() uint64 {
return wordSize
}
func makeArgValueInt64(val int64) argValue {
return argValueInt64{v: val}
}
type argValueUint64 struct {
v uint64
}
func (u64 argValueUint64) writeArgumentHeaderAndName(p *payload, nameRef *stringRef) {
p.writeArgumentHeaderAndName(uint64ArgT, nameRef, wordSize, 0)
p.writeUint64(u64.v)
}
func (u64 argValueUint64) encodedSize() uint64 {
return wordSize
}
func makeArgValueUint64(val uint64) argValue {
return argValueUint64{v: val}
}
type argValueDouble struct {
v float64
}
func (d argValueDouble) writeArgumentHeaderAndName(p *payload, nameRef *stringRef) {
p.writeArgumentHeaderAndName(doubleArgT, nameRef, wordSize, 0)
p.writeUint64(*(*uint64)(unsafe.Pointer(&d.v)))
}
func (d argValueDouble) encodedSize() uint64 {
return wordSize
}
func makeArgValueDouble(val float64) argValue {
return argValueDouble{v: val}
}
type argValueString struct {
v *stringRef
}
func (s argValueString) writeArgumentHeaderAndName(p *payload, nameRef *stringRef) {
p.writeArgumentHeaderAndName(stringArgT, nameRef,
sizeOfEncodedStringRef(s.v),
s.v.encodedValue<<argumentHeaderValueShift)
p.writeStringRef(s.v)
}
func (s argValueString) encodedSize() uint64 {
return sizeOfEncodedStringRef(s.v)
}
func makeArgValueString(val stringRef) argValue {
return argValueString{v: &val}
}
type argValuePointer struct {
v uintptr
}
func (pt argValuePointer) writeArgumentHeaderAndName(p *payload, nameRef *stringRef) {
p.writeArgumentHeaderAndName(pointerArgT, nameRef, wordSize, 0)
p.writeUint64(uint64(pt.v))
}
func (pt argValuePointer) encodedSize() uint64 {
return wordSize
}
func makeArgValuePointer(val uintptr) argValue {
return argValuePointer{v: val}
}
type argValueKoid struct {
v uint64
}
func (k argValueKoid) writeArgumentHeaderAndName(p *payload, nameRef *stringRef) {
p.writeArgumentHeaderAndName(koidArgT, nameRef, wordSize, 0)
p.writeUint64(k.v)
}
func (k argValueKoid) encodedSize() uint64 {
return wordSize
}
func makeArgValueKoid(val uint64) argValue {
return argValueKoid{v: val}
}
type argValueBool struct {
v bool
}
func (b argValueBool) writeArgumentHeaderAndName(p *payload, nameRef *stringRef) {
var boolValue uint64
if b.v {
boolValue = uint64(1)
}
p.writeArgumentHeaderAndName(boolArgT, nameRef, 0, boolValue<<argumentHeaderValueShift)
}
func (b argValueBool) encodedSize() uint64 {
return 0
}
func makeArgValueBool(val bool) argValue {
return argValueBool{v: val}
}
// Arg is a typed key value pair.
// Many trace record types can optionally include an array of Args to provide
// additional information.
// TODO(https://fxbug.dev/42171138): Export APIs to use Arg.
type Arg struct {
NameRef stringRef
Value argValue
}
func sizeOfEncodedArg(arg *Arg) uint64 {
return argumentHeaderSize + sizeOfEncodedStringRef(&arg.NameRef) +
arg.Value.encodedSize()
}
func sizeOfEncodedArgs(args []Arg) uint64 {
total := uint64(0)
for _, arg := range args {
total += sizeOfEncodedArg(&arg)
}
return total
}
func (p *payload) writeArgumentHeaderAndName(typ argumentType, nameRef *stringRef, contentSize uint64, headerBits uint64) {
argumentSize := argumentHeaderSize + sizeOfEncodedStringRef(nameRef) + contentSize
p.writeUint64(makeArgumentHeader(typ, argumentSize, nameRef) | headerBits)
p.writeStringRef(nameRef)
}
func (p *payload) writeArg(arg *Arg) {
arg.Value.writeArgumentHeaderAndName(p, &arg.NameRef)
}
func (p *payload) writeArgs(args []Arg) {
for i := range args {
p.writeArg(&args[i])
}
}
// Initialization record.
const initializationRecordSize = recordHeaderSize + wordSize
func (c *context) writeInitializationRecord(ticksPerSecond uint64) {
p, ok := c.newPayload(true, initializationRecordSize)
if !ok {
return
}
p.writeUint64(makeRecordHeader(initializationT, initializationRecordSize))
p.writeUint64(ticksPerSecond)
}
// StringRef.
type stringRef struct {
encodedValue uint64
inlineString []byte
}
const (
encodedStringRefEmpty uint64 = 0
encodedStringRefInlineFlag = 0x8000
encodedStringRefLengthMask = 0x7fff
encodedStringRefMaxLength = 32000
encodedStringRefMinIndex = 1
encodedStringRefMaxIndex = 0x7fff
)
func makeEmptyStringRef() stringRef {
return stringRef{
encodedValue: encodedStringRefEmpty,
inlineString: nil,
}
}
func makeInlineStringRef(str []byte) stringRef {
l := uint64(len(str))
if l > encodedStringRefMaxLength {
l = encodedStringRefMaxLength
}
return stringRef{
encodedValue: encodedStringRefInlineFlag | l,
inlineString: str[:l],
}
}
func makeIndexedStringRef(index uint64) stringRef {
return stringRef{
encodedValue: index,
inlineString: nil,
}
}
func (r *stringRef) IsInlineStringRef() bool {
return (r.encodedValue & encodedStringRefInlineFlag) != 0
}
func sizeOfEncodedStringRef(r *stringRef) uint64 {
if r.IsInlineStringRef() {
return roundUpToWords(uint64(len(r.inlineString)))
}
return 0
}
func (p *payload) writeStringRef(r *stringRef) {
if r.IsInlineStringRef() {
p.writeBytes(r.inlineString)
}
}
func (c *context) checkCategory(category string) bool {
return c.handler.IsCategoryEnabled(category)
}
// String record.
const (
stringRecordStringIndexShift = 16
stringRecordStringLengthShift = 32
)
func (c *context) writeStringRecord(isDurable bool, index uint64, str []byte) bool {
l := uint64(len(str))
if l > encodedStringRefMaxLength {
l = encodedStringRefMaxLength
}
recordSize := recordHeaderSize + roundUpToWords(l)
p, ok := c.newPayload(isDurable, recordSize)
if !ok {
return false
}
p.writeUint64(makeRecordHeader(stringT, recordSize) |
uint64(index<<stringRecordStringIndexShift) |
uint64(l<<stringRecordStringLengthShift))
p.writeBytes(str[:l])
return true
}
func (c *context) cacheStringEntryLocked(s string) *stringEntry {
if entry, ok := c.mu.stringTable[s]; ok {
return entry
}
entry := &stringEntry{
stringLiteral: s,
}
c.mu.stringTable[s] = entry
return entry
}
func (c *context) registerStringLocked(s string, checkCategory bool) (stringRef, bool) {
entry := c.cacheStringEntryLocked(s)
if checkCategory {
if entry.flags&categoryChecked == 0 {
if c.checkCategory(s) {
entry.flags |= categoryChecked | categoryEnabled
}
}
if entry.flags&categoryEnabled == 0 {
// If the category is not enabled, return immediately.
return stringRef{}, false
}
}
if entry.flags&allocIndexAttempted == 0 {
entry.flags |= allocIndexAttempted
if index, ok := c.allocStringIndexLocked(); ok && c.writeStringRecord(true, index, []byte(s)) {
entry.flags |= allocIndexSucceeded
entry.index = index
}
}
if entry.flags&allocIndexSucceeded != 0 {
return makeIndexedStringRef(entry.index), true
} else {
return makeInlineStringRef([]byte(s)), true
}
}
// ThreadRef.
type threadRef struct {
encodedValue uint64
processKoid uint64
threadKoid uint64
}
const (
encodedThreadRefInline uint64 = 0
encodedThreadRefMinIndex = 1
encodedThreadRefMaxIndex = 0xff
)
const KoidInvalid uint64 = C.ZX_KOID_INVALID
func makeUnknownThreadRef() threadRef {
return threadRef{
encodedValue: encodedThreadRefInline,
processKoid: KoidInvalid,
threadKoid: KoidInvalid,
}
}
func makeInlineThreadRef(processKoid uint64, threadKoid uint64) threadRef {
return threadRef{
encodedValue: encodedThreadRefInline,
processKoid: processKoid,
threadKoid: threadKoid,
}
}
func makeIndexedThreadRef(index uint64) threadRef {
return threadRef{
encodedValue: index,
processKoid: KoidInvalid,
threadKoid: KoidInvalid,
}
}
func (r *threadRef) IsInlineThreadRef() bool {
return (r.encodedValue == encodedThreadRefInline) &&
(r.processKoid != KoidInvalid || r.threadKoid != KoidInvalid)
}
func (r *threadRef) IsUnknownThreadRef() bool {
return (r.encodedValue == encodedThreadRefInline) &&
(r.processKoid == KoidInvalid && r.threadKoid == KoidInvalid)
}
func sizeOfEncodedThreadRef(r *threadRef) uint64 {
if r.IsInlineThreadRef() || r.IsUnknownThreadRef() {
return 2 * wordSize
}
return 0
}
func (p *payload) writeThreadRef(r *threadRef) {
if r.IsInlineThreadRef() || r.IsUnknownThreadRef() {
p.writeUint64(r.processKoid)
p.writeUint64(r.threadKoid)
}
}
// Thread record.
const threadRecordThreadIndexShift = 16
func (c *context) writeThreadRecord(index uint64, processKoid uint64, threadKoid uint64) bool {
recordSize := recordHeaderSize + 2*wordSize
p, ok := c.newPayload(true, recordSize)
if !ok {
return false
}
p.writeUint64(makeRecordHeader(threadT, recordSize) |
uint64(index<<threadRecordThreadIndexShift))
p.writeUint64(processKoid)
p.writeUint64(threadKoid)
return true
}
func (c *context) registerCurrentThreadLocked() threadRef {
threadKoid := zx.GetCurrentThreadKoid()
if entry, ok := c.mu.threadTable[threadKoid]; ok {
return makeIndexedThreadRef(entry.index)
}
processKoid := GetCurrentProcessKoid()
if index, ok := c.allocThreadIndexLocked(); ok && c.writeThreadRecord(index, processKoid, threadKoid) {
entry := &threadEntry{
threadKoid: threadKoid,
index: index,
}
c.mu.threadTable[threadKoid] = entry
return makeIndexedThreadRef(entry.index)
} else {
return makeInlineThreadRef(processKoid, threadKoid)
}
}
// Event record.
type eventType int
const (
instantT eventType = 0
counterT = 1
durationBeginT = 2
durationEndT = 3
durationCompleteT = 4
asyncBeginT = 5
asyncInstantT = 6
asyncEndT = 7
flowBeginT = 8
flowStepT = 9
flowEndT = 10
)
type EventScope int
const (
ScopeThread EventScope = C.TRACE_SCOPE_THREAD
ScopeProcess = C.TRACE_SCOPE_PROCESS
ScopeGlobal = C.TRACE_SCOPE_GLOBAL
)
const (
eventRecordEventTypeShift = 16
eventRecordArgumentCountShift = 20
eventRecordThreadRefShift = 24
eventRecordCategoryStringRefShift = 32
eventRecordNameStringRefShift = 48
)
func (c *context) writeEventRecordBase(typ eventType, eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, args []Arg, contentSize uint64) (payload, bool) {
recordSize := recordHeaderSize + wordSize + sizeOfEncodedThreadRef(threadRef) +
sizeOfEncodedStringRef(categoryRef) + sizeOfEncodedStringRef(nameRef) +
sizeOfEncodedArgs(args) + contentSize
p, ok := c.newPayload(false, recordSize)
if !ok {
return payload{}, false
}
p.writeUint64(makeRecordHeader(eventT, recordSize) |
uint64(typ<<eventRecordEventTypeShift) |
uint64(len(args)<<eventRecordArgumentCountShift) |
uint64(threadRef.encodedValue<<eventRecordThreadRefShift) |
uint64(categoryRef.encodedValue<<eventRecordCategoryStringRefShift) |
uint64(nameRef.encodedValue<<eventRecordNameStringRefShift))
p.writeUint64(uint64(eventTime))
p.writeThreadRef(threadRef)
p.writeStringRef(categoryRef)
p.writeStringRef(nameRef)
p.writeArgs(args)
return p, true
}
func (c *context) writeInstantEventRecord(eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, scope EventScope, args []Arg) {
p, ok := c.writeEventRecordBase(instantT, eventTime, threadRef, categoryRef, nameRef, args, wordSize)
if !ok {
return
}
p.writeUint64(uint64(scope))
}
func (c *context) writeCounterEventRecord(eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, counterID CounterID, args []Arg) {
p, ok := c.writeEventRecordBase(counterT, eventTime, threadRef, categoryRef, nameRef, args, wordSize)
if !ok {
return
}
p.writeUint64(uint64(counterID))
}
func (c *context) writeDurationEventRecord(startTime ticksT, endTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, args []Arg) {
p, ok := c.writeEventRecordBase(durationCompleteT, startTime, threadRef, categoryRef, nameRef, args, wordSize)
if !ok {
return
}
p.writeUint64(uint64(endTime))
}
func (c *context) writeDurationBeginEventRecord(eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, args []Arg) {
c.writeEventRecordBase(durationBeginT, eventTime, threadRef, categoryRef, nameRef, args, 0)
}
func (c *context) writeDurationEndEventRecord(eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, args []Arg) {
c.writeEventRecordBase(durationEndT, eventTime, threadRef, categoryRef, nameRef, args, 0)
}
func (c *context) writeAsyncBeginEventRecord(eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, asyncID AsyncID, args []Arg) {
p, ok := c.writeEventRecordBase(asyncBeginT, eventTime, threadRef, categoryRef, nameRef, args, wordSize)
if !ok {
return
}
p.writeUint64(uint64(asyncID))
}
func (c *context) writeAsyncInstantEventRecord(eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, asyncID AsyncID, args []Arg) {
p, ok := c.writeEventRecordBase(asyncInstantT, eventTime, threadRef, categoryRef, nameRef, args, wordSize)
if !ok {
return
}
p.writeUint64(uint64(asyncID))
}
func (c *context) writeAsyncEndEventRecord(eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, asyncID AsyncID, args []Arg) {
p, ok := c.writeEventRecordBase(asyncEndT, eventTime, threadRef, categoryRef, nameRef, args, wordSize)
if !ok {
return
}
p.writeUint64(uint64(asyncID))
}
func (c *context) writeFlowBeginEventRecord(eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, flowID FlowID, args []Arg) {
p, ok := c.writeEventRecordBase(flowBeginT, eventTime, threadRef, categoryRef, nameRef, args, wordSize)
if !ok {
return
}
p.writeUint64(uint64(flowID))
}
func (c *context) writeFlowStepEventRecord(eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, flowID FlowID, args []Arg) {
p, ok := c.writeEventRecordBase(flowStepT, eventTime, threadRef, categoryRef, nameRef, args, wordSize)
if !ok {
return
}
p.writeUint64(uint64(flowID))
}
func (c *context) writeFlowEndEventRecord(eventTime ticksT, threadRef *threadRef, categoryRef *stringRef, nameRef *stringRef, flowID FlowID, args []Arg) {
p, ok := c.writeEventRecordBase(flowEndT, eventTime, threadRef, categoryRef, nameRef, args, wordSize)
if !ok {
return
}
p.writeUint64(uint64(flowID))
}
// Blob record.
type BlobType int
const (
BlobTypeData BlobType = 1
BlobTypeLastBranch = 2
)
const (
blobRecordBlobNameShift = 16
blobRecordBlobSizeShift = 32
blobRecordBlobTypeShift = 48
)
func (c *context) writeBlobRecord(typ BlobType, nameRef *stringRef, blob []byte) (payload, bool) {
blobSize := uint64(len(blob))
recordSize := recordHeaderSize + sizeOfEncodedStringRef(nameRef) + roundUpToWords(blobSize)
if recordSize > maxRecordSizeBytes {
return payload{}, false
}
p, ok := c.newPayload(false, recordSize)
if !ok {
return payload{}, false
}
p.writeUint64(makeRecordHeader(blobT, recordSize) |
uint64(typ<<blobRecordBlobTypeShift) |
uint64(nameRef.encodedValue<<blobRecordBlobNameShift) |
uint64(blobSize<<blobRecordBlobSizeShift))
p.writeStringRef(nameRef)
p.writeBytes(blob)
return p, true
}
func init() {
info, err := zx.ProcHandle.GetInfoHandleBasic()
if err != nil {
currentProcessKoid = KoidInvalid
return
}
currentProcessKoid = info.Koid
}