Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions O365.Security.Native.ETW.Debug.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>Microsoft.O365.Security.Native.ETW.Debug</id>
<version>4.4.8</version>
<version>4.4.9</version>
<title>Microsoft.O365.Security.Native.ETW Debug - managed wrappers for krabsetw</title>
<authors>Microsoft</authors>
<owners>Microsoft</owners>
Expand All @@ -12,9 +12,9 @@
<description>Microsoft.O365.Security.Native.ETW Debug is a managed wrapper around the krabsetw ETW library. This is the Debug build.</description>
<summary>Microsoft.O365.Security.Native.ETW Debug is a managed wrapper around the krabsetw ETW library. This is the Debug build.</summary>
<releaseNotes>
Version 4.4.8:
- Optimize parser property lookup with persistent name-to-index map
- Use std::wstring_view for parser property name parameters
Version 4.4.9:
- Optimize parser property lookup with last property hinted linear scan
- Add hinted parse/try_parse/view_of overloads accepting a ULONG property index hint
</releaseNotes>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<language />
Expand Down
8 changes: 4 additions & 4 deletions O365.Security.Native.ETW.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>Microsoft.O365.Security.Native.ETW</id>
<version>4.4.8</version>
<version>4.4.9</version>
<title>Microsoft.O365.Security.Native.ETW - managed wrappers for krabsetw</title>
<authors>Microsoft</authors>
<owners>Microsoft</owners>
Expand All @@ -12,9 +12,9 @@
<description>Microsoft.O365.Security.Native.ETW is a managed wrapper around the krabsetw ETW library.</description>
<summary>Microsoft.O365.Security.Native.ETW is a managed wrapper around the krabsetw ETW library.</summary>
<releaseNotes>
Version 4.4.8:
- Optimize parser property lookup with persistent name-to-index map
- Use std::wstring_view for parser property name parameters
Version 4.4.9:
- Optimize parser property lookup with last property hinted linear scan
- Add hinted parse/try_parse/view_of overloads accepting a ULONG property index hint
</releaseNotes>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<language />
Expand Down
110 changes: 78 additions & 32 deletions krabs/krabs/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ namespace krabs {
template <typename T>
bool try_parse(std::wstring_view name, T &out);

/**
* <summary>
* Attempts to retrieve the given property by name and type,
* starting the name scan at the given hint index.
* </summary>
*/
template <typename T>
bool try_parse(std::wstring_view name, T &out, ULONG hint);

/**
* <summary>
* Attempts to parse the given property by name and type. If the
Expand All @@ -87,20 +96,32 @@ namespace krabs {
template <typename T>
T parse(std::wstring_view name);

/**
* <summary>
* Attempts to parse the given property by name and type,
* starting the name scan at the given hint index.
* </summary>
*/
template <typename T>
T parse(std::wstring_view name, ULONG hint);

template <typename Adapter>
auto view_of(std::wstring_view name, Adapter &adapter) -> collection_view<typename Adapter::const_iterator>;

template <typename Adapter>
auto view_of(std::wstring_view name, ULONG hint, Adapter &adapter) -> collection_view<typename Adapter::const_iterator>;

private:
property_info find_property(std::wstring_view name);
property_info find_property(std::wstring_view name, ULONG hint);
void cache_property(ULONG index, property_info info);

private:
const schema &schema_;
const BYTE *pEndBuffer_;
BYTE *pBufferIndex_;
ULONG lastPropertyIndex_;
// Persistent name to index map shared across all events of the same type.
const property_name_map *pPropertyNames_;
ULONG nextHint_;
// Maintain a mapping from property index to blob data location.
std::vector<property_info> propertyCache_;
};
Expand All @@ -113,7 +134,7 @@ namespace krabs {
, pEndBuffer_((BYTE*)s.record_.UserData + s.record_.UserDataLength)
, pBufferIndex_((BYTE*)s.record_.UserData)
, lastPropertyIndex_(0)
, pPropertyNames_(s.pPropertyNames_)
, nextHint_(0)
, propertyCache_(s.pSchema_->PropertyCount)
{}

Expand All @@ -123,44 +144,49 @@ namespace krabs {
}

inline property_info parser::find_property(std::wstring_view name)
{
return find_property(name, nextHint_);
}

inline property_info parser::find_property(std::wstring_view name, ULONG hint)
{
// A schema contains a collection of properties that are keyed by name.
// These properties are stored in a blob of bytes that needs to be
// interpreted according to information that is packaged up in the
// schema and that can be retrieved using the Tdh* APIs. This format
// requires a linear traversal over the blob, incrementing according to
// the contents within it. This is janky, so our strategy is to
// minimize this as much as possible via caching.
// schema and that can be retrieved using the Tdh* APIs.

const ULONG totalPropCount = schema_.pSchema_->PropertyCount;
if (totalPropCount == 0) {
return property_info();
}

// Resolve property name to index via hinted linear scan.
// Optimistically start at hint, then wrap around if not found.
// ** This assumes that callers typically access properties in event order. **
// An alternative is to maintain a static name->index map, but this was slower
// in practice for events with < ~12 properties.

if (hint >= totalPropCount) hint = 0;

// Resolve property name to index.
ULONG index = totalPropCount; // sentinel = not found
if (pPropertyNames_) {
// Fast path: use the persistent name to index map shared across
// all events of the same type.
auto it = pPropertyNames_->find(name);
if (it != pPropertyNames_->end()) {
index = it->second;
}
} else {
// Fallback: linear scan of property names in the schema.
for (ULONG i = 0; i < totalPropCount; ++i) {
auto &propInfo = schema_.pSchema_->EventPropertyInfoArray[i];
const wchar_t *pName = reinterpret_cast<const wchar_t*>(
reinterpret_cast<const BYTE*>(schema_.pSchema_) +
propInfo.NameOffset);
if (name == pName) {
index = i;
break;
}
for (ULONG n = 0; n < totalPropCount; ++n) {
const ULONG i = (hint + n) % totalPropCount;
const auto &propInfo = schema_.pSchema_->EventPropertyInfoArray[i];
const wchar_t *pName = reinterpret_cast<const wchar_t*>(
reinterpret_cast<const BYTE*>(schema_.pSchema_) +
propInfo.NameOffset);
if (name == pName) {
index = i;
break;
}
}

if (index >= totalPropCount) {
if (index >= totalPropCount) { // not found
return property_info();
}

nextHint_ = (index + 1) % totalPropCount;

// The first step is to use our cache for the property to see if we've
// discovered it already.
if (index < lastPropertyIndex_) {
Expand All @@ -180,11 +206,9 @@ namespace krabs {
// We've not looked up this property before, so we have to do the work
// to find it. While we're going through the blob to find it, we'll
// remember what we've seen to save time later.
//
// Note: The name-to-index map is built once per schema type (cheap
// metadata scan). But the blob walk below is lazy per-event -- we
// only walk forward to the requested index, avoiding overhead when
// only a subset of properties are needed.
// The blob walk is lazy per-event -- we only walk forward to the
// requested index, avoiding overhead when only a subset of properties
// are needed.
while (lastPropertyIndex_ <= index) {

auto &currentPropInfo = schema_.pSchema_->EventPropertyInfoArray[lastPropertyIndex_];
Expand Down Expand Up @@ -244,6 +268,13 @@ namespace krabs {
// try_parse
// ------------------------------------------------------------------------

template <typename T>
bool parser::try_parse(std::wstring_view name, T &out, ULONG hint)
{
nextHint_ = hint;
return try_parse(name, out);
}

template <typename T>
bool parser::try_parse(std::wstring_view name, T &out)
{
Expand All @@ -269,6 +300,13 @@ namespace krabs {
// parse
// ------------------------------------------------------------------------

template <typename T>
T parser::parse(std::wstring_view name, ULONG hint)
{
nextHint_ = hint;
return parse<T>(name);
}

template <typename T>
T parser::parse(std::wstring_view name)
{
Expand Down Expand Up @@ -435,6 +473,14 @@ namespace krabs {
// view_of
// ------------------------------------------------------------------------

template <typename Adapter>
auto parser::view_of(std::wstring_view name, ULONG hint, Adapter &adapter)
-> collection_view<typename Adapter::const_iterator>
{
nextHint_ = hint;
return view_of(name, adapter);
}

template <typename Adapter>
auto parser::view_of(std::wstring_view name, Adapter &adapter)
-> collection_view<typename Adapter::const_iterator>
Expand Down
4 changes: 0 additions & 4 deletions krabs/krabs/schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,6 @@ namespace krabs {
private:
const EVENT_RECORD &record_;
const TRACE_EVENT_INFO *pSchema_;
// Persistent name to index map, owned by schema_locator. May be nullptr.
const property_name_map *pPropertyNames_;

private:
friend std::wstring event_name(const schema &);
Expand Down Expand Up @@ -339,13 +337,11 @@ namespace krabs {
inline schema::schema(const EVENT_RECORD &record, const krabs::schema_locator &schema_locator)
: record_(record)
, pSchema_(schema_locator.get_event_schema(record))
, pPropertyNames_(schema_locator.get_property_names(pSchema_))
{ }

inline schema::schema(const EVENT_RECORD &record, const PTRACE_EVENT_INFO pSchema)
: record_(record)
, pSchema_(pSchema)
, pPropertyNames_(nullptr)
{ }

inline bool schema::operator==(const schema &other) const
Expand Down
49 changes: 2 additions & 47 deletions krabs/krabs/schema_locator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,6 @@ namespace krabs {
*/
std::string_view get_trace_logger_event_name(const EVENT_RECORD &);

/**
* <summary>
* Maps property names to their index in the schema.
* Keys are wstring_views pointing into stable TRACE_EVENT_INFO memory.
* </summary>
*/
using property_name_map = std::unordered_map<std::wstring_view, ULONG>;

/**
* <summary>
* Fetches and caches schemas from TDH.
Expand Down Expand Up @@ -215,21 +207,8 @@ namespace krabs {
*/
bool has_event_schema(const EVENT_RECORD& record) const;

/**
* <summary>
* Returns the persistent property name to index map for a schema.
* The map is built when the schema is first cached.
* Returns nullptr if pSchema is null or not in the cache.
* </summary>
*/
const property_name_map* get_property_names(const TRACE_EVENT_INFO* pSchema) const;

private:
void build_property_names(const TRACE_EVENT_INFO* pSchema) const;

mutable std::unordered_map<schema_key, std::variant<std::unique_ptr<char[]>, TDHSTATUS>> cache_;
// Persistent property name to index maps, keyed by schema pointer.
mutable std::unordered_map<const TRACE_EVENT_INFO*, property_name_map> property_name_cache_;
};

// Implementation
Expand Down Expand Up @@ -332,10 +311,9 @@ namespace krabs {

// Add the new instance to the cache.
// NB: key's 'internalize_name' gets called by the cctor here.
if (status == ERROR_SUCCESS) {
if (status == ERROR_SUCCESS)
cache_.emplace(key, std::move(buffer));
build_property_names(returnVal);
} else
else
cache_.emplace(key, status);

return returnVal;
Expand All @@ -348,29 +326,6 @@ namespace krabs {
return status == ERROR_SUCCESS;
}

inline void schema_locator::build_property_names(const TRACE_EVENT_INFO* pSchema) const
{
property_name_map names;
for (ULONG i = 0; i < pSchema->PropertyCount; ++i) {
const wchar_t* pName = reinterpret_cast<const wchar_t*>(
reinterpret_cast<const BYTE*>(pSchema) +
pSchema->EventPropertyInfoArray[i].NameOffset);
names.emplace(std::wstring_view(pName), i);
}
property_name_cache_.emplace(pSchema, std::move(names));
}

inline const property_name_map* schema_locator::get_property_names(const TRACE_EVENT_INFO* pSchema) const
{
if (!pSchema) return nullptr;

auto it = property_name_cache_.find(pSchema);
if (it != property_name_cache_.end()) {
return &it->second;
}
return nullptr;
}

inline std::unique_ptr<char[]> get_event_schema_from_tdh(const EVENT_RECORD &record)
{
TDHSTATUS status = ERROR_SUCCESS;
Expand Down
8 changes: 4 additions & 4 deletions krabsetw.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>Microsoft.O365.Security.Krabsetw</id>
<version>4.4.8</version>
<version>4.4.9</version>
<title>Krabs ETW Wrappers</title>
<authors>Microsoft</authors>
<owners>Microsoft</owners>
Expand All @@ -12,9 +12,9 @@
<description>Krabs ETW provides a modern C++ wrapper around the low-level ETW trace consumption functions</description>
<summary>Krabs ETW provides a modern C++ wrapper around the low-level ETW trace consumption functions</summary>
<releaseNotes>
Version 4.4.8:
- Optimize parser property lookup with persistent name-to-index map
- Use std::wstring_view for parser property name parameters
Version 4.4.9:
- Optimize parser property lookup with last property hinted linear scan
- Add hinted parse/try_parse/view_of overloads accepting a ULONG property index hint
</releaseNotes>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<language />
Expand Down
Loading