Source for file MongoRandomElements.class.php
Documentation is available at MongoRandomElements.class.php
* MongoRandomElements.class.php
* File with the class used to generate random elements and save then in MongoDB (users, URL's ...)
* @author José Manuel Ciges Regueiro <jmanuel@ciges.net>, Web page {@link http://www.ciges.net}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPLv3
* @package InternetAccessLog
require_once("RandomElements.class.php");
* This class is used to generate random elements (users, IP's and URL's) and save them into MongoDB
* With this elements created we can simulate non FTP and FTP log entries (in our demo the acces by FTP are stored in a separate collection)
* Default names for random data collections
* Default prefixes for monthly reports
* Constants for default connection values
* Connection to the database
private $db_databasename;
* Number of element of each created collection in MongoDB (for cache purposes)
private $rnd_users_number;
private $rnd_domains_number;
* This function queries the database to return the users number (records in Random_UsersList)
return $this->rnd_users_number;
* This function returns the username searching by the id
* @return string $username
if ($cursor->hasNext()) {
$row = $cursor->getNext();
* This function queries the database to return the domains number (records in Random_DomainsList)
return $this->rnd_domains_number;
* This function returns the domain searching by the id. If the domain does not exist null is returner
if ($cursor->hasNext()) {
$row = $cursor->getNext();
* This function returns the user data from the reports for a year and month specified. If there is no data returns null
* @param string $username
* @param integer $month Number from 1 to 12
$col = self::USERS_REPORT_PREFIX. $year. sprintf("%02d", $month);
$cursor = $this->getDB()->$col->find(array('_id' => $username));
if ($cursor->hasNext()) {
return $cursor->getNext();
* This function returns the domain data from the reports for a year and month specified. If there is no data returns null
* @param string $domainname
* @param integer $month Number from 1 to 12
$col = self::DOMAINS_REPORT_PREFIX. $year. sprintf("%02d", $month);
$cursor = $this->getDB()->$col->find(array('_id' => $domainname));
if ($cursor->hasNext()) {
return $cursor->getNext();
* This function add a fake user to the collection passed as second argument. If not collection done then the user will be added to Random_UsersList.
* This function is coded for load tests, not for real user. The id is the id generated by Mongo
* Returns true if the user has been succesfull added, false if not
* @param string $username
* @param string $collectionname
public function addFakeUser($username, $collectionname = self::RNDUSERSC_NAME) {
$user_col = $this->getDB()->$collectionname;
$doc = array("user" => $username);
$user_col->insert($doc, array("safe" => $this->safemode));
catch (MongoConnectionException $e) {
* This function verifies if the user exists in the collection passed as second argument. If not collection done then the user will be added to Random_UsersList.
* @param string $username
* @param string $tablename
public function existUser($username, $collectionname = self::RNDUSERSC_NAME) {
$cursor = $this->getDB()->$collectionname->find(array('user' => $username))->limit(1);
* Constructor. For creating an instance we need to pass all the parameters for the MongoDB database where the data will be stored (user, password, host & database name). The fifth parameter tells if the insertions will we made in safe mode or not (by default they are NOT safe)
* <li>The default user and password will be mongodb
* <li>The default host will be localhost
* <li>The default database name will be InternetAccessLog
* <li>Inertions will be NOT safe by default
* @param string $password
* @param string $database
* @param boolean $safemode
function __construct($user = self::DEFAULT_USER, $password = self::DEFAULT_PASSWORD, $host = self::DEFAULT_HOST, $database = self::DEFAULT_DB, $safemode = self::DEFAULT_SAFEMODE) {
// Open a connection to MongoDB
$this->db_conn = new Mongo("mongodb://". $user. ":". $password. "@". $host. "/". $database);
$this->db_databasename = $database;
catch (MongoConnectionException $e) {
die("Connection to MongoDB impossible: (". $e->getCode(). ") ". $e->getMessage(). "\n");
// Stores the number of elements of each stored random elements collection
$db = $this->db_databasename;
$this->safemode = $safemode;
$userscol_name = self::RNDUSERSC_NAME;
$ipscol_name = self::RNDIPSC_NAME;
$domainscol_name = self::RNDDOMAINSC_NAME;
$nonftp_log_name = self::NONFTPLOG_NAME;
$ftp_log_name = self::FTPLOG_NAME;
$this->rnd_users_number = $this->db_conn->$db->$userscol_name->count();
$this->rnd_ips_number = $this->db_conn->$db->$ipscol_name->count();
$this->rnd_domains_number = $this->db_conn->$db->$domainscol_name->count();
* Destructor. Close the open connection to MongoDB database
* Get the connection created to the database (the db, not the server)
public function getDB() {
$databasename = $this->db_databasename;
return $this->db_conn->$databasename;
* Sends an aggregation array of commands to the database and returns the results (array of documents). If no documents are got null is returned
* @param string $collection
public function getResults($pipeline, $collection = "NonFTP_Access_log") {
MongoCursor::$timeout = - 1;
$data = $this->getDB()->command (
"aggregate" => $collection,
* Sends an aggregation array of commands to the database and returns the first document. If no documents are got null is returned
* @param string $collection
public function getRow($pipeline, $collection = "NonFTP_Access_log") {
$data = $this->getResults($pipeline, $collection);
* Sends an aggregation array of commands to the database and returns the field '_id' of the first document. If no documents are got null is returned
* @param string $collection
public function getOne($pipeline, $collection) {
$row = $this->getRow($pipeline, $collection);
* Get a link to the users collection
* @return MongoCollection
$user_col_name = MongoRandomElements::RNDUSERSC_NAME;
return $this->getDB()->$user_col_name;
* Get a link to the domains collection
* @return MongoCollection
return $this->getDB()->$user_col_name;
* Save random users in MongoDB.
* The parameters are the number of users to create and to booleans: if we want an unique index to be created for the user name (default is TRUE) and if we want that the user name is unique (default TRUE).
* If the user name is going to be unique the existence of the name is verified with a query before inserting a new one.
* The id will be autonumeric (1, 2, 3 ....)
* @param boolean $use_index
* @param boolean $dont_repeat
function createUsers($number, $use_index = TRUE, $dont_repeat = TRUE) {
$id = $this->rnd_users_number + 1; // Autonumeric
$db = $this->db_databasename;
$col_name = self::RNDUSERSC_NAME;
$col = $this->db_conn->$db->$col_name;
$col->ensureIndex(array('user' => 1), array("unique" => true)); // Unique index for the 'user' field
// We verify if the user is in the collection only if it is needed
$cursor = $col->find(array("user" => $user))->limit(1);
if ($cursor->count() > 0) {
$col->insert(array("_id" => $id, "user" => $user), array("safe" => $this->safemode));
catch (MongoConnectionException $e) {
die("Save of user document in MongoDB not possible: (". $e->getCode(). ") ". $e->getMessage(). "\n");
$this->rnd_users_number = $col->count();
* Returns true if the random users collection has at least one user
$db = $this->db_databasename;
$col = self::RNDUSERSC_NAME;
$this->db_conn->$db->$col->count() > 0 ? true : false;
* Save random IPs in MongoDB
* The parameters are the number of IPs to create and to booleans: if we want an unique index to be created for the ip (default is TRUE) and if we want that the ip is unique (default TRUE).
* If the ip is going to be unique the existence of the name is verified with a query before inserting a new one
* The id will be autonumeric (1, 2, 3 ....)
* @param boolean $use_index
* @param boolean $dont_repeat_users
function createIPs($number, $use_index = TRUE, $dont_repeat = TRUE) {
$id = $this->rnd_ips_number + 1; // Autonumeric
$db = $this->db_databasename;
$col_name = self::RNDIPSC_NAME;
$col = $this->db_conn->$db->$col_name;
$col->ensureIndex(array('ip' => 1), array("unique" => true)); // Unique index for the 'ip' field
// We verify if the user is in the collection only if it is needed
$cursor = $col->find(array("ip" => $ip))->limit(1);
if ($cursor->count() > 0) {
$col->insert(array("_id" => $id, "ip" => $ip), array("safe" => $this->safemode));
catch (MongoConnectionException $e) {
die("Save of IP document in MongoDB not possible: (". $e->getCode(). ") ". $e->getMessage(). "\n");
$this->rnd_ips_number = $col->count();
* Returns true if the IPs collection has at least one IP
$db = $this->db_databasename;
$col = self::RNDIPSC_NAME;
$this->db_conn->$db->$col->count() > 0 ? true : false;
* Save random domains in MongoDB
* The parameter are the number of domains to create and to booleans: if we want an unique index to be created for the domain (default is TRUE) and if we want that the domain is unique (default TRUE).
* If the domain is going to be unique the existence of the name is verified with a query before inserting a new one
* The id will be autonumeric (1, 2, 3 ....)
* @param boolean $use_index
* @param boolean $dont_repeat_users
function createDomains($number, $use_index = TRUE, $dont_repeat = TRUE) {
$id = $this->rnd_domains_number + 1; // Autonumeric
$db = $this->db_databasename;
$col_name = self::RNDDOMAINSC_NAME;
$col = $this->db_conn->$db->$col_name;
$col->ensureIndex(array('domain' => 1), array("unique" => true)); // Unique index for the 'domain' field
// We verify if the user is in the collection only if it is needed
$cursor = $col->find(array("domain" => $domain))->limit(1);
if ($cursor->count() > 0) {
$col->insert(array("_id" => $id, "domain" => $domain), array("safe" => $this->safemode));
catch (MongoConnectionException $e) {
die("Save of domain document in MongoDB not possible: (". $e->getCode(). ") ". $e->getMessage(). "\n");
$this->rnd_domains_number = $col->count();
* Returns true if the random domains collection has at least one domain
$db = $this->db_databasename;
$col = self::RNDDOMAINSC_NAME;
$this->db_conn->$db->$col->count() > 0 ? true : false;
* Returns a random IP from the generated collection
$position = mt_rand(1, $this->rnd_ips_number);
$col = self::RNDIPSC_NAME;
$cursor = $this->getDB()->$col->find(array('_id' => $position));
if ($cursor->hasNext()) {
$row = $cursor->getNext();
* Returns a random user from the generated collection
$position = mt_rand(1, $this->rnd_users_number);
$db = $this->db_databasename;
$col = self::RNDUSERSC_NAME;
$cursor = $this->getDB()->$col->find(array('_id' => $position));
if ($cursor->hasNext()) {
$row = $cursor->getNext();
* Returns a random HTTP method from the generated collection
* Returns a random FTP method from the generated collection
* Returns a random domain
$position = mt_rand(1, $this->rnd_domains_number);
$col = self::RNDDOMAINSC_NAME;
$cursor = $this->getDB()->$col->find(array("_id" => $position));
if ($cursor->hasNext()) {
$row = $cursor->getNext();
* Returns a random protocol
* Returns a random return code
* Return a random log entry for non FTP access (http and tunnel)
* It has two optional arguments, initial and final timestamps, if we want to get a random time in log entry created
* @param integer $initial_timestamp
* @param integer $final_timestamp
$ts = mt_rand($initial_timestamp, $final_timestamp);
die("Incorrect arguments number in getRrandomSORLogEntry function: ". implode(" ", $arguments). "\n");
'size' => $this->searchSize() // Size is recorded in the database as string
* Update Users or Domains monthly and daily report
* This function is private and is meant to be used each time an access log is processed to have real time statistics
* @param string $collection_name
* @param string $id document id (user or domain name)
* @param timestamp $timestamp
function saveReport($collection_name, $id, $timestamp, $volume) {
$db = $this->db_databasename;
$col = $this->db_conn->$db->$collection_name;
# Document creation if it not exists
$cursor = $col->find(array("_id" => $id));
if ($cursor->count() == 0) {
$col->insert(array('_id' => $id), array("safe" => $this->safemode));
# Updating monthly and daily values
$day = (int) strftime("%e", $timestamp);
"daily.$day.volume" => $volume
array("safe" => $this->safemode)
catch (MongoException $e) {
die("Saving/Updating data to ". $collection_name. " collection for id ". $id. " not possible: (". $e->getCode(). ") ". $e->getMessage(). "\n");
* Reads ALL logs entries fron both collections NonFTP and FTP and saves reports entries for users and domains
$log_collections = array(self::NONFTPLOG_NAME, self::FTPLOG_NAME);
foreach ($log_collections as $col) {
$cursor = $this->getDB()->$col->find();
while ($cursor->hasNext()) {
$log_entry = $cursor->getNext();
$timestamp = $log_entry["datetime"]->sec;
$yearmonth = strftime("%Y%m", $timestamp);
$this->saveReport(self::USERS_REPORT_PREFIX. $yearmonth, $log_entry["user"], $timestamp, $log_entry['size']);
$this->saveReport(self::DOMAINS_REPORT_PREFIX. $yearmonth, $log_entry["domain"], $timestamp, $log_entry['size']);
* Receives a log entry and saves the data and, optionally, monthly and daily precalculated values in database.
* By default the reports are created. If the second argument is FALSE they will not be generated
* The id for the document in Mongo is created automatically by MongoDB (as an ObjectID)
* @param array $log_entry log entry as returned by {@link getRandomNonFTPLogEntry}
* @param boolean $create_reports
$document["datetime"] = new MongoDate($log_entry["datetime"]);
$db = $this->db_databasename;
$nonftp_log_name = self::NONFTPLOG_NAME;
$col = $this->db_conn->$db->$nonftp_log_name;
$col->insert($document, array("safe" => $this->safemode));
catch (MongoException $e) {
die("Saving document to SOR_Access_log collection not possible: (". $e->getCode(). ") ". $e->getMessage(). "\n");
# Monthly reports data update
$timestamp = $log_entry["datetime"];
$yearmonth = strftime("%Y%m", $timestamp);
$this->saveReport(self::USERS_REPORT_PREFIX. $yearmonth, $document["user"], $timestamp, $document['size']);
$this->saveReport(self::DOMAINS_REPORT_PREFIX. $yearmonth, $document["domain"], $timestamp, $document['size']);
* Return a random log entry for FTP access. It is very similar to HTTP and tunnel access but with less fields (there is no protocol and return code)
* It has two optional arguments, initial and final timestamps, if we want to get a random time in log entry created
* @param integer $initial_timestamp
* @param integer $final_timestamp
$ts = mt_rand($initial_timestamp, $final_timestamp);
die("Incorrect arguments number in getRrandomSORLogEntry function: ". implode(" ", $arguments). "\n");
'size' => $this->searchSize() // Size is recorded in the database as string
* Receives a FTP log entry and saves the data and, optionally, monthly and daily precalculated values in database.
* By default the reports are created. If the second argument is FALSE they will not be generated
* The id for the document in Mongo is created automatically by MongoDB (as an ObjectID)
* @param array $log_entry log entry as returned by {@link getRandomNonFTPLogEntry}
* @param boolean $create_reports
$document["datetime"] = new MongoDate($log_entry["datetime"]);
$db = $this->db_databasename;
$nonftp_log_name = self::FTPLOG_NAME;
$col = $this->db_conn->$db->$nonftp_log_name;
$col->insert($document, array("safe" => $this->safemode));
catch (MongoException $e) {
die("Saving document to SOR_Access_log collection not possible: (". $e->getCode(). ") ". $e->getMessage(). "\n");
# Monthly reports data update
$timestamp = $log_entry["datetime"];
$yearmonth = strftime("%Y%m", $timestamp);
$this->saveReport(self::USERS_REPORT_PREFIX. $yearmonth, $document["user"], $timestamp, $document['size']);
$this->saveReport(self::DOMAINS_REPORT_PREFIX. $yearmonth, $document["domain"], $timestamp, $document['size']);
|