In my last post I showed how to use Log Analytics data to analyse Power BI query activity. The problem with looking at a long list of queries, though, is that it can be overwhelming and it can be hard to get a sense of when users were and weren’t actively interacting with a report. In this post I’ll show you how you can write a KQL query that gives you a summary view that solves this problem by grouping queries into sessions.
What is a session? I’m going to define it as a group of DAX queries – sessions – run by the same user that occur within 90 seconds of each other. If a user opens a Power BI report, clicks on a slicer or filter, changes a page or whatever, then so long as each of the DAX queries that are generated in the background when they do this have end times that are no more than 90 seconds apart, then that will count as a single session.
The key to solving this problem is the KQL scan operator. If, like me, you’re the kind of geek that likes data analysis you’re going to love the scan operator! There’s a great blog post explaining what it does here; in summary it allows you to do process mining, ie find sequences of events that match a given pattern. I can think of a few cool things I could do with it but grouping DAX queries into sessions is fairly straightforward. Here’s my KQL query:
PowerBIDatasetsWorkspace
| where TimeGenerated > ago(4h)
| where isempty(ExecutingUser)==false
and ExecutingUser<>"Power BI Service"
and OperationName=="QueryEnd"
| project OperationName, OperationDetailName, TimeGenerated,
EventText, DurationMs, ExecutingUser
| partition by ExecutingUser
(
sort by TimeGenerated asc
| scan declare (SessionStartTime:datetime) with
(
step x: true =>
SessionStartTime =
iif(isempty(x.SessionStartTime),
TimeGenerated,
iif((TimeGenerated-x.TimeGenerated)>90s,
TimeGenerated,x.SessionStartTime));
)
)
| summarize
SessionEndTime=max(TimeGenerated),
QueryCount=count()
by ExecutingUser, SessionStartTime
| extend SessionLength = SessionEndTime - SessionStartTime
| sort by SessionStartTime asc
This is how the query works:
- Filters the Log Analytics data down to the queries run in the last four hours which were run by a real user and not the Power BI Service
- Splits this data into separate tables (using the KQL partition operator) for each user
- Sorts these tables by the TimeGenerated column
- For each of these tables, add a new column called SessionStartTime that contains the value from the TimeGenerated column and copies it down for each subsequent query if its start time is less than 90 seconds; if a query starts more than 90 seconds after the previous one then SessionStartTime contains that query’s TimeGenerated value again
- Uses the summarize operator to group the data by the values in SessionStartTime and ExecutingUser
Here’s the output:

This query returns one row per user session where:
- SessionStartTime is the end time of the first DAX query run in the session
- SessionEnd is the end time of the last DAX query run in the session
- QueryCount is the number of DAX queries run in the session
- SessionLength is the duration of the session
Let me know if you have any ideas for modifying or improving this…