From 43531b3f1705f387c07ef2aee91189cfb8498bf2 Mon Sep 17 00:00:00 2001
From: Robert Anderson <randerson@lbsd.net>
Date: Tue, 3 Nov 2009 13:21:29 +0000
Subject: [PATCH] Correctly handle large numbers for accounting_summary Note:
 database schema for accounting_summary has been changed

---
 UPGRADING                                     |  15 +-
 database/core.tsql                            |  10 +-
 .../modules/accounting/mod_accounting_sql.pm  | 226 +++++++-----------
 3 files changed, 109 insertions(+), 142 deletions(-)

diff --git a/UPGRADING b/UPGRADING
index 55e6707e..fdafa042 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -1,4 +1,17 @@
-r509:	
+r538:
+	# Database
+
+	ALTER TABLE accounting_summary DROP COLUMN AcctSessionTime;
+	ALTER TABLE accounting_summary DROP COLUMN AcctInputOctets;
+	ALTER TABLE accounting_summary DROP COLUMN AcctInputGigawords;
+	ALTER TABLE accounting_summary DROP COLUMN AcctOutputOctets;
+	ALTER TABLE accounting_summary DROP COLUMN AcctOutputGigawords;
+
+	ALTER TABLE accounting_summary ADD COLUMN TotalSessionTime INT UNSIGNED;
+	ALTER TABLE accounting_summary ADD COLUMN TotalInput INT UNSIGNED;
+	ALTER TABLE accounting_summary ADD COLUMN TotalOutput INT UNSIGNED;
+
+r509:
 	# Database
 
 	ALTER TABLE accounting ADD PeriodKey VARCHAR(7);
diff --git a/database/core.tsql b/database/core.tsql
index 16d868c6..c5c1564e 100644
--- a/database/core.tsql
+++ b/database/core.tsql
@@ -255,13 +255,9 @@ CREATE TABLE @PREFIX@accounting_summary (
 
 	PeriodKey		DATETIME,
 
-	AcctSessionTime		@INT_UNSIGNED@,
-
-	AcctInputOctets		@INT_UNSIGNED@,
+	TotalSessionTime	@INT_UNSIGNED@,
 
-	AcctInputGigawords	@INT_UNSIGNED@,
-
-	AcctOutputOctets	@INT_UNSIGNED@,
+	TotalInput		@INT_UNSIGNED@,
 
-	AcctOutputGigawords	@INT_UNSIGNED@
+	TotalOutput		@INT_UNSIGNED@
 ) @CREATE_TABLE_SUFFIX@;
diff --git a/smradius/modules/accounting/mod_accounting_sql.pm b/smradius/modules/accounting/mod_accounting_sql.pm
index 3cf07022..bea441d5 100644
--- a/smradius/modules/accounting/mod_accounting_sql.pm
+++ b/smradius/modules/accounting/mod_accounting_sql.pm
@@ -721,22 +721,22 @@ sub cleanup
 	# New datetime
 	my $lastMonth = DateTime->new( year => $prevYear, month => $prevMonth, day => 1 );
 	my $periodKey = $lastMonth->strftime("%Y-%m");
+	# Sanitize
+	$lastMonth = $lastMonth->ymd();
 
 	# Select totals for last month
 	my $sth = DBSelect('
 		SELECT
 			Username,
-			SUM(AcctSessionTime) as AcctSessionTime,
-			SUM(AcctInputOctets) as AcctInputOctets,
-			SUM(AcctInputGigawords) as AcctInputGigawords,
-			SUM(AcctOutputOctets) as AcctOutputOctets,
-			SUM(AcctOutputGigawords) as AcctOutputGigawords
+			AcctSessionTime,
+			AcctInputOctets,
+			AcctInputGigawords,
+			AcctOutputOctets,
+			AcctOutputGigawords
 		FROM
 			@TP@accounting
 		WHERE
 			PeriodKey = ?
-		GROUP BY
-			Username
 		',
 		$periodKey
 	);
@@ -747,161 +747,119 @@ sub cleanup
 		return;
 	}
 
-	# Set blank array
-	my @allRecords;
-
 	# Load items into array
-	my $index = 0;
-	while (my $usageTotals = $sth->fetchrow_hashref()) {
-		$usageTotals = hashifyLCtoMC(
-			$usageTotals,
+	my %usageTotals;
+	while (my $row = hashifyLCtoMC($sth->fetchrow_hashref(),
 			qw(Username AcctSessionTime AcctInputOctets AcctInputGigawords AcctOutputOctets AcctOutputGigawords)
-		);
+	)) {
+
+		# check if we've seen this user, if so just add up
+		if (defined($usageTotals{$row->{'Username'}})) {
+			# Look for session time
+			if (defined($row->{'AcctSessionTime'}) && $row->{'AcctSessionTime'} > 0) {
+				$usageTotals{$row->{'Username'}}{'TotalSessionTime'} += ceil($row->{'AcctSessionTime'} / 60);
+			}
+			# Add input usage if we have any
+			if (defined($row->{'AcctInputOctets'}) && $row->{'AcctInputOctets'} > 0) {
+				$usageTotals{$row->{'Username'}}{'TotalInput'} += ceil($row->{'AcctInputOctets'} / 1024 / 1024);
+			}
+			if (defined($row->{'AcctInputGigawords'}) && $row->{'AcctInputGigawords'} > 0) {
+				$usageTotals{$row->{'Username'}}{'TotalInput'} += ceil($row->{'AcctInputGigawords'} * 4096);
+			}
+			# Add output usage if we have any
+			if (defined($row->{'AcctOutputOctets'}) && $row->{'AcctOutputOctets'} > 0) {
+				$usageTotals{$row->{'Username'}}{'TotalOutput'} += ceil($row->{'AcctOutputOctets'} / 1024 / 1024);
+			}
+			if (defined($row->{'AcctOutputGigawords'}) && $row->{'AcctOutputGigawords'} > 0) {
+				$usageTotals{$row->{'Username'}}{'TotalOutput'} += ceil($row->{'AcctOutputGigawords'} * 4096);
+			}
+
+		# This is a new record...
+		} else {
+			# Look for session time
+			if (defined($row->{'AcctSessionTime'}) && $row->{'AcctSessionTime'} > 0) {
+				$usageTotals{$row->{'Username'}}{'TotalSessionTime'} = ceil($row->{'AcctSessionTime'} / 60);
+			} else {
+				$usageTotals{$row->{'Username'}}{'TotalSessionTime'} = 0;
+			}
+
+			# Total up the input usage
+			$usageTotals{$row->{'Username'}}{'TotalInput'} = 0;
+			if (defined($row->{'AcctInputOctets'}) && $row->{'AcctInputOctets'} > 0) {
+				$usageTotals{$row->{'Username'}}{'TotalInput'} = ceil($row->{'AcctInputOctets'} / 1024 / 1024);
+			}
+			if (defined($row->{'AcctInputGigawords'}) && $row->{'AcctInputGigawords'} > 0) {
+				$usageTotals{$row->{'Username'}}{'TotalInput'} = ceil($row->{'AcctInputGigawords'} * 4096);
+			}
 
-		# Set array items
-		$allRecords[$index] = {
-			Username => $usageTotals->{'Username'},
-			PeriodKey => $lastMonth->ymd,
-			SessionTime => $usageTotals->{'AcctSessionTime'},
-			InputOctets => $usageTotals->{'AcctInputOctets'},
-			InputGigawords => $usageTotals->{'AcctInputGigawords'},
-			OutputOctets => $usageTotals->{'AcctOutputOctets'},
-			OutputGigawords => $usageTotals->{'AcctOutputGigawords'}
-		};
-
-		# Increase size
-		$index++;
+			# Total up the output usage
+			$usageTotals{$row->{'Username'}}{'TotalOutput'} = 0;
+			if (defined($row->{'AcctOutputOctets'}) && $row->{'AcctOutputOctets'} > 0) {
+				$usageTotals{$row->{'Username'}}{'TotalOutput'} = ceil($row->{'AcctOutputOctets'} / 1024 / 1024);
+			}
+			if (defined($row->{'AcctOutputGigawords'}) && $row->{'AcctOutputGigawords'} > 0) {
+				$usageTotals{$row->{'Username'}}{'TotalOutput'} = ceil($row->{'AcctOutputGigawords'} * 4096);
+			}
+		}
 	}
 
 	# Begin transaction
 	DBBegin();
 
-	# Update totals for last month
-	if ($index > 0) {
+	# Delete duplicate records
+	my @dbDoParams;
+	@dbDoParams = ('
+		DELETE FROM
+			@TP@accounting_summary
+		WHERE
+			PeriodKey = ?',
+		$lastMonth
+	);
+
+	if ($sth) {
+		# Do query
+		$sth = DBDo(@dbDoParams);
+	}
 
-		# Delete duplicate records
-		my @dbDoParams;
+	# Loop through users and insert totals
+	foreach my $username (keys %usageTotals) {
 		@dbDoParams = ('
-			DELETE FROM
+			INSERT INTO
 				@TP@accounting_summary
-			WHERE
-				PeriodKey = ?',
-			$lastMonth->ymd
+			(
+				Username,
+				PeriodKey,
+				TotalSessionTime,
+				TotalInput,
+				TotalOutput
+			)
+			VALUES
+				(?,?,?,?,?)
+			',
+			$username,
+			$lastMonth,
+			$usageTotals{$username}{'TotalSessionTime'},
+			$usageTotals{$username}{'TotalInput'},
+			$usageTotals{$username}{'TotalOutput'}
 		);
 
 		if ($sth) {
 			# Do query
 			$sth = DBDo(@dbDoParams);
 		}
-
-		my @insertArray;
-		for (my $i = 0; $i < $index; $i++) {
-
-			# Check if this record exists
-			my $sth = DBSelect('
-				SELECT
-					COUNT(*) as rowCount
-				FROM
-					@TP@accounting
-				WHERE
-					PeriodKey = ?
-					AND Username = ?
-				',
-				$allRecords[$i]->{'PeriodKey'},
-				$allRecords[$i]->{'Username'}
-			);
-
-			if (!$sth) {
-				$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to check for existing record: ".
-						awitpt::db::dblayer::Error());
-				return;
-			}
-
-			my $recordCheck = $sth->fetchrow_hashref();
-			$recordCheck = hashifyLCtoMC(
-				$recordCheck,
-				qw(rowCount)
-			);
-
-			if (defined($recordCheck->{'rowCount'}) && $recordCheck->{'rowCount'} > 0) {
-				@insertArray = (
-					$allRecords[$i]->{'SessionTime'},
-					$allRecords[$i]->{'InputOctets'},
-					$allRecords[$i]->{'InputGigawords'},
-					$allRecords[$i]->{'OutputOctets'},
-					$allRecords[$i]->{'OutputGigawords'},
-					$allRecords[$i]->{'Username'},
-					$allRecords[$i]->{'PeriodKey'}
-				);
-
-				@dbDoParams = ('
-					UPDATE
-						@TP@accounting_summary
-					SET
-						AcctSessionTime = ?,
-						AcctInputOctets = ?,
-						AcctInputGigawords = ?,
-						AcctOutputOctets = ?,
-						AcctOutputGigawords = ?
-					WHERE
-						Username = ?
-						AND	PeriodKey = ?
-					',
-					@insertArray
-				);
-
-				if ($sth) {
-					# Do query
-					$sth = DBDo(@dbDoParams);
-				}
-			} else {
-				@insertArray = (
-					$allRecords[$i]->{'Username'},
-					$allRecords[$i]->{'PeriodKey'},
-					$allRecords[$i]->{'SessionTime'},
-					$allRecords[$i]->{'InputOctets'},
-					$allRecords[$i]->{'InputGigawords'},
-					$allRecords[$i]->{'OutputOctets'},
-					$allRecords[$i]->{'OutputGigawords'}
-				);
-
-				@dbDoParams = ('
-					INSERT INTO
-						@TP@accounting_summary
-					(
-						Username,
-						PeriodKey,
-						AcctSessionTime,
-						AcctInputOctets,
-						AcctInputGigawords,
-						AcctOutputOctets,
-						AcctOutputGigawords
-					)
-					VALUES
-						(?,?,?,?,?,?,?)
-					',
-					@insertArray
-				);
-
-				if ($sth) {
-					# Do query
-					$sth = DBDo(@dbDoParams);
-				}
-			}
-		}
 	}
 
 	# Rollback with error if failed
 	if (!$sth) {
 		DBRollback();
-		$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to insert or update accounting record: ".
+		$server->log(LOG_ERR,"[MOD_ACCOUNTING_SQL] Cleanup => Failed to insert accounting summary record: ".
 				awitpt::db::dblayer::Error());
 		return;
 	}
 
 	# Commit if succeeded
 	DBCommit();
-	$server->log(LOG_NOTICE,"[MOD_ACCOUNTING_SQL] Cleanup => Totals have been updated");
+	$server->log(LOG_NOTICE,"[MOD_ACCOUNTING_SQL] Cleanup => Accounting summary updated");
 }
 
 
-- 
GitLab