Eye Describe Anatomy

SRUM: the System Resource Usage Monitor, table by table

The quiet ESE database that records what ran, for how long, and how much it moved over the network — per application, per user, per hour, for weeks. How SRUDB.dat is built, what each of its five provider tables holds, when it overwrites itself, and how Crow-Eye decodes it.

What this page covers

  1. SRUDB.dat — one ESE database, several tables
  2. How SRUM records what happens
  3. What "ESE database" means
  4. How much it saves — and when it overwrites itself
  5. Every provider, every column
  6. SruDbIdMapTable — the join that makes a row readable
  7. Two kinds of time in one row
  8. Why SRUM is forensic gold
  9. Limitations & anti-forensics
  10. How Crow-Eye parses SRUM
  11. Reading the SRUM activity dashboard
  12. A short history
  13. Related anatomy
What is SRUM?
TL;DR — The System Resource Usage Monitor is a Windows 8+ diagnostic feature, run by the Diagnostic Policy Service (DPS), that quietly accounts for how much CPU, disk, network and energy every application uses — broken down per application, per user, per hour. It persists that history in an ESE database, SRUDB.dat, and keeps roughly 30–60 days of it. For an investigator it answers a question few other artifacts can: not just that a program ran, but how much it moved over the wire, and when.
01 · The container

SRUDB.dat — one ESE database, several tables

SRUDB.dat — the persistent SRUM store
On-disk location: C:\Windows\System32\sru\SRUDB.dat · ESE / JET Blue engine, B-tree tables
Alongside it: SRU.chk (checkpoint), SRUDB.jfm (flush map) and the SRU*.log transaction logs. Held open (locked) by the DPS service on a running system.
EngineESE / EDBJET Blue
Format
ESE / EDB
Extensible Storage Engine (JET Blue) — the same engine behind Windows Search and older Exchange stores.
Granularity
1 hour
One row per app, per user, per interface, per hourly window.
Retention
~30–60 d
Then the oldest hourly windows are overwritten.
Since
Win 8
Windows 8 / Server 2012 onward, including 8.1, 10 and 11.
Figure 1 · interactive

Inside SRUDB.dat — click a table to inspect it below

App Resource
Network Data
Network Connectivity
Energy
App Timeline
SruDbIdMapTable
SRUDB.dat
ESE database · MSysObjects catalog
Application Resource Usage
{D10CA2FE-…FA89}
Network Data Usage
{973F5D5C-…}
Network Connectivity
{DD6636C4-…}
Energy Usage (+ long-term)
{FEE4E14F-…} · {DA73FB89-…}
Application Timeline
{5C8CF1C7-…}
SruDbIdMapTable
IdIndex → App / SID
SruDbCheckpointTable · not a provider
ESE engine bookkeeping — not clickable
Provider Inspector

Why six provider GUIDs become five Crow-Eye tables: two of the six — a short-term and a long-term energy provider — hold the same kind of battery data, so Crow-Eye merges them into one srum_energy_usage table. That leaves five data providers. The SruDbIdMapTable is not a provider at all — it is the lookup table every provider row depends on: each row stores only numeric AppId and UserId values, and the ID map turns those back into real app paths and user SIDs (see section 6). And SruDbCheckpointTable is neither a provider nor data — it is ESE's own transaction bookkeeping, which is why it has no inspector here and Crow-Eye writes no table for it. It does, however, collect the on-disk checkpoint set (SRU.chk, SRUDB.jfm and the SRU*.log logs) alongside the database, so a dirty SRUDB.dat can be soft-recovered rather than repaired — and records that it did in srum_metadata (see section 10).

02 · The mechanism

How SRUM records what happens

SRUM is not a log that can be watched in real time. Each provider is an "extension" registered under HKLM\SYSTEM\CurrentControlSet\Services\SRUM\Extensions\{GUID}, and it keeps the current hour's running counters in the registry. The SRUM service — part of the Diagnostic Policy Service — flushes those counters into SRUDB.dat periodically (about once an hour) and at shutdown, aggregating them into hourly windows: one row per application, per user, per hour, per interface.

That two-stage design has a direct forensic consequence: SRUM survives a reboot, but it is never perfectly live. The most recent activity may still be sitting in the registry buffer, not yet written to the database — so the last hour before an image was taken can be incomplete on disk.

Figure 2

The SRUM data lifecycle — from live activity to overwrite

LiveApplication activityCPU cycles, bytes sent/received, focus time accrue as a process runs.
BufferRegistry counters…\Services\SRUM\Extensions\{GUID} holds the current hour.
Flush ~hourlySRUDB.dat (ESE)The service writes the window and starts a new one.
~30–60 daysRolling overwriteOnce retention is exceeded, the oldest windows are purged.

Crow-Eye reads the persistent SRUDB.dat directly — the registry buffer is the operating system's staging area, shown here to explain the timing.

On disk · C:\Windows\System32\sru

The SRUM files, and the job each one does

SRUM is not a single file. The database sits beside an ESE checkpoint, a flush map and a set of transaction logs — together they are how ESE stays crash-safe, and why the newest activity can be recovered even when it has not reached the database yet.
SRUDB.dat
The database · ESE / EDB
The persistent store. Every provider's hourly rows live here in B-tree tables — this is what Crow-Eye parses. The DPS service holds it open on a running system, so it must be copied before it can be read.
SRU*.log
Transaction logs · write-ahead
Where the newest data lives first. ESE commits every change to a log before it is flushed into SRUDB.dat, so the last minutes-to-an-hour of activity sit here before they reach the database. Replaying them recovers data not yet saved. SRU.log is the current log; SRU<gen>.log are the earlier generations.
SRU.chk
Checkpoint
The bookmark for recovery. Records how far the logs have already been applied into the database. Recovery replays every log record after this point; without the checkpoint, replay would not know where to start.
SRUDB.jfm
Flush map
The JET flush map: tracks which database pages have been safely written to disk. It lets ESE skip re-verifying pages it already knows are durable, making recovery faster and safer.
SRUtmp.log
Next log · pre-allocated
An empty log the engine has already created and will rename into the next generation the moment the current log fills — so logging never stalls waiting to allocate a file.
SRUres00001.jrs · SRUres00002.jrs
Reserve logs · emergency space
Two reserved log files kept as a safety margin so the engine can always finish writing and shut down cleanly — even if the disk fills up.

Crow-Eye collects the whole set (see section 10). On a dirty database — one imaged mid-write, where committed rows are still only in the logs — it replays the logs against the checkpoint to recover that unsaved activity before reading.

03 · The engine

What "ESE database" means

The Extensible Storage Engine (ESE, historically "JET Blue") is Microsoft's embedded, transactional, indexed-sequential database engine. It is not SQL — it is a low-level B-tree store that Windows uses wherever it needs a fast local database: Windows Search, the certificate store, and SRUM all sit on it. Three names appear for essentially the same thing — they are defined side by side below.

An ESE database is a set of tables whose definitions live in an internal catalog, MSysObjects. SRUM's tables are named by provider GUID (e.g. {D10CA2FE-…}), plus the SruDbIdMapTable and a SruDbCheckpointTable. Large values are stored out-of-row as long values. The engine keeps a checkpoint (SRU.chk), a flush map (SRUDB.jfm) and transaction logs (SRU*.log) so an unclean shutdown can be recovered.

Two practical consequences follow. First, the file is locked on a running system — the DPS service holds it open, so it cannot simply be copied. Second, a database that was not cleanly closed is "dirty" and must be recovered (log replay) or repaired before it will open — which is exactly why forensic tooling reaches for the native ESE engine or a dirty-tolerant reader rather than a naive parser.

Glossary · the words on this page

ESE, EDB, JET Blue, and "provider GUID"

TermWhat it actually is
ESEExtensible Storage Engine — the database engine itself: Microsoft's embedded, transactional B-tree store. The code that reads and writes the file.
JET BlueThe original internal codename for ESE — same engine, older name. Not to be confused with JET Red, the unrelated engine behind Microsoft Access; the shared word "JET" is the only thing they have in common.
EDBThe on-disk database file format ESE produces (historically the .edb extension). SRUDB.dat is an ESE/EDB database that simply doesn't use the .edb name — the format is the same.
Provider GUIDA GUID is a 128-bit globally-unique identifier written like {D10CA2FE-6FCF-4F6D-848E-B2E99266FA89}. Each SRUM provider is registered under one, and ESE stores that provider's table named by the GUID — which is why the raw table names look like GUIDs, not words.
04 · Coverage & retention

How much it saves — and when it overwrites itself

Each hourly window is a wide row of counters. Across the providers, SRUM captures CPU cycle time (foreground and background), disk bytes read and written, network bytes sent and received per interface, connection windows, focus / keyboard / mouse seconds, and battery / energy state. Multiply that by every application and every user, once an hour, for weeks, and SRUM becomes one of the densest behavioural records on the machine — often tens of megabytes of SRUDB.dat holding many thousands of rows.

The cost of that density is that the database is a rolling window. Once activity ages past the retention period (roughly 30–60 days), the oldest hourly buckets are purged to make room. SRUM answers "the last month or two," not "the life of the machine" — so it is a time-limited artifact, best collected promptly.

Why a case often holds far less than 60 days. That range is a typical ceiling, not a guarantee — retention is bounded by volume, not by a timer counting down days. A busy machine with many applications and several user accounts writes wide hourly rows quickly, fills the window, and starts overwriting its oldest hours sooner — heavy use can shrink real coverage to a couple of weeks. A lightly used machine can hold longer. And anything that resets the store cuts it shorter still: stopping the SRUM service, deleting or clearing SRUDB.dat, a corruption-triggered rebuild, or reimaging. So the earliest row present is the floor of what this machine kept, not as proof of when activity began.

Figure 3

A rolling window of hourly buckets

~30–60 days ago — overwrittennow

Faded buckets on the left have aged out of the retention window and been overwritten; the bright buckets on the right are what an examiner recovers.

05 · The provider tables

Every provider, every column

SRUM splits its data across five providers — each a registered ESE extension identified by a GUID, each its own table inside SRUDB.dat — plus the internal SruDbIdMapTable. Crow-Eye writes each provider to its own SQLite table in srum_data.db. Every row of every provider table carries the same resolved identity and window time, so those shared columns are listed once below; each provider card then adds only its own columns, one per row.

Orientation · three app-centric tables, three different questions

application_usage vs energy_usage vs app_timeline

Three tables are all "about an application," which is easy to confuse. They answer different questions — keep them apart:
TableThe question it answersSignature columns
srum_application_usageHow much did it cost to run? The resource price of execution — processor and disk — per app, per hour.*_cycle_time, *_bytes_read/written
srum_energy_usageWhat did it cost the battery? Power and charge state over time — populated mostly on laptops.charge_level, state_transition
srum_app_timelineHow was it used, and was a human there? Interaction and lifecycle, not just resources (Windows 10+).keyboard_input_s, in_focus_s, hosted_services
Shared · on every provider row

Resolved identity & window time

Where the App id and User id come from: every provider row stores its own raw numeric AppId and UserId — they live on the row itself, not in a separate index. Crow-Eye resolves both through SruDbIdMapTable (see section 6) into the real app path and user SID, then attaches the columns below to every row, in every table.
ColumnTypeWhat it means
app_nameTEXTExecutable name, resolved from the numeric AppId
app_pathTEXTFull path the app ran from (a device path for svchost.exe — see section 6)
user_sidTEXTSID of the user the activity is attributed to, resolved from UserId
user_nameTEXTAccount name for that SID — a live LookupAccountSid, or offline from the SOFTWARE hive ProfileList
timestampDATETIME (UTC)The hourly aggregation window this row covers — the event time; use this for the timeline
parsed_atTEXTBookkeeping — when Crow-Eye read the DB; never evidence
Provider 1 · APPLICATION_RESOURCE_USAGE

srum_application_usage — the CPU & disk cost of running

{D10CA2FE-6FCF-4F6D-848E-B2E99266FA89} · one row per app, per user, per hour · the primary evidence-of-execution table.
ColumnTypeWhat it means
foreground_cycle_timeINTEGERCPU cycles used while the app was in the foreground (100-ns units)
background_cycle_timeINTEGERCPU cycles used in the background
face_timeINTEGERSeconds the app was on screen / in focus this window
foreground_bytes_readINTEGERDisk bytes read while focused
foreground_bytes_writtenINTEGERDisk bytes written while focused
background_bytes_readINTEGERDisk bytes read while backgrounded
background_bytes_writtenINTEGERDisk bytes written while backgrounded
foreground_num_read_operationsINTEGERRead operations behind the foreground byte total
foreground_num_write_operationsINTEGERWrite operations, foreground
background_num_read_operationsINTEGERRead operations, background
background_num_write_operationsINTEGERWrite operations, background
foreground_number_of_flushesINTEGERCache flushes, foreground
background_number_of_flushesINTEGERCache flushes, background
foreground_context_switchesINTEGERScheduler context switches, foreground
background_context_switchesINTEGERScheduler context switches, background
Reading these columns. Foreground means the app held the active window / UI focus at the time; background means it was running without focus — a service, a minimized window, or work continuing behind another app. Splitting every cost this way separates deliberate use from silent activity. The operation counts (*_num_read_operations, *_num_write_operations) are the number of individual I/O operations, not bytes: divide bytes by operations for the average I/O size, and a pattern of many tiny operations versus a few large ones is itself a behavioural tell (log spam vs a bulk copy).
Provider 2 · NETWORK_DATA_USAGE

srum_network_data_usage — bytes on the wire

{973F5D5C-1D90-4944-BE8E-24B94231A174} · one row per app, per interface, per hour.
ColumnTypeWhat it means
bytes_sentINTEGERBytes the app sent this hour — the exfiltration signal
bytes_receivedINTEGERBytes the app received this hour
interface_luidINTEGERLUID of the network interface that carried the traffic
l2_profile_idINTEGERLayer-2 (wireless) profile the traffic went over — which SSID/network
l2_profile_flagsINTEGERFlags on that L2 profile (0 when unused)
wake_countINTEGERTimes this traffic woke the machine from a low-power state
What a "network profile" is. The layer-2 (L2) profile (l2_profile_id) identifies which network the interface was actually on — a specific named Wi-Fi SSID or wired network profile — while interface_luid only says which adapter. Together they tie an app's traffic to a particular network: home vs the corporate LAN vs an unknown coffee-shop hotspot. The same profile fields appear in Network Connectivity below.
Provider 3 · NETWORK_CONNECTIVITY

srum_network_connectivity — connection windows

{DD6636C4-8929-4683-974E-22C046A43763} · when an interface was connected, and for how long.
ColumnTypeWhat it means
connected_timeINTEGERSeconds the interface was connected in this window
connect_start_timeFILETIMEPrecise moment the connection began (Int64 FILETIME → UTC)
interface_luidINTEGERLUID of the network interface
l2_profile_idINTEGERLayer-2 (wireless) profile
l2_profile_flagsINTEGERFlags on that L2 profile
Provider 4 · ENERGY_USAGE

srum_energy_usage — battery & power state

{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37} (short-term), with a long-term variant {DA73FB89-2BEA-4DDC-86B8-6E048C6DA477} Crow-Eye falls back to when the short-term table is absent — both feed this one table.
ColumnTypeWhat it means
charge_levelINTEGERBattery charge (percentage, or mWh depending on the row)
state_transitionINTEGERPower / charge state change
event_timestampFILETIMEPrecise moment of the transition (Int64 FILETIME → UTC)
cycle_countINTEGERBattery charge-cycle count (0 on hardware that does not report it)
designed_capacityINTEGERThe battery's original design capacity (mWh)
full_charged_capacityINTEGERCurrent full-charge capacity (mWh) — against designed_capacity this is battery wear
battery_countINTEGERNumber of batteries present
configuration_hashINTEGERHash identifying the power configuration this row was recorded under
battery_charge_limitedINTEGERWhether charging was capped (e.g. a vendor "battery care" limit)
Provider 5 · APPLICATION_TIMELINE · Windows 10+

srum_app_timeline — how the app was used, not just that it ran

{5C8CF1C7-7257-4F13-B223-970EF5939312} · the richest provider · the only table that carries hosted_services.
ColumnTypeWhat it means
in_focus_sINTEGERSeconds the app held foreground focus
keyboard_input_sINTEGERSeconds of keyboard input — a non-NULL value is strong evidence a person was present
mouse_input_sINTEGERSeconds of mouse input — likewise, human-presence evidence
user_input_sINTEGERCombined user-input seconds
psm_foreground_sINTEGERProcess-state-manager foreground seconds
display_required_sINTEGERSeconds the app kept the display on
duration_msINTEGERMilliseconds the app was running in this window
span_msINTEGERMilliseconds the window itself spanned
end_timeFILETIMEPrecise end of the window (Int64 FILETIME → UTC)
timeline_endINTEGERTimeline end marker for the window
flagsINTEGERTimeline record flags
hosted_servicesTEXTFor svchost.exe: the services it hosted, from the !! AppId form (see section 6)
audio_in_s / audio_out_sINTEGERSeconds of audio captured / rendered
comp_rendered_s / comp_dirtied_s / comp_propagated_sINTEGERDesktop-compositor render / dirty / propagate seconds
cycles / cycles_attr / cycles_wobINTEGERCPU-cycle accounting for the window (total / attributed / work-on-behalf)
disk_raw / network_bytes_raw / network_tail_rawINTEGERRaw disk and network roll-up counters for the window
cpu_timeline / disk_timeline / network_timelineINTEGERPer-window packed activity timelines — a bitfield of when in the window CPU / disk / network was busy
in_focus_timeline / user_input_timeline / keyboard_input_timeline / display_required_timelineINTEGERThe same packed-timeline form of focus, user input, keyboard and display-on activity
comp_rendered_timeline / comp_dirtied_timeline / comp_propagated_timeline / audio_in_timeline / audio_out_timelineINTEGERPacked timelines for the compositor and audio counters
cycles_breakdown / cycles_attr_breakdown / cycles_wob_breakdownINTEGERPer-window breakdowns behind the CPU-cycle totals
mbb_timeline / mbb_bytes_raw / mbb_tail_rawINTEGERMobile-broadband (cellular) activity, timeline and byte counters — populated only on machines with a WWAN radio
Crow-Eye captures every native column this provider carries — the compact *_s second-counts above and these raw *_timeline / *_breakdown packed counters — so nothing SRUM records is dropped.
Where to find human-presence evidence. keyboard_input_s and mouse_input_s come only from this Application Timeline provider (srum_app_timeline), on Windows 10 and later — no other SRUM table records them. That is why "was a person at the keyboard?" is always a question for this table, and why a non-NULL value here (and nowhere else) is the human-presence signal.

Why they are rare. SRUM records a keyboard or mouse value only for a window where that input actually occurred, and most application-hours have none: services, scheduled tasks and background processes run for hours with no window on screen and never see a keystroke, and even a foreground app is only typed into for part of the time it is open. So the overwhelming majority of timeline rows have empty input columns — that is normal, not a decode failure. The value is precisely what makes a non-NULL entry meaningful: it marks the specific windows where a human was genuinely interacting, against a backdrop of automated activity.
Bookkeeping · not evidence

srum_metadata — one row per parse

ColumnTypeWhat it means
parsed_atTEXTWhen Crow-Eye read the database (UTC)
srudb_pathTEXTSource path of the SRUDB.dat that was parsed
total_records_parsedINTEGERHow many rows the parse produced across all tables
parsing_duration_secondsREALHow long the parse took
windows_versionTEXTRecorded as Unknown — SRUM does not stamp the source OS build, and offline must not report the analyst's own
notesTEXTFree-text run notes
06 · The join

SruDbIdMapTable — the join that makes a row readable

A provider row does not store an app name or a user name. It stores small integers — an AppId, a UserId, an interface id. The SruDbIdMapTable maps each IdIndex to its real value in IdBlob, tagged by IdType:

  • IdType = 3 → a binary SID — decoded structurally into S-1-5-… so it joins cleanly against the registry's user list. (Decoding it as a string via the wrong API yields a broken PySID: value that never matches — a real trap.)
  • IdType = 0 / 1 / 2 → an application identity, a UTF-16LE string. Two forms appear: a device path (\Device\HarddiskVolume3\…\svchost.exe [DcomLaunch], used by the resource and network tables) and the timeline's !!svchost.exe!2054/02/06:15:19:25!1642e![netsvcs] [Winmgmt] form.
Telling one svchost.exe from another: the resource and network tables carry the full device path with the service in brackets — so the column to query is app_path, not app_name (app_name is svchost.exe for every one of them). The Application Timeline provider instead splits the service list into its own hosted_services column.
Special IdResolves to
App 1 / 2System · Unknown Application (their IdBlob is NULL)
User 1S-1-0-0 — Nobody
User 2S-1-5-18 — SYSTEM
User 3S-1-5-19 — LOCAL SERVICE
User 4S-1-5-20 — NETWORK SERVICE
07 · Time

Two kinds of time in one row

Every provider table carries a timestamp — the aggregation-window event time, taken from the provider's own TimeStamp column. That is the value a timeline plots: it dates the hourly bucket, not a single instant.

Some tables carry a second, more precise time inside that window: event_timestamp (energy state transitions), connect_start_time (connectivity) and end_time (timeline) are Int64 FILETIME values naming the exact moment the row is really about. Do not confuse either with parsed_at in the metadata table — that is only when Crow-Eye read the database, and is never evidence.

08 · Why it matters

Why SRUM is forensic gold

  • Execution with a cost. Where Prefetch says a program ran, SRUM says how much CPU, disk and network it used, hour by hour — and keeps it for weeks after Prefetch has rolled over.
  • Data exfiltration. bytes_sent per application, per hour, is often the cleanest evidence that a process moved gigabytes off the machine, and exactly when.
  • Human presence. In the Application Timeline, a non-NULL keyboard_input_s or mouse_input_s is strong evidence a person was physically at the keyboard in that window. A service accrues CPU for hours and never sees a keystroke, so NULL there means "no such activity," not a decode failure.
  • Attribution. Every row carries the resolved user SID, tying the activity to an account.
Network usage by application
SELECT app_name, app_path, bytes_sent, bytes_received, timestamp
FROM srum_network_data_usage
ORDER BY bytes_sent DESC LIMIT 20;
Evidence a person was at the keyboard
SELECT timestamp, app_name, in_focus_s, keyboard_input_s, mouse_input_s, user_name
FROM srum_app_timeline
WHERE keyboard_input_s > 0 OR mouse_input_s > 0
ORDER BY timestamp DESC;
Telling one svchost.exe apart from another
SELECT timestamp, hosted_services, in_focus_s, duration_ms
FROM srum_app_timeline
WHERE app_name = 'svchost.exe' AND hosted_services != '';
Reading raw values: SRUM's counters (duration_ms, byte totals, cycle time) are stored as large raw integers. Any thousands-separator formatting is a display concern — comparing or sorting must use the stored integer, or a formatted "1,024" silently sorts as text.
09 · Limits

Limitations & anti-forensics

  • Time-boxed. ~30–60 days only. Older activity is gone — collect SRUM early.
  • Hourly, not instant. A row dates the window, not the exact second an event happened (except the precise FILETIME columns).
  • Can be disabled. The SRUM service can be stopped or its providers disabled; deleting SRUDB.dat destroys history (though the current hour may still be in the registry buffer, and file recovery may recover the deleted database).
  • Sparse by design — and why NULL is meaningful. SRUM writes a value only when that activity actually occurred, so an empty cell is a positive fact, not a gap in the data. A background service that never had a window on screen shows NULL keyboard and mouse seconds — that reads as "no human interaction," which is exactly what happened. A desktop with no battery shows NULL energy — "there was nothing to measure." The distinction that matters forensically: NULL here means "this did not happen," not "the parser failed to decode it." So a NULL is evidence in its own right — energy columns are populated mostly on laptops, and timeline input columns only on windows with real user interaction.
  • Dirty databases. An uncleanly-closed SRUDB.dat needs ESE recovery or repair before it opens — a naive reader will fail or return partial data.
10 · In practice

How Crow-Eye parses SRUM

Figure 4

From a locked SRUDB.dat to srum_data.db

1 · CopyUnlock the fileCopied through Crow-Eye's file-access chain (standard copy → VSS shadow copy → raw disk) into a temp dir — with the checkpoint (SRU.chk), flush map (SRUDB.jfm) and SRU*.log logs beside it.
2 · OpenRead-only, recover if dirtyNative esent.dll JET API (live) or dissect.esedb (offline). A dirty database is soft-recovered by replaying the collected logs (esentutl /r) before falling back to an esentutl /p repair.
3 · ResolveLoad SruDbIdMapTableDecode SIDs and app paths first, so every provider row resolves.
4 · Writesrum_data.dbFive provider tables + a metadata row, evidence never modified.

The evidence file is opened read-only and never written to; recovery for a dirty database is handled by the ESE engine itself. The live path uses the Windows native JET API through ctypes; the offline path prefers dissect.esedb because it tolerates dirty databases better, falling back to pyesedb / libesedb and, in the worst case, an esentutl /p repair. The result is one queryable SQLite database, srum_data.db.

Every native column, nothing dropped. Crow-Eye reads every column each provider table defines — not a fixed subset. That includes the battery-health columns (designed_capacity vs full_charged_capacity is battery wear), the network wake_count, and the Application Timeline's full set of *_timeline and *_breakdown packed counters. Every metric is stored as a raw integer; formatting is left to the display layer so sorting and range filters stay correct.

Nothing left in the logs. Because the newest activity is committed to SRU*.log before it is flushed into SRUDB.dat (see section 2), Crow-Eye records what state the database was in: a clean database reports db_state = clean with the ESE header's “Clean Shutdown, Log Required 0-0” proof — meaning everything was already flushed and there was nothing in the logs to recover. A dirty database (an image captured mid-write) reports recovered, because the logs were replayed to fold in exactly that unflushed activity. Either way the metadata row states plainly whether any data was still pending.

Output · srum_data.db

The six tables Crow-Eye writes

Note the shape: the parser resolves five providers into five data tables plus one bookkeeping table — there is no single srum_data table, the name is the file.
TableFrom providerWhat it holds
srum_application_usageApp ResourcePer-app, per-hour CPU & disk cost
srum_network_data_usageNetwork DataBytes sent / received per app, per interface
srum_network_connectivityNetwork Conn.When and how long an interface was connected
srum_energy_usageEnergy (×2)Battery charge & state — short- and long-term providers merged
srum_app_timelineApp TimelineFocus / input seconds, hosted services (Win 10+)
srum_metadata—One row per parse: source path, counts, duration, and the recovery record — whether the checkpoint/logs were collected and whether the database was read clean, recovered or repaired (db_state)

Resolving identity — the step that makes a row mean something

Provider rows store only numeric ids, so Crow-Eye loads SruDbIdMapTable before any provider table and builds the lookup once. Each map entry has an IdType that says how to decode its blob:

  • IdType 3 — a binary SID (a user). Crow-Eye decodes the SID structurally from its bytes with its own decode_binary_sid. It deliberately does not route this through win32security: that path produced a PySID object whose text form corrupted a large fraction of the rows, so the raw-byte decoder is the correct one.
  • IdType 0 / 1 / 2 — an application identity (a UTF-16LE string). The !!svchost.exe!…![netsvcs] form only appears in the timeline provider and is split out into hosted_services.
  • Special ids bypass the map entirely — app id 1 = System, 2 = Unknown; user id 1 = S-1-0-0, 2 = S-1-5-18 (Local System), 3 = S-1-5-19, 4 = S-1-5-20.

A SID is only half an identity. Live, Crow-Eye turns it into an account name with LookupAccountSid; offline, there is no live registry to ask, so it reads the account names from the SOFTWARE hive's ProfileList instead — the answer belongs to the image, never the analyst's own machine.

Time and numbers — two rules that keep the data honest

SRUM mixes two clocks in one row (see section 7): the hourly window TimeStamp is an OLE-automation date, while event_timestamp, connect_start_time and end_time are Int64 FILETIME (100-ns ticks since 1601). Crow-Eye normalises both to timezone-aware UTC and applies a 1980–2200 sanity bound, which is what catches the classic "year 6916" wrong-offset misread before it ever reaches a table.

Every metric — cycles, bytes, seconds — is stored as a raw integer, never a pre-formatted string. That is not a stylistic choice: SQLite keeps a value it cannot convert as TEXT, and TEXT sorts above every integer, so a single "1,024" in a numeric column silently breaks every ORDER BY and > comparison over it. Formatting is the display layer's job.

The finished srum_data.db is a first-class citizen of the case: Timeline plots its window timestamps alongside every other artifact, Database Search queries it directly, Eye AI reads it to answer questions like “what sent data the night of the incident?”, and the activity dashboard charts it interactively — the same evidence, seen four ways.

11 · Read it

Reading the SRUM activity dashboard

Parsing writes five tables; the Charts button on any SRUM table tab opens the same srum_data.db as an interactive dashboard. One rule holds everywhere: provider = colour. Each of the five provider tables owns a colour, and that colour means the same thing in every chart on the page.

Colour language

Five providers, five colours

Provider tableOn the dashboardWhat its heat-map counts
srum_application_usageApp ResourceRecords of CPU & disk cost, per app / hour
srum_network_data_usageNetwork DataRecords of bytes sent / received
srum_network_connectivityNet Conn.Records of interface connect time
srum_energy_usageEnergyRecords of battery charge & state
srum_app_timelineApp TimelineRecords of focus / input seconds
The five heat-maps run down the top of the dashboard, one per provider. Every cell is a day; its shade is that provider's record volume for the day, normalised within its own map. They share a weekday / month axis, so a column is the same day in all five — and clicking any day selects it in all five at once and fills the section beneath.

Why five heat-maps but only four timelines

A heat-map counts records, and all five providers write records — so there are five heat-maps. The timeline below is different: it plots one bubble per app per hour and sizes each bubble by a magnitude. Only four metric families give an app a per-hour magnitude worth sizing — network bytes, CPU cycles, disk bytes and presence seconds — so there are four timeline modes.

Network Connectivity and Energy have no per-app activity magnitude to size a bubble: connectivity is about when an interface was up, and energy is a battery charge level, not an amount an app “did”. They still earn a heat-map (record volume still shows when they were active), and Energy's charge shows up as the battery % line inside an app's pop-up — just not as a timeline mode.

The day timeline · four modes

Apps × hour of day — the button row switches what bubble size means

ModeComes fromBubble size / marker
Network (sent / received)srum_network_data_usageBytes moved that hour — ▲ filled = sent, ▿ outline = received
CPU cyclessrum_application_usageForeground + background cycle time
Disk bytessrum_application_usageForeground + background bytes read + written
User presencesrum_app_timelineSeconds the app was in focus
Every mode shares the same frame: X = hour of day (the ruler stays pinned at the top while the rows scroll), Y = application, colour = the user who ran it, and size = the chosen magnitude. The user chips beneath the chart toggle each user on and off.

The three regions

Left — the five heat-maps, then the day section for whichever day is selected: the bubble timeline above, and below it Activity by hour (stacked bars, one colour per provider), Top applications (each app's bar split by provider), a Records by provider breakdown for that day, and user-presence tiles (keyboard / mouse / in-focus).

Right — the overview, scoped to the whole selected date range rather than one day: totals tiles (active days, distinct apps, bytes sent, bytes received, CPU cycles, user presence); a Network activity card (bytes sent vs received per day, plus the top apps by network); Most active applications and Activity by user, both bars split by provider colour; and a Records by provider total.

Click for detail

What each click opens

Heat-map dayFocus a daySelects that day in all five maps and rebuilds the day section for it.
An appFull profileClick a bubble or an app chip: totals; Data & resources over time (sent/received bars + CPU line + battery-% line); Network by time (each hour's sent / received); a Resource footprint radar — this app's share of all apps across CPU, disk, net-out, net-in and presence; and Who ran it, per user.
An hour barThat hour, in detailClick a bar in Activity by hour: the records-by-provider split for that hour, and every app active in it with its user, bytes sent / received, CPU and focus.
Two filters sit in the header and apply to the whole dashboard: a search box (several terms, matched ANY or ALL) narrows to matching apps, paths or users; the date dropdown picks a month and then a day to focus the range.
Background

A short history

2012 · Windows 8 / Server 2012
SRUM introduced
The System Resource Usage Monitor ships as a DPS diagnostic feature, writing SRUDB.dat under System32\sru.
Windows 10
Application Timeline added
The {5C8CF1C7-…} provider adds focus, keyboard and mouse seconds — the "was a human present" signal.
Windows 11
Same core, still one of the richest artifacts
The provider set and ESE format carry forward; SRUM remains a first-stop artifact for execution and network evidence.