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

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