Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[windows][wkint-492] Update ETW with new functions; add ability to get ETW stats #25494

Merged
merged 10 commits into from
May 14, 2024
34 changes: 32 additions & 2 deletions comp/etw/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,33 @@ type ProviderConfiguration struct {
// ProviderConfigurationFunc is a function used to configure a provider
type ProviderConfigurationFunc func(cfg *ProviderConfiguration)

// SessionConfiguration is a structure containing all the configuration options for an ETW session
type SessionConfiguration struct {
//MinBuffers is the minimum number of trace buffers for ETW to allocate for a session. The default is 0
MinBuffers uint32
//MaxBuffers is the maximum number of buffers for ETW to allocate. The default is 0
MaxBuffers uint32
}

// SessionStatistics contains statistics about the session
type SessionStatistics struct {
// NumberOfBuffers is the number of buffers allocated for the session
NumberOfBuffers uint32
// FreeBuffers is the number of buffers that are free
FreeBuffers uint32
// EventsLost is the number of events not recorded
EventsLost uint32
// BuffersWritten is the number of buffers written
BuffersWritten uint32
// LogBuffersLost is the number of log buffers lost
LogBuffersLost uint32
// RealTimeBuffersLost is the number of real-time buffers lost
RealTimeBuffersLost uint32
}

// SessionConfigurationFunc is a function used to configure a session
type SessionConfigurationFunc func(cfg *SessionConfiguration)

// Session represents an ETW session. A session can have multiple tracing providers enabled.
type Session interface {
// ConfigureProvider configures a particular ETW provider identified by its GUID for this session.
Expand All @@ -180,12 +207,15 @@ type Session interface {
// StopTracing stops all tracing activities.
// It's not possible to use the session anymore after a call to StopTracing.
StopTracing() error

// GetSessionStatistics returns statistics about the session
GetSessionStatistics() (SessionStatistics, error)
}

// Component offers a way to create ETW tracing sessions with a given name.
type Component interface {
NewSession(sessionName string) (Session, error)
NewWellKnownSession(sessionName string) (Session, error)
NewSession(sessionName string, f SessionConfigurationFunc) (Session, error)
NewWellKnownSession(sessionName string, f SessionConfigurationFunc) (Session, error)
}

// UserData offers a wrapper around the UserData field of an ETW event.
Expand Down
8 changes: 4 additions & 4 deletions comp/etw/impl/etwImpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ func NewEtw() (etw.Component, error) {
type etwImpl struct {
}

func (s *etwImpl) NewSession(sessionName string) (etw.Session, error) {
session, err := createEtwSession(sessionName)
func (s *etwImpl) NewSession(sessionName string, f etw.SessionConfigurationFunc) (etw.Session, error) {
session, err := createEtwSession(sessionName, f)
if err != nil {
return nil, err
}
return session, nil
}

func (s *etwImpl) NewWellKnownSession(sessionName string) (etw.Session, error) {
session, err := createWellKnownEtwSession(sessionName)
func (s *etwImpl) NewWellKnownSession(sessionName string, f etw.SessionConfigurationFunc) (etw.Session, error) {
session, err := createWellKnownEtwSession(sessionName, f)
if err != nil {
return nil, err
}
Expand Down
70 changes: 57 additions & 13 deletions comp/etw/impl/etwSession.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type etwSession struct {
propertiesBuf []byte
providers map[windows.GUID]etw.ProviderConfiguration
utf16name []uint16
sessionConfig etw.SessionConfiguration
}

func (e *etwSession) ConfigureProvider(providerGUID windows.GUID, configurations ...etw.ProviderConfigurationFunc) {
Expand Down Expand Up @@ -151,6 +152,30 @@ func (e *etwSession) StopTracing() error {
return globalError
}

func (e *etwSession) GetSessionStatistics() (etw.SessionStatistics, error) {
var stats etw.SessionStatistics
// it is not clear if we can safely reuse the properties buffer here
// so we allocate a new one
_, ptp := initializeRealtimeSessionProperties(e)

ret := windows.Errno(C.ControlTraceW(
e.hSession,
nil,
ptp,
C.EVENT_TRACE_CONTROL_QUERY))
if ret != windows.ERROR_SUCCESS {
return stats, ret
}

stats.NumberOfBuffers = uint32(ptp.NumberOfBuffers)
stats.FreeBuffers = uint32(ptp.FreeBuffers)
stats.EventsLost = uint32(ptp.EventsLost)
stats.BuffersWritten = uint32(ptp.BuffersWritten)
stats.LogBuffersLost = uint32(ptp.LogBuffersLost)
stats.RealTimeBuffersLost = uint32(ptp.RealTimeBuffersLost)
return stats, nil
}

// deleteEtwSession deletes an ETW session by name, typically after a crash since we don't have access to the session
// handle anymore.
func deleteEtwSession(name string) error {
Expand Down Expand Up @@ -179,7 +204,7 @@ func deleteEtwSession(name string) error {
return ret
}

func createEtwSession(name string) (*etwSession, error) {
func createEtwSession(name string, f etw.SessionConfigurationFunc) (*etwSession, error) {
_ = deleteEtwSession(name)

utf16SessionName, err := windows.UTF16FromString(name)
Expand All @@ -193,16 +218,16 @@ func createEtwSession(name string) (*etwSession, error) {
if err != nil {
return nil, fmt.Errorf("incorrect session name; %w", err)
}
sessionNameSize := (len(utf16SessionName) * int(unsafe.Sizeof(utf16SessionName[0])))
bufSize := int(unsafe.Sizeof(C.EVENT_TRACE_PROPERTIES{})) + sessionNameSize
propertiesBuf := make([]byte, bufSize)

pProperties := (C.PEVENT_TRACE_PROPERTIES)(unsafe.Pointer(&propertiesBuf[0]))
pProperties.Wnode.BufferSize = C.ulong(bufSize)
pProperties.Wnode.ClientContext = 1
pProperties.Wnode.Flags = C.WNODE_FLAG_TRACED_GUID
// get any caller supplied configuration
if f != nil {
f(&s.sessionConfig)
if s.sessionConfig.MaxBuffers != 0 && s.sessionConfig.MaxBuffers < s.sessionConfig.MinBuffers {
return nil, fmt.Errorf("max buffers must be greater than or equal to min buffers")
}
}

pProperties.LogFileMode = C.EVENT_TRACE_REAL_TIME_MODE
propertiesBuf, pProperties := initializeRealtimeSessionProperties(s)

ret := windows.Errno(C.StartTraceW(
&s.hSession,
Expand All @@ -223,7 +248,7 @@ func createEtwSession(name string) (*etwSession, error) {
return nil, fmt.Errorf("StartTraceW failed; %w", err)
}

func createWellKnownEtwSession(name string) (*etwSession, error) {
func createWellKnownEtwSession(name string, f etw.SessionConfigurationFunc) (*etwSession, error) {
utf16SessionName, err := windows.UTF16FromString(name)
if err != nil {
return nil, fmt.Errorf("incorrect session name; %w", err)
Expand All @@ -234,7 +259,21 @@ func createWellKnownEtwSession(name string) (*etwSession, error) {
utf16name: utf16SessionName,
wellKnown: true,
}
sessionNameSize := (len(utf16SessionName) * int(unsafe.Sizeof(utf16SessionName[0])))

// get any caller supplied configuration
if f != nil {
f(&s.sessionConfig)
if s.sessionConfig.MaxBuffers != 0 && s.sessionConfig.MaxBuffers < s.sessionConfig.MinBuffers {
return nil, fmt.Errorf("max buffers must be greater than or equal to min buffers")
}
}

s.propertiesBuf, _ = initializeRealtimeSessionProperties(s)
return s, nil
}

func initializeRealtimeSessionProperties(s *etwSession) ([]byte, C.PEVENT_TRACE_PROPERTIES) {
sessionNameSize := (len(s.utf16name) * int(unsafe.Sizeof(s.utf16name[0])))
bufSize := int(unsafe.Sizeof(C.EVENT_TRACE_PROPERTIES{})) + sessionNameSize
propertiesBuf := make([]byte, bufSize)

Expand All @@ -244,6 +283,11 @@ func createWellKnownEtwSession(name string) (*etwSession, error) {
pProperties.Wnode.Flags = C.WNODE_FLAG_TRACED_GUID

pProperties.LogFileMode = C.EVENT_TRACE_REAL_TIME_MODE
s.propertiesBuf = propertiesBuf
return s, nil
if s.sessionConfig.MaxBuffers > 0 {
pProperties.MaximumBuffers = C.ulong(s.sessionConfig.MaxBuffers)
}
if s.sessionConfig.MinBuffers > 0 {
pProperties.MinimumBuffers = C.ulong(s.sessionConfig.MinBuffers)
}
return propertiesBuf, pProperties
}
2 changes: 1 addition & 1 deletion comp/trace/etwtracer/etwtracerimpl/etwtracerimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ func (a *etwtracerimpl) start(_ context.Context) error {
a.log.Infof("Starting Datadog APM ETW tracer component")
var err error
etwSessionName := "Datadog APM ETW tracer"
a.session, err = a.etw.NewSession(etwSessionName)
a.session, err = a.etw.NewSession(etwSessionName, func(cfg *etw.SessionConfiguration) {})
if err != nil {
a.log.Errorf("Failed to create the ETW session '%s': %v", etwSessionName, err)
// Don't fail the Agent startup
Expand Down
2 changes: 1 addition & 1 deletion pkg/network/protocols/http/etw_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func NewEtwInterface(c *config.Config) (*EtwInterface, error) {
return nil, err
}

ei.session, err = etwcomp.NewSession(etwSessionName)
ei.session, err = etwcomp.NewSession(etwSessionName, func(cfg *etw.SessionConfiguration) {})
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/security/probe/probe_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ func (p *WindowsProbe) initEtwFIM() error {
if err != nil {
return err
}
p.fimSession, err = etwcomp.NewSession(etwSessionName)
p.fimSession, err = etwcomp.NewSession(etwSessionName, nil)

if err != nil {
return err
}
Expand Down