source: trunk/FACT++/src/dataLogger.cc@ 10943

Last change on this file since 10943 was 10943, checked in by tbretz, 13 years ago
Fixed the output of the free space.
File size: 89.0 KB
Line 
1//****************************************************************
2/** @class DataLogger
3
4 @brief Logs all message and infos between the services
5
6 This is the main logging class facility.
7 It derives from StateMachineDim and DimInfoHandler. the first parent is here to enforce
8 a state machine behaviour, while the second one is meant to make the dataLogger receive
9 dim services to which it subscribed from.
10 The possible states and transitions of the machine are:
11 \dot
12 digraph datalogger {
13 node [shape=record, fontname=Helvetica, fontsize=10];
14 e [label="Error" color="red"];
15 r [label="Ready"]
16 d [label="NightlyOpen"]
17 w [label="WaitingRun"]
18 l [label="Logging"]
19 b [label="BadNightlyconfig" color="red"]
20 c [label="BadRunConfig" color="red"]
21
22 e -> r
23 r -> e
24 r -> d
25 r -> b
26 d -> w
27 d -> r
28 w -> r
29 l -> r
30 l -> w
31 b -> d
32 w -> c
33 w -> l
34 b -> r
35 c -> r
36 c -> l
37 }
38 \enddot
39 */
40 //****************************************************************
41#include "Dim.h"
42#include "Event.h"
43#include "Time.h"
44#include "StateMachineDim.h"
45#include "WindowLog.h"
46#include "Configuration.h"
47#include "ServiceList.h"
48#include "Converter.h"
49#include "MessageImp.h"
50#include "LocalControl.h"
51#include "DimDescriptionService.h"
52
53#include "Description.h"
54
55//#include "DimServiceInfoList.h"
56#include "DimNetwork.h"
57//for getting stat of opened files
58#include <unistd.h>
59//for getting disk free space
60#include <sys/statvfs.h>
61//for getting files sizes
62#include <sys/stat.h>
63
64#include <fstream>
65
66#include <boost/bind.hpp>
67#if BOOST_VERSION < 104400
68#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 4))
69#undef BOOST_HAS_RVALUE_REFS
70#endif
71#endif
72#include <boost/thread.hpp>
73
74#ifdef HAVE_FITS
75#include "Fits.h"
76#endif
77
78//Dim structures
79///Distributes the writing statistics
80struct DataLoggerStats {
81 long sizeWritten;
82 long freeSpace;
83 long writingRate;
84};
85///distributes the number of opened subscriptions and fits files
86struct NumSubAndFitsType {
87 int numSubscriptions;
88 int numOpenFits;
89};
90///distributes which files were opened.
91struct OpenFileToDim {
92 int code;
93 char fileName[FILENAME_MAX];
94};
95
96///Run number record. Used to keep track of which run numbers are still active
97struct RunNumberType {
98#ifdef RUN_LOGS
99 ///the run number log file
100 shared_ptr<ofstream> logFile;
101#endif
102 ///the run number report file
103 shared_ptr<ofstream> reportFile;
104#ifdef HAVE_FITS
105 ///the run number group fits file
106 shared_ptr<CCfits::FITS> runFitsFile;
107#endif
108#ifdef RUN_LOGS
109 ///the log filename
110 string logName;
111#endif
112 ///the report filename
113 string reportName;
114 ///the actual run number
115 uint32_t runNumber;
116 ///the time at which the run number was received
117 Time time;
118 ///list of opened fits used to create the fits grouping when the run ends
119 map<string, vector<string> > openedFits;
120 ///default constructor
121 RunNumberType()
122 {
123#ifdef RUN_LOGS
124 logFile = shared_ptr<ofstream>(new ofstream());
125#endif
126 reportFile = shared_ptr<ofstream>(new ofstream());
127#ifdef HAVE_FITS
128 runFitsFile = shared_ptr<CCfits::FITS>();
129#endif
130 runNumber = 0;
131 }
132 ///default destructor
133 ~RunNumberType()
134 {
135
136 }
137 ///copy operator
138 /*
139 void operator = (const RunNumberType& other)
140 {
141#ifdef RUN_LOGS
142 logFile = other.logFile;
143 logName = other.logName;
144#endif
145 reportFile = other.reportFile;
146 reportName = other.reportName;
147 runNumber = other.runNumber;
148 time = other.time;
149#ifdef HAVE_FITS
150 runFitsFile = other.runFitsFile;
151#endif
152}*/
153
154 ///copy constructor
155 RunNumberType(const RunNumberType& other)
156 {
157#ifdef RUN_LOGS
158 logFile = other.logFile;
159 logName = other.logName;
160#endif
161 reportFile = other.reportFile;
162 reportName = other.reportName;
163 runNumber = other.runNumber;
164 time = other.time;
165#ifdef HAVE_FITS
166 runFitsFile = other.runFitsFile;
167#endif
168 }
169
170 void addServiceToOpenedFits(const string& fileName, const string& serviceName)
171 {
172 //most likely I should add this service name.
173 //the only case for which I should not add it is if a service disapeared, hence the file was closed
174 //and reopened again. Unlikely to happen, but well it may
175
176 if (find(openedFits[fileName].begin(), openedFits[fileName].end(),
177 serviceName)==openedFits[fileName].end())
178 openedFits[fileName].push_back(serviceName);
179 }
180};
181///Dim subscription type. Stores all the relevant info to handle a Dim subscription
182struct SubscriptionType
183{
184#ifdef HAVE_FITS
185 ///Nightly FITS output file
186 Fits nightlyFile;
187 ///run-specific FITS output file
188 Fits runFile;
189#endif
190 ///the actual dimInfo pointer
191 shared_ptr<DimStampedInfo> dimInfo;
192 ///the server
193 string server;
194 ///the service
195 string service;
196 ///the converter for outputting the data according to the format
197 shared_ptr<Converter> fConv;
198 ///the current run number used by this subscription
199 uint32_t runNumber;
200 ///copy operator
201 /*
202 void operator = (const SubscriptionType& other)
203 {
204#ifdef HAVE_FITS
205 nightlyFile = other.nightlyFile;
206 runFile = other.runFile;
207#endif
208 dimInfo = other.dimInfo;
209 server = other.server;
210 service = other.service;
211 fConv = other.fConv;
212 runNumber = other.runNumber;
213 }*/
214
215/* ///copy constructor
216 SubscriptionType(const SubscriptionType& other)
217 {
218#ifdef HAVE_FITS
219 nightlyFile = other.nightlyFile;
220 runFile = other.runFile;
221#endif
222 dimInfo = other.dimInfo;
223 server = other.server;
224 service = other.service;
225 fConv = other.fConv;
226 runNumber = other.runNumber;
227 }*/
228 ///Dim info constructor
229 SubscriptionType(DimStampedInfo* info=NULL)
230 {
231 dimInfo = shared_ptr<DimStampedInfo>(info);
232 fConv = shared_ptr<Converter>();
233 runNumber = 0;
234 }
235 ///default constructor
236/* SubscriptionType()
237 {
238 dimInfo = shared_ptr<DimStampedInfo>();
239 fConv = shared_ptr<Converter>();
240 runNumber = 0;
241 }*/
242 ///default destructor
243 ~SubscriptionType()
244 {
245 }
246};
247
248class DataLogger : public StateMachineDim, DimServiceInfoList
249{
250public:
251 /// The list of existing states specific to the DataLogger
252 enum
253 {
254 kSM_NightlyOpen = 20, ///< Nightly file openned and writing
255 kSM_WaitingRun = 30, ///< waiting for the run number to open the run file
256 kSM_Logging = 40, ///< both files openned and writing
257 kSM_BadNightlyConfig = 0x101, ///< the folder specified for Nightly logging does not exist or has bad permissions
258 kSM_BadRunConfig = 0x102, ///< the folder specified for the run logging does not exist or has wrong permissions or no run number
259 } localstates_t;
260
261 DataLogger(ostream &out);
262 ~DataLogger();
263
264 bool SetConfiguration(Configuration& conf);
265
266private:
267 /************************************************
268 * MEMBER VARIABLES
269 ************************************************/
270 /// ofstream for the NightlyLogfile
271 ofstream fNightlyLogFile;
272 /// ofstream for the Nightly report file
273 ofstream fNightlyReportFile;
274 /// base path of the Nightlyfile
275 string fNightlyFilePath;
276 ///base path of the run file
277 string fRunFilePath;
278 ///run numbers
279 list<RunNumberType> fRunNumber;
280 ///previous run number. to check if changed while logging
281 int fPreviousRunNumber;
282 ///Current Service Quality
283 int fQuality;
284 ///Modified Julian Date
285 double fMjD;
286 ///for obtaining the name of the existing services
287// ServiceList fServiceList;
288 typedef map<const string, map<string, SubscriptionType> > SubscriptionsListType;
289 ///All the services to which we have subscribed to, sorted by server name.
290 SubscriptionsListType fServiceSubscriptions;
291 ///full name of the nightly log file
292 string fFullNightlyLogFileName;
293 ///full name of the nightly report file
294 string fFullNightlyReportFileName;
295
296public:
297 /***************************************************
298 * STATIC COMMAND NAMES
299 ***************************************************/
300 ///Define all the static names
301 static const char* fConfigDay;
302 static const char* fConfigRun;
303 static const char* fConfigRunNumber;
304 static const char* fConfigLog;
305 static const char* fTransStart;
306 static const char* fTransStop;
307 static const char* fTransStartRun;
308 static const char* fTransStopRun;
309 static const char* fTransReset;
310 static const char* fTransWait;
311 static const char* fRunNumberInfo; ///< This is the name of the dimInfo received to specify the run number. It must be updated once the final name will be defined
312 static const char* fPrintCommand;
313 static const char* fDebugOnOff;
314 static const char* fStatsPeriod;
315 static const char* fStartStopOpenedFiles;
316 static const char* fStartStopNumSubsAndFits;
317private:
318 /***************************************************
319 * DIM INFO HANDLER
320 ***************************************************/
321 //overloading of DIM's infoHandler function
322 void infoHandler();
323
324 /***************************************************
325 * TRANSITION FUNCTIONS
326 ***************************************************/
327 ///Reporting method for the services info received
328 void ReportPlease(DimInfo* I, SubscriptionType& sub);
329 ///Configuration of the nightly file path
330 int ConfigureNightlyFileName(const Event& evt);
331 ///Configuration fo the file name
332 int ConfigureRunFileName(const Event& evt);
333 ///DEPREC - configuration of the run number
334 int ConfigureRunNumber(const Event& evt);
335 ///print the current state of the dataLogger
336 int PrintStatePlease(const Event& evt);
337 ///checks whether or not the current info being treated is a run number
338 void CheckForRunNumber(DimInfo* I);
339 /// start transition
340 int StartPlease();
341 ///from waiting to logging transition
342 int StartRunPlease();
343 /// from logging to waiting transition
344 int StopRunPlease();
345 ///stop and reset transition
346 int GoToReadyPlease();
347 ///from NightlyOpen to waiting transition
348 int NightlyToWaitRunPlease();
349#ifdef HAVE_FITS
350 ///Open fits files
351 void OpenFITSFilesPlease(SubscriptionType& sub, RunNumberType* cRunNumber);
352 ///Write data to FITS files
353 void WriteToFITS(SubscriptionType& sub);
354 ///Allocate the buffers required for fits
355 void AllocateFITSBuffers(SubscriptionType& sub);
356#endif//has_fits
357
358 /***************************************
359 * DIM SERVICES PROVIDED BY THE DATA LOGGER
360 ***************************************/
361 ///monitoring notification loop
362 void ServicesMonitoring();
363 inline void NotifyOpenedFile(const string &name, int type, DimDescribedService* service);
364 ///services notification thread
365 boost::thread fMonitoringThread;
366 ///end of the monitoring
367 bool fContinueMonitoring;
368 ///stores the size of each file that is or was open
369 map<string, long> fFileSizesMap;
370 ///total size of the opened files BEFORE they were opened by the logger
371 long fBaseSizeNightly;
372 long fPreviousSize;
373 long fBaseSizeRun;
374 ///Service for opened files
375 DimDescribedService* fOpenedNightlyFiles;
376 DimDescribedService* fOpenedRunFiles;
377 DimDescribedService* fNumSubAndFits;
378 NumSubAndFitsType fNumSubAndFitsData;
379 ///Small function for calculating the total size written so far
380 bool calculateTotalSizeWritten(DataLoggerStats& statVar, bool isPrinting);
381
382 /***************************************************
383 * DATA LOGGER's CONFIGURATION STUFF
384 ***************************************************/
385 ///black/white listing
386 set<string> fBlackList;
387 set<string> fWhiteList;
388 ///list of services to be grouped
389 set<string> fGrouping;
390 ///configuration flags
391 bool fDebugIsOn;
392 float fStatsPeriodDuration;
393 bool fOpenedFilesIsOn;
394 bool fNumSubAndFitsIsOn;
395 //functions for controlling the services behavior
396 int SetDebugOnOff(const Event& evt);
397 int SetStatsPeriod(const Event& evt);
398 int SetOpenedFilesOnOff(const Event& evt);
399 int SetNumSubsAndFitsOnOff(const Event& evt);
400
401 ///boolean to prevent DIM update while desctructing the dataLogger
402 bool fDestructing;
403 /***************************************************
404 * UTILITIES
405 ***************************************************/
406 ///vectors to keep track of opened Fits files, for grouping purposes.
407 map<string, vector<string> > fOpenedNightlyFits;
408 ///creates a group fits file based on a list of files to be grouped
409 void CreateFitsGrouping(map<string, vector<string> >& filesToGroup, int runNumber);
410 ///Open the relevant text files related to a particular run
411 int OpenRunFile(RunNumberType& run);
412 ///add a new run number
413 void AddNewRunNumber(int64_t newRun, Time time);
414 ///removes the oldest run number, and close the relevant files.
415 void RemoveOldestRunNumber();
416 ///retrieves the size of a file
417 off_t GetFileSize(const string&);
418 ///Get the digits of year, month and day for filenames and paths
419 void GetYearMonthDayForFiles(unsigned short& year, unsigned short& month, unsigned short& day);
420 ///Appends the relevant year month day to a given path
421 void AppendYearMonthDaytoPath(string& path);
422 ///Form the files path
423 string CompileFileName(const string &path, const string &service, const string & extension, const Time &time=Time());
424 ///Form the files path
425 string CompileFileName(const string &path, uint32_t run, const string &service, const string & extension, const Time &time=Time());
426 ///Check whether service is in black and/or white list
427 bool ShouldSubscribe(const string& server, const string& service);
428 ///Subscribe to a given server and service
429 DimStampedInfo* SubscribeToPlease(const string& server, const string& service);
430 ///Open a text file and checks for ofstream status
431 void OpenTextFilePlease(ofstream& stream, const string& name);
432 ///Check if a dir is . and returns the actual string corresponding to .
433// string CheckIfDirIsDot(const string& dir);
434 ///Remembers the size of newly opened files. for statistic purposes
435 bool RememberFileOrigSizePlease(string& fileName, bool nightly);
436 ///Checks if the input osftream is in error state, and if so close it.
437 void CheckForOfstreamError(ofstream& out);
438 ///Checks if a given path exist
439 bool DoesPathExist(string path);
440public:
441 ///Create a given directory
442 bool CreateDirectory(string path);
443 /***************************************************
444 * INHERITED FROM DIMSERVICEINFOLIST
445 ***************************************************/
446 ///Add a new service subscription
447 void AddService(const string&, const string&, const string&, bool);
448 ///Remove a given service subscription
449 void RemoveService(const string&, const string&, bool);
450 ///Remove all the services associated with a given server
451 void RemoveAllServices(const string&);
452}; //DataLogger
453
454// --------------------------------------------------------------------------
455//
456//! Check if a given path exists
457//! @param path the path to be checked
458//! @return whether or not the creation has been successfull
459//
460bool DataLogger::CreateDirectory(string path)
461{
462 //remove last '/', if present
463 if (path[path.size()-1] == '/')
464 path = path.substr(0, path.size()-1);
465 //create boost path
466 boost::filesystem::path fullPath = boost::filesystem::system_complete(boost::filesystem::path(path));
467
468 //if path does not exist, check if upper levels exist already
469 if (!boost::filesystem::exists(fullPath))
470 {
471 if (path.size() <= 1)
472 {//we're hitting "/", which SHOULD have existed...
473 Error("Something unexpected happened while creating a path");
474 }
475 CreateDirectory(path.substr(0, path.find_last_of('/')));
476 }
477 else
478 {//if path already exist, make sure it does not designate a file (filenames cannot be checked if they do not exist)
479 if (!boost::filesystem::is_directory(fullPath))
480 {
481 Error("Path to be created contains a file name: '" + path + "'");
482 return false;
483 }
484
485 return true;
486 }
487 //path does not exist, and upper level have been created already by recusrion.
488 mode_t rightsMask = S_IRWXU | S_IXGRP | S_IRGRP | S_IXOTH | S_IROTH; //everybody read, owner writes
489
490 int returnValue = mkdir(path.c_str(), rightsMask);
491
492 if (returnValue != 0)
493 {
494 ostringstream str;
495 str << "Could not create directory " << path << " mkdir error code: " << errno;
496 Error(str.str());
497 return false;
498 }
499
500 return true;
501}
502// --------------------------------------------------------------------------
503//
504//! Check if a given path exists
505//! @param path the path to be checked
506//! @return whether or not the given path exists
507//
508bool DataLogger::DoesPathExist(string path)
509{
510 boost::filesystem::path fullPath = boost::filesystem::system_complete(boost::filesystem::path(path));
511
512
513 if (!boost::filesystem::exists(fullPath))
514 return false;
515
516 if (!boost::filesystem::is_directory(fullPath))
517 {
518 Error("Path given for checking '" + path + "' designate a file name. Please provide a path name only");
519 return false;
520 }
521
522 if (access(path.c_str(), R_OK|W_OK|X_OK) != 0)
523 {
524 Error("Missing read, write or execute permissions on directory '" + path + "'");
525 return false;
526 }
527
528 return true;
529}
530// --------------------------------------------------------------------------
531//
532//! Add a new service subscription
533//! @param server the server for which the subscription should be created
534//! @param service the service for which the subscription should be created
535//! @param isCmd whether this is a Dim Command or not. Commands are not logged
536//
537void DataLogger::AddService(const string& server, const string& service, const string&, bool isCmd)
538{
539 //dataLogger does not subscribe to commands
540 if (isCmd)
541 return;
542
543 //check the given subscription against black and white lists
544 if (!ShouldSubscribe(server, service))
545 return;
546
547 map<string, SubscriptionType> &list = fServiceSubscriptions[server];
548
549 if (list.find(service) != list.end())
550 {
551 Error("Service " + server + "/" + service + " is already in the dataLogger's list. ignoring its update.");
552 return;
553 }
554
555 list[service].dimInfo = shared_ptr<DimStampedInfo>(SubscribeToPlease(server, service));
556 list[service].server = server;
557 list[service].service = service;
558
559 if (fDebugIsOn)
560 Debug("Added subscription to " + server + "/" + service);
561}
562// --------------------------------------------------------------------------
563//
564//! Remove a given service subscription
565//! @param server the server for which the subscription should be removed
566//! @param service the service that should be removed
567//! @param isCmd whether or not this is a command
568//
569void DataLogger::RemoveService(const string& server, const string& service, bool isCmd)
570{
571 if (isCmd)
572 return;
573
574 fServiceSubscriptions[server].erase(service);
575 if (fDebugIsOn)
576 {
577 Debug("Removed subscription to " + server + "/" + service);
578 }
579}
580// --------------------------------------------------------------------------
581//
582//! Remove all the services associated with a given server
583//! @param server the server for which all the services should be removed
584//
585void DataLogger::RemoveAllServices(const string& server)
586{
587 fServiceSubscriptions[server].clear();
588 if (fDebugIsOn)
589 {
590 Debug("Removed all subscriptions to " + server + "/");
591 }
592}
593// --------------------------------------------------------------------------
594//
595//! Checks if the given ofstream is in error state and if so, close it
596//! @param out the ofstream that should be checked
597//
598void DataLogger::CheckForOfstreamError(ofstream& out)
599{
600 if (!out.good())
601 {
602 Error("An error occured while writing to a text file. Closing it");
603 if (out.is_open())
604 out.close();
605 }
606}
607// --------------------------------------------------------------------------
608//
609//! Checks the size on disk of a given size, and remembers it in the relevant member variable
610//! @param fileName the file for which the size on disk should be retrieved
611//! @param nightly whether this is a run or nightly file, so that its size is added to the correct member variable
612//
613bool DataLogger::RememberFileOrigSizePlease(string& fileName, bool nightly)
614{
615 //get the size of the file we're about to open
616 if (fFileSizesMap.find(fileName) != fFileSizesMap.end())
617 return false;
618
619 if (nightly)
620 fBaseSizeNightly += GetFileSize(fileName);
621 else
622 fBaseSizeRun += GetFileSize(fileName);
623 fFileSizesMap[fileName] = 0;
624 return true;
625}
626
627// --------------------------------------------------------------------------
628//
629//! Open a text file and checks for error code
630//! @param stream the ofstream for which the file should be opened
631//! @name the file name
632//
633void DataLogger::OpenTextFilePlease(ofstream& stream, const string& name)
634{
635 errno = 0;
636 stream.open(name.c_str(), ios_base::out | ios_base::app);
637 if (errno != 0)
638 {
639 ostringstream str;
640 str << "Unable to open file: " << name << ". Reason: " << strerror(errno) << " [" << errno << "]";
641 Error(str);
642 }
643}
644// --------------------------------------------------------------------------
645//
646//! Create a new dim subscription to a given server and service
647//! @param server the server name
648//! @param service the service name
649//
650DimStampedInfo* DataLogger::SubscribeToPlease(const string& server, const string& service)
651{
652 if (fDebugIsOn)
653 {
654 ostringstream str;
655 str << "Subscribing to service " << server << "/" << service;
656 Debug(str);
657 }
658 return new DimStampedInfo((server + "/" + service).c_str(), const_cast<char*>(""), this);
659}
660// --------------------------------------------------------------------------
661//
662//! Check whether a service should be subscribed to, based on the black/white list entries
663//! @param server the server name associated with the service being checked
664//! @param service the service name associated with the service being checked
665//
666bool DataLogger::ShouldSubscribe(const string& server, const string& service)
667{
668 if (fWhiteList.size()>0 &&
669 (fWhiteList.find(server + "/") == fWhiteList.end()) &&
670 (fWhiteList.find(server + "/" + service) == fWhiteList.end()) &&
671 (fWhiteList.find("/" + service) == fWhiteList.end()))
672 return false;
673
674 if ((fBlackList.find(server + "/") != fBlackList.end()) ||
675 (fBlackList.find(server + "/" + service) != fBlackList.end()) ||
676 (fBlackList.find("/" + service) != fBlackList.end()))
677 return false;
678
679 return true;
680}
681// --------------------------------------------------------------------------
682//
683//! Compiles a file name
684//! @param path the base path where to put the file
685//! @param time the time at which the file is created
686//! @param service the service name, if any
687//! @param extension the extension to add, if any
688//
689string DataLogger::CompileFileName(const string &path, const string &service, const string & extension, const Time &time)
690{
691 ostringstream str;
692 //calculate time suitable for naming files.
693 const Time ftime(time-boost::posix_time::time_duration(12,0,0));
694
695 //output it
696 str << path << Time::fmt("/%Y/%m/%d") << ftime;
697
698 //check if target directory exist
699 if (!DoesPathExist(str.str()))
700 CreateDirectory(str.str());
701
702 //output base of file name
703 str << Time::fmt("/%Y_%m_%d") << ftime;
704
705 //output service name
706 if (!service.empty())
707 str << "_" << service;
708
709 //output appropriate extension
710 if (!extension.empty())
711 str << "." << extension;
712
713 return str.str();
714}
715// --------------------------------------------------------------------------
716//
717//! Compiles a file name
718//! @param path the base path where to put the file
719//! @param time the time at which the file is created
720//! @param run the run number
721//! @param service the service name, if any
722//! @param extension the extension to add, if any
723//
724string DataLogger::CompileFileName(const string &path, uint32_t run, const string &service, const string & extension, const Time &time)
725{
726 ostringstream str;
727 //calculate suitable time for naming files and output it
728 str << path << Time::fmt("/%Y/%m/%d") << (time-boost::posix_time::time_duration(12,0,0));
729
730 //check if target directory exist
731 if (!DoesPathExist(str.str()))
732 CreateDirectory(str.str());
733
734 //output base of file name
735 str << '/' << setfill('0') << setw(8) << run;
736
737 //output service name
738 if (!service.empty())
739 str << "_" << service;
740
741 //output appropriate extension
742 if (!extension.empty())
743 str << "." << extension;
744 return str.str();
745}
746
747// --------------------------------------------------------------------------
748//
749//!retrieves the size on disk of a file
750//! @param fileName the full file name for which the size on disk should be retrieved
751//! @return the size of the file on disk, in bytes. 0 if the file does not exist or if an error occured
752//
753off_t DataLogger::GetFileSize(const string& fileName)
754{
755 errno = 0;
756 struct stat st;
757 if (!stat(fileName.c_str(), &st))
758 return st.st_size;
759
760 if (errno != 0 && errno != 2)//ignoring error #2: no such file or directory is not an error for new files
761 {
762 ostringstream str;
763 str << "Unable to stat " << fileName << ". Reason: " << strerror(errno) << " [" << errno << "]";
764 Error(str);
765 }
766
767 return 0;
768}
769// --------------------------------------------------------------------------
770//
771//! Removes the oldest run number and closes the fits files that should be closed
772//! Also creates the fits grouping file
773//
774void DataLogger::RemoveOldestRunNumber()
775{
776 if (fDebugIsOn)
777 {
778 ostringstream str;
779 str << "Removing run number " << fRunNumber.front().runNumber;
780 Debug(str);
781 }
782 CreateFitsGrouping(fRunNumber.front().openedFits, fRunNumber.front().runNumber);
783
784 //crawl through the subscriptions to see if there are still corresponding fits files opened.
785 for (SubscriptionsListType::iterator x=fServiceSubscriptions.begin();
786 x!=fServiceSubscriptions.end(); x++)
787 for (map<string, SubscriptionType>::iterator y=x->second.begin();
788 y!=x->second.end(); y++)
789 if (y->second.runFile.fRunNumber == fRunNumber.front().runNumber && y->second.runFile.IsOpen())
790 {
791 if (fDebugIsOn)
792 {
793 ostringstream str;
794 str << "Closing Fits run file " << y->second.runFile.fFileName;
795 Debug(str);
796 }
797 y->second.runFile.Close();
798 }
799 //if a grouping file is on, decrease the number of opened fits manually
800 if (fRunNumber.front().runFitsFile)
801 (fNumSubAndFitsData.numOpenFits)--;
802 //remove the entry
803 fRunNumber.pop_front();
804}
805
806// --------------------------------------------------------------------------
807//
808//! Calculate the total number of written bytes since the logger was started
809//! @param statVar the data structure that should be updated
810//! @param shouldWarn whether or not error messages should be outputted
811//! @param isPrinting whether this function was called from the PRINT command or not. If so, displays relevant information
812//
813bool DataLogger::calculateTotalSizeWritten(DataLoggerStats& statVar, bool isPrinting)
814{
815#ifdef HAVE_FITS
816 if (isPrinting)
817 {
818 ostringstream str;
819 str << "There are " << fNumSubAndFitsData.numOpenFits << " FITS files open:";
820 Message(str);
821 }
822
823 ///TODO the grouping file is dealt with several times. This should not be a problem but well, better to fix it I guess.
824 for (SubscriptionsListType::const_iterator x=fServiceSubscriptions.begin();
825 x!=fServiceSubscriptions.end(); x++)
826 {
827 for (map<string, SubscriptionType>::const_iterator y=x->second.begin();
828 y!=x->second.end(); y++)
829 {
830 if (y->second.runFile.IsOpen())
831 {
832 fFileSizesMap[y->second.runFile.fFileName] = y->second.runFile.GetWrittenSize();
833 if (isPrinting)
834 Message("-> "+y->second.runFile.fFileName);
835 }
836 if (y->second.nightlyFile.IsOpen())
837 {
838 fFileSizesMap[y->second.nightlyFile.fFileName] = y->second.nightlyFile.GetWrittenSize();
839 if (isPrinting)
840 Message("-> "+y->second.nightlyFile.fFileName);
841 }
842 }
843 }
844#else
845 if (isPrinting)
846 Message("FITS output disabled at compilation");
847#endif
848 //gather log and report files sizes on disk
849 if (fNightlyLogFile.is_open())
850 fFileSizesMap[fFullNightlyLogFileName] = GetFileSize(fFullNightlyLogFileName);
851 if (fNightlyReportFile.is_open())
852 fFileSizesMap[fFullNightlyReportFileName] = GetFileSize(fFullNightlyReportFileName);
853 for (list<RunNumberType>::iterator it = fRunNumber.begin(); it != fRunNumber.end(); it++)
854 {
855 if (it->reportFile->is_open())
856 fFileSizesMap[it->reportName] = GetFileSize(it->reportName);
857#ifdef RUN_LOGS
858 if (it->logFile->is_open())
859 fFileSizesMap[it->logName] = GetFileSize(it->logName);
860#endif
861 }
862
863 bool shouldWarn = false;
864 struct statvfs vfs;
865 if (!statvfs(fNightlyFilePath.c_str(), &vfs))
866 statVar.freeSpace = vfs.f_bsize*vfs.f_bavail;
867 else
868 {
869 ostringstream str;
870 str << "Unable to retrieve stats for " << fNightlyFilePath << ". Reason: " << strerror(errno) << " [" << errno << "]";
871 if (!shouldWarn)
872 Error(str);
873 statVar.freeSpace = -1;
874 }
875 //sum up all the file sizes. past and present
876 statVar.sizeWritten = 0;
877 for (map<string, long>::const_iterator it=fFileSizesMap.begin(); it != fFileSizesMap.end(); it++)
878 statVar.sizeWritten += it->second;
879 statVar.sizeWritten -= fBaseSizeNightly;
880 statVar.sizeWritten -= fBaseSizeRun;
881
882 return shouldWarn;
883}
884//static members initialization
885//since I do not check the transition/config names any longer, indeed maybe these could be hard-coded... but who knows what will happen in the future ?
886const char* DataLogger::fConfigDay = "CONFIG_DAY";
887const char* DataLogger::fConfigRun = "CONFIG_RUN";
888const char* DataLogger::fConfigRunNumber = "CONFIG_RUN_NUMBER";
889const char* DataLogger::fConfigLog = "LOG";
890const char* DataLogger::fTransStart = "START";
891const char* DataLogger::fTransStop = "STOP";
892const char* DataLogger::fTransStartRun = "START_RUN";
893const char* DataLogger::fTransStopRun = "STOP_RUN";
894const char* DataLogger::fTransReset = "RESET";
895const char* DataLogger::fTransWait = "WAIT_RUN_NUMBER";
896const char* DataLogger::fRunNumberInfo = "RUN_NUMBER";
897const char* DataLogger::fPrintCommand = "PRINT";
898const char* DataLogger::fDebugOnOff = "DEBUG";
899const char* DataLogger::fStatsPeriod = "STATS_PERIOD";
900const char* DataLogger::fStartStopOpenedFiles = "OPENED_FILES_SRVC";
901const char* DataLogger::fStartStopNumSubsAndFits = "NUM_SUBS_SRVC";
902
903// --------------------------------------------------------------------------
904//
905//! Monitor the number of opened files and total size written, and distributes this data through a Dim service
906//
907//
908void DataLogger::ServicesMonitoring()
909{
910 DataLoggerStats statVar;
911 statVar.sizeWritten = 0;
912 statVar.freeSpace = 0;
913 statVar.writingRate = 0;
914
915 struct statvfs vfs;
916 if (!statvfs(fNightlyFilePath.c_str(), &vfs))
917 statVar.freeSpace = vfs.f_bsize*vfs.f_bavail;
918 else
919 statVar.freeSpace = -1;
920
921 DimDescribedService srvc ("DATA_LOGGER/STATS", "X:3", statVar, "Add description here");
922 fPreviousSize = 0;
923 //bool statWarning = false;
924 bool resetDone = false;
925 //loop-wait for broadcast
926 while (fContinueMonitoring)
927 {
928 //check if some run number entries can be deleted
929 while (fRunNumber.size() > 1 && (Time() - fRunNumber.front().time) > boost::posix_time::time_duration(0,0,10,0))
930 {
931 RemoveOldestRunNumber();
932 }
933 //check if daily files should be closed and reopened.
934 //FIXME when Time().h() == 12, the actual time is 2pm
935 if (Time().h() == 12 && !resetDone)
936 {
937 if (fDebugIsOn)
938 Debug("Change of day detected. Closing daily files and restarting the dataLogger");
939 int cState = GetCurrentState();
940 GoToReadyPlease();
941 //it's noon: no run number should survive
942 //this I'm not too sure about...
943// while (fRunNumber.size() > 0)
944// RemoveOldestRunNumber();
945
946 if (cState >= kSM_NightlyOpen)
947 StartPlease();
948 if (cState >= kSM_WaitingRun)
949 NightlyToWaitRunPlease();
950 if (cState >= kSM_Logging)
951 StartRunPlease();
952
953 resetDone = true;
954 }
955 else
956 // {
957 // if (Time().h() != 12 && resetDone)
958 resetDone = false;
959// }
960 if (fStatsPeriodDuration == 0.0f)
961 {
962 sleep(0.1f);
963 continue;
964 }
965
966 sleep(fStatsPeriodDuration);
967
968 //update the fits files sizes
969 /*statWarning =*/ calculateTotalSizeWritten(statVar, false);
970 if (fStatsPeriodDuration == 0.0f)
971 continue;
972 statVar.writingRate = (statVar.sizeWritten - fPreviousSize)/fStatsPeriodDuration;
973
974 fPreviousSize = statVar.sizeWritten;
975 if (statVar.writingRate != 0) //if data has been written
976 {
977 srvc.updateService();
978
979 if(fDebugIsOn)
980 {
981 ostringstream str;
982 str << "Size written: " << statVar.sizeWritten/1000 << " kB; writing rate: ";
983 str << statVar.writingRate/1000 << " kB/s; free space: ";
984 str << statVar.freeSpace/(1000*1000) << " MB";
985 Debug(str);
986 }
987 }
988 }
989}
990
991// --------------------------------------------------------------------------
992//
993//! Default constructor. The name of the machine is given DATA_LOGGER
994//! and the state is set to kSM_Ready at the end of the function.
995//
996//!Setup the allows states, configs and transitions for the data logger
997//
998DataLogger::DataLogger(ostream &out) : StateMachineDim(out, "DATA_LOGGER")
999{
1000 //initialize member data
1001 fNightlyFilePath = ".";
1002 fRunFilePath = ".";
1003
1004 //Give a name to this machine's specific states
1005 AddStateName(kSM_NightlyOpen, "NightlyFileOpen", "The summary files for the night are open.");
1006 AddStateName(kSM_WaitingRun, "WaitForRun", "The summary files for the night are open and we wait for a run to be started.");
1007 AddStateName(kSM_Logging, "Logging", "The summary files for the night and the files for a single run are open.");
1008 AddStateName(kSM_BadNightlyConfig, "ErrNightlyFolder", "The folder for the nighly summary files is invalid.");
1009 AddStateName(kSM_BadRunConfig, "ErrRunFolder", "The folder for the run files is invalid.");
1010
1011 /*Add the possible transitions for this machine*/
1012 AddEvent(kSM_NightlyOpen, fTransStart, kSM_Ready, kSM_BadNightlyConfig)
1013 (boost::bind(&DataLogger::StartPlease, this))
1014 ("Start the nightly logging. Nightly file location must be specified already");
1015
1016 AddEvent(kSM_Ready, fTransStop, kSM_NightlyOpen, kSM_WaitingRun, kSM_Logging)
1017 (boost::bind(&DataLogger::GoToReadyPlease, this))
1018 ("Stop all data logging, close all files.");
1019
1020 AddEvent(kSM_Logging, fTransStartRun, kSM_WaitingRun, kSM_BadRunConfig)
1021 (boost::bind(&DataLogger::StartRunPlease, this))
1022 ("Start the run logging. Run file location must be specified already.");
1023
1024 AddEvent(kSM_WaitingRun, fTransStopRun, kSM_Logging)
1025 (boost::bind(&DataLogger::StopRunPlease, this))
1026 ("Wait for a run to be started, open run-files as soon as a run number arrives.");
1027
1028 AddEvent(kSM_Ready, fTransReset, kSM_Error, kSM_BadNightlyConfig, kSM_BadRunConfig, kSM_Error)
1029 (boost::bind(&DataLogger::GoToReadyPlease, this))
1030 ("Transition to exit error states. Closes the nightly file if already opened.");
1031
1032 AddEvent(kSM_WaitingRun, fTransWait, kSM_NightlyOpen)
1033 (boost::bind(&DataLogger::NightlyToWaitRunPlease, this));
1034
1035 /*Add the possible configurations for this machine*/
1036 AddEvent(fConfigDay, "C", kSM_Ready, kSM_BadNightlyConfig)
1037 (boost::bind(&DataLogger::ConfigureNightlyFileName, this, _1))
1038 ("Configure the folder for the nightly files."
1039 "|Path[string]:Absolute or relative path name where the nightly files should be stored.");
1040
1041 AddEvent(fConfigRun, "C", kSM_Ready, kSM_BadNightlyConfig, kSM_NightlyOpen, kSM_WaitingRun, kSM_BadRunConfig)
1042 (boost::bind(&DataLogger::ConfigureRunFileName, this, _1))
1043 ("Configure the folder for the run files."
1044 "|Path[string]:Absolute or relative path name where the run files should be stored.");
1045
1046 AddEvent(fConfigRunNumber, "X", kSM_Ready, kSM_NightlyOpen, kSM_WaitingRun, kSM_BadRunConfig, kSM_Logging)
1047 (boost::bind(&DataLogger::ConfigureRunNumber, this, _1))
1048 ("Configure the run number. Cannot be done in logging state");
1049
1050 //Provide a print command
1051 ostringstream str;
1052 str << kSM_Ready << " " << kSM_NightlyOpen << " " << kSM_WaitingRun << " " << kSM_Logging << " " << kSM_BadNightlyConfig;
1053 str << " " << kSM_BadRunConfig;
1054 AddEvent(fPrintCommand, str.str().c_str(), "")
1055 (boost::bind(&DataLogger::PrintStatePlease, this, _1))
1056 ("Print information about the internal status of the data logger.");
1057
1058 //start the monitoring service
1059 fContinueMonitoring = true;
1060 fMonitoringThread = boost::thread(boost::bind(&DataLogger::ServicesMonitoring, this));
1061 fBaseSizeNightly = 0;
1062 fBaseSizeRun = 0;
1063 OpenFileToDim fToDim;
1064 fToDim.code = 0;
1065 fToDim.fileName[0] = '\0';
1066
1067 fOpenedNightlyFiles = new DimDescribedService(GetName() + "/FILENAME_NIGHTLY", "I:1;C", fToDim,
1068 "Path and base name which is used to compile the filenames for the nightly files."
1069 "|Type[int]:type of open files (1=log, 2=rep, 4=fits)"
1070 "|Name[string]:path and base file name");
1071
1072 fOpenedRunFiles = new DimDescribedService(GetName() + "/FILENAME_RUN", "I:1;C", fToDim,
1073 "Path and base name which is used to compile the filenames for the run files."
1074 "|Type[int]:type of open files (1=log, 2=rep, 4=fits)"
1075 "|Name[string]:path and base file name");
1076
1077 fNumSubAndFitsData.numSubscriptions = 0;
1078 fNumSubAndFitsData.numOpenFits = 0;
1079 fNumSubAndFits = new DimDescribedService(GetName() + "/NUM_SUBS", "I:2", fNumSubAndFitsData,
1080 "Shows number of services to which the data logger is currently subscribed and the total number of open files."
1081 "|Subscriptions[int]:number of dim services to which the data logger is currently subscribed."
1082 "|NumOpenFiles[int]:number of files currently open by the data logger");
1083
1084 //services parameters
1085 fDebugIsOn = false;
1086 fStatsPeriodDuration = 1.0f;
1087 fOpenedFilesIsOn = true;
1088 fNumSubAndFitsIsOn = true;
1089
1090 //provide services control commands
1091 AddEvent(fDebugOnOff, "B:1", kSM_NightlyOpen, kSM_Logging, kSM_WaitingRun, kSM_Ready)
1092 (boost::bind(&DataLogger::SetDebugOnOff, this, _1))
1093 ("Switch debug mode on off. Debug mode prints ifnormation about every service written to a file."
1094 "|Enable[bool]:Enable of disable debug mode (yes/no).");
1095
1096 AddEvent(fStatsPeriod, "F", kSM_NightlyOpen, kSM_Logging, kSM_WaitingRun, kSM_Ready)
1097 (boost::bind(&DataLogger::SetStatsPeriod, this, _1))
1098 ("Interval in which the data-logger statitistics service (STATS) is updated."
1099 "Interval[s]:Floating point value in seconds.");
1100
1101 AddEvent(fStartStopOpenedFiles, "B:1", kSM_NightlyOpen, kSM_Logging, kSM_WaitingRun, kSM_Ready)
1102 (boost::bind(&DataLogger::SetOpenedFilesOnOff ,this, _1))
1103 ("Switch off the service which distributes information about the open files.");
1104
1105 AddEvent(fStartStopNumSubsAndFits, "B:1", kSM_NightlyOpen, kSM_Logging, kSM_WaitingRun, kSM_Ready)
1106 (boost::bind(&DataLogger::SetNumSubsAndFitsOnOff, this, _1))
1107 ("Switch off the service which distributes information about the number of subscriptions and open files.");
1108
1109 fDestructing = false;
1110 if(fDebugIsOn)
1111 {
1112 Debug("DataLogger Init Done.");
1113 }
1114}
1115
1116// --------------------------------------------------------------------------
1117//
1118//! Destructor
1119//
1120DataLogger::~DataLogger()
1121{
1122 if (fDebugIsOn)
1123 {
1124 Debug("DataLogger destruction starts");
1125 }
1126 fDestructing = true;
1127 //first let's go to the ready state
1128 GoToReadyPlease();
1129 //release the services subscriptions
1130 fServiceSubscriptions.clear();
1131 //exit the monitoring loop
1132 fContinueMonitoring = false;
1133
1134 fMonitoringThread.join();
1135 //clear any remaining run number (should remain only one)
1136 while (fRunNumber.size() > 0)
1137 {
1138 RemoveOldestRunNumber();
1139 }
1140
1141 delete fOpenedNightlyFiles;
1142 delete fOpenedRunFiles;
1143 delete fNumSubAndFits;
1144
1145 if (fDebugIsOn)
1146 {
1147 Debug("DataLogger desctruction ends");
1148 }
1149}
1150
1151// --------------------------------------------------------------------------
1152//
1153//! Inherited from DimInfo. Handles all the Infos to which we subscribed, and log them
1154//
1155void DataLogger::infoHandler()
1156{
1157 // Make sure getTimestamp is called _before_ getTimestampMillisecs
1158 if (fDestructing)
1159 return;
1160
1161 DimInfo* I = getInfo();
1162
1163 if (I==NULL)
1164 return;
1165 //check if the service pointer corresponds to something that we subscribed to
1166 //this is a fix for a bug that provides bad Infos when a server starts
1167 bool found = false;
1168 SubscriptionsListType::iterator x;
1169 map<string, SubscriptionType>::iterator y;
1170 for (x=fServiceSubscriptions.begin(); x != fServiceSubscriptions.end(); x++)
1171 {//find current service is subscriptions
1172 for (y=x->second.begin(); y!=x->second.end();y++)
1173 if ((y->second.dimInfo).get() == I)
1174 {
1175 found = true;
1176 break;
1177 }
1178 if (found)
1179 break;
1180 }
1181 if (!found)
1182 {
1183 DimServiceInfoList::infoHandler();
1184 return;
1185 }
1186 if (I->getSize() <= 0)
1187 return;
1188
1189 // Make sure that getTimestampMillisecs is NEVER called before
1190 // getTimestamp is properly called
1191 // check that the message has been updated by something, i.e. must be different from its initial value
1192 if (I->getTimestamp() == 0)
1193 return;
1194
1195 CheckForRunNumber(I);
1196
1197 ReportPlease(I, y->second);
1198
1199}
1200// --------------------------------------------------------------------------
1201//
1202//! Open the text files associated with the given run number
1203//! @param run the run number to be dealt with
1204//
1205int DataLogger::OpenRunFile(RunNumberType& run)
1206{
1207#ifdef RUN_LOGS
1208 if (run.logFile->is_open())
1209 {
1210 ostringstream str;
1211 str << "Log file " << run.logName << " was already open when trying to open it in OpenRunFile";
1212 Error(str);
1213 return -1;
1214 }
1215 run.logName = CompileFileName(fRunFilePath, run.runNumber, "", "log");
1216 errno = 0;
1217 run.logFile->open(run.logName.c_str(), ios_base::out | ios_base::app);
1218 if (errno != 0)
1219 {
1220 ostringstream str;
1221 str << "Unable to open run Log " << run.logName << ". Reason: " << strerror(errno) << " [" << errno << "]";
1222 Error(str);
1223 }
1224#endif
1225 //open report file
1226 run.reportName = CompileFileName(fRunFilePath, run.runNumber, "", "rep");
1227 if (run.reportFile->is_open())
1228 {
1229 ostringstream str;
1230 str << "Report file " << run.reportName << " was already open when trying to open it in OpenRunFile";
1231 Error(str);
1232 return -1;
1233 }
1234 errno = 0;
1235 run.reportFile->open(run.reportName.c_str(), ios_base::out | ios_base::app);
1236 if (errno != 0)
1237 {
1238 ostringstream str;
1239 str << "Unable to open run report " << run.reportName << ". Reason: " << strerror(errno) << " [" << errno << "]";
1240 Error(str);
1241 }
1242
1243#ifdef RUN_LOGS
1244 if (!run.logFile->is_open() || !run.reportFile->is_open())
1245#else
1246 if (!run.reportFile->is_open())
1247#endif
1248 {
1249 ostringstream str;
1250 str << "Something went wrong while openning files ";
1251#ifdef RUN_LOGS
1252 str << run.logName << " and ";
1253#endif
1254 str << run.reportName;
1255 Error(str);
1256 return -1;
1257 }
1258 //get the size of the newly opened file.
1259#ifdef RUN_LOGS
1260 RememberFileOrigSizePlease(run.logName, false);
1261#endif
1262 RememberFileOrigSizePlease(run.reportName, false);
1263
1264 //TODO this notification scheme might be messed up now.... fix it !
1265 string baseFileName = CompileFileName(fRunFilePath, run.runNumber, "", "");
1266 NotifyOpenedFile(baseFileName, 3, fOpenedRunFiles);
1267 run.openedFits.clear();
1268 return 0;
1269}
1270// --------------------------------------------------------------------------
1271//
1272//! Add a new active run number
1273//! @param newRun the new run number
1274//! @param time the time at which the new run number was issued
1275//
1276void DataLogger::AddNewRunNumber(int64_t newRun, Time time)
1277{
1278
1279 if (newRun > 0xffffffff)
1280 {
1281 Error("New run number too large, out of range. Ignoring.");
1282 return;
1283 }
1284 if (fDebugIsOn)
1285 {
1286 ostringstream str;
1287 str << "Adding new run number " << newRun << " that was issued on " << time;
1288 Debug(str);
1289 }
1290 //Add new run number to run number list
1291 fRunNumber.push_back(RunNumberType());
1292 fRunNumber.back().runNumber = uint32_t(newRun);
1293 fRunNumber.back().time = time;
1294
1295 ostringstream str;
1296 str << "The new run number is: " << fRunNumber.back().runNumber;
1297 Message(str);
1298
1299 if (GetCurrentState() != kSM_Logging)
1300 return;
1301 //open the log and report files
1302 OpenRunFile(fRunNumber.back());
1303}
1304// --------------------------------------------------------------------------
1305//
1306//! Checks whether or not the current info is a run number.
1307//! If so, then remember it. A run number is required to open the run-log file
1308//! @param I
1309//! the current DimInfo
1310//
1311void DataLogger::CheckForRunNumber(DimInfo* I)
1312{
1313 if (strstr(I->getName(), fRunNumberInfo) != NULL)
1314 {//assumes that the run number is an integer
1315 AddNewRunNumber(I->getLonglong(), Time(I->getTimestamp(), I->getTimestampMillisecs()*1000));
1316 }
1317}
1318
1319// --------------------------------------------------------------------------
1320//
1321//! write infos to log files.
1322//! @param I
1323//! The current DimInfo
1324//! @param sub
1325//! The dataLogger's subscription corresponding to this DimInfo
1326//
1327void DataLogger::ReportPlease(DimInfo* I, SubscriptionType& sub)
1328{
1329 //should we log or report this info ? (i.e. is it a message ?)
1330 bool isItaReport = ((strstr(I->getName(), "Message") == NULL) && (strstr(I->getName(), "MESSAGE") == NULL));
1331 if (I->getFormat()[0] == 'C')
1332 isItaReport = false;
1333
1334 if (!fNightlyReportFile.is_open())
1335 return;
1336
1337 //create the converter for that service
1338 if (sub.fConv == NULL && isItaReport)
1339 {
1340 //trick the converter in case of 'C'. why do I do this ? well simple: the converter checks that the right number
1341 //of bytes was written. because I skip 'C' with fits, the bytes will not be allocated, hence the "size copied ckeck"
1342 //of the converter will fail, hence throwing an exception.
1343 string fakeFormat(I->getFormat());
1344 if (fakeFormat[fakeFormat.size()-1] == 'C')
1345 fakeFormat = fakeFormat.substr(0, fakeFormat.size()-1);
1346 sub.fConv = shared_ptr<Converter>(new Converter(Out(), I->getFormat()));
1347 if (!sub.fConv)
1348 {
1349 ostringstream str;
1350 str << "Couldn't properly parse the format... service " << sub.dimInfo->getName() << " ignored.";
1351 Error(str);
1352 return;
1353 }
1354 }
1355 //construct the header
1356 ostringstream header;
1357 const Time cTime(I->getTimestamp(), I->getTimestampMillisecs()*1000);
1358 fQuality = I->getQuality();
1359 fMjD = cTime.Mjd();
1360
1361 //figure out which run file should be used
1362 ofstream* targetRunFile = NULL;
1363 RunNumberType* cRunNumber = NULL;
1364 if (GetCurrentState() == kSM_Logging)
1365 {
1366 list<RunNumberType>::reverse_iterator rit;
1367 for (rit=fRunNumber.rbegin(); rit!=fRunNumber.rend(); rit++)
1368 {
1369 if (rit->time < cTime) //this is the run number that we want to use
1370 {
1371 //Find something better to convert iterator to pointer than the ugly line below....
1372 cRunNumber = &(*rit);
1373 sub.runNumber = rit->runNumber;
1374#ifdef RUN_LOGS
1375 targetRunFile = isItaReport ? (rit->reportFile).get() : (rit->logFile).get();
1376#else
1377 targetRunFile = isItaReport ? (rit->reportFile).get() : NULL;
1378#endif
1379 break;
1380 }
1381 }
1382 if (rit == fRunNumber.rend() && fRunNumber.size() != 0)
1383 {
1384 ostringstream str;
1385 str << "Could not find an appropriate run number for info coming at time: " << cTime;
1386 Error(str);
1387 Error("Active run numbers: ");
1388 for (rit=fRunNumber.rbegin(); rit != fRunNumber.rend(); rit++)
1389 {
1390 str.str("");
1391 str << rit->runNumber;
1392 Error(str);
1393 }
1394
1395 }
1396 }
1397
1398 if (isItaReport)
1399 {
1400 //write text header
1401 header << I->getName() << " " << fQuality << " ";
1402 header << cTime.Y() << " " << cTime.M() << " " << cTime.D() << " ";
1403 header << cTime.h() << " " << cTime.m() << " " << cTime.s() << " ";
1404 header << cTime.ms() << " " << I->getTimestamp() << " ";
1405
1406 string text;
1407 try
1408 {
1409 text = sub.fConv->GetString(I->getData(), I->getSize());
1410 }
1411 catch (const runtime_error &e)
1412 {
1413 Out() << kRed << e.what() << endl;
1414 ostringstream str;
1415 str << "Could not properly parse the data for service " << sub.dimInfo->getName();
1416 str << " reason: " << e.what() << ". Entry ignored";
1417 Error(str);
1418 return;
1419 }
1420
1421 if (text.empty())
1422 {
1423 ostringstream str;
1424 str << "Service " << sub.dimInfo->getName() << " sent an empty string";
1425 Info(str);
1426 return;
1427 }
1428 //replace bizarre characters by white space
1429 replace(text.begin(), text.end(), '\n', '\\');
1430 replace_if(text.begin(), text.end(), ptr_fun<int, int>(&iscntrl), ' ');
1431
1432 //write entry to Nightly report
1433 if (fNightlyReportFile.is_open())
1434 {
1435 fNightlyReportFile << header.str() << text << endl;
1436 CheckForOfstreamError(fNightlyReportFile);
1437 }
1438 //write entry to run-report
1439 if (targetRunFile && targetRunFile->is_open())
1440 {
1441 *targetRunFile << header.str() << text << endl;
1442 CheckForOfstreamError(*targetRunFile);
1443 }
1444 }
1445 else
1446 {//write entry to both Nightly and run logs
1447 string n = I->getName();
1448 ostringstream msg;
1449 msg << n << ": " << I->getString();
1450
1451 if (fNightlyLogFile.is_open())
1452 {
1453 MessageImp nightlyMess(fNightlyLogFile);
1454 nightlyMess.Write(cTime, msg.str().c_str(), fQuality);
1455 CheckForOfstreamError(fNightlyLogFile);
1456 }
1457 if (targetRunFile && targetRunFile->is_open())
1458 {
1459 MessageImp runMess(*targetRunFile);
1460 runMess.Write(cTime, msg.str().c_str(), fQuality);
1461 CheckForOfstreamError(*targetRunFile);
1462 }
1463 }
1464
1465#ifdef HAVE_FITS
1466 if (isItaReport)
1467 {
1468 if (!sub.nightlyFile.IsOpen() || !sub.runFile.IsOpen() || sub.runNumber != sub.runFile.fRunNumber)
1469 OpenFITSFilesPlease(sub, cRunNumber);
1470 WriteToFITS(sub);
1471 }
1472#endif
1473
1474}
1475
1476// --------------------------------------------------------------------------
1477//
1478//! print the dataLogger's current state. invoked by the PRINT command
1479//! @param evt
1480//! the current event. Not used by the method
1481//! @returns
1482//! the new state. Which, in that case, is the current state
1483//!
1484int DataLogger::PrintStatePlease(const Event& )
1485{
1486 Message("------------------------------------------");
1487 Message("------- DATA LOGGER CURRENT STATE --------");
1488 Message("------------------------------------------");
1489
1490 //print the path configuration
1491 Message("Nightly Path: " + boost::filesystem::system_complete(boost::filesystem::path(fNightlyFilePath)).directory_string());
1492 Message("Run Path: " + boost::filesystem::system_complete(boost::filesystem::path(fRunFilePath)).directory_string());
1493
1494 //print active run numbers
1495 ostringstream str;
1496 str << "Active Run Numbers:";
1497 for (list<RunNumberType>::const_iterator it=fRunNumber.begin(); it!=fRunNumber.end(); it++)
1498 str << " " << it->runNumber;
1499 if (fRunNumber.size()==0)
1500 str << " <none>";
1501 Message(str);
1502
1503 //print all the open files.
1504 Message("------------ OPENED FILES ----------------");
1505 if (fNightlyLogFile.is_open())
1506 Message("Nightly log-file: OPEN");
1507
1508 if (fNightlyReportFile.is_open())
1509 Message("Nightly report-file: OPEN");
1510
1511 for (list<RunNumberType>::const_iterator it=fRunNumber.begin(); it!=fRunNumber.end(); it++)
1512 {
1513#ifdef RUN_LOGS
1514 if (it->logFile->is_open())
1515 Message("Run log-file: " + it->logName + " (OPEN)");
1516#endif
1517 if (it->reportFile->is_open())
1518 Message("Run report-file: " + it->reportName + " (OPEN)");
1519 }
1520
1521 DataLoggerStats statVar;
1522 /*const bool statWarning =*/ calculateTotalSizeWritten(statVar, false);
1523
1524 Message("----------------- STATS ------------------");
1525 str.str("");
1526 str << "Total Size written: " << statVar.sizeWritten << " bytes.";
1527 Message(str);
1528 str.str("");
1529 str << "Disk free space: " << statVar.freeSpace << " bytes.";
1530 Message(str);
1531 str.str("");
1532 str << "Statistics are updated every " << fStatsPeriodDuration << " seconds";
1533 if (fStatsPeriodDuration != 0)
1534 Message(str);
1535 else
1536 Message("Statistics updates are currently disabled");
1537
1538 Message("------------ DIM SUBSCRIPTIONS -----------");
1539 str.str("");
1540 str << "There are " << fNumSubAndFitsData.numSubscriptions << " active DIM subscriptions.";
1541 Message(str);
1542
1543 for (map<const string, map<string, SubscriptionType> >::const_iterator it=fServiceSubscriptions.begin(); it!= fServiceSubscriptions.end();it++)
1544 {
1545 Message("Server "+it->first);
1546 for (map<string, SubscriptionType>::const_iterator it2=it->second.begin(); it2!=it->second.end(); it2++)
1547 Message(" -> "+it2->first);
1548 }
1549
1550 Message("--------------- BLOCK LIST ---------------");
1551 for (set<string>::const_iterator it=fBlackList.begin(); it != fBlackList.end(); it++)
1552 Message(" -> "+*it);
1553 if (fBlackList.size()==0)
1554 Message(" <empty>");
1555
1556 Message("--------------- ALLOW LIST ---------------");
1557 for (set<string>::const_iterator it=fWhiteList.begin(); it != fWhiteList.end(); it++)
1558 Message(" -> "+*it);
1559 if (fWhiteList.size()==0)
1560 Message(" <empty>");
1561
1562 Message("-------------- GROUPING LIST -------------");
1563 Message("The following servers and/or services will");
1564 Message("be grouped into a single fits file:");
1565 for (set<string>::const_iterator it=fGrouping.begin(); it != fGrouping.end(); it++)
1566 Message(" -> "+*it);
1567 if (fGrouping.size()==0)
1568 Message(" <no grouping>");
1569
1570 Message("------------------------------------------");
1571 Message("-------- END OF DATA LOGGER STATE --------");
1572 Message("------------------------------------------");
1573
1574 return GetCurrentState();
1575}
1576
1577// --------------------------------------------------------------------------
1578//
1579//! turn debug mode on and off
1580//! @param evt
1581//! the current event. contains the instruction string: On, Off, on, off, ON, OFF, 0 or 1
1582//! @returns
1583//! the new state. Which, in that case, is the current state
1584//!
1585int DataLogger::SetDebugOnOff(const Event& evt)
1586{
1587 bool backupDebug = fDebugIsOn;
1588 fDebugIsOn = evt.GetBool();
1589 if (fDebugIsOn == backupDebug)
1590 Warn("Warning: debug mode was already in the requested state");
1591 else
1592 {
1593 ostringstream str;
1594 str << "Debug mode is now " << fDebugIsOn;
1595 Message(str);
1596 }
1597 return GetCurrentState();
1598}
1599// --------------------------------------------------------------------------
1600//
1601//! set the statistics update period duration. 0 disables the statistics
1602//! @param evt
1603//! the current event. contains the new duration.
1604//! @returns
1605//! the new state. Which, in that case, is the current state
1606//!
1607int DataLogger::SetStatsPeriod(const Event& evt)
1608{
1609 float backupDuration = fStatsPeriodDuration;
1610 fStatsPeriodDuration = evt.GetFloat();
1611 if (fStatsPeriodDuration < 0)
1612 {
1613 Error("Statistics period duration should be greater than zero. Discarding provided value.");
1614 fStatsPeriodDuration = backupDuration;
1615 return GetCurrentState();
1616 }
1617 if (!finite(fStatsPeriodDuration))// != fStatsPeriodDuration)
1618 {
1619 Error("Provided duration does not appear to be a valid float. discarding it.");
1620 fStatsPeriodDuration = backupDuration;
1621 return GetCurrentState();
1622 }
1623 if (backupDuration == fStatsPeriodDuration)
1624 Warn("Warning: statistics period was not modified: supplied value already in use");
1625 else
1626 {
1627 if (fStatsPeriodDuration == 0.0f)
1628 Message("Statistics are now OFF");
1629 else
1630 {
1631 ostringstream str;
1632 str << "Statistics period is now " << fStatsPeriodDuration << " seconds";
1633 Message(str);
1634 }
1635 }
1636 return GetCurrentState();
1637}
1638// --------------------------------------------------------------------------
1639//
1640//! set the opened files service on or off.
1641//! @param evt
1642//! the current event. contains the instruction string. similar to setdebugonoff
1643//! @returns
1644//! the new state. Which, in that case, is the current state
1645//!
1646int DataLogger::SetOpenedFilesOnOff(const Event& evt)
1647{
1648 bool backupOpened = fOpenedFilesIsOn;
1649 fOpenedFilesIsOn = evt.GetBool();
1650 if (fOpenedFilesIsOn == backupOpened)
1651 Warn("Warning: opened files service mode was already in the requested state");
1652 else
1653 {
1654 ostringstream str;
1655 str << "Opened files service mode is now " << fOpenedFilesIsOn;
1656 Message(str);
1657 }
1658 return GetCurrentState();
1659
1660}
1661// --------------------------------------------------------------------------
1662//
1663//! set the number of subscriptions and opened fits on and off
1664//! @param evt
1665//! the current event. contains the instruction string. similar to setdebugonoff
1666//! @returns
1667//! the new state. Which, in that case, is the current state
1668//!
1669int DataLogger::SetNumSubsAndFitsOnOff(const Event& evt)
1670{
1671 bool backupSubs = fNumSubAndFitsIsOn;
1672 fNumSubAndFitsIsOn = evt.GetBool();
1673 if (fNumSubAndFitsIsOn == backupSubs)
1674 Warn("Warning: Number of subscriptions service mode was already in the requested state");
1675 else
1676 {
1677 ostringstream str;
1678 str << "Number of subscriptions service mode is now " << fNumSubAndFitsIsOn;
1679 Message(str);
1680 }
1681 return GetCurrentState();
1682}
1683// --------------------------------------------------------------------------
1684//
1685//! Sets the path to use for the Nightly log file.
1686//! @param evt
1687//! the event transporting the path
1688//! @returns
1689//! currently only the current state.
1690//
1691int DataLogger::ConfigureNightlyFileName(const Event& evt)
1692{
1693 if (evt.GetText() != NULL)
1694 {
1695 string givenPath = string(evt.GetText());
1696 if (!DoesPathExist(givenPath))
1697 {
1698 Error("Provided path is not a valid folder. Ignored");
1699 return GetCurrentState();
1700 }
1701 fNightlyFilePath = givenPath;
1702 Message("New Nightly folder specified: " + fNightlyFilePath);
1703 }
1704 else
1705 Error("Empty Nightly folder given. Please specify a valid path.");
1706
1707 return GetCurrentState();
1708}
1709// --------------------------------------------------------------------------
1710//
1711//! Sets the path to use for the run log file.
1712//! @param evt
1713//! the event transporting the path
1714//! @returns
1715//! currently only the current state
1716int DataLogger::ConfigureRunFileName(const Event& evt)
1717{
1718 if (evt.GetText() != NULL)
1719 {
1720 string givenPath = string(evt.GetText());
1721 if (!DoesPathExist(givenPath))
1722 {
1723 Error("Provided path is not a valid folder. Ignored");
1724 return GetCurrentState();
1725 }
1726 fRunFilePath = givenPath;
1727 Message("New Run folder specified: " + fRunFilePath);
1728 }
1729 else
1730 Error("Empty Nightly folder given. Please specify a valid path");
1731
1732 return GetCurrentState();
1733}
1734// --------------------------------------------------------------------------
1735//
1736//! Sets the run number.
1737//! @param evt
1738//! the event transporting the run number
1739//! @returns
1740//! currently only the current state
1741int DataLogger::ConfigureRunNumber(const Event& evt)
1742{
1743 AddNewRunNumber(evt.GetXtra(), evt.GetTime());
1744// fRunNumber = evt.GetInt();
1745 return GetCurrentState();
1746}
1747// --------------------------------------------------------------------------
1748//
1749//! Notifies the DIM service that a particular file was opened
1750//! @ param name the base name of the opened file, i.e. without path nor extension.
1751//! WARNING: use string instead of string& because I pass values that do not convert to string&.
1752//! this is not a problem though because file are not opened so often.
1753//! @ param type the type of the opened file. 0 = none open, 1 = log, 2 = text, 4 = fits
1754inline void DataLogger::NotifyOpenedFile(const string &name, int type, DimDescribedService* service)
1755{
1756 if (!fOpenedFilesIsOn)
1757 return;
1758
1759 if (fDebugIsOn)
1760 {
1761 ostringstream str;
1762 str << "Updating " << service->getName() << " file '" << name << "' (type=" << type << ")";
1763 Debug(str);
1764
1765 str.str("");
1766 str << "Num subs: " << fNumSubAndFitsData.numSubscriptions << " Num open FITS: " << fNumSubAndFitsData.numOpenFits;
1767 Debug(str);
1768 }
1769
1770 if (name.size()+1 > FILENAME_MAX)
1771 {
1772 Error("Provided file name '" + name + "' is longer than allowed file name length");
1773 }
1774
1775 OpenFileToDim fToDim;
1776 fToDim.code = type;
1777 memcpy(fToDim.fileName, name.c_str(), name.size()+1);
1778
1779 service->setData(reinterpret_cast<void*>(&fToDim), name.size()+1+sizeof(int));
1780 service->setQuality(0);
1781 service->updateService();
1782}
1783// --------------------------------------------------------------------------
1784//
1785//! Implements the Start transition.
1786//! Concatenates the given path for the Nightly file and the filename itself (based on the day),
1787//! and tries to open it.
1788//! @returns
1789//! kSM_NightlyOpen if success, kSM_BadNightlyConfig if failure
1790int DataLogger::StartPlease()
1791{
1792 if (fDebugIsOn)
1793 {
1794 Debug("Starting...");
1795 }
1796 fFullNightlyLogFileName = CompileFileName(fNightlyFilePath, "", "log");
1797 OpenTextFilePlease(fNightlyLogFile, fFullNightlyLogFileName);
1798
1799 fFullNightlyReportFileName = CompileFileName(fNightlyFilePath, "", "rep");
1800 OpenTextFilePlease(fNightlyReportFile, fFullNightlyReportFileName);
1801
1802 if (!fNightlyLogFile.is_open() || !fNightlyReportFile.is_open())
1803 {
1804 ostringstream str;
1805 str << "Something went wrong while openning nightly files " << fFullNightlyLogFileName << " and " << fFullNightlyReportFileName;
1806 Error(str);
1807 return kSM_BadNightlyConfig;
1808 }
1809 //get the size of the newly opened file.
1810 fBaseSizeNightly = GetFileSize(fFullNightlyLogFileName);
1811 fBaseSizeNightly += GetFileSize(fFullNightlyReportFileName);
1812 fFileSizesMap.clear();
1813 fBaseSizeRun = 0;
1814 fPreviousSize = 0;
1815
1816 //notify that a new file has been opened.
1817 string baseFileName = CompileFileName(fNightlyFilePath, "", "");
1818 NotifyOpenedFile(baseFileName, 3, fOpenedNightlyFiles);
1819
1820 fOpenedNightlyFits.clear();
1821
1822 return kSM_NightlyOpen;
1823}
1824
1825#ifdef HAVE_FITS
1826// --------------------------------------------------------------------------
1827//
1828//! open if required a the FITS files corresponding to a given subscription
1829//! @param sub
1830//! the current DimInfo subscription being examined
1831void DataLogger::OpenFITSFilesPlease(SubscriptionType& sub, RunNumberType* cRunNumber)
1832{
1833 string serviceName(sub.dimInfo->getName());
1834 //if run number has changed, reopen a new fits file with the correct run number.
1835 if (sub.runFile.IsOpen() && sub.runFile.fRunNumber != sub.runNumber)
1836 {
1837 if (fDebugIsOn)
1838 Debug("Run number changed. Closing " + sub.runFile.fFileName);
1839 sub.runFile.Close();
1840 }
1841 //we must check if we should group this service subscription to a single fits file before we replace the / by _
1842 bool hasGrouping = false;
1843 if (!sub.runFile.IsOpen() && (GetCurrentState() == kSM_Logging))
1844 {//will we find this service in the grouping list ?
1845 for (set<string>::const_iterator it=fGrouping.begin(); it!=fGrouping.end(); it++)
1846 {
1847 if (serviceName.find(*it) != string::npos)
1848 {
1849 hasGrouping = true;
1850 break;
1851 }
1852 }
1853 }
1854 hasGrouping = true;
1855 for (unsigned int i=0;i<serviceName.size(); i++)
1856 {
1857 if (serviceName[i] == '/')
1858 {
1859 serviceName[i] = '_';
1860 break;
1861 }
1862 }
1863 //we open the NightlyFile anyway, otherwise this function shouldn't have been called.
1864 if (!sub.nightlyFile.IsOpen())
1865 {
1866 string fileNameOnly, partialName;
1867 partialName = CompileFileName(fNightlyFilePath, serviceName, "fits");
1868 fileNameOnly = partialName.substr(partialName.find_last_of('/')+1, partialName.size());
1869 AllocateFITSBuffers(sub);
1870 //get the size of the file we're about to open
1871 if (RememberFileOrigSizePlease(partialName, true))//and remember that the file was opened (i.e. not an update)
1872 fOpenedNightlyFits[fileNameOnly].push_back(serviceName);
1873
1874 sub.nightlyFile.Open(partialName, serviceName, NULL, &fNumSubAndFitsData.numOpenFits, this, 0);//Out());
1875
1876 //notify the opening
1877 string baseFileName = CompileFileName(fNightlyFilePath, "", "");
1878 NotifyOpenedFile(baseFileName, 7, fOpenedNightlyFiles);
1879 if (fNumSubAndFitsIsOn)
1880 fNumSubAndFits->updateService();
1881 if (fDebugIsOn)
1882 {
1883 ostringstream str;
1884 str << "Opened Nightly FITS: " << partialName << " and table: " << serviceName << ".current number of opened FITS: " << fNumSubAndFitsData.numOpenFits;
1885 Debug(str);
1886 }
1887 }
1888 //do the actual file open
1889 if (!sub.runFile.IsOpen() && (GetCurrentState() == kSM_Logging) && sub.runNumber != 0)
1890 {//buffer for the run file have already been allocated when doing the Nightly file
1891 string fileNameOnly;
1892 string partialName;
1893 if (hasGrouping)
1894 {
1895 partialName = CompileFileName(fRunFilePath, sub.runNumber, "group", "fits");
1896 fileNameOnly = partialName.substr(partialName.find_last_of('/')+1, partialName.size());
1897 }
1898 else
1899 {
1900 partialName = CompileFileName(fRunFilePath, sub.runNumber, serviceName, "fits");
1901 fileNameOnly = partialName.substr(partialName.find_last_of('/')+1, partialName.size());
1902 }
1903 //get the size of the file we're about to open
1904 if (RememberFileOrigSizePlease(partialName, false))//and remember that the file was opened (i.e. not an update)
1905 cRunNumber->openedFits[fileNameOnly].push_back(serviceName);
1906 else
1907 if (hasGrouping)
1908 {
1909 cRunNumber->addServiceToOpenedFits(fileNameOnly, serviceName);
1910 }
1911
1912 if (hasGrouping && cRunNumber->runFitsFile == NULL)
1913 try
1914 {
1915 cRunNumber->runFitsFile = shared_ptr<CCfits::FITS>(new CCfits::FITS(partialName, CCfits::RWmode::Write));
1916 (fNumSubAndFitsData.numOpenFits)++;
1917 }
1918 catch (CCfits::FitsException e)
1919 {
1920 ostringstream str;
1921 str << "Could not open FITS Run file " << partialName << " reason: " << e.message();
1922 Error(str);
1923 cRunNumber->runFitsFile = shared_ptr<CCfits::FITS>();//NULL;
1924 }
1925
1926 string baseFileName = CompileFileName(fRunFilePath, sub.runNumber, "", "");
1927 NotifyOpenedFile(baseFileName, 7, fOpenedRunFiles);// + '_' + serviceName, 4);
1928 if (hasGrouping)
1929 sub.runFile.Open(partialName, serviceName, (cRunNumber->runFitsFile).get(), &fNumSubAndFitsData.numOpenFits, this, sub.runNumber);//Out());
1930 else
1931 sub.runFile.Open(partialName, serviceName, NULL, &fNumSubAndFitsData.numOpenFits, this, sub.runNumber);//Out());
1932
1933 if (fNumSubAndFitsIsOn)
1934 fNumSubAndFits->updateService();
1935 if (fDebugIsOn)
1936 {
1937 ostringstream str;
1938 str << "Opened Run FITS: " << partialName << " and table: " << serviceName << ".current number of opened FITS: " << fNumSubAndFitsData.numOpenFits;
1939 Debug(str);
1940 }
1941 }
1942}
1943// --------------------------------------------------------------------------
1944//
1945//! Allocates the required memory for a given pair of fits files (nightly and run)
1946//! @param sub the subscription of interest.
1947//
1948void DataLogger::AllocateFITSBuffers(SubscriptionType& sub)
1949{
1950 int size = sub.dimInfo->getSize();
1951
1952 //Init the time columns of the file
1953 Description dateDesc(string("Time"), string("Modified Julian Date"), string("MjD"));
1954 sub.nightlyFile.AddStandardColumn(dateDesc, "1D", &fMjD, sizeof(double));
1955 sub.runFile.AddStandardColumn(dateDesc, "1D", &fMjD, sizeof(double));
1956
1957 Description QoSDesc("Qos", "Quality of service", "None");
1958 sub.nightlyFile.AddStandardColumn(QoSDesc, "1J", &fQuality, sizeof(int));
1959 sub.runFile.AddStandardColumn(QoSDesc, "1J", &fQuality, sizeof(int));
1960
1961 const Converter::FormatList flist = sub.fConv->GetList();
1962 // Compilation failed
1963 if (!sub.fConv->valid())
1964 {
1965 Error("Compilation of format string failed.");
1966 return;
1967 }
1968
1969 //we've got a nice structure describing the format of this service's messages.
1970 //Let's create the appropriate FITS columns
1971 vector<string> dataFormatsLocal;
1972 for (unsigned int i=0;i<flist.size()-1;i++)
1973 {
1974 ostringstream dataQualifier;
1975
1976 dataQualifier << flist[i].second.first;
1977 switch (flist[i].first.first->name()[0])
1978 {
1979 case 'c':
1980 case 'C':
1981 dataQualifier.str("S");
1982 break;
1983 case 's':
1984 dataQualifier << "I";
1985 break;
1986 case 'i':
1987 case 'I':
1988 dataQualifier << "J";
1989 break;
1990 case 'l':
1991 case 'L':
1992 dataQualifier << "J";
1993 break;
1994 case 'f':
1995 case 'F':
1996 dataQualifier << "E";
1997 break;
1998 case 'd':
1999 case 'D':
2000 dataQualifier << "D";
2001 break;
2002 case 'x':
2003 case 'X':
2004 dataQualifier << "K";
2005 break;
2006 case 'S':
2007 //for strings, the number of elements I get is wrong. Correct it
2008 dataQualifier.str(""); //clear
2009 dataQualifier << size-1 << "A";
2010 size = size-1;
2011 break;
2012
2013 default:
2014 Fatal("THIS SHOULD NEVER BE REACHED. dataLogger.cc ln 1198.");
2015 };
2016 //we skip the variable length strings for now (in fits only)
2017 if (dataQualifier.str() != "S")
2018 dataFormatsLocal.push_back(dataQualifier.str());
2019 }
2020 sub.nightlyFile.InitDataColumns(GetDescription(sub.server, sub.service), dataFormatsLocal, sub.dimInfo->getData(), size);
2021 sub.runFile.InitDataColumns(GetDescription(sub.server, sub.service), dataFormatsLocal, sub.dimInfo->getData(), size);
2022}
2023// --------------------------------------------------------------------------
2024//
2025//! write a dimInfo data to its corresponding FITS files
2026//
2027void DataLogger::WriteToFITS(SubscriptionType& sub)
2028{
2029 //nightly File status (open or not) already checked
2030 if (sub.nightlyFile.IsOpen())
2031 {
2032 sub.nightlyFile.Write(sub.fConv.get());
2033 if (fDebugIsOn)
2034 {
2035 Debug("Writing to nightly FITS " + sub.nightlyFile.fFileName);
2036 }
2037 }
2038 if (sub.runFile.IsOpen())
2039 {
2040 sub.runFile.Write(sub.fConv.get());
2041 if (fDebugIsOn)
2042 {
2043 Debug("Writing to Run FITS " + sub.runFile.fFileName);
2044 }
2045 }
2046}
2047#endif //if has_fits
2048// --------------------------------------------------------------------------
2049//
2050//! Implements the StartRun transition.
2051//! Concatenates the given path for the run file and the filename itself (based on the run number),
2052//! and tries to open it.
2053//! @returns
2054//! kSM_Logging if success, kSM_BadRunConfig if failure.
2055int DataLogger::StartRunPlease()
2056{
2057 if (fDebugIsOn)
2058 {
2059 Debug("Starting Run Logging...");
2060 }
2061 //open all the relevant run-files. i.e. all the files associated with run numbers.
2062 for (list<RunNumberType>::iterator it=fRunNumber.begin(); it != fRunNumber.end(); it++)
2063 OpenRunFile(*it);
2064
2065 return kSM_Logging;
2066}
2067
2068#ifdef HAVE_FITS
2069// --------------------------------------------------------------------------
2070//
2071//! Create a fits group file with all the run-fits that were written (either daily or run)
2072//! @param filesToGroup a map of filenames mapping to table names to be grouped (i.e. a
2073//! single file can contain several tables to group
2074//! @param runNumber the run number that should be used for grouping. 0 means nightly group
2075//
2076void DataLogger::CreateFitsGrouping(map<string, vector<string> > & filesToGroup, int runNumber)
2077{
2078 if (fDebugIsOn)
2079 {
2080 ostringstream str;
2081 str << "Creating fits group for ";
2082 if (runNumber != 0)
2083 str << "run files";
2084 else
2085 str << "nightly files";
2086 Debug(str);
2087 }
2088 //create the FITS group corresponding to the ending run.
2089 CCfits::FITS* groupFile;
2090 unsigned int numFilesToGroup = 0;
2091 for (map<string, vector<string> >::const_iterator it=filesToGroup.begin(); it != filesToGroup.end(); it++)
2092 {
2093 numFilesToGroup += it->second.size();
2094 }
2095 if (fDebugIsOn)
2096 {
2097 ostringstream str;
2098 str << "There are " << numFilesToGroup << " tables to group";
2099 Debug(str);
2100 }
2101 if (numFilesToGroup <= 1)
2102 {
2103 filesToGroup.clear();
2104 return;
2105 }
2106 string groupName;
2107 if (runNumber != 0)
2108 groupName = CompileFileName(fRunFilePath, runNumber, "", "fits");
2109 else
2110 groupName = CompileFileName(fNightlyFilePath, "", "fits");
2111
2112 CCfits::Table* groupTable;
2113 int maxCharLength = 50;//FILENAME_MAX;
2114 try
2115 {
2116 groupFile = new CCfits::FITS(groupName, CCfits::RWmode::Write);
2117 //setup the column names
2118 ostringstream pathTypeName;
2119 pathTypeName << maxCharLength << "A";
2120 vector<string> names;
2121 vector<string> dataTypes;
2122 names.push_back("MEMBER_XTENSION");
2123 dataTypes.push_back("8A");
2124 names.push_back("MEMBER_URI_TYPE");
2125 dataTypes.push_back("3A");
2126 names.push_back("MEMBER_LOCATION");
2127 dataTypes.push_back(pathTypeName.str());
2128 names.push_back("MEMBER_NAME");
2129 dataTypes.push_back(pathTypeName.str());
2130
2131 groupTable = groupFile->addTable("GROUPING", numFilesToGroup, names, dataTypes);
2132//TODO handle the case when the logger was stopped and restarted during the same day, i.e. the grouping file must be updated
2133 }
2134 catch (CCfits::FitsException e)
2135 {
2136 ostringstream str;
2137 str << "Could not open or create FITS table GROUPING in file " << groupName << " reason: " << e.message();
2138 Error(str);
2139 return;
2140 }
2141
2142 //CCfits seems to be buggy somehow: can't use the column's function "write": it create a compilation error: maybe strings were not thought about.
2143 //use cfitsio routines instead
2144 groupTable->makeThisCurrent();
2145 //create appropriate buffer.
2146 const unsigned int n = 8 + 3 + 2*maxCharLength + 1; //+1 for trailling character
2147
2148 vector<unsigned char> realBuffer;
2149 realBuffer.resize(n);
2150 unsigned char* fitsBuffer = &realBuffer[0];
2151 memset(fitsBuffer, 0, n);
2152
2153 char* startOfExtension = reinterpret_cast<char*>(fitsBuffer);
2154 char* startOfURI = reinterpret_cast<char*>(&fitsBuffer[8]);
2155 char* startOfLocation = reinterpret_cast<char*>(&fitsBuffer[8 + 3]);
2156 char* startOfName = reinterpret_cast<char*>(&fitsBuffer[8+3+maxCharLength]);
2157
2158 strcpy(startOfExtension, "BINTABLE");
2159 strcpy(startOfURI, "URL");
2160
2161 int i=1;
2162 for (map<string, vector<string> >::const_iterator it=filesToGroup.begin(); it!=filesToGroup.end(); it++)
2163 for (vector<string>::const_iterator jt=it->second.begin(); jt != it->second.end(); jt++, i++)
2164 {
2165 strcpy(startOfLocation, it->first.c_str());
2166 strcpy(startOfName, jt->c_str());
2167
2168 if (fDebugIsOn)
2169 {
2170 ostringstream str;
2171 str << "Grouping " << it->first << " " << *jt;
2172 Debug(str);
2173 }
2174
2175 int status = 0;
2176 fits_write_tblbytes(groupFile->fitsPointer(), i, 1, 8+3+2*maxCharLength, fitsBuffer, &status);
2177 if (status)
2178 {
2179 ostringstream str;
2180 str << "Could not write row #" << i << "In the fits grouping file " << groupName << ". Cfitsio error code: " << status;
2181 Error(str);
2182 }
2183 }
2184
2185 filesToGroup.clear();
2186 delete groupFile;
2187}
2188#endif //HAVE_FITS
2189
2190// --------------------------------------------------------------------------
2191//
2192//! Implements the StopRun transition.
2193//! Attempts to close the run file.
2194//! @returns
2195//! kSM_WaitingRun if success, kSM_FatalError otherwise
2196int DataLogger::StopRunPlease()
2197{
2198
2199 if (fDebugIsOn)
2200 {
2201 Debug("Stopping Run Logging...");
2202 }
2203 for (list<RunNumberType>::const_iterator it=fRunNumber.begin(); it != fRunNumber.end(); it++)
2204 {
2205#ifdef RUN_LOGS
2206 if (!it->logFile->is_open() || !it->reportFile->is_open())
2207#else
2208 if (!it->reportFile->is_open())
2209#endif
2210 return kSM_FatalError;
2211#ifdef RUN_LOGS
2212 it->logFile->close();
2213#endif
2214 it->reportFile->close();
2215 }
2216
2217#ifdef HAVE_FITS
2218 for (SubscriptionsListType::iterator i = fServiceSubscriptions.begin(); i != fServiceSubscriptions.end(); i++)
2219 for (map<string, SubscriptionType>::iterator j = i->second.begin(); j != i->second.end(); j++)
2220 {
2221 if (j->second.runFile.IsOpen())
2222 j->second.runFile.Close();
2223 }
2224#endif
2225 NotifyOpenedFile("", 0, fOpenedRunFiles);
2226 if (fNumSubAndFitsIsOn)
2227 fNumSubAndFits->updateService();
2228
2229 while (fRunNumber.size() > 0)
2230 {
2231 RemoveOldestRunNumber();
2232 }
2233
2234 return kSM_WaitingRun;
2235}
2236// --------------------------------------------------------------------------
2237//
2238//! Implements the Stop and Reset transitions.
2239//! Attempts to close any openned file.
2240//! @returns
2241//! kSM_Ready
2242int DataLogger::GoToReadyPlease()
2243{
2244 if (fDebugIsOn)
2245 {
2246 Debug("Going to the Ready state...");
2247 }
2248 if (GetCurrentState() == kSM_Logging)
2249 StopRunPlease();
2250
2251 if (fNightlyLogFile.is_open())
2252 fNightlyLogFile.close();
2253 if (fNightlyReportFile.is_open())
2254 fNightlyReportFile.close();
2255
2256#ifdef HAVE_FITS
2257 for (SubscriptionsListType::iterator i = fServiceSubscriptions.begin(); i != fServiceSubscriptions.end(); i++)
2258 for (map<string, SubscriptionType>::iterator j = i->second.begin(); j != i->second.end(); j++)
2259 {
2260 if (j->second.nightlyFile.IsOpen())
2261 j->second.nightlyFile.Close();;
2262 }
2263#endif
2264 if (GetCurrentState() == kSM_Logging ||
2265 GetCurrentState() == kSM_WaitingRun ||
2266 GetCurrentState() == kSM_NightlyOpen)
2267 {
2268 NotifyOpenedFile("", 0, fOpenedNightlyFiles);
2269 if (fNumSubAndFitsIsOn)
2270 fNumSubAndFits->updateService();
2271 }
2272#ifdef HAVE_FITS
2273 CreateFitsGrouping(fOpenedNightlyFits, 0);
2274#endif
2275 return kSM_Ready;
2276}
2277// --------------------------------------------------------------------------
2278//
2279//! Implements the transition towards kSM_WaitingRun
2280//! Does nothing really.
2281//! @returns
2282//! kSM_WaitingRun
2283int DataLogger::NightlyToWaitRunPlease()
2284{
2285 if (fDebugIsOn)
2286 {
2287 Debug("Going to Wait Run Number state...");
2288 }
2289 return kSM_WaitingRun;
2290}
2291// --------------------------------------------------------------------------
2292//
2293//! Setup Logger's configuration from a Configuration object
2294//! @param conf the configuration object that should be used
2295//!
2296bool DataLogger::SetConfiguration(Configuration& conf)
2297{
2298 fDebugIsOn = conf.Get<bool>("debug");
2299
2300 //Set the block or allow list
2301 fBlackList.clear();
2302 fWhiteList.clear();
2303
2304 //Adding entries that should ALWAYS be ignored
2305 //fBlackList.insert("DATA_LOGGER/");
2306 fBlackList.insert("/SERVICE_LIST");
2307 fBlackList.insert("DIS_DNS/");
2308
2309 if (conf.Has("block"))
2310 {
2311 const vector<string> vec = conf.Get<vector<string>>("block");
2312
2313 fBlackList.insert(vec.begin(), vec.end());
2314 }
2315
2316 if (conf.Has("allow"))
2317 {
2318 const vector<string> vec = conf.Get<vector<string>>("allow");
2319 fWhiteList.insert(vec.begin(), vec.end());
2320 }
2321
2322 //Set the grouping
2323 if (conf.Has("group"))
2324 {
2325 const vector<string> vec = conf.Get<vector<string>>("group");
2326 fGrouping.insert(vec.begin(), vec.end());
2327 }
2328 return true;
2329}
2330
2331// --------------------------------------------------------------------------
2332int RunDim(Configuration &conf)
2333{
2334 WindowLog wout;
2335
2336 //log.SetWindow(stdscr);
2337 if (conf.Has("log"))
2338 if (!wout.OpenLogFile(conf.Get<string>("log")))
2339 wout << kRed << "ERROR - Couldn't open log-file " << conf.Get<string>("log") << ": " << strerror(errno) << endl;
2340
2341 // Start io_service.Run to use the StateMachineImp::Run() loop
2342 // Start io_service.run to only use the commandHandler command detaching
2343 DataLogger logger(wout);
2344 if (!logger.SetConfiguration(conf))
2345 return -1;
2346
2347 logger.Run(true);
2348
2349 return 0;
2350}
2351// --------------------------------------------------------------------------
2352void RunThread(DataLogger* logger)
2353{
2354 // This is necessary so that the StateMachine Thread can signal the
2355 // Readline thread to exit
2356 logger->Run(true);
2357 Readline::Stop();
2358}
2359// --------------------------------------------------------------------------
2360template<class T>
2361int RunShell(Configuration &conf)
2362{
2363 static T shell(conf.GetName().c_str(), conf.Get<int>("console")!=1);
2364
2365 WindowLog &win = shell.GetStreamIn();
2366 WindowLog &wout = shell.GetStreamOut();
2367
2368 if (conf.Has("log"))
2369 if (!wout.OpenLogFile(conf.Get<string>("log")))
2370 win << kRed << "ERROR - Couldn't open log-file " << conf.Get<string>("log") << ": " << strerror(errno) << endl;
2371
2372 DataLogger logger(wout);
2373
2374 if (!logger.SetConfiguration(conf))
2375 return -1;
2376
2377 shell.SetReceiver(logger);
2378
2379 boost::thread t(boost::bind(RunThread, &logger));
2380
2381 shell.Run(); // Run the shell
2382
2383 logger.Stop();
2384
2385 //Wait until the StateMachine has finished its thread
2386 //before returning and destroyinng the dim objects which might
2387 //still be in use.
2388 t.join();
2389
2390 return 0;
2391}
2392
2393/*
2394 Extract usage clause(s) [if any] for SYNOPSIS.
2395 Translators: "Usage" and "or" here are patterns (regular expressions) which
2396 are used to match the usage synopsis in program output. An example from cp
2397 (GNU coreutils) which contains both strings:
2398 Usage: cp [OPTION]... [-T] SOURCE DEST
2399 or: cp [OPTION]... SOURCE... DIRECTORY
2400 or: cp [OPTION]... -t DIRECTORY SOURCE...
2401 */
2402void PrintUsage()
2403{
2404 cout << "\n"
2405 "The data logger connects to all available Dim services and "
2406 "writes them to ascii and fits files.\n"
2407 "\n"
2408 "The default is that the program is started without user interaction. "
2409 "All actions are supposed to arrive as DimCommands. Using the -c "
2410 "option, a local shell can be initialized. With h or help a short "
2411 "help message about the usage can be brought to the screen.\n"
2412 "\n"
2413 "Usage: dataLogger [-c type] [OPTIONS]\n"
2414 " or: dataLogger [OPTIONS]\n";
2415 cout << endl;
2416
2417}
2418// --------------------------------------------------------------------------
2419void PrintHelp()
2420{
2421 /* Additional help text which is printed after the configuration
2422 options goes here */
2423 cout <<
2424 "\n"
2425 "If the allow list has any element, only the servers and/or services "
2426 "specified in the list will be used for subscription. The black list "
2427 "will disable service subscription and has higher priority than the "
2428 "allow list. If the allow list is not present by default all services "
2429 "will be subscribed."
2430 "\n"
2431 "For example, block=DIS_DNS/ will skip all the services offered by "
2432 "the DIS_DNS server, while block=/SERVICE_LIST will skip all the "
2433 "SERVICE_LIST services offered by any server and DIS_DNS/SERVICE_LIST "
2434 "will skip DIS_DNS/SERVICE_LIST.\n"
2435 "\n"
2436 "The commands offered by the dataLoger are the following: \n";
2437 cout << setw(20) << DataLogger::fConfigDay << " : specify the path where to put the nightly files\n";
2438 cout << setw(20) << DataLogger::fConfigRun << " : specify the path where to put the run files\n";
2439 cout << setw(20) << DataLogger::fConfigRunNumber << " : specify the run number\n";
2440 cout << setw(20) << DataLogger::fConfigLog << " : log a particular message\n";
2441 cout << setw(20) << DataLogger::fTransStart << " : start the nightly logging\n";
2442 cout << setw(20) << DataLogger::fTransStop << " : stop the nightly logging\n";
2443 cout << setw(20) << DataLogger::fTransStartRun << " : start the run logging\n";
2444 cout << setw(20) << DataLogger::fTransStopRun << " : stop the run logging\n";
2445 cout << setw(20) << DataLogger::fTransReset << " : stop any logging and/or recover from an error state\n";
2446 cout << setw(20) << DataLogger::fTransWait << " : go to the wait for run number state\n";
2447 cout << setw(20) << DataLogger::fPrintCommand << " : print the current state of the logger to the shell\n";
2448 cout << setw(20) << DataLogger::fDebugOnOff << " : turn on or off the debug mode\n";
2449 cout << setw(20) << DataLogger::fStatsPeriod << " : set the periodicity of the statistics. 0 disable them\n";
2450 cout << endl;
2451}
2452// --------------------------------------------------------------------------
2453void SetupConfiguration(Configuration &conf)
2454{
2455 const string n = conf.GetName()+".log";
2456
2457 po::options_description configp("Program options");
2458 configp.add_options()
2459 ("dns", var<string>("localhost"), "Dim nameserver host name (Overwites DIM_DNS_NODE environment variable)")
2460 ("log,l", var<string>(n), "Write log-file")
2461 ("console,c", var<int>(), "Use console (0=shell, 1=simple buffered, X=simple unbuffered)")
2462 ;
2463
2464 po::options_description configs("DataLogger options");
2465 configs.add_options()
2466 ("block,b", vars<string>(), "Black-list of services")
2467 ("allow,a", vars<string>(), "White-list of services")
2468 ("debug", po_bool(), "Debug mode. Print clear text of received service reports to log-stream")
2469 ("group,g", vars<string>(), "Grouping of services into a single run-Fits")
2470 ;
2471
2472 conf.AddEnv("dns", "DIM_DNS_NODE");
2473
2474 conf.AddOptions(configp);
2475 conf.AddOptions(configs);
2476}
2477// --------------------------------------------------------------------------
2478int main(int argc, const char* argv[])
2479{
2480 Configuration conf(argv[0]);
2481 conf.SetPrintUsage(PrintUsage);
2482 SetupConfiguration(conf);
2483
2484 po::variables_map vm;
2485 try
2486 {
2487 vm = conf.Parse(argc, argv);
2488 }
2489#if BOOST_VERSION > 104000
2490 catch (po::multiple_occurrences &e)
2491 {
2492 cerr << "Program options invalid due to: " << e.what() << " of '" << e.get_option_name() << "'." << endl;
2493 return -1;
2494 }
2495#endif
2496 catch (exception& e)
2497 {
2498 cerr << "Program options invalid due to: " << e.what() << endl;
2499 return -1;
2500 }
2501
2502 if (conf.HasVersion() || conf.HasPrint())
2503 return -1;
2504
2505 if (conf.HasHelp())
2506 {
2507 PrintHelp();
2508 return -1;
2509 }
2510
2511 Dim::Setup(conf.Get<string>("dns"));
2512
2513// try
2514 {
2515 // No console access at all
2516 if (!conf.Has("console"))
2517 return RunDim(conf);
2518
2519 // Console access w/ and w/o Dim
2520 if (conf.Get<int>("console")==0)
2521 return RunShell<LocalShell>(conf);
2522 else
2523 return RunShell<LocalConsole>(conf);
2524 }
2525/* catch (exception& e)
2526 {
2527 cerr << "Exception: " << e.what() << endl;
2528 return -1;
2529 }*/
2530
2531 return 0;
2532}
Note: See TracBrowser for help on using the repository browser.