Skip to content

About

SQL Server Extended Events session driver + ring-buffer XML parser. Builds the CREATE EVENT SESSION DDL, polls the ring_buffer target, turns the payload into typed events.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

libxesession

SQL Server Extended Events session driver + event XML parser. Builds the CREATE EVENT SESSION DDL, reads the session's event files incrementally, turns the payload into typed events.

Maintained by Tirion. Used by Calliper. MIT, zero deps.

Status

1.0. Stable API.

What it does

Three things every SQL Server XEvent tool ends up rewriting:

  1. The CREATE EVENT SESSION DDL (with the ADD EVENT list, an event_file target in SQL Server's log folder, dispatch latency).
  2. The poll loop reading the event files with sys.fn_xe_file_target_read_file from the last block read, delivering each event once by its package0.event_sequence (every event carries it), and counting events missing from that sequence. Reads are bounded (max_events_per_poll, whole file blocks), optionally COMPRESSed (gunzip), and, when the host can open more connections (open_reader), spread over up to max_readers connections while the reader is behind the server; those connections are excluded from the session's events by application name.
  3. The XML parser. <data> vs <action>, numeric character refs, escaped-text vs CDATA vs nested-markup values (xml-typed fields such as showplan_xml arrive as nested markup and are kept verbatim), per-event-type column dispatch.

Supported events

  • sp_statement_completed
  • sql_batch_completed
  • rpc_completed
  • query_post_execution_showplan
  • sqlos.wait_info (filtered by duration)
  • login, added only with sp_statement_completed plus a request completion: it follows a new connection's logon-trigger statements, which complete at nest level 1 before its first request.

XeSessionOptions toggles events on/off and sets dispatch latency.

Completion and showplan events capture request IDs and package0.event_sequence for conservative consumer-side association. Unknown identities remain -1. Wait durations are exposed in microseconds like other events, converting the server's millisecond wait_info.duration; duration predicates use server units. Enum display text (including CDATA) supplies wait names and Begin/End opcodes. Server filters (XeSessionOptions::filters, ServerFilter) are emitted inside each event definition: AND across filters, OR within In / NotIn, text compared case-insensitively as N'…' literals with quotes doubled and LIKE wildcards escaped. A filter applies to the events that carry its field and lets the others through (Profiler's column-filter rule; see ServerFilter). set_filters() changes them on a running session by dropping and re-adding its events (SQL Server cannot alter a predicate in place); the session keeps running and event_sequence continues. server_filter_error() says why a filter cannot run. Every event, including wait_info, also excludes the starting connection (sqlserver.session_id <> <spid>), so the session never records its own polling.

Sessions are server-wide, so each is named owner_prefix plus <spid>_<login time> of the connection that starts it; name() returns it. The prefix has no default: the caller supplies one unique to its application (letters, digits and _), and start() rejects an empty or invalid one. start() first drops sessions with that prefix whose owning connection no longer exists (orphans of crashed hosts) — only with VIEW SERVER STATE, since without it other connections are invisible. A session that is created but fails to start is dropped before start() rethrows. session_started_utc(conn, name) reports when a named session (for example one left by an older version of the caller) started, without touching it. orphan_sweep_sql(prefix) exposes the sweep for tests.

Events go to event_file files in SQL Server's log folder, shared by the captures of one login: event_file_base(prefix, login, slot) names a family per capture running at the same time (<prefix><login>_<hash>_s<slot>, the login lowercased to [a-z0-9_], the hash keeping distinct logins apart). start() takes the lowest slot no defined session uses, then checks again before starting: of two captures that chose the same slot at once, the one with the lower session name keeps it and the other moves on. Each capture reuses its family, and SQL Server's rollover (max_file_size = 32 MB, max_rollover_files = 4) counts the files left by earlier captures, so a family never holds more than about 128 MB. A running capture's open file is never rolled away by another session. poll() reads only the files this session created (named after its start) and continues from the last file offset read, returning each event once in event_sequence order; the sequence also filters any overlap, for example when the file read last was rolled away and the read restarts. Holes in the sequence are events dropped under load or rolled away unread; events_lost() counts them (cumulative since start()). finish() stops the session, which flushes SQL Server's buffers to the files, returns the events not yet read and drops the session; stop() abandons it. The files stay after the session is dropped (T-SQL cannot delete them) until the login's next capture rolls them over; event_file_pattern() names them so the caller can tell the user.

Reading the files needs VIEW SERVER STATE. File targets in Azure SQL Managed Instance must be blob URLs and are not supported.

Usage

#include <xesession/xesession.hpp>

class MyConn : public xesession::IConn {
public:
    explicit MyConn(MyOdbcWrapper& c) : conn_(c) {}
    void exec(const std::string& sql) override { conn_.exec(sql); }
    std::string exec_scalar_text(const std::string& sql) override {
        return conn_.exec_scalar_text(sql);
    }
    std::vector<std::string> exec_text_rows(const std::string& sql) override {
        return conn_.exec_text_rows(sql);  // every column of every row, row-major
    }
private:
    MyOdbcWrapper& conn_;
};

MyConn ax{my_odbc};
xesession::XeSessionOptions options;
options.owner_prefix = "myapp_live_";
// Optional: on SQL Server 2016+ event data is then read COMPRESS()ed
// (about 8x less traffic). Any RFC 1952 decoder will do.
options.gunzip = [](std::string_view gz) { return my_gunzip(gz); };
xesession::XeSession session{ax, options};
session.start();

while (running) {
    auto events = session.poll();
    for (const auto& e : events) {
        if (e.name == "sp_statement_completed") {
            log("stmt {} ms, {} reads, in {}",
                e.duration_us / 1000, e.logical_reads, e.object_name);
        }
    }
    if (session.events_lost() > 0) warn("{} events lost", session.events_lost());
    // A backlog comes in batches of options.max_events_per_poll: read on at once.
    if (!session.backlog()) std::this_thread::sleep_for(std::chrono::seconds(2));
}
// The events flushed at stop (one batch at most; a reader still behind the
// server leaves the rest, counted by session.events_unread()).
for (const auto& e : session.finish()) handle(e);
// session.event_file_pattern() names the files SQL Server keeps.

Building

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build   # checks run in Release too (no assert/NDEBUG)

CMake integration:

add_subdirectory(path/to/libxesession)
target_link_libraries(my_app PRIVATE xesession::xesession)

API

All in include/xesession/xesession.hpp:

Type Purpose
xesession::IConn Abstract host-provided connection (three methods).
xesession::XeEvent One parsed event.
xesession::XeSessionOptions Session knobs, including filters.
xesession::ServerFilter One server-side filter (field, operator, values).
xesession::XeSession RAII session + poll loop.
xesession::parse_event_xml(xml) Free function. Test + offline replay.
xesession::session_started_utc(conn, name) Free function. Creation time of a running session, read-only.
xesession::event_file_base(prefix, login, slot) Free function. A login's event file family name.
xesession::server_filter_error(filter) Free function. Why a filter cannot run at the server; empty when it can.

What it doesn't do

  • Drive the connection. Bring your own.
  • Delete its event files (T-SQL cannot); each login's files stay in SQL Server's log folder, capped by rollover.
  • Predicate trees: filters AND together, with OR only inside In / NotIn.
  • History beyond one XeSession lifetime.

License

MIT. Issues at github.com/tirion-tools/libxesession.

About

SQL Server Extended Events session driver + ring-buffer XML parser. Builds the CREATE EVENT SESSION DDL, polls the ring_buffer target, turns the payload into typed events.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages