RI Title
RI logo

A guide for Guru Administrators



What Cassini Monitor does
Cassini Monitor (Cassini Monitor v#, Guru App RB0J6C1A) records who logs on to each Cassini tester and for how long, so usage can be reported by location, cost center, project and division.
    • It listens to the local Guru Server and turns each Guru user change into a LOGON and LOGOUT event.
    • A Map table charges each user and host to a location, cost center, project and division. A shared login such as Engineer is still charged to the right site.
    • Remote Users can use unique User IDs to identify their unique location, cost center, or project.
    • Events are written as small JSON files and uploaded by FTP or FTPS to a landing folder, for example /incoming/cassini.
    • Commercial 3rd Party Reporting Tools (like Microsoft Power BI) reads that folder as a Folder source.
It is a single Java jar (Java 1.6 or later) with no other dependencies. It runs on Windows, Linux, macOS and ArcaOS. Guru passwords, server keys and certificates are never written to the JSON or the logs.

Importing the app
Cassini Monitor ships as one .gzp package. Import it once to ant tester's Guru.
    1. Choose Apps > Guru Browser.
    2. Choose Guru Import List and press Get Import File(s).
    3. Choose the Cassini Monitor .gzp and press Import to select the file, then press Import again to start importing to Guru.
    4. Check that the Cassini Monitor v# button now appears in Apps button.
The package contains the app (RiApplication RB0J6C1A).
Preferences are not part of the package. Until you save them on a tester, the monitor uses the defaults and uploads nothing.
To update to a new release, import the new .gzp the same way. Close the current running monitor and choose Stop monitoring then choose Apps > Cassini Monitor v# to launch the new version. Otherwise the update will be loaded automatically the next time the app or the tester is restarted.

First run on a tester
The first run sets the FTP target and the Map table, then saves the preferences to that tester's Guru. It is recommended to also create an Guru Autostart Agent to launch the app when Guru starts up.
    1. Choose Apps > Cassini Monitor v#. The monitor window opens and shows the live log.
    2. Choose Edit > Preferences.... The editor has three tabs: FTP, Monitor and Map. The monitor keeps running while it is open.
    3. On the FTP tab, enter the host, mode (ftp or ftps), port, user, password and directory (for example /incoming/cassini). Typing a host turns on ftp.enabled.
    4. Press Test FTP.... It connects, logs in, opens or creates the directory, and writes and deletes a test file. It reports "FTP connection OK" or names the setting to fix.
    5. Set the upload interval with the slider: Immediate to Never, 20 minutes by default.
    6. On the Map tab, add one row per user and host (see the example below). The first matching row wins. If the user credentials are known, from Guru, choose System > Logoff then proceed to Logon with all known users. This will pre-populate the table with default blank values that can be easily updated.
    7. Press Save. This stores the settings in Guru as a RiPreferences object named RB0J6C1A, owned by this tester's Guru.
    8. Choose Edit > Refresh settings to apply them now without a restart.
Example Map table:
user
host
location
costCenter
project
division
Engineercassini-01Santa ClaraCC-4100Cassini-DevEngineering
Engineercassini-2*Austin, TXCC-4200Cassini-AppsApplications
**UnassignedCC-0000UnassignedUnassigned
User and host names ignore case, and * is a wildcard. Users with no matching row are marked "mapped": false in the JSON.
Tips for the first run:
    • To set up many testers, configure one, use Export Settings... to write a monitor-settings.properties file, and load it on the others with Import local files.... The exported file holds ftp.password in plain text, so store it carefully.
    • Use ftps where the server supports it. Plain FTP sends the password unencrypted.
    • Anyone who can read the RiPreferences object can read the FTP password. Use an upload-only FTP account.

Enabling the Guru Autostart Agent
The Guru Autostart Agent starts the monitor silently in the background every time Guru starts, so no one has to press Apps > Cassini Monitor v#.
    1. In the monitor window, open the File menu.
    2. Choose Enable Guru Autostart Agent.... This saves a RiAutoStart object (run mode startup, application RB0J6C1A) owned by this tester's Guru, titled Cassini Monitor autostart <host>.
    3. Close Cassini Monitor and choose any setting.
    4. Restart Guru by choosing System > Shutdown. Guru will automatically restart.
    5. Open RiApps/GuruApps/RB0J6C1A/logs/cassini-monitor.log and look for Guru launch: autostart ..., then Started monitor ... and Monitoring silently in the background.
How it behaves after that:
    • The File menu shows Enable while no agent is active and Disable Guru Autostart Agent... while one is. Disable saves a new revision marked RiAutoStart_OFF; Enable turns it back on rather than creating a second agent.
    • Choosing Apps > Cassini Monitor v# while the background monitor runs opens the window, which takes over the same session. The earlier log appears above the live lines.
    • Closing the window asks Run in background, Stop monitoring or Cancel. Choose Run in background to keep recording. Stop monitoring ends the session, and nothing is recorded until Guru starts the app again.
    • If Guru exits, the monitor keeps running and reconnects when Guru comes back.
Hide details for Example Output JSONExample Output JSON
Example Output JSON
Each upload is one UTF-8 file holding a JSON array of flat event objects, schema cassini-monitor/1. Files are named cassini_<host>_<yyyyMMddTHHmmssZ>_<seq>.json. Each file goes up as .part and is then renamed, so a reader never sees a half-written file.
A session for the shared Engineer account, one LOGON and its LOGOUT:
[
{
"schema": "cassini-monitor/1",
"eventId": "cassini-01-1790604191000-1",
"type": "LOGON",
"timestampUtc": "2026-09-28T14:03:11Z",
"epochMs": 1790604191000,
"host": "cassini-01",
"ip": "10.0.0.5",
"os": "Linux",
"osVersion": "2.6.18-419.el5",
"osUser": "engineer",
"guruUser": "Engineer",
"guruUserId": "63TX4N0",
"location": "Santa Clara",
"costCenter": "CC-4100",
"project": "Cassini-Dev",
"division": "Engineering",
"mapped": true,
"sessionId": "cassini-01-1790604191000",
"sessionStartUtc": "2026-09-28T14:03:11Z",
"durationSec": null,
"reason": null,
"agentVersion": "2"
},
{
"schema": "cassini-monitor/1",
"eventId": "cassini-01-1790607791000-2",
"type": "LOGOUT",
"timestampUtc": "2026-09-28T15:03:11Z",
"epochMs": 1790607791000,
"host": "cassini-01",
"ip": "10.0.0.5",
"os": "Linux",
"osVersion": "2.6.18-419.el5",
"osUser": "engineer",
"guruUser": "Engineer",
"guruUserId": "63TX4N0",
"location": "Santa Clara",
"costCenter": "CC-4100",
"project": "Cassini-Dev",
"division": "Engineering",
"mapped": true,
"sessionId": "cassini-01-1790604191000",
"sessionStartUtc": "2026-09-28T14:03:11Z",
"durationSec": 3600,
"reason": "userLogout",
"agentVersion": "2"
}
]
The agent also writes AGENT_START and AGENT_STOP events, with null user and session fields, which show when monitoring was running:
{"schema":"cassini-monitor/1","eventId":"cassini-01-1792501200000-1","type":"AGENT_START","timestampUtc":"2026-10-20T13:00:00Z","epochMs":1792501200000,"host":"cassini-01","ip":"10.0.0.5","os":"Linux","osVersion":"2.6.18-419.el5","osUser":"engineer","guruUser":null,"guruUserId":null,"location":"Unassigned","costCenter":"CC-0000","project":"Unassigned","division":"Unassigned","mapped":true,"sessionId":null,"sessionStartUtc":null,"durationSec":null,"reason":null,"agentVersion":"1"}
Key fields:

Field
Meaning
eventIdUnique per host; use it to remove duplicates
typeLOGON, LOGOUT, AGENT_START or AGENT_STOP
timestampUtc / epochMsWhen the event happened, in UTC
host, ipThe tester
guruUser, guruUserIdGuru logon name as typed (compare ignoring case) and Guru UserID
location, costCenter, project, divisionFrom the Map table, at the time of the event
mappedfalse when no Map row matched
sessionIdShared by a LOGON and its LOGOUT
sessionStartUtcStart of the session, on LOGON and LOGOUT
durationSecSession length in seconds, on LOGOUT only
reasonWhy the session ended, on LOGOUT only
agentVersionCassini Monitor release number
LOGOUT reasons:

reason
Cause
userChangedA different user logged on
userLogoutGuru reported no user
guruExitGuru exited
connectionLostThe connection to Guru dropped
agentStopThe monitor was stopped normally
agentRestartA session left open by a crash or power loss, closed at the last heartbeat
Map values are resolved when each event is written. Editing the Map table changes only later events; old files keep what was true at the time.

Hide details for Example: Building dashboards and reports in Power BIExample: Building dashboards and reports in Power BI
Point Power BI at the FTP landing folder, build one Events table, and treat every LOGOUT row as a finished session.

1. Load the events
In Power BI Desktop choose Get Data > Folder and point it at the landing folder, for example through an SMB share on the FTP host. Then replace the query with this in the Advanced Editor:
let
Source = Folder.Files("\\ftp-host\incoming\cassini"),
JsonOnly = Table.SelectRows(Source, each Text.EndsWith([Name], ".json")),
Parsed = Table.AddColumn(JsonOnly, "Rows", each Json.Document([Content])),
Rows = Table.ExpandListColumn(Table.SelectColumns(Parsed, {"Rows"}), "Rows"),
Events = Table.ExpandRecordColumn(Rows, "Rows",
{"eventId","type","timestampUtc","host","osUser","guruUser","guruUserId",
"location","costCenter","project","division","mapped",
"sessionId","sessionStartUtc","durationSec","reason","agentVersion"}),
Typed = Table.TransformColumnTypes(Events, {
{"timestampUtc", type datetimezone}, {"sessionStartUtc", type datetimezone},
{"durationSec", Int64.Type}, {"mapped", type logical}}),
Deduped = Table.Distinct(Typed, {"eventId"})
in
Deduped
For scheduled refresh in the Power BI Service, install the on-premises data gateway on a machine that can reach the share.

2. Add sessions and measures
Sessions = FILTER ( Events, Events[type] = "LOGOUT" )

Hours = DIVIDE ( SUM ( Sessions[durationSec] ), 3600 )

Session Count = COUNTROWS ( Sessions )

Avg Session (h) = DIVIDE ( [Hours], [Session Count] )

Open Sessions =
VAR Started = CALCULATETABLE ( VALUES ( Events[sessionId] ), Events[type] = "LOGON" )
VAR Ended = CALCULATETABLE ( VALUES ( Events[sessionId] ), Events[type] = "LOGOUT" )
RETURN COUNTROWS ( EXCEPT ( Started, Ended ) )
Add a Date table and relate it to the date of sessionStartUtc in Sessions, so slicers and time charts work by day, week and month.

3. Suggested dashboard pages
Page
Visual
Fields
Usage overviewKPI cardsHours, Session Count, Avg Session (h), Open Sessions
ChargebackStacked barHours by costCenter, split by division
ProjectsMatrixproject rows × month columns, Hours values
Sites and testersBar or mapHours by location, drill down to host
TrendLineHours by week, one line per division
Shared accountsTableFilter guruUser = Engineer, break down by location and host
Data qualityTableRows where mapped = false, and AGENT_START/AGENT_STOP per host

4. Report ideas
    • Monthly chargeback: hours per cost center and project for the month, exported to Excel or PDF for finance. Use a paginated report or a subscription in the Power BI Service to email it on the first of each month.
    • Tester utilization: hours used ÷ hours available per host. Testers well below the fleet average are candidates to move or share.
    • Peak demand: a heatmap of sessions by weekday and hour, to plan maintenance windows and shifts.
    • Coverage check: hosts with no AGENT_START in the last 7 days are not reporting; check their Autostart Agent and FTP settings.

5. Tips
    • Always remove duplicates on eventId. A retried upload can deliver the same events twice.
    • Group users with an upper-cased copy of guruUser, because Guru keeps the case as typed (Engineer and engineer are one account).
    • Timestamps are UTC. Convert to local time with DateTimeZone.ToLocal or a per-location offset before reporting by hour.
    • A session that runs past midnight counts on its start date when you relate Sessions on sessionStartUtc. Split long sessions by day if daily totals must be exact.
    • Count only LOGOUT rows for hours. A session still in progress has no duration yet; show it with Open Sessions instead.
    • Fix mapped = false rows by adding Map rows on that tester. Only later events pick up the change, so fix gaps early.

    Published: 10/28/2026 03:19:52 PM Modified: 10/28/2026 03:47:07 PM
    Print Email Link
    https://roos.com/docs/RBEH-DZAU7G
    Roos Instruments CONFIDENTIAL AND PROPRIETARY
    ©2026 Roos Instruments, Inc. All rights reserved.