Skip to content

Commit c92d942

Browse files
mergify[bot]intxgo
andauthored
[winlogbeat] performance improvment; avoid rendering event message twice (#39544) (#39573)
* wineventlog performance improvment; avoid rendering message twice * ignore missing or mismatched parameter values * add comment * changelog * actually increase the buffer (cherry picked from commit d2ebffe) Co-authored-by: Leszek Kubik <[email protected]>
1 parent e090084 commit c92d942

File tree

3 files changed

+59
-32
lines changed

3 files changed

+59
-32
lines changed

CHANGELOG.next.asciidoc

+1
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff]
299299

300300
*Winlogbeat*
301301

302+
- Use fixed size buffer at first pass for event parsing, improving throughput {issue}39530[39530] {pull}39544[39544]
302303

303304
*Functionbeat*
304305

winlogbeat/sys/wineventlog/format_message.go

+28-12
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,39 @@ func evtFormatMessage(metadataHandle EvtHandle, eventHandle EvtHandle, messageID
7575
valuesPtr = &values[0]
7676
}
7777

78-
// Determine the buffer size needed (given in WCHARs).
79-
var bufferUsed uint32
80-
err := _EvtFormatMessage(metadataHandle, eventHandle, messageID, valuesCount, valuesPtr, messageFlag, 0, nil, &bufferUsed)
81-
if err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // This is an errno.
82-
return "", fmt.Errorf("failed in EvtFormatMessage: %w", err)
83-
}
78+
// best guess render buffer size, 16KB, to avoid rendering message twice in most cases
79+
const bestGuessRenderBufferSize = 1 << 14
80+
81+
// EvtFormatMessage operates with WCHAR buffer, assuming the size of the buffer in characters.
82+
// https://docs.microsoft.com/en-us/windows/win32/api/winevt/nf-winevt-evtformatmessage
83+
var bufferNeeded uint32
84+
bufferSize := uint32(bestGuessRenderBufferSize / 2)
8485

8586
// Get a buffer from the pool and adjust its length.
8687
bb := sys.NewPooledByteBuffer()
8788
defer bb.Free()
88-
// The documentation for EventFormatMessage specifies that the buffer is
89-
// requested "in characters", and the buffer itself is LPWSTR, meaning the
90-
// characters are WCHAR so double the value.
91-
// https://docs.microsoft.com/en-us/windows/win32/api/winevt/nf-winevt-evtformatmessage
92-
bb.Reserve(int(bufferUsed * 2))
89+
bb.Reserve(int(bufferSize * 2))
90+
91+
err := _EvtFormatMessage(metadataHandle, eventHandle, messageID, valuesCount, valuesPtr, messageFlag, bufferSize, bb.PtrAt(0), &bufferNeeded)
92+
switch err { //nolint:errorlint // This is an errno or nil.
93+
case nil: // OK
94+
return sys.UTF16BytesToString(bb.Bytes())
95+
96+
// Ignore some errors so it can tolerate missing or mismatched parameter values.
97+
case windows.ERROR_EVT_UNRESOLVED_VALUE_INSERT,
98+
windows.ERROR_EVT_UNRESOLVED_PARAMETER_INSERT,
99+
windows.ERROR_EVT_MAX_INSERTS_REACHED:
100+
return sys.UTF16BytesToString(bb.Bytes())
101+
102+
case windows.ERROR_INSUFFICIENT_BUFFER:
103+
bb.Reserve(int(bufferNeeded * 2))
104+
bufferSize = bufferNeeded
105+
106+
default:
107+
return "", fmt.Errorf("failed in EvtFormatMessage: %w", err)
108+
}
93109

94-
err = _EvtFormatMessage(metadataHandle, eventHandle, messageID, valuesCount, valuesPtr, messageFlag, bufferUsed, bb.PtrAt(0), &bufferUsed)
110+
err = _EvtFormatMessage(metadataHandle, eventHandle, messageID, valuesCount, valuesPtr, messageFlag, bufferSize, bb.PtrAt(0), &bufferNeeded)
95111
switch err { //nolint:errorlint // This is an errno or nil.
96112
case nil: // OK
97113

winlogbeat/sys/wineventlog/wineventlog_windows.go

+30-20
Original file line numberDiff line numberDiff line change
@@ -239,15 +239,9 @@ func RenderEvent(
239239

240240
// Only a single string is returned when rendering XML.
241241
err = FormatEventString(EvtFormatMessageXml,
242-
eventHandle, providerName, EvtHandle(publisherHandle), lang, out)
242+
eventHandle, providerName, EvtHandle(publisherHandle), lang, renderBuf, out)
243243
// Recover by rendering the XML without the RenderingInfo (message string).
244244
if err != nil {
245-
// Do not try to recover from InsufficientBufferErrors because these
246-
// can be retried with a larger buffer.
247-
if errors.Is(err, sys.InsufficientBufferError{}) {
248-
return err
249-
}
250-
251245
err = RenderEventXML(eventHandle, renderBuf, out)
252246
}
253247

@@ -256,8 +250,8 @@ func RenderEvent(
256250

257251
// Message reads the event data associated with the EvtHandle and renders
258252
// and returns the message only.
259-
func Message(h EvtHandle, buf []byte, pubHandleProvider func(string) sys.MessageFiles) (message string, err error) {
260-
providerName, err := evtRenderProviderName(buf, h)
253+
func Message(h EvtHandle, renderBuf []byte, pubHandleProvider func(string) sys.MessageFiles) (message string, err error) {
254+
providerName, err := evtRenderProviderName(renderBuf, h)
261255
if err != nil {
262256
return "", err
263257
}
@@ -386,12 +380,15 @@ func Close(h EvtHandle) error {
386380
// publisherHandle is a handle to the publisher's metadata as provided by
387381
// EvtOpenPublisherMetadata.
388382
// lang is the language ID.
383+
// renderBuf is a scratch buffer to render the message, if not provided or of
384+
// insufficient size then a buffer from a system pool will be used
389385
func FormatEventString(
390386
messageFlag EvtFormatMessageFlag,
391387
eventHandle EvtHandle,
392388
publisher string,
393389
publisherHandle EvtHandle,
394390
lang uint32,
391+
renderBuf []byte,
395392
out io.Writer,
396393
) error {
397394
// Open a publisher handle if one was not provided.
@@ -405,29 +402,42 @@ func FormatEventString(
405402
defer _EvtClose(ph) //nolint:errcheck // This is just a resource release.
406403
}
407404

408-
// Determine the buffer size needed (given in WCHARs).
409-
var bufferUsed uint32
410-
err := _EvtFormatMessage(ph, eventHandle, 0, 0, nil, messageFlag, 0, nil, &bufferUsed)
411-
if err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // This is an errno.
405+
var bufferPtr *byte
406+
if renderBuf != nil {
407+
bufferPtr = &renderBuf[0]
408+
}
409+
410+
// EvtFormatMessage operates with WCHAR buffer, assuming the size of the buffer in characters.
411+
// https://docs.microsoft.com/en-us/windows/win32/api/winevt/nf-winevt-evtformatmessage
412+
var bufferNeeded uint32
413+
bufferSize := uint32(len(renderBuf) / 2)
414+
415+
err := _EvtFormatMessage(ph, eventHandle, 0, 0, nil, messageFlag, bufferSize, bufferPtr, &bufferNeeded)
416+
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // This is an errno.
412417
return fmt.Errorf("failed in EvtFormatMessage: %w", err)
418+
} else if err == nil {
419+
// Windows API returns a null terminated WCHAR C-style string in the buffer. bufferNeeded applies
420+
// only when ERROR_INSUFFICIENT_BUFFER is returned. Luckily the UTF16ToUTF8Bytes/UTF16ToString
421+
// functions stop at null termination. Note, as signaled in a comment at the end of this function,
422+
// this behavior is bad for EvtFormatMessageKeyword as then the API returns a list of null terminated
423+
// strings in the buffer (it's fine for now as we don't use this parameter value).
424+
return common.UTF16ToUTF8Bytes(renderBuf, out)
413425
}
414426

415427
// Get a buffer from the pool and adjust its length.
416428
bb := sys.NewPooledByteBuffer()
417429
defer bb.Free()
418-
// The documentation for EvtFormatMessage specifies that the buffer is
419-
// requested "in characters", and the buffer itself is LPWSTR, meaning the
420-
// characters are WCHAR so double the value.
421-
// https://docs.microsoft.com/en-us/windows/win32/api/winevt/nf-winevt-evtformatmessage
422-
bb.Reserve(int(bufferUsed * 2))
423430

424-
err = _EvtFormatMessage(ph, eventHandle, 0, 0, nil, messageFlag, bufferUsed, bb.PtrAt(0), &bufferUsed)
431+
bb.Reserve(int(bufferNeeded * 2))
432+
bufferSize = bufferNeeded
433+
434+
err = _EvtFormatMessage(ph, eventHandle, 0, 0, nil, messageFlag, bufferSize, bb.PtrAt(0), &bufferNeeded)
425435
if err != nil {
426436
return fmt.Errorf("failed in EvtFormatMessage: %w", err)
427437
}
428438

429439
// This assumes there is only a single string value to read. This will
430-
// not work to read keys (when messageFlag == EvtFormatMessageKeyword).
440+
// not work to read keys (when messageFlag == EvtFormatMessageKeyword)
431441
return common.UTF16ToUTF8Bytes(bb.Bytes(), out)
432442
}
433443

0 commit comments

Comments
 (0)