Files
zoneminder/scripts/zmstats.pl.in
Ben Dailey 4fe2103fcd Replace Events_Hour/Day/Week/Month/Archived and Event_Summaries tables with views
Remove denormalized event summary tables and their associated triggers,
replacing them with views that query the Events table directly. This
eliminates trigger maintenance overhead and periodic reconciliation in
zmaudit/zmstats, since the views compute stats on the fly.

- Remove trigger definitions for event summary table maintenance
- Remove event summary table inserts from zm_event.cpp
- Remove event count reconciliation queries from zmaudit.pl
- Remove DELETE-on-views calls from zmstats.pl (views filter by date inherently)
- Remove Event_Summaries DELETE from Monitor.php (can't delete from a view)
- Add db/views.sql with view definitions and covering index
- Add upgrade script zm_update-1.37.78.sql.in (drop triggers, drop tables, create views)
- Update zm_create.sql.in to use views instead of tables for fresh installs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-07 11:29:30 -04:00

200 lines
7.7 KiB
Perl

#!@PERL_EXECUTABLE@ -wT
use strict;
use warnings;
use bytes;
# ==========================================================================
#
# These are the elements you can edit to suit your installation
#
# ==========================================================================
use constant START_DELAY => 30; # To give everything else time to start
# ==========================================================================
#
# Don't change anything below here
#
# ==========================================================================
@EXTRA_PERL_LIB@
use ZoneMinder;
use DBI;
use Sys::MemInfo qw(totalmem freemem totalswap freeswap);
use ZoneMinder::Server;
$| = 1;
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
logInit();
logSetSignal();
my $zm_terminate = 0;
sub TermHandler {
Info('Received TERM, exiting');
$zm_terminate = 1;
}
$SIG{TERM} = \&TermHandler;
$SIG{INT} = \&TermHandler;
Info('Stats Daemon starting in '.START_DELAY.' seconds');
sleep(START_DELAY);
my $dbh = zmDbConnect();
$dbh->do('SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED');
my $server = new ZoneMinder::Server($Config{ZM_SERVER_ID});
while (!$zm_terminate) {
while ( ! ( $dbh and $dbh->ping() ) ) {
Info('Reconnecting to db');
if ( !($dbh = zmDbConnect()) ) {
#What we do here is not that important, so just skip this interval
sleep($Config{ZM_STATS_UPDATE_INTERVAL});
}
}
my @cpuload = $server->CpuLoad();
Debug("Cpuload: @cpuload");
my ($user_percent, $nice_percent, $sys_percent, $idle_percent, $usage_percent) = $server->CpuUsage();
if ($server->Id()) {
my $in_transaction = ZoneMinder::Database::start_transaction($dbh);
$server->lock_and_load(); # get fresh other values
if ($_=$server->save({
CpuLoad=>$cpuload[0],
TotalMem=>&totalmem, FreeMem=>&freemem, TotalSwap=>&totalswap, FreeSwap=>&freeswap,
CpuUserPercent=>$user_percent, CpuNicePercent=>$nice_percent, CpuSystemPercent=>$sys_percent, CpuIdlePercent=>$idle_percent, CpuUsagePercent=>$usage_percent,
})) {
Error('Failed Updating status of Server record for Id='.$server->Id().': '.$dbh->errstr());
}
ZoneMinder::Database::end_transaction($dbh, $in_transaction);
}
zmDbDo('INSERT INTO Server_Stats (ServerId, TimeStamp, CpuLoad, CpuUserPercent, CpuNicePercent, CpuSystemPercent, CpuIdlePercent, CpuUsagePercent, TotalMem, FreeMem, TotalSwap, FreeSwap) VALUES (?,NOW(),?,?,?,?,?,?,?,?,?,?)',
($Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : 0),
$cpuload[0], $user_percent, $nice_percent, $sys_percent, $idle_percent, $usage_percent,
&totalmem, &freemem, &totalswap, &freeswap);
{
my $rows = zmDbDo('DELETE FROM `Server_Stats` WHERE `TimeStamp` < now() - interval 1 DAY LIMIT 100');
Debug("Deleted $rows Server Stats table entries by time");
}
# Clear out statuses for Monitors that aren't updating themselves.
my $monitor_ids = $dbh->selectcol_arrayref('SELECT MonitorId FROM Monitor_Status WHERE UpdatedOn < timestamp(DATE_SUB(NOW(), INTERVAL 1 MINUTE))');
zmDbDo('DELETE FROM Monitor_Status WHERE MonitorId IN ('.join(',', map { '?' } @$monitor_ids).')', @$monitor_ids) if $monitor_ids and @$monitor_ids;
# Prune the Logs table if required (excluding AUDIT entries)
if ( $Config{ZM_LOG_DATABASE_LIMIT} ) {
my $audit_level = ZoneMinder::Logger::AUDIT;
if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) {
# Number of rows
my $selectLogRowCountSql = 'SELECT count(*) AS `Rows` FROM `Logs` WHERE `Level` != ?';
my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql )
or Fatal("Can't prepare '$selectLogRowCountSql': ".$dbh->errstr());
my $res = $selectLogRowCountSth->execute($audit_level)
or Fatal("Can't execute: ".$selectLogRowCountSth->errstr());
my $row = $selectLogRowCountSth->fetchrow_hashref();
my $logRows = $row->{Rows};
if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) {
my $rows = zmDbDo('DELETE low_priority FROM `Logs` WHERE `Level` != ? ORDER BY `TimeKey` ASC LIMIT ?', $audit_level, $logRows - $Config{ZM_LOG_DATABASE_LIMIT});
Debug('Deleted '.$rows.' log table entries by count') if defined $rows;
}
} else {
# Time of record
# 7 days is invalid. We need to remove the s
if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^(.*)s$/ ) {
$Config{ZM_LOG_DATABASE_LIMIT} = $1;
}
my $rows;
do {
$rows = zmDbDo('DELETE low_priority FROM `Logs` WHERE `Level` != ? AND `TimeKey` < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 100', $audit_level);
Debug("Deleted $rows log table entries by time") if $rows;
} while ($rows and ($rows == 100) and !$zm_terminate);
}
} # end if ZM_LOG_DATABASE_LIMIT
# Prune AUDIT log entries separately with their own retention period
if ( $Config{ZM_LOG_AUDIT_DATABASE_LIMIT} ) {
my $audit_level = ZoneMinder::Logger::AUDIT;
my $audit_limit = $Config{ZM_LOG_AUDIT_DATABASE_LIMIT};
if ( $audit_limit =~ /^\d+$/ ) {
# Number of rows
my $sth = $dbh->prepare_cached('SELECT count(*) AS `Rows` FROM `Logs` WHERE `Level` = ?')
or Fatal("Can't prepare audit log count: ".$dbh->errstr());
my $res = $sth->execute($audit_level)
or Fatal("Can't execute audit log count: ".$sth->errstr());
my $row = $sth->fetchrow_hashref();
my $logRows = $row->{Rows};
if ( $logRows > $audit_limit ) {
my $rows = zmDbDo('DELETE low_priority FROM `Logs` WHERE `Level` = ? ORDER BY `TimeKey` ASC LIMIT ?', $audit_level, $logRows - $audit_limit);
Debug('Deleted '.$rows.' audit log entries by count') if defined $rows;
}
} else {
# Time of record
$audit_limit =~ s/s$//;
my $rows;
do {
$rows = zmDbDo('DELETE low_priority FROM `Logs` WHERE `Level` = ? AND `TimeKey` < unix_timestamp(now() - interval '.$audit_limit.') LIMIT 100', $audit_level);
Debug("Deleted $rows audit log entries by time") if $rows;
} while ($rows and ($rows == 100) and !$zm_terminate);
}
} # end if ZM_LOG_AUDIT_DATABASE_LIMIT
{
my $rows;
do {
# Delete any sessions that are more than a week old. Limiting to 100 because mysql sucks
$rows = zmDbDo('DELETE FROM Sessions WHERE access < ? LIMIT 100', time - $Config{ZM_COOKIE_LIFETIME});
Debug("Deleted $rows sessions") if $rows;
} while ($rows and ($rows == 100) and !$zm_terminate);
}
sleep($Config{ZM_STATS_UPDATE_INTERVAL});
} # end while (!$zm_terminate)
Info('Stats Daemon exiting');
exit();
1;
__END__
#
# ==========================================================================
#
# ZoneMinder WatchDog Script, $Date$, $Revision$
# Copyright (C) 2001-2008 Philip Coombes
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
=head1 NAME
zmstats.pl - ZoneMinder Stats Updating Script
=head1 SYNOPSIS
zmstats.pl
=head1 DESCRIPTION
This does background updating various stats in the db like event counts, diskspace, etc.
=cut