[Positioning] Add logging support

Add interface InputLogMedium and class InputLogCSV.

Input: Add logging support.

UserInterface: Add logging options.

Configuration:
- Rename functions (suppress "get_").
- Add functions to allow multiple values in one option (needed by
  logging options):
  - value_exists_in_string_vector()
  - string_vector_value()

InputCSV: Add a destructor.

posexcept.hh: Add no_log_csv_file exception.

TestUtil:
- Improve set_up().
- Add functions used in InputLogCSV_test:
  - functions to compare Request & Co,
  - functions to compare the sizes of two files.
This commit is contained in:
Matteo Cypriani 2010-02-16 16:19:39 +01:00
parent 8257e9e67d
commit a5aa1af433
17 changed files with 502 additions and 32 deletions

View File

@ -31,8 +31,10 @@ TARGET = owlps-positioning
OBJ = posutil.o stock.o timestamp.o point3d.o referencepoint.o \
waypoint.o building.o area.o wifidevice.o accesspoint.o \
mobile.o measurement.o calibrationmeasurement.o request.o \
inputcsv.o configuration.o userinterface.o input.o
configuration.o userinterface.o input.o \
inputcsv.o inputlogcsv.o
OBJ_NOTEST = posexcept.o
INTERFACES = inputmedium.hh inputlogmedium.hh
TESTS_XX = $(TESTS_DIR)/tests.cc
TESTS_OBJ = $(TESTS_DIR)/tests.o
@ -67,6 +69,7 @@ measurement.o: accesspoint.o
calibrationmeasurement.o: measurement.o referencepoint.o
request.o: timestamp.o measurement.o
inputcsv.o: inputmedium.hh request.o stock.o
inputlogcsv.o: inputlogmedium.hh request.o
input.o: posexcept.o
# Specific targets
@ -114,7 +117,7 @@ style:
$(OBJ:.o=.cc) \
$(OBJ_NOTEST:.o=.hh) \
$(OBJ_NOTEST:.o=.cc) \
inputmedium.hh \
$(INTERFACES) \
$(TESTS_DIR)/*.hh \
$(TESTS_DIR)/*.cc
@ -122,4 +125,4 @@ check:
@$(CPPCHECK) \
$(OBJ:.o=.hh) $(OBJ:.o=.cc) \
$(OBJ_NOTEST:.o=.hh) $(OBJ_NOTEST:.o=.cc) \
inputmedium.hh
$(INTERFACES)

View File

@ -1,5 +1,6 @@
#include "configuration.hh"
using namespace std ;
namespace po = boost::program_options ;
@ -19,7 +20,7 @@ po::variables_map& Configuration::getw_configuration()
}
bool Configuration::is_configured(const std::string &key)
bool Configuration::is_configured(const string &key)
{
if (configuration.count(key))
return true ;
@ -28,13 +29,36 @@ bool Configuration::is_configured(const std::string &key)
const std::string&
Configuration::get_string_value(const std::string &key)
Configuration::string_value(const string &key)
{
return configuration[key].as<std::string>() ;
return configuration[key].as<string>() ;
}
int Configuration::get_int_value(const std::string &key)
int Configuration::int_value(const string &key)
{
return configuration[key].as<int>() ;
}
bool Configuration::
value_exists_in_string_vector(const string &key, const string &value)
{
if (! is_configured(key))
return false ;
vector<string> key_values = configuration[key].as< vector<string> >() ;
for (vector<string>::const_iterator i = key_values.begin() ;
i != key_values.end() ; ++i)
if (*i == value)
return true ;
return false ;
}
const vector<string>& Configuration::string_vector_value(const string &key)
{
return configuration[key].as< vector<string> >() ;
}

View File

@ -2,6 +2,7 @@
#define _OWLPS_POSITIONING_CONFIGURATION_HH_
#include <string>
#include <vector>
#include <boost/program_options/variables_map.hpp>
/// Stocks the configuration of the program
@ -18,9 +19,14 @@ public:
/// Checks if the key exists in the configuration
static bool is_configured(const std::string &key) ;
/// Get the string value corresponding to \em key
static const std::string& get_string_value(const std::string &key) ;
static const std::string& string_value(const std::string &key) ;
/// Get the int value corresponding to \em key
static int get_int_value(const std::string &key) ;
static int int_value(const std::string &key) ;
static bool value_exists_in_string_vector(
const std::string &key,
const std::string &value) ;
static const std::vector<std::string>&
string_vector_value(const std::string &key) ;
} ;
#endif // _OWLPS_POSITIONING_CONFIGURATION_HH_

View File

@ -1,5 +1,6 @@
#include "input.hh"
#include "inputcsv.hh"
#include "inputlogcsv.hh"
#include "request.hh"
#include "configuration.hh"
#include "posexcept.hh"
@ -14,6 +15,7 @@ Input::Input()
{
medium = NULL ;
initialise_input_medium() ;
initialise_log_media() ;
}
@ -33,14 +35,14 @@ void Input::initialise_input_medium()
throw no_input_medium() ;
const string &medium_name =
Configuration::get_string_value("input.medium") ;
Configuration::string_value("input.medium") ;
if (medium_name == "CSV")
{
if (! Configuration::is_configured("input.csv-file"))
throw no_input_csv_file() ;
medium = new
InputCSV(Configuration::get_string_value("input.csv-file")) ;
InputCSV(Configuration::string_value("input.csv-file")) ;
}
else
@ -48,11 +50,49 @@ void Input::initialise_input_medium()
}
void Input::initialise_log_media()
{
if (! Configuration::is_configured("log.medium"))
return ;
if (Configuration::value_exists_in_string_vector("log.medium", "none"))
return ;
const vector<string> &media_names =
Configuration::string_vector_value("log.medium") ;
for (vector<string>::const_iterator i = media_names.begin() ;
i != media_names.end() ; ++i)
{
if (*i == "CSV")
initialise_log_csv() ;
else
throw input_medium_type_unknown(*i) ;
}
}
void Input::initialise_log_csv()
{
if (! Configuration::is_configured("log.csv-file"))
throw no_log_csv_file() ;
log_media.push_back(
new InputLogCSV(
Configuration::string_value("log.csv-file"))) ;
}
const Request& Input::get_next_request() const
{
if (medium == NULL)
throw null_input_medium() ;
return medium->get_next_request() ;
if (! eof())
{
medium->get_next_request() ;
log_current_request() ;
}
return medium->get_current_request() ;
}
@ -62,3 +102,11 @@ bool Input::eof() const
throw null_input_medium() ;
return medium->eof() ;
}
void Input::log_current_request() const
{
for (vector<InputLogMedium*>::const_iterator i = log_media.begin() ;
i != log_media.end() ; ++i)
(*i)->log_request(medium->get_current_request()) ;
}

View File

@ -2,17 +2,24 @@
#define _OWLPS_POSITIONING_INPUT_HH_
class InputMedium ;
class InputLogMedium ;
class Request ;
#include <string>
#include <vector>
/// Handles the inputs
class Input
{
protected:
InputMedium *medium ; ///< Input medium used
/// List of input log media used
std::vector<InputLogMedium*> log_media ;
void initialise_input_medium(void) ;
void initialise_log_media(void) ;
void initialise_log_csv(void) ;
void log_current_request(void) const ;
public:
Input(void) ;

View File

@ -38,6 +38,12 @@ InputCSV::InputCSV(const string &filename)
}
InputCSV::~InputCSV()
{
input_file.close() ;
}
/* *** Operations *** */

View File

@ -28,6 +28,7 @@ protected:
public:
/// Constructs an InputCSV from a CSV input file name
InputCSV(const std::string &filename) ;
~InputCSV(void) ;
/** @name Read accessors */
//@{

View File

@ -0,0 +1,81 @@
#include "inputlogcsv.hh"
#include "posexcept.hh"
#include "mobile.hh"
#include <sstream>
using namespace std ;
using std::tr1::unordered_map ;
/* *** Constructors *** */
/**
* Prepares the InputLogCSV to write to a CSV file.
* @param filename The name of the file to open.
* @throw error_opening_input_file if the file cannot be opened.
*/
InputLogCSV::InputLogCSV(const string &filename)
{
log_file_name = filename ;
log_file.open(log_file_name.c_str()) ;
if (! log_file)
throw error_opening_input_file(log_file_name) ;
}
InputLogCSV::~InputLogCSV()
{
log_file.close() ;
}
/* *** Operations *** */
/**
* @return true if the request has been logged successfuly, or false
* if not.
*/
bool InputLogCSV::log_request(const Request &request)
{
if (! log_file)
return false ;
log_file << request_to_csv(request).c_str() ;
return true ;
}
const string InputLogCSV::request_to_csv(const Request &request) const
{
ostringstream csv_line ;
if (request.get_mobile() != NULL)
csv_line << request.get_mobile()->get_mac_addr() ;
csv_line << ';' << request.get_timestamp().get_timestamp_ms() ;
csv_line << ";0;0;0;0" ; // TODO: handle calibration request
const unordered_map<string, Measurement> &measurements =
request.get_measurements() ;
for (unordered_map<string, Measurement>::const_iterator i
= measurements.begin() ; i != measurements.end() ; ++i)
{
const vector<int> &ss_list = i->second.get_ss_list() ;
const string &ap_mac = i->second.get_ap() != NULL
? i->second.get_ap()->get_mac_addr()
: "" ;
for (vector<int>::const_iterator i = ss_list.begin() ;
i != ss_list.end() ; ++i)
{
csv_line << ';' << ap_mac ;
csv_line << ';' << *i ;
}
}
csv_line << '\n' ;
return csv_line.str() ;
}

View File

@ -0,0 +1,28 @@
#ifndef _OWLPS_POSITIONING_INPUTLOGCSV_HH_
#define _OWLPS_POSITIONING_INPUTLOGCSV_HH_
#include "inputlogmedium.hh"
#include <string>
#include <fstream>
/// Log Request to a CSV file
class InputLogCSV: public InputLogMedium
{
protected:
std::string log_file_name ;
std::ofstream log_file ;
const std::string request_to_csv(const Request &request) const ;
public:
InputLogCSV(const std::string &filename) ;
~InputLogCSV(void) ;
/** @name Operations */
//@{
bool log_request(const Request &request) ;
//@}
} ;
#endif // _OWLPS_POSITIONING_INPUTLOGCSV_HH_

View File

@ -0,0 +1,22 @@
#ifndef _OWLPS_POSITIONING_INPUTLOGMEDIUM_HH_
#define _OWLPS_POSITIONING_INPUTLOGMEDIUM_HH_
#include "request.hh"
/// Super class of all input log media
/**
* Provide interface for input log media, i.e. to log Request received
* by the InputMedium.
*/
class InputLogMedium
{
public:
virtual ~InputLogMedium(void) {}
/** @name Operations */
//@{
virtual bool log_request(const Request &request) = 0 ;
//@} // End Operations
} ;
#endif // _OWLPS_POSITIONING_INPUTLOGMEDIUM_HH_

View File

@ -49,6 +49,12 @@ const char* no_input_csv_file::what() const throw()
}
const char* no_log_csv_file::what() const throw()
{
return "No log CSV file specified in the configuration!" ;
}
error_opening_input_file::
error_opening_input_file(const string &_file_name) throw():
file_name(_file_name) {}

View File

@ -50,6 +50,13 @@ public:
} ;
class no_log_csv_file: public std::exception
{
public:
const char* what() const throw() ;
} ;
class error_opening_input_file: public std::exception
{
private:

View File

@ -0,0 +1,89 @@
#include <cxxtest/TestSuite.h>
#include "inputlogcsv.hh"
#include <vector>
class InputLogCSV_test: public CxxTest::TestSuite
{
private:
std::string csv_file_name ;
std::string log_file_name ;
public:
InputLogCSV_test(void)
{
// If we cannot open the file, we want to stop the test
CxxTest::setAbortTestOnFail(true) ;
// Clear the stock
Stock::clear() ;
csv_file_name = "/tmp/InputLogCSV_test_csv_file.csv" ;
TestUtil::create_test_csv_file(csv_file_name) ;
log_file_name = "/tmp/InputLogCSV_test_log_file.csv" ;
// Back to the normal behaviour (i.e. do not abort on fail)
CxxTest::setAbortTestOnFail(false) ;
}
~InputLogCSV_test(void)
{
TestUtil::remove_file(csv_file_name) ;
TestUtil::remove_file(log_file_name) ;
Stock::clear() ;
}
static InputLogCSV_test* createSuite(void)
{
return new InputLogCSV_test() ;
}
static void destroySuite(InputLogCSV_test *suite)
{
delete suite ;
}
void test_inputlogcsv(void)
{
// Note: we need to dynamically create and delete an InputLogCSV
// instance in order to be able to close the file (and therefore
// flush the stream) before to read it with InputCSV
InputLogCSV *inputlogcsv1 = new InputLogCSV(log_file_name) ;
for (std::vector<Request>::const_iterator i =
TestUtil::requests.begin() ;
i != TestUtil::requests.end() ; ++i)
inputlogcsv1->log_request(*i) ;
delete inputlogcsv1 ; // Close the output file
// Even if data is not in the same order, size of the two files
// must be identical
TS_ASSERT(TestUtil::file_size_equals(csv_file_name, log_file_name)) ;
// Read the two files and compare to the reference Request list
InputCSV inputcsv_csv(csv_file_name) ;
InputCSV inputcsv_log(log_file_name) ;
for (std::vector<Request>::const_iterator i =
TestUtil::requests.begin() ;
i != TestUtil::requests.end() ; ++i)
{
Stock::clear() ;
Request request_csv = inputcsv_csv.get_next_request() ;
TS_ASSERT(TestUtil::request_equals(*i, request_csv)) ;
Stock::clear() ;
Request request_log = inputcsv_log.get_next_request() ;
TS_ASSERT(TestUtil::request_equals(*i, request_log)) ;
}
TS_ASSERT(inputcsv_csv.eof()) ;
TS_ASSERT(inputcsv_log.eof()) ;
}
} ;

View File

@ -1,11 +1,9 @@
#include "testutil.hh"
#include <cxxtest/TestSuite.h>
#include "measurement.hh"
#include <fstream>
#include <sstream>
#include <boost/tr1/unordered_map.hpp>
#include <sys/stat.h>
using namespace std ;
using std::tr1::unordered_map ;
@ -28,15 +26,22 @@ void TestUtil::set_up()
tear_down() ;
// Create mobile list
mobiles.push_back(Mobile("192.168.0.42", "aa:bb:cc:dd:ee:ff")) ;
mobiles.push_back(Mobile("192.168.1.87", "aa:bb:cc:dd:ee:77")) ;
Mobile mobile1 ;
mobile1.set_mac_addr("aa:bb:cc:dd:ee:ff") ;
mobiles.push_back(mobile1) ;
mobile1.set_mac_addr("aa:bb:cc:dd:ee:77") ;
mobiles.push_back(mobile1) ;
// Create AP list
aps.push_back(AccessPoint(Point3D(1,2,3), "10.0.0.1", "11:22:33:44:55:01")) ;
aps.push_back(AccessPoint(Point3D(4,5,6), "10.0.0.2", "11:22:33:44:55:02")) ;
aps.push_back(AccessPoint(Point3D(7,8,9), "10.0.0.3", "11:22:33:44:55:03")) ;
AccessPoint ap1 ;
ap1.set_mac_addr("11:22:33:44:55:01") ;
aps.push_back(ap1) ;
ap1.set_mac_addr("11:22:33:44:55:02") ;
aps.push_back(ap1) ;
ap1.set_mac_addr("11:22:33:44:55:03") ;
aps.push_back(ap1) ;
// Create measurement list
// Create request list
vector < unordered_map<string,Measurement> > measurements(3) ;
Measurement measurement1 ;
@ -91,9 +96,12 @@ void TestUtil::set_up()
timestamps.push_back(Timestamp(1265120912345)) ;
// Create request list
requests.push_back(Request(timestamps[0], measurements[0])) ;
requests.push_back(Request(timestamps[1], measurements[1])) ;
requests.push_back(Request(timestamps[2], measurements[2])) ;
requests.push_back(Request(&mobiles[0], timestamps[0],
measurements[0])) ;
requests.push_back(Request(&mobiles[1], timestamps[1],
measurements[1])) ;
requests.push_back(Request(&mobiles[0], timestamps[2],
measurements[2])) ;
}
@ -241,3 +249,114 @@ create_test_csv_file(const string &file_name, bool with_spaces)
// Create and fill the test CSV file
fill_file(file_name, csv_lines) ;
}
// Test equality of two Request comparing pointed values instead of
// pointers and taking care of the unordered_map
bool TestUtil::request_equals(const Request &first,
const Request &second)
{
// Try a classical comparison
if (first == second)
return true ;
// Compare timestamp
if (first.get_timestamp() != second.get_timestamp())
return false ;
// Compare mobile values
if (first.get_mobile() == NULL || second.get_mobile() == NULL)
{
if (first.get_mobile() != NULL || second.get_mobile() != NULL)
return false ;
}
else if (*first.get_mobile() != *second.get_mobile())
return false ;
// Compare measurements
if (first.get_measurements() != second.get_measurements())
if (! measurements_unordered_map_equals(first.get_measurements(),
second.get_measurements()))
return false ;
return true ;
}
// Test equality of two unordered_map<string,Measurement>
bool TestUtil::
measurements_unordered_map_equals(
unordered_map<string,Measurement> first,
unordered_map<string,Measurement> second)
{
if (first.size() != second.size())
return false ;
// For each element in 'first', we look for the same element
// in 'second'
for (unordered_map<string,Measurement>::const_iterator i =
first.begin() ; i != first.end() ; ++i)
{
unordered_map<string,Measurement>::const_iterator
second_measurement = second.find(i->first) ;
if (second_measurement == second.end())
return false ;
if (! measurement_equals(i->second, second_measurement->second))
return false ;
}
return true ;
}
// Test equality of two Measurement comparing pointed values instead of
// pointers
bool TestUtil::measurement_equals(const Measurement &first,
const Measurement &second)
{
// Try a classical comparison
if (first == second)
return true ;
// Compare average_ss
if (first.get_average_ss() != second.get_average_ss())
return false ;
// Compare ap values
if (first.get_ap() == NULL || second.get_ap() == NULL)
{
if (first.get_ap() != NULL || second.get_ap() != NULL)
return false ;
}
else if (*first.get_ap() != *second.get_ap())
return false ;
// Compare ss_list
if (first.get_ss_list() != second.get_ss_list())
return false ;
return true ;
}
bool TestUtil::file_size_equals(const string &file1_name,
const string &file2_name)
{
try
{
return (file_size(file1_name) == file_size(file2_name)) ;
}
catch (...)
{
return false ;
}
}
off_t TestUtil::file_size(const string &file_name)
{
struct stat file_info ;
if (stat(file_name.c_str(), &file_info) != 0)
throw "Cannot get information for file `" + file_name + "`!" ;
return file_info.st_size ;
}

View File

@ -4,9 +4,11 @@
#include "accesspoint.hh"
#include "mobile.hh"
#include "request.hh"
#include "measurement.hh"
#include <string>
#include <vector>
#include <boost/tr1/unordered_map.hpp>
class TestUtil
{
@ -24,6 +26,20 @@ public:
static void create_test_csv_file(const std::string &file_name,
bool with_spaces = false) ;
static bool request_equals(const Request &first,
const Request &second) ;
static bool measurements_unordered_map_equals(
std::tr1::unordered_map<std::string,Measurement> first,
std::tr1::unordered_map<std::string,Measurement> second) ;
static bool measurement_equals(const Measurement &first,
const Measurement &second) ;
static bool file_size_equals(const std::string &file1_name,
const std::string &file2_name) ;
static off_t file_size(const std::string &file_name) ;
} ;
#endif // _OWLPS_POSITIONING_TESTS_TESTUTIL_HH_

View File

@ -67,8 +67,8 @@ public:
TS_ASSERT(Configuration::is_configured("server.port")) ;
TS_ASSERT(Configuration::is_configured("config-file")) ;
TS_ASSERT_EQUALS(Configuration::get_int_value("server.port"), 42) ;
TS_ASSERT_EQUALS(Configuration::get_string_value("config-file"),
TS_ASSERT_EQUALS(Configuration::int_value("server.port"), 42) ;
TS_ASSERT_EQUALS(Configuration::string_value("config-file"),
config_file_name) ;
}

View File

@ -71,12 +71,19 @@ void UserInterface::fill_file_options()
// Server options
("server.port,l", po::value<int>()
->default_value(DEFAULT_LISTENING_PORT),
"Server listening port")
"Server listening port.")
// Input options
("input.medium,I", po::value<string>(),
"Medium from which requests are read")
"Medium from which requests are read. Allowed: CSV.")
("input.csv-file,C", po::value<string>(),
"CSV file to use for input (when input.medium = CSV)")
"CSV file to use for input (when input.medium = CSV).")
// Log options
("log.medium,L", po::value< vector<string> >()->composing(),
"Medium to which the requests will be logged. You can specify \
this option more than once. Allowed: none, CSV. The `none` value \
completely disables logging.")
("log.csv-file", po::value<string>(),
"CSV file to use for logging (when log.medium = CSV).")
;
}
@ -88,7 +95,7 @@ void UserInterface::parse_options()
// Was the config file name specified on the command line?
if (Configuration::is_configured("config-file"))
config_file_name = Configuration::get_string_value("config-file") ;
config_file_name = Configuration::string_value("config-file") ;
parse_file() ;