I wanted to share this with anyone else that noticed the speed when you hover over connections on status shows 0/0 always. Just in case also if anyone has any recommendations let me know.
sqstat.class.php - Changes Log
pfSense Squid Realtime Stats (SQStat) Speed Fix
Date: March 12, 2026
PROBLEM SUMMARY
Current Speed and Avg Speed columns always showed 0 in SQStat.
Three root causes were identified and fixed.
CHANGE 1: Connection ID Key (both makeHtmlReport and parseRequest)
LOCATION: makeHtmlReport (~line 341) and parseRequest (~line 499)
BEFORE:
$con_id = $con['connection'];
AFTER:
$con_id = md5($con['uri'] . $con['peer']);
WHY:
Squid reports connection IDs as memory addresses (e.g. 0xbe97ec98)
which change on every request. This meant the session key for a
connection never matched between page loads, so previous byte counts
could never be found and current speed was always 0.
Using md5(uri + peer) creates a stable, consistent key that matches
the same connection across multiple refreshes.
CHANGE 2: Session replaced with temp file (both functions)
LOCATION: makeHtmlReport (~line 280) and parseRequest (~line 460)
BEFORE (session read):
unset($session_data);
if (isset($_SESSION['time']) && ((time() - $_SESSION['time']) < 3*60)
&& isset($_SESSION['sqdata']) && is_array($_SESSION['sqdata'])) {
$session_data = $_SESSION['sqdata'];
}
AFTER (file read):
$sqstat_file = '/tmp/sqstat_data.json';
$session_data = array();
if (file_exists($sqstat_file) && (time() - filemtime($sqstat_file)) < 180) {
$session_data = json_decode(file_get_contents($sqstat_file), true) ?: array();
}
BEFORE (session write):
$_SESSION['time'] = time();
if (isset($new_data)) {
$_SESSION['sqdata'] = $new_data;
}
AFTER (file write):
if (isset($new_data)) {
file_put_contents($sqstat_file, json_encode($new_data));
}
WHY:
pfSense uses jQuery AJAX to refresh SQStat. Each AJAX call was
receiving a new PHP session ID, meaning $_SESSION was always empty
on every refresh. Data written to $_SESSION on one call was never
available on the next call. This was confirmed by debug logging
showing a different session_id() on every request.
/tmp on pfSense is a tmpfs (RAM) filesystem so writing to
/tmp/sqstat_data.json has zero SSD impact and persists correctly
between AJAX calls.
CHANGE 3: Session start blocks removed (both functions)
LOCATION: makeHtmlReport (~line 213) and parseRequest (~line 395)
BEFORE:
if ($this->use_sessions) {
if (session_status() == PHP_SESSION_NONE) {
session_name('SQDATA');
session_start();
}
}
AFTER:
(removed entirely)
WHY:
pfSense already starts its own PHP session before SQStat loads.
Attempting to start a second session named 'SQDATA' was conflicting
with pfSense's session management. Since we moved to file-based
storage this code is no longer needed at all.
CHANGE 4: Avg speed moved outside session check (parseRequest)
LOCATION: parseRequest (~line 520)
BEFORE:
if (isset($session_data[$con_id]) && !empty($session_data[$con_id])) {
// ... curr_speed calculation ...
// avg speed
$avg_speed = $con['bytes'] / 1024;
if ($con['seconds'] > 0) {
$avg_speed /= $con['seconds'];
}
}
AFTER:
if (isset($session_data[$con_id])) {
// ... curr_speed calculation only ...
}
// avg speed - always calculate
if ($con['bytes'] > 0 && $con['seconds'] > 0) {
$avg_speed = ($con['bytes'] / 1024) / $con['seconds'];
}
WHY:
Avg speed does not need previous request data - it can always be
calculated as total bytes transferred divided by connection duration.
By moving it outside the session/file data check it shows immediately
on first load for any connection alive more than 1 second, without
needing a previous snapshot.
CHANGE 5: Current speed calculation simplified (both functions)
LOCATION: makeHtmlReport (~line 345) and parseRequest (~line 503)
BEFORE:
if ($was_time && $was_size) {
$delta = $is_time - $was_time;
if ($delta == 0) {
$delta = 1;
}
if ($con['bytes'] >= $was_size) {
$curr_speed = ($con['bytes'] - $was_size) / 1024 / $delta;
}
} else {
$curr_speed = $con['bytes'] / 1024;
}
AFTER:
$delta_time = max(1, $is_time - $was_time);
$delta_bytes = $con['bytes'] - $was_size;
if ($delta_bytes > 0) {
$curr_speed = ($delta_bytes / 1024) / $delta_time;
}
WHY:
Simplified the delta calculation using max(1, ...) to avoid division
by zero more cleanly. Removed the fallback that set curr_speed to
total bytes when no previous size was recorded - that was showing
inflated incorrect values. Now only shows current speed when there
is a genuine positive byte delta between refreshes.
NOTES
/tmp/sqstat_data.json is written on every refresh (tmpfs = RAM, no SSD writes)
Old closed connections remain in the JSON file but are ignored since
they won't match any active Squid connection on the next poll
makeHtmlReport is not used by pfSense's version of SQStat (pfSense uses
parseRequest + sqstat_resultHTML via AJAX) but was updated for consistency
Current speed requires at least 2 refreshes with the same connection
active to show a value - this is expected behavior
Avg speed shows immediately on first load for connections > 1 second old
===============================
End of change log