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

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