blob: 501a62c2e5b799bcbbcc5e80a59b81a30d92d490 [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/engine.cc
package trace
import (
"errors"
"fmt"
"sync/atomic"
"syscall/zx"
"time"
syslog "go.fuchsia.dev/fuchsia/src/lib/syslog/go"
)
const tag = "trace-engine"
const (
Stopped uint64 = iota
Started
Stopping
)
// gState holds the state of the trace engine.
var gState uint64
// gContext holds the pointer to the current Context.
var gContext *context
// gContextRefs maintains the reference count for the current Context. There are
// actually two separate counters allocated in one word so that the two counter
// values are retrieved with one atomic action:
//
// |63 ... 8| = buffer acquisition count
// |7 ... 0| = prolonged acquisition count
//
// Buffer acquisition count is updated by acquireContext/releaseContext when a
// trace site writes to the trace buffer. Prolonged acquisition count is
// updated by Provider via EngineStart/EngineStop.
//
// When the engine is stopped, both counters are 0. If both counters are 0,
// acquireContext cannot update the counter. Only EngineStart can update the
// counter if both counters are 0.
var gContextRefs int64
const (
prolongedCounterShift = 0
prolongedCounterIncrement = 1 << prolongedCounterShift
maxProlongedCounter = 127
prolongedCounterMask = 0xff
bufferCounterShift = 8
bufferCounterIncrement = 1 << bufferCounterShift
)
var (
ErrNotSupported = errors.New("not supported")
)
func getProlongedContextRefs() int64 {
return atomic.LoadInt64(&gContextRefs) & prolongedCounterMask
}
func getBufferContextRefs() int64 {
return atomic.LoadInt64(&gContextRefs) >> bufferCounterShift
}
// EngineState returns the current state of the trace engine.
func EngineState() uint64 {
return atomic.LoadUint64(&gState)
}
// EngineInitialize is called by Provider to initialize the trace engine.
// (We assume Engine{Initialize,Start,Stop,Terminate} are not called concurrently)
func EngineInitialize(start uintptr, size uint64, mode BufferingMode, handler Handler) error {
switch mode {
case Oneshot:
// do nothing
case Circular:
// TODO(https://fxbug.dev/42158370): Support circular mode.
return fmt.Errorf("Circular mode (%d): %w", mode, ErrNotSupported)
case Streaming:
// TODO(https://fxbug.dev/42158372): Support streaming mode.
return fmt.Errorf("Streaming mode (%d): %w", mode, ErrNotSupported)
default:
panic(fmt.Sprintf("unknown mode (%d)", mode))
}
gContext = newContext(start, size, mode, handler)
gContext.clearEntireBuffer()
gContext.writeInitializationRecord(uint64(zx.Sys_ticks_per_second()))
return nil
}
// EngineStart is called by Provider when the trace is started.
// (We assume Engine{Initialize,Start,Stop,Terminate} are not called concurrently)
func EngineStart(startMode StartMode) error {
switch state := atomic.LoadUint64(&gState); state {
case Started, Stopping:
return fmt.Errorf("Engine is not stopped (state = %d)", atomic.LoadUint64(&gState))
case Stopped:
// do nothing
default:
panic(fmt.Sprintf("unknown state (%d)", state))
}
atomic.StoreUint64(&gState, Started)
gContext.handler.TraceStarted()
switch startMode {
case ClearEntireBuffer:
gContext.clearEntireBuffer()
gContext.writeInitializationRecord(uint64(zx.Sys_ticks_per_second()))
case ClearNonDurableBuffer:
gContext.clearRollingBuffers()
gContext.writeInitializationRecord(uint64(zx.Sys_ticks_per_second()))
case RetainBuffer:
// do nothing.
default:
panic(fmt.Sprintf("unknown startMode (%d)", startMode))
}
atomic.AddInt64(&gContextRefs, prolongedCounterIncrement)
return nil
}
// EngineStop is called by Provider when the trace is stopped.
// (We assume Engine{Initialize,Start,Stop,Terminate} are not called concurrently)
func EngineStop() {
switch state := atomic.LoadUint64(&gState); state {
case Stopping, Stopped:
return
case Started:
// do nothing
default:
panic(fmt.Sprintf("unknown state (%d)", state))
}
engineStopCommon()
gContext.handler.TraceStopped()
}
var engineStopErrorCounter uint64
func engineStopCommon() {
switch state := atomic.LoadUint64(&gState); state {
case Stopped:
return
case Started:
atomic.AddInt64(&gContextRefs, -prolongedCounterIncrement)
atomic.StoreUint64(&gState, Stopping)
case Stopping:
// do nothing.
default:
panic(fmt.Sprintf("unknown state (%d)", state))
}
// Wait until all clients call releaseContext.
interval := time.Millisecond
maxElapsedTime := time.Second
elapsedTime := time.Duration(0)
for atomic.LoadInt64(&gContextRefs) > 0 {
if elapsedTime >= maxElapsedTime {
atomic.AddUint64(&engineStopErrorCounter, 1)
_ = syslog.WarnTf(tag, "EngineStop: gContextRefs = %d (want: 0)", atomic.LoadInt64(&gContextRefs))
break
}
time.Sleep(interval)
elapsedTime += interval
interval *= 2
}
atomic.StoreInt64(&gContextRefs, 0)
atomic.StoreUint64(&gState, Stopped)
gContext.UpdateBufferHeaderAfterStopped()
}
// EngineTerminate is called by Provider when the trace is terminated.
// (We assume Engine{Initialize,Start,Stop,Terminate} are not called concurrently)
func EngineTerminate() {
switch state := atomic.LoadUint64(&gState); state {
case Started, Stopping:
engineStopCommon()
case Stopped:
// do nothing
default:
panic(fmt.Sprintf("unknown state (%d)", state))
}
gContext.handler.TraceTerminated()
gContext = nil
}
// acquireContext is called from the trace sites when they start recording
// events.
func acquireContext() (*context, bool) {
// Check if we could write into the trace buffer. gContextRef has two
// counters inside. prolongedCounter is incremented when the trace is
// started and decremented when the trace is stopped. Another is the
// buffer reference counter. If both counters are zero, we can't write
// into the buffer. See the comments for gContextRefs for more details.
if atomic.LoadInt64(&gContextRefs) == 0 {
return nil, false
}
// Increment the buffer reference counter.
atomic.AddInt64(&gContextRefs, bufferCounterIncrement)
return gContext, true
}
// releaseContext is called by the trace sites when they finished recording
// events.
func releaseContext() {
// Decrement the buffer reference counter.
atomic.AddInt64(&gContextRefs, -bufferCounterIncrement)
}