source: trunk/FACT++/src/datalogger.cc@ 11702

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