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

Last change on this file since 12555 was 12538, checked in by lyard, 13 years ago
moved the flush from every row entry to every minute
File size: 85.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
634 if ((fBlackList.find(server + "/") != fBlackList.end()) ||
635 (fBlackList.find(server + "/" + service) != fBlackList.end()) ||
636 (fBlackList.find("/" + service) != fBlackList.end()))
637 {
638 if (fWhiteList.size()>0 &&
639 (fWhiteList.find(server + "/" + service) != fWhiteList.end()))
640 {
641 if (fDebugIsOn)
642 Debug("White list saved service " + server + "/" + service + " from blacklisting");
643 return true;
644 }
645 if (fDebugIsOn)
646 Debug("Blacklist banned service " + server + "/" + service);
647 return false;
648 }
649 return true;
650}
651// --------------------------------------------------------------------------
652//
653//! Compiles a file name
654//! @param path the base path where to put the file
655//! @param time the time at which the file is created
656//! @param service the service name, if any
657//! @param extension the extension to add, if any
658//
659//string DataLogger::CompileFileName(const string &path, const string &service, const string & extension, const Time &time)
660string DataLogger::CompileFileName(const string& service, const string& extension, uint32_t run, const Time& time) const
661{
662 ostringstream str;
663
664 const Time ftime(time);//removed this as already done by nightAsInt: -boost::posix_time::hours(12));
665 str << ftime.NightAsInt();
666
667 if (run>0)
668 str << '_' << setfill('0') << setw(3) << run;
669
670 if (!service.empty())
671 str << '.' << service;
672
673 if (!extension.empty())
674 str << "." << extension;
675
676 return str.str();
677}
678
679string DataLogger::CompileFileNameWithPath(const string& path, const string& service, const string& extension, uint32_t run/*, const Time& time*/)
680{
681 ostringstream str;
682
683 const Time time;
684
685 //calculate time suitable for naming files.
686 const Time ftime = time-boost::posix_time::hours(12);
687
688 //output it
689 str << path << ftime.GetAsStr("/%Y/%m/%d");
690
691 //check if target directory exist
692 if (!DoesPathExist(str.str()))
693 CreateDirectory(str.str());
694
695 str << '/' << CompileFileName(service, extension, run, time);
696
697 return str.str();
698
699
700}
701
702// --------------------------------------------------------------------------
703//
704//!retrieves the size on disk of a file
705//! @param fileName the full file name for which the size on disk should be retrieved
706//! @return the size of the file on disk, in bytes. 0 if the file does not exist or if an error occured
707//
708off_t DataLogger::GetFileSize(const string& fileName)
709{
710 return DimWriteStatistics::GetFileSizeOnDisk(fileName, *this);
711}
712
713// --------------------------------------------------------------------------
714//
715//! Removes the oldest run number and closes the fits files that should be closed
716//! Also creates the fits grouping file
717//
718void DataLogger::RemoveOldestRunNumber()
719{
720 if (fDebugIsOn)
721 {
722 ostringstream str;
723 str << "Removing run number " << fRunNumber.front().runNumber;
724 Debug(str);
725 }
726 CreateFitsGrouping(fRunNumber.front().openedFits, fRunNumber.front().runNumber);
727
728 //crawl through the subscriptions to see if there are still corresponding fits files opened.
729 for (SubscriptionsListType::iterator x=fServiceSubscriptions.begin();
730 x!=fServiceSubscriptions.end(); x++)
731 for (map<string, SubscriptionType>::iterator y=x->second.begin();
732 y!=x->second.end(); y++)
733 if (y->second.runFile.fRunNumber == fRunNumber.front().runNumber && y->second.runFile.IsOpen())
734 {
735 y->second.runFile.Close();
736 }
737 //if a grouping file is on, decrease the number of opened fits manually
738 if (fRunNumber.front().runFitsFile)
739 (fNumSubAndFitsData.numOpenFits)--;
740 //remove the entry
741 fRunNumber.pop_front();
742}
743
744// --------------------------------------------------------------------------
745//
746//! Default constructor. The name of the machine is given DATA_LOGGER
747//! and the state is set to kSM_Ready at the end of the function.
748//
749//!Setup the allows states, configs and transitions for the data logger
750//
751DataLogger::DataLogger(ostream &out) : StateMachineDim(out, "DATA_LOGGER"),
752 fFilesStats("DATA_LOGGER", *this)
753{
754 shouldBackLog = true;
755 //initialize member data
756 fFilePath = ".";
757
758 //calculate time "centered" around noon instead of midnight
759 const Time timeNow;
760 const Time nowMinusTwelve = timeNow-boost::posix_time::hours(12);
761 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
762 lastFlush = Time();
763
764 //Give a name to this machine's specific states
765 AddStateName(kSM_NightlyOpen, "NightlyFileOpen", "The summary files for the night are open.");
766 AddStateName(kSM_WaitingRun, "WaitForRun", "The summary files for the night are open and we wait for a run to be started.");
767 AddStateName(kSM_Logging, "Logging", "The summary files for the night and the files for a single run are open.");
768 AddStateName(kSM_BadFolder, "ErrInvalidFolder", "The folder for the files is not invalid.");
769 AddStateName(kSM_DailyWriteError, "ErrDailyWrite", "An error occured while writing to a daily (and run) file.");
770 AddStateName(kSM_RunWriteError, "ErrRunWrite", "An error occured while writing to a run file.");
771
772 // Add the possible transitions for this machine
773 AddEvent(kSM_NightlyOpen, "START", kSM_Ready, kSM_BadFolder)
774 (bind(&DataLogger::StartPlease, this))
775 ("Start the nightly logging. Nightly file location must be specified already");
776
777 AddEvent(kSM_Ready, "STOP", kSM_NightlyOpen, kSM_WaitingRun, kSM_Logging, kSM_DailyWriteError, kSM_RunWriteError)
778 (bind(&DataLogger::GoToReadyPlease, this))
779 ("Stop all data logging, close all files.");
780/*
781 AddEvent(kSM_Logging, "START_RUN", kSM_WaitingRun, kSM_BadRunConfig)
782 (bind(&DataLogger::StartRunPlease, this))
783 ("Start the run logging. Run file location must be specified already.");
784
785 AddEvent(kSM_WaitingRun, "STOP_RUN", kSM_Logging)
786 (bind(&DataLogger::StopRunLogging, this))
787 ("Wait for a run to be started, open run-files as soon as a run number arrives.");
788*/
789 AddEvent(kSM_Ready, "RESET", kSM_Error, kSM_BadFolder, kSM_DailyWriteError, kSM_RunWriteError)
790 (bind(&DataLogger::GoToReadyPlease, this))
791 ("Transition to exit error states. Closes the any open file.");
792
793 AddEvent(kSM_WaitingRun, "START_RUN_LOGGING", /*kSM_Logging,*/ kSM_NightlyOpen, kSM_Ready)
794 (bind(&DataLogger::NightlyToWaitRunPlease, this))
795 ("Go to waiting for run number state. In this state with any received run-number a new file is opened.");
796
797 AddEvent(kSM_NightlyOpen, "STOP_RUN_LOGGING", kSM_WaitingRun, kSM_Logging)
798 (bind(&DataLogger::BackToNightlyOpenPlease, this))
799 ("Go from the wait for run to nightly open state.");
800
801 /*
802 // Add the possible configurations for this machine
803 AddEvent("SET_FOLDER", "C", kSM_Ready, kSM_BadNightlyConfig)
804 (bind(&DataLogger::ConfigureNightlyFileName, this, placeholders::_1))
805 ("Configure the base folder for the nightly files."
806 "|Path[string]:Absolute or relative path name where the nightly files should be stored.");
807
808 AddEvent("SET_RUN_FOLDER", "C", kSM_Ready, kSM_BadNightlyConfig, kSM_NightlyOpen, kSM_WaitingRun, kSM_BadRunConfig)
809 (bind(&DataLogger::ConfigureRunFileName, this, placeholders::_1))
810 ("Configure the base folder for the run files."
811 "|Path[string]:Absolute or relative path name where the run files should be stored.");
812 */
813 // Provide a print command
814 AddEvent("PRINT_INFO")
815 (bind(&DataLogger::PrintStatePlease, this, placeholders::_1))
816 ("Print information about the internal status of the data logger.");
817
818 OpenFileToDim fToDim;
819 fToDim.code = 0;
820 fToDim.fileName[0] = '\0';
821
822 fOpenedNightlyFiles = new DimDescribedService(GetName() + "/FILENAME_NIGHTLY", "I:1;C", fToDim,
823 "Path and base name used for the nightly files."
824 "|Type[int]:type of open files (1=log, 2=rep, 4=fits)"
825 "|Name[string]:path and base file name");
826
827 fOpenedRunFiles = new DimDescribedService(GetName() + "/FILENAME_RUN", "I:1;C", fToDim,
828 "Path and base name used for the run files."
829 "|Type[int]:type of open files (1=log, 2=rep, 4=fits)"
830 "|Name[string]:path and base file name");
831
832 fNumSubAndFitsData.numSubscriptions = 0;
833 fNumSubAndFitsData.numOpenFits = 0;
834 fNumSubAndFits = new DimDescribedService(GetName() + "/NUM_SUBS", "I:2", fNumSubAndFitsData,
835 "Num. open files + num. subscribed services"
836 "|NSubAndOpenFiles[int]:Num. of subs and open files");
837
838 //services parameters
839 fDebugIsOn = false;
840 fOpenedFilesIsOn = true;
841 fNumSubAndFitsIsOn = true;
842
843 // provide services control commands
844 AddEvent("SET_DEBUG_MODE", "B:1", kSM_NightlyOpen, kSM_Logging, kSM_WaitingRun, kSM_Ready)
845 (bind(&DataLogger::SetDebugOnOff, this, placeholders::_1))
846 ("Switch debug mode on or off. Debug mode prints information about every service written to a file."
847 "|Enable[bool]:Enable of disable debug mode (yes/no).");
848
849 AddEvent("SET_STATISTICS_UPDATE_INTERVAL", "S:1", kSM_NightlyOpen, kSM_Logging, kSM_WaitingRun, kSM_Ready)
850 (bind(&DataLogger::SetStatsPeriod, this, placeholders::_1))
851 ("Interval in which the data-logger statistics service (STATS) is updated."
852 "|Interval[ms]:Value in milliseconds (<=0: no update).");
853
854 AddEvent("ENABLE_FILENAME_SERVICES", "B:1", kSM_NightlyOpen, kSM_Logging, kSM_WaitingRun, kSM_Ready)
855 (bind(&DataLogger::SetOpenedFilesOnOff ,this, placeholders::_1))
856 ("Switch service which distributes information about the open files on or off."
857 "|Enable[bool]:Enable of disable filename services (yes/no).");
858
859 AddEvent("ENABLE_NUMSUBS_SERVICE", "B:1", kSM_NightlyOpen, kSM_Logging, kSM_WaitingRun, kSM_Ready)
860 (bind(&DataLogger::SetNumSubsAndFitsOnOff, this, placeholders::_1))
861 ("Switch the service which distributes information about the number of subscriptions and open files on or off."
862 "|Enable[bool]:Enable of disable NUM_SUBS service (yes/no).");
863
864 AddEvent("SET_RUN_TIMEOUT", "L:1", kSM_Ready, kSM_NightlyOpen, kSM_Logging, kSM_WaitingRun)
865 (bind(&DataLogger::SetRunTimeoutDelay, this, placeholders::_1))
866 ("Set the timeout delay for old run numbers."
867 "|timeout[min]:Time out in minutes after which files for expired runs are closed.");
868
869 fDestructing = false;
870
871 fPreviousOldRunNumberCheck = Time().Mjd();
872
873 fDailyFileDayChangedAlready = true;
874 fRunNumberTimeout = 60000; //default run-timeout set to 1 minute
875 fRunNumber.push_back(RunNumberType());
876 fRunNumber.back().runNumber = -1;
877 fRunNumber.back().time = Time();
878 NotifyOpenedFile("", 0, fOpenedNightlyFiles);
879 NotifyOpenedFile("", 0, fOpenedRunFiles);
880
881 fRunNumberService = NULL;
882 fShouldAutoStart = false;
883 fAutoStarted = false;
884 if(fDebugIsOn)
885 {
886 Debug("DataLogger Init Done.");
887 }
888}
889
890// --------------------------------------------------------------------------
891//
892//! Destructor
893//
894DataLogger::~DataLogger()
895{
896 if (fDebugIsOn)
897 Debug("DataLogger destruction starts");
898
899 //this boolean should not be required anymore
900 fDestructing = true;
901
902 //first, let's backup the datalogger/message service subscription
903// shared_ptr<DimStampedInfo> messageBackup;
904// const SubscriptionsListType::iterator x = fServiceSubscriptions.find("DATA_LOGGER");
905// if (x != fServiceSubscriptions.end())
906// {
907// const map<string, SubscriptionType>::iterator y = x->second.find("MESSAGE");
908// if (y != x->second.end())
909// messageBackup = y->second.dimInfo;
910// }
911
912
913 //now clear the services subscriptions
914 dim_lock();
915 fServiceSubscriptions.clear();
916 dim_unlock();
917
918 //clear any remaining run number (should remain only one)
919 while (fRunNumber.size() > 0)
920 {
921 RemoveOldestRunNumber();
922 }
923 //go to the ready state. i.e. close all files, run-wise first
924 GoToReadyPlease();
925
926
927 Info("Will soon close the daily log file");
928
929 delete fOpenedNightlyFiles;
930 delete fOpenedRunFiles;
931 delete fNumSubAndFits;
932
933 //release message service before closing nightly log file
934// if (messageBackup)
935 // messageBackup.reset();
936
937 if (fNightlyLogFile.is_open())//this file is the only one that has not been closed by GoToReadyPlease
938 {
939// dim_lock();
940 fNightlyLogFile << endl;
941 fNightlyLogFile.close();
942// dim_unlock();
943 }
944
945 if (fDebugIsOn)
946 Debug("DataLogger desctruction ends");
947}
948
949// --------------------------------------------------------------------------
950//
951//! checks if old run numbers should be trimmed and if so, do it
952//
953void DataLogger::TrimOldRunNumbers()
954{
955 const Time cTime = Time();
956
957 if (cTime - fPreviousOldRunNumberCheck < boost::posix_time::milliseconds(fRunNumberTimeout))
958 return;
959
960 while (fRunNumber.size() > 1 && (cTime - fRunNumber.back().time) > boost::posix_time::milliseconds(fRunNumberTimeout))
961 {
962 RemoveOldestRunNumber();
963 }
964 fPreviousOldRunNumberCheck = cTime;
965}
966// --------------------------------------------------------------------------
967//
968//! Inherited from DimInfo. Handles all the Infos to which we subscribed, and log them
969//
970void DataLogger::infoHandler()
971{
972 if ((GetCurrentState() == kSM_Ready) && (!fAutoStarted) && fShouldAutoStart)
973 {
974 fAutoStarted = true;
975 SetCurrentState(StartPlease());
976 }
977 else {if (GetCurrentState() > kSM_Ready)
978 fAutoStarted = true;
979 }
980 DimInfo* I = getInfo();
981
982 if (I==NULL)
983 return;
984
985 //check if the service pointer corresponds to something that we subscribed to
986 //this is a fix for a bug that provides bad Infos when a server starts
987 bool found = false;
988 SubscriptionsListType::iterator x;
989 map<string, SubscriptionType>::iterator y;
990 for (x=fServiceSubscriptions.begin(); x != fServiceSubscriptions.end(); x++)
991 {//find current service is subscriptions
992 for (y=x->second.begin(); y!=x->second.end();y++)
993 if ((y->second.dimInfo).get() == I)
994 {
995 found = true;
996 break;
997 }
998 if (found)
999 break;
1000 }
1001 if (!found)
1002 {
1003 DimServiceInfoList::infoHandler();
1004 return;
1005 }
1006 if (I->getSize() <= 0 || I->getData()==NULL)
1007 {
1008 return;
1009 }
1010 if (strlen(I->getFormat()) == 0)
1011 {
1012 ostringstream str;
1013 str << "Format of " << I->getName() << " is empty (ptr=" << I->getData() << ", size=" << I->getSize() << ")... ignoring it.";
1014 Error(str);
1015 return;
1016 }
1017 // Make sure that getTimestampMillisecs is NEVER called before
1018 // getTimestamp is properly called
1019 // check that the message has been updated by something, i.e. must be different from its initial value
1020 if (I->getTimestamp() == 0)
1021 {
1022 return;
1023 }
1024 // FIXME: Here we have to check if we have received the
1025 // service with the run-number.
1026 // CheckForRunNumber(I); has been removed because we have to
1027 // subscribe to this service anyway and hence we have the pointer
1028 // (no need to check for the name)
1029 CheckForRunNumber(I);
1030
1031 ReportPlease(I, y->second);
1032
1033 //remove old run numbers
1034 TrimOldRunNumbers();
1035}
1036
1037// --------------------------------------------------------------------------
1038//
1039//! Open the text files associated with the given run number
1040//! @param run the run number to be dealt with
1041//
1042int DataLogger::OpenRunFile(RunNumberType& run)
1043{
1044#ifdef RUN_LOGS
1045 // open log file
1046 run.logName = CompileFileName(fFilePath, "", "log", run.runNumber);
1047 if (!OpenStream(run.logFile, run.logName))
1048 return -1;
1049#endif
1050
1051 // open report file
1052 run.reportName = CompileFileNameWithPath(fFilePath, "", "rep", run.runNumber);
1053 if (!OpenStream(run.reportFile, run.reportName))
1054 return -1;
1055
1056 //get the size of the newly opened file.
1057#ifdef RUN_LOGS
1058 fFilesStats.FileOpened(run.logName);
1059#endif
1060 fFilesStats.FileOpened(run.reportName);
1061 //TODO this notification scheme might be messed up now.... fix it !
1062 const string baseFileName = CompileFileNameWithPath(fFilePath, "", "", run.runNumber);
1063 NotifyOpenedFile(baseFileName, 3, fOpenedRunFiles);
1064 run.openedFits.clear();
1065 return 0;
1066}
1067// --------------------------------------------------------------------------
1068//
1069//! Add a new active run number
1070//! @param newRun the new run number
1071//! @param time the time at which the new run number was issued
1072//
1073void DataLogger::AddNewRunNumber(int64_t newRun, Time time)
1074{
1075
1076 if (newRun > 0xffffffff)
1077 {
1078 Error("New run number too large, out of range. Ignoring.");
1079 return;
1080 }
1081 for (std::vector<int64_t>::const_iterator it=previousRunNumbers.begin(); it != previousRunNumbers.end(); it++)
1082 {
1083 if (*it == newRun)
1084 {
1085 Error("Newly provided run number has already been used (or is still in use). Going to error state");
1086 SetCurrentState(kSM_BadFolder);
1087 return;
1088 }
1089 }
1090 if (fDebugIsOn)
1091 {
1092 ostringstream str;
1093 str << "Adding new run number " << newRun << " issued at " << time;
1094 Debug(str);
1095 }
1096 //Add new run number to run number list
1097 fRunNumber.push_back(RunNumberType());
1098 fRunNumber.back().runNumber = int32_t(newRun);
1099 fRunNumber.back().time = time;
1100
1101 ostringstream str;
1102 str << "The new run number is: " << fRunNumber.back().runNumber;
1103 Message(str);
1104
1105 if (GetCurrentState() != kSM_Logging && GetCurrentState() != kSM_WaitingRun )
1106 return;
1107 //open the log and report files
1108 if (fRunNumber.back().runNumber > 0)
1109 if (OpenRunFile(fRunNumber.back()) != 0)
1110 {//an error occured. close current run files and go to error state
1111 for (list<RunNumberType>::iterator it=fRunNumber.begin(); it != fRunNumber.end(); it++)
1112 {
1113 if (it->reportFile->is_open())
1114 {
1115 it->reportFile->close();
1116 Info("Closed: "+it->reportName);
1117 }
1118#ifdef RUN_LOGS
1119 if (it->logFile->is_open())
1120 {
1121 it->logFile->close();
1122 Info("Closed: "+it->logName);
1123 }
1124#endif
1125 }
1126 StopRunLogging();
1127 SetCurrentState(kSM_BadFolder);
1128 }
1129
1130 if (newRun > 0 && GetCurrentState() == kSM_WaitingRun)
1131 SetCurrentState(kSM_Logging);
1132 if (newRun < 0 && GetCurrentState() == kSM_Logging)
1133 SetCurrentState(kSM_WaitingRun);
1134}
1135// --------------------------------------------------------------------------
1136//
1137//! Checks whether or not the current info is a run number.
1138//! If so, then remember it. A run number is required to open the run-log file
1139//! @param I
1140//! the current DimInfo
1141//
1142void DataLogger::CheckForRunNumber(DimInfo* I)
1143{
1144 if (I != fRunNumberService)
1145 return;
1146
1147 AddNewRunNumber(I->getLonglong(), Time(I->getTimestamp(), I->getTimestampMillisecs()*1000));
1148}
1149
1150// --------------------------------------------------------------------------
1151//
1152//! write infos to log files.
1153//! @param I
1154//! The current DimInfo
1155//! @param sub
1156//! The dataLogger's subscription corresponding to this DimInfo
1157//
1158void DataLogger::ReportPlease(DimInfo* I, SubscriptionType& sub)
1159{
1160 const string fmt(I->getFormat());
1161
1162 const bool isItaReport = fmt!="C";
1163
1164 if (!fNightlyLogFile.is_open())
1165 return;
1166
1167 if (fDebugIsOn && string(I->getName())!="DATA_LOGGER/MESSAGE")
1168 {
1169 ostringstream str;
1170 str << "Logging " << I->getName() << " [" << I->getFormat() << "] (" << I->getSize() << ")";
1171 Debug(str);
1172 }
1173
1174 //
1175 // Check whether we should close and reopen daily text files or not
1176 // calculate time "centered" around noon instead of midnight
1177 // if number of days has changed, then files should be closed and reopenned.
1178 const Time timeNow;
1179 const Time nowMinusTwelve = timeNow-boost::posix_time::hours(12);
1180 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
1181
1182 //also check if we should flush the nightly files
1183 if (lastFlush < timeNow-boost::posix_time::minutes(1))
1184 {
1185 lastFlush = timeNow;
1186 SubscriptionsListType::iterator x;
1187 map<string, SubscriptionType>::iterator y;
1188 for (x=fServiceSubscriptions.begin(); x != fServiceSubscriptions.end(); x++)
1189 {//find current service is subscriptions
1190 for (y=x->second.begin(); y!=x->second.end();y++)
1191 if (y->second.nightlyFile.IsOpen())
1192 {
1193 y->second.nightlyFile.Flush();
1194 }
1195 }
1196 if (fDebugIsOn)
1197 Debug("Just flushed nightly fits files to the disk");
1198 }
1199
1200// if (Time().h() == 12 && !fDailyFileDayChangedAlready)
1201 if (newDayNumber != fCurrentDay)
1202 {
1203 fCurrentDay = newDayNumber;
1204 //crawl through the subcriptions and close any open nightly file
1205 SubscriptionsListType::iterator x;
1206 map<string, SubscriptionType>::iterator y;
1207 for (x=fServiceSubscriptions.begin(); x != fServiceSubscriptions.end(); x++)
1208 {//find current service is subscriptions
1209 for (y=x->second.begin(); y!=x->second.end();y++)
1210 if (y->second.nightlyFile.IsOpen())
1211 {
1212 y->second.nightlyFile.Close();
1213 }
1214 }
1215
1216 if (fDebugIsOn)
1217 Debug("Day have changed! Closing and reopening nightly files");
1218
1219 fNightlyLogFile << endl;
1220 fNightlyLogFile.close();
1221 fNightlyReportFile.close();
1222
1223 Info("Closed: "+fFullNightlyLogFileName);
1224 Info("Closed: "+fFullNightlyReportFileName);
1225
1226 fFullNightlyLogFileName = CompileFileNameWithPath(fFilePath, "", "log");
1227 if (!OpenTextFilePlease(fNightlyLogFile, fFullNightlyLogFileName))
1228 {
1229 GoToReadyPlease();
1230 SetCurrentState(kSM_BadFolder);
1231 return;
1232 }
1233 fNightlyLogFile << endl;
1234
1235 fFullNightlyReportFileName = CompileFileNameWithPath(fFilePath, "", "rep");
1236 if (!OpenTextFilePlease(fNightlyReportFile, fFullNightlyReportFileName))
1237 {
1238 GoToReadyPlease();
1239 SetCurrentState(kSM_BadFolder);
1240 return;
1241 }
1242
1243 // fDailyFileDayChangedAlready = true;
1244 }
1245// if (Time().h() != 12 && fDailyFileDayChangedAlready)
1246// fDailyFileDayChangedAlready = false;
1247
1248 //create the converter for that service
1249 if (!sub.fConv)
1250 {
1251 sub.fConv = shared_ptr<Converter>(new Converter(Out(), I->getFormat()));
1252 if (!sub.fConv->valid())
1253 {
1254 ostringstream str;
1255 str << "Couldn't properly parse the format... service " << sub.dimInfo->getName() << " ignored.";
1256 Error(str);
1257 return;
1258 }
1259 }
1260 //construct the header
1261 ostringstream header;
1262 const Time cTime(I->getTimestamp(), I->getTimestampMillisecs()*1000);
1263 fQuality = I->getQuality();
1264 fMjD = cTime.Mjd();
1265
1266 //figure out which run file should be used
1267 ofstream* targetRunFile = NULL;
1268 RunNumberType* cRunNumber = NULL;
1269 if (GetCurrentState() == kSM_Logging || GetCurrentState() == kSM_WaitingRun)
1270 {
1271 list<RunNumberType>::reverse_iterator rit;
1272 for (rit=fRunNumber.rbegin(); rit!=fRunNumber.rend(); rit++)
1273 {
1274 if (rit->time < cTime) //this is the run number that we want to use
1275 {
1276 //Find something better to convert iterator to pointer than the ugly line below....
1277 cRunNumber = &(*rit);
1278 sub.runNumber = rit->runNumber;
1279
1280 if (rit->runNumber <= 0)//take only positive numbers.
1281 break;
1282#ifdef RUN_LOGS
1283 targetRunFile = isItaReport ? (rit->reportFile).get() : (rit->logFile).get();
1284#else
1285 targetRunFile = isItaReport ? (rit->reportFile).get() : NULL;
1286#endif
1287 break;
1288 }
1289 }
1290 if (rit == fRunNumber.rend() && fRunNumber.size() != 0)
1291 {
1292 Error("Could not find an appropriate run number for info coming at time "+cTime.GetAsStr());
1293 Error("Active run numbers: ");
1294 for (rit=fRunNumber.rbegin(); rit != fRunNumber.rend(); rit++)
1295 {
1296 ostringstream str;
1297 str << " -> " << rit->runNumber;
1298 Error(str);
1299 }
1300
1301 }
1302 }
1303
1304 if (isItaReport)
1305 {
1306 //write text header
1307 header << I->getName() << " " << fQuality << " ";
1308 header << cTime.Y() << " " << cTime.M() << " " << cTime.D() << " ";
1309 header << cTime.h() << " " << cTime.m() << " " << cTime.s() << " ";
1310 header << cTime.ms() << " " << I->getTimestamp() << " ";
1311
1312 string text;
1313 try
1314 {
1315 text = sub.fConv->GetString(I->getData(), I->getSize());
1316 }
1317 catch (const runtime_error &e)
1318 {
1319 ostringstream str;
1320 str << "Parsing service " << sub.dimInfo->getName();
1321 str << " failed: " << e.what() << " removing the subscription for now.";
1322 Error(str);
1323 //remove this subscription from the list.
1324 //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 !
1325 RemoveService(sub.server, sub.service, false);
1326// string server = sub.server;
1327// string service = sub.service;
1328// fServiceSubscriptions.find(server)->second.erase(service);
1329 return;
1330 }
1331
1332 if (text.empty())
1333 {
1334 ostringstream str;
1335 str << "Service " << sub.dimInfo->getName() << " sent an empty string";
1336 Info(str);
1337 return;
1338 }
1339 //replace bizarre characters by white space
1340 replace(text.begin(), text.end(), '\n', '\\');
1341 replace_if(text.begin(), text.end(), ptr_fun<int, int>(&iscntrl), ' ');
1342
1343 //write entry to Nightly report
1344 if (fNightlyReportFile.is_open())
1345 {
1346 fNightlyReportFile << header.str() << text << endl;
1347 if (!CheckForOfstreamError(fNightlyReportFile, true))
1348 return;
1349 }
1350 //write entry to run-report
1351 if (targetRunFile && targetRunFile->is_open())
1352 {
1353 *targetRunFile << header.str() << text << endl;
1354 if (!CheckForOfstreamError(*targetRunFile, false))
1355 return;
1356 }
1357#ifdef HAVE_FITS
1358 //check if the last received event was before noon and if current one is after noon.
1359 //if so, close the file so that it gets reopened.
1360// if (sub.nightlyFile.IsOpen())
1361// if ((sub.lastReceivedEvent != Time::None) && (sub.lastReceivedEvent.h() < 12) && (cTime.h() >= 12))
1362// {
1363// sub.nightlyFile.Close();
1364// }
1365 sub.lastReceivedEvent = cTime;//ici
1366 if (!sub.nightlyFile.IsOpen() || !sub.runFile.IsOpen() || sub.runNumber != sub.runFile.fRunNumber)
1367 if (GetCurrentState() != kSM_Ready)
1368 OpenFITSFilesPlease(sub, cRunNumber);
1369 WriteToFITS(sub);
1370#endif
1371 }
1372 else
1373 {//write entry to both Nightly and run logs
1374 vector<string> strings;
1375 try
1376 {
1377 strings = sub.fConv->ToStrings(I->getData());
1378 }
1379 catch (const runtime_error &e)
1380 {
1381 ostringstream str;
1382 str << "Parsing service " << sub.dimInfo->getName();
1383 str << " failed: " << e.what() << " removing the subscription for now.";
1384 Error(str);
1385 //remove this subscription from the list.
1386 //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 !
1387 RemoveService(sub.server, sub.service, false);
1388// string server = sub.server;
1389// string service = sub.service;
1390// fServiceSubscriptions.find(server)->second.erase(service);
1391 return;
1392 }
1393 if (strings.size() > 1)
1394 {
1395 ostringstream err;
1396 err << "There was more than one string message in service " << I->getName() << " going to fatal error state";
1397 Error(err.str());
1398 }
1399 ostringstream msg;
1400 msg << I->getName() << ": " << strings[0];
1401
1402 if (fNightlyLogFile.is_open())
1403 {
1404 MessageImp(fNightlyLogFile).Write(cTime, msg.str().c_str(), fQuality);
1405 if (!CheckForOfstreamError(fNightlyLogFile, true))
1406 return;
1407 }
1408 if (targetRunFile && targetRunFile->is_open())
1409 {
1410 MessageImp(*targetRunFile).Write(cTime, msg.str().c_str(), fQuality);
1411 if (!CheckForOfstreamError(*targetRunFile, false))
1412 return;
1413 }
1414 }
1415
1416}
1417
1418// --------------------------------------------------------------------------
1419//
1420//! print the dataLogger's current state. invoked by the PRINT command
1421//! @param evt
1422//! the current event. Not used by the method
1423//! @returns
1424//! the new state. Which, in that case, is the current state
1425//!
1426int DataLogger::PrintStatePlease(const Event& )
1427{
1428 Message("------------------------------------------");
1429 Message("------- DATA LOGGER CURRENT STATE --------");
1430 Message("------------------------------------------");
1431
1432 //print the path configuration
1433 Message("File path: " + boost::filesystem::system_complete(boost::filesystem::path(fFilePath)).directory_string());
1434
1435 //print active run numbers
1436 ostringstream str;
1437 //timeout value
1438 str << "Timeout delay for old run numbers: " << fRunNumberTimeout << " ms";
1439 Message(str);
1440 str.str("");
1441 str << "Active Run Numbers:";
1442 for (list<RunNumberType>::const_iterator it=fRunNumber.begin(); it!=fRunNumber.end(); it++)
1443 str << " " << it->runNumber;
1444 if (fRunNumber.size()==0)
1445 str << " <none>";
1446 Message(str);
1447
1448 //print all the open files.
1449 Message("------------ OPEN FILES ----------------");
1450 if (fNightlyLogFile.is_open())
1451 Message("Nightly log-file: "+fFullNightlyLogFileName);
1452
1453 if (fNightlyReportFile.is_open())
1454 Message("Nightly report-file: "+fFullNightlyReportFileName);
1455
1456 for (list<RunNumberType>::const_iterator it=fRunNumber.begin(); it!=fRunNumber.end(); it++)
1457 {
1458#ifdef RUN_LOGS
1459 if (it->logFile->is_open())
1460 Message("Run log-file: " + it->logName);
1461#endif
1462 if (it->reportFile->is_open())
1463 Message("Run report-file: " + it->reportName);
1464 }
1465
1466 const DimWriteStatistics::Stats statVar = fFilesStats.GetTotalSizeWritten();
1467 // /*const bool statWarning =*/ calculateTotalSizeWritten(statVar, true);
1468#ifdef HAVE_FITS
1469 str.str("");
1470 str << "Number of open FITS files: " << fNumSubAndFitsData.numOpenFits;
1471 Message(str);
1472 // FIXME: Print list of open FITS files
1473#else
1474 Message("FITS output disabled at compilation");
1475#endif
1476 Message("----------------- STATS ------------------");
1477 if (fFilesStats.GetUpdateInterval()>0)
1478 {
1479 str.str("");
1480 str << "Statistics are updated every " << fFilesStats.GetUpdateInterval() << " ms";
1481 Message(str);
1482 }
1483 else
1484 Message("Statistics updates are currently disabled.");
1485 str.str("");
1486 str << "Total Size written: " << statVar.sizeWritten/1000 << " kB";
1487 Message(str);
1488 str.str("");
1489 str << "Disk free space: " << statVar.freeSpace/1000000 << " MB";
1490 Message(str);
1491
1492 Message("------------ DIM SUBSCRIPTIONS -----------");
1493 str.str("");
1494 str << "There are " << fNumSubAndFitsData.numSubscriptions << " active DIM subscriptions.";
1495 Message(str);
1496 for (map<const string, map<string, SubscriptionType> >::const_iterator it=fServiceSubscriptions.begin(); it!= fServiceSubscriptions.end();it++)
1497 {
1498 Message("Server "+it->first);
1499 for (map<string, SubscriptionType>::const_iterator it2=it->second.begin(); it2!=it->second.end(); it2++)
1500 Message(" -> "+it2->first);
1501 }
1502 Message("--------------- BLOCK LIST ---------------");
1503 for (set<string>::const_iterator it=fBlackList.begin(); it != fBlackList.end(); it++)
1504 Message(" -> "+*it);
1505 if (fBlackList.size()==0)
1506 Message(" <empty>");
1507
1508 Message("--------------- ALLOW LIST ---------------");
1509 for (set<string>::const_iterator it=fWhiteList.begin(); it != fWhiteList.end(); it++)
1510 Message(" -> "+*it);
1511 if (fWhiteList.size()==0)
1512 Message(" <empty>");
1513
1514 Message("-------------- GROUPING LIST -------------");
1515 Message("The following servers and/or services will");
1516 Message("be grouped into a single fits file:");
1517 for (set<string>::const_iterator it=fGrouping.begin(); it != fGrouping.end(); it++)
1518 Message(" -> "+*it);
1519 if (fGrouping.size()==0)
1520 Message(" <no grouping>");
1521
1522 Message("------------------------------------------");
1523 Message("-------- END OF DATA LOGGER STATE --------");
1524 Message("------------------------------------------");
1525
1526 return GetCurrentState();
1527}
1528
1529// --------------------------------------------------------------------------
1530//
1531//! turn debug mode on and off
1532//! @param evt
1533//! the current event. contains the instruction string: On, Off, on, off, ON, OFF, 0 or 1
1534//! @returns
1535//! the new state. Which, in that case, is the current state
1536//!
1537int DataLogger::SetDebugOnOff(const Event& evt)
1538{
1539 const bool backupDebug = fDebugIsOn;
1540
1541 fDebugIsOn = evt.GetBool();
1542
1543 if (fDebugIsOn == backupDebug)
1544 Message("Debug mode was already in the requested state.");
1545
1546 ostringstream str;
1547 str << "Debug mode is now " << fDebugIsOn;
1548 Message(str);
1549
1550 fFilesStats.SetDebugMode(fDebugIsOn);
1551
1552 return GetCurrentState();
1553}
1554// --------------------------------------------------------------------------
1555//
1556//! set the statistics update period duration. 0 disables the statistics
1557//! @param evt
1558//! the current event. contains the new duration.
1559//! @returns
1560//! the new state. Which, in that case, is the current state
1561//!
1562int DataLogger::SetStatsPeriod(const Event& evt)
1563{
1564 fFilesStats.SetUpdateInterval(evt.GetShort());
1565 return GetCurrentState();
1566}
1567// --------------------------------------------------------------------------
1568//
1569//! set the opened files service on or off.
1570//! @param evt
1571//! the current event. contains the instruction string. similar to setdebugonoff
1572//! @returns
1573//! the new state. Which, in that case, is the current state
1574//!
1575int DataLogger::SetOpenedFilesOnOff(const Event& evt)
1576{
1577 const bool backupOpened = fOpenedFilesIsOn;
1578
1579 fOpenedFilesIsOn = evt.GetBool();
1580
1581 if (fOpenedFilesIsOn == backupOpened)
1582 Message("Opened files service mode was already in the requested state.");
1583
1584 ostringstream str;
1585 str << "Opened files service mode is now " << fOpenedFilesIsOn;
1586 Message(str);
1587
1588 return GetCurrentState();
1589}
1590
1591// --------------------------------------------------------------------------
1592//
1593//! set the number of subscriptions and opened fits on and off
1594//! @param evt
1595//! the current event. contains the instruction string. similar to setdebugonoff
1596//! @returns
1597//! the new state. Which, in that case, is the current state
1598//!
1599int DataLogger::SetNumSubsAndFitsOnOff(const Event& evt)
1600{
1601 const bool backupSubs = fNumSubAndFitsIsOn;
1602
1603 fNumSubAndFitsIsOn = evt.GetBool();
1604
1605 if (fNumSubAndFitsIsOn == backupSubs)
1606 Message("Number of subscriptions service mode was already in the requested state");
1607
1608 ostringstream str;
1609 str << "Number of subscriptions service mode is now " << fNumSubAndFitsIsOn;
1610 Message(str);
1611
1612 return GetCurrentState();
1613}
1614// --------------------------------------------------------------------------
1615//
1616//! set the timeout delay for old run numbers
1617//! @param evt
1618//! the current event. contains the timeout delay long value
1619//! @returns
1620//! the new state. Which, in that case, is the current state
1621//!
1622int DataLogger::SetRunTimeoutDelay(const Event& evt)
1623{
1624 if (evt.GetUInt() == 0)
1625 {
1626 Error("Timeout delays for old run numbers must be greater than 0... ignored.");
1627 return GetCurrentState();
1628 }
1629
1630 if (fRunNumberTimeout == evt.GetUInt())
1631 Message("New timeout for old run numbers is same value as previous one.");
1632
1633 fRunNumberTimeout = evt.GetUInt();
1634
1635 ostringstream str;
1636 str << "Timeout delay for old run numbers is now " << fRunNumberTimeout << " ms";
1637 Message(str);
1638
1639 return GetCurrentState();
1640}
1641
1642// --------------------------------------------------------------------------
1643//
1644//! Sets the path to use for the Nightly log file.
1645//! @param evt
1646//! the event transporting the path
1647//! @returns
1648//! currently only the current state.
1649/*
1650int DataLogger::ConfigureFilePath(const Event& evt)
1651{
1652 if (!evt.GetText())
1653 {
1654 Error("Empty folder given. Please specify a valid path.");
1655 return GetCurrentState();
1656 }
1657
1658 const string givenPath = evt.GetText();
1659 if (!DoesPathExist(givenPath))
1660 {
1661 Error("Provided path '"+givenPath+"' is not a valid folder... ignored.");
1662 return GetCurrentState();
1663 }
1664
1665 Message("New folder: "+givenPath);
1666
1667 fFilePath = givenPath;
1668
1669 fFilesStats.SetCurrentFolder(givenPath);
1670
1671 return GetCurrentState();
1672}
1673*/
1674
1675// --------------------------------------------------------------------------
1676//
1677//! Notifies the DIM service that a particular file was opened
1678//! @ param name the base name of the opened file, i.e. without path nor extension.
1679//! WARNING: use string instead of string& because I pass values that do not convert to string&.
1680//! this is not a problem though because file are not opened so often.
1681//! @ param type the type of the opened file. 0 = none open, 1 = log, 2 = text, 4 = fits
1682inline void DataLogger::NotifyOpenedFile(const string &name, int type, DimDescribedService* service)
1683{
1684 if (!fOpenedFilesIsOn)
1685 return;
1686
1687 if (fDebugIsOn)
1688 {
1689 ostringstream str;
1690 str << "Updating " << service->getName() << " file '" << name << "' (type=" << type << ")";
1691 Debug(str);
1692
1693 str.str("");
1694 str << "Num subscriptions: " << fNumSubAndFitsData.numSubscriptions << " Num open FITS files: " << fNumSubAndFitsData.numOpenFits;
1695 Debug(str);
1696 }
1697
1698 if (name.size()+1 > FILENAME_MAX)
1699 {
1700 Error("Provided file name '" + name + "' is longer than allowed file name length.");
1701 return;
1702 }
1703
1704 OpenFileToDim fToDim;
1705 fToDim.code = type;
1706 memcpy(fToDim.fileName, name.c_str(), name.size()+1);
1707
1708 service->setData(reinterpret_cast<void*>(&fToDim), name.size()+1+sizeof(uint32_t));
1709 service->setQuality(0);
1710 service->Update();
1711}
1712// --------------------------------------------------------------------------
1713//
1714//! Implements the Start transition.
1715//! Concatenates the given path for the Nightly file and the filename itself (based on the day),
1716//! and tries to open it.
1717//! @returns
1718//! kSM_NightlyOpen if success, kSM_BadFolder if failure
1719int DataLogger::StartPlease()
1720{
1721 if (fDebugIsOn)
1722 {
1723 Debug("Starting...");
1724 }
1725 fFullNightlyLogFileName = CompileFileNameWithPath(fFilePath, "", "log");
1726 bool nightlyLogOpen = fNightlyLogFile.is_open();
1727 if (!OpenTextFilePlease(fNightlyLogFile, fFullNightlyLogFileName))
1728 return kSM_BadFolder;
1729 if (!nightlyLogOpen)
1730 fNightlyLogFile << endl;
1731
1732 fFullNightlyReportFileName = CompileFileNameWithPath(fFilePath, "", "rep");
1733 if (!OpenTextFilePlease(fNightlyReportFile, fFullNightlyReportFileName))
1734 {
1735 fNightlyLogFile.close();
1736 Info("Closed: "+fFullNightlyReportFileName);
1737 return kSM_BadFolder;
1738 }
1739
1740 fFilesStats.FileOpened(fFullNightlyLogFileName);
1741 fFilesStats.FileOpened(fFullNightlyReportFileName);
1742 //notify that a new file has been opened.
1743 const string baseFileName = CompileFileNameWithPath(fFilePath, "", "");
1744 NotifyOpenedFile(baseFileName, 3, fOpenedNightlyFiles);
1745
1746 fOpenedNightlyFits.clear();
1747
1748 return kSM_NightlyOpen;
1749}
1750
1751#ifdef HAVE_FITS
1752// --------------------------------------------------------------------------
1753//
1754//! open if required a the FITS files corresponding to a given subscription
1755//! @param sub
1756//! the current DimInfo subscription being examined
1757void DataLogger::OpenFITSFilesPlease(SubscriptionType& sub, RunNumberType* cRunNumber)
1758{
1759 string serviceName(sub.dimInfo->getName());
1760
1761 //if run number has changed, reopen a new fits file with the correct run number.
1762 if (sub.runFile.IsOpen() && sub.runFile.fRunNumber != sub.runNumber)
1763 {
1764 sub.runFile.Close();
1765 Info("Closed: "+sub.runFile.GetName()+" (new run number)");
1766 }
1767
1768 //we must check if we should group this service subscription to a single fits file before we replace the / by _
1769 bool hasGrouping = false;
1770 if (!sub.runFile.IsOpen() && ((GetCurrentState() == kSM_Logging) || (GetCurrentState() == kSM_WaitingRun)))
1771 {//will we find this service in the grouping list ?
1772 for (set<string>::const_iterator it=fGrouping.begin(); it!=fGrouping.end(); it++)
1773 {
1774 if (serviceName.find(*it) != string::npos)
1775 {
1776 hasGrouping = true;
1777 break;
1778 }
1779 }
1780 }
1781 for (unsigned int i=0;i<serviceName.size(); i++)
1782 {
1783 if (serviceName[i] == '/')
1784 {
1785 serviceName[i] = '_';
1786 break;
1787 }
1788 }
1789 //we open the NightlyFile anyway, otherwise this function shouldn't have been called.
1790 if (!sub.nightlyFile.IsOpen())
1791 {
1792 const string partialName = CompileFileNameWithPath(fFilePath, serviceName, "fits");
1793
1794 const string fileNameOnly = partialName.substr(partialName.find_last_of('/')+1, partialName.size());
1795 if (!sub.fitsBufferAllocated)
1796 AllocateFITSBuffers(sub);
1797 //get the size of the file we're about to open
1798 if (fFilesStats.FileOpened(partialName))
1799 fOpenedNightlyFits[fileNameOnly].push_back(serviceName);
1800
1801 if (!sub.nightlyFile.Open(partialName, serviceName, &fNumSubAndFitsData.numOpenFits, this, 0))
1802 {
1803 GoToRunWriteErrorState();
1804 return;
1805 }
1806
1807 ostringstream str;
1808 str << "Opened: " << partialName << " (Nfits=" << fNumSubAndFitsData.numOpenFits << ")";
1809 Info(str);
1810
1811 //notify the opening
1812 const string baseFileName = CompileFileNameWithPath(fFilePath, "", "");
1813 NotifyOpenedFile(baseFileName, 7, fOpenedNightlyFiles);
1814 if (fNumSubAndFitsIsOn)
1815 fNumSubAndFits->Update();
1816 }
1817 //do the actual file open
1818 if (!sub.runFile.IsOpen() && (GetCurrentState() == kSM_WaitingRun || GetCurrentState() == kSM_Logging) && sub.runNumber > 0)
1819 {//buffer for the run file have already been allocated when doing the Nightly file
1820
1821 const string partialName =
1822 CompileFileNameWithPath(fFilePath, hasGrouping ? "" : serviceName, "fits", sub.runNumber);
1823
1824 const string fileNameOnly =
1825 partialName.substr(partialName.find_last_of('/')+1, partialName.size());
1826
1827 //get the size of the file we're about to open
1828 if (fFilesStats.FileOpened(partialName))
1829 cRunNumber->openedFits[fileNameOnly].push_back(serviceName);
1830 else
1831 if (hasGrouping)
1832 {
1833 cRunNumber->addServiceToOpenedFits(fileNameOnly, serviceName);
1834 }
1835
1836 if (hasGrouping && (!cRunNumber->runFitsFile.get()))
1837 try
1838 {
1839 cRunNumber->runFitsFile = shared_ptr<CCfits::FITS>(new CCfits::FITS(partialName, CCfits::RWmode::Write));
1840 (fNumSubAndFitsData.numOpenFits)++;
1841 }
1842 catch (CCfits::FitsException e)
1843 {
1844 ostringstream str;
1845 str << "Open FITS file " << partialName << ": " << e.message();
1846 Error(str);
1847 cRunNumber->runFitsFile = shared_ptr<CCfits::FITS>();
1848 GoToRunWriteErrorState();
1849 return;
1850 }
1851
1852 const string baseFileName = CompileFileNameWithPath(fFilePath, "", "", sub.runNumber);
1853 NotifyOpenedFile(baseFileName, 7, fOpenedRunFiles);
1854
1855 if (hasGrouping)
1856 {
1857 if (!sub.runFile.Open(partialName, serviceName, &fNumSubAndFitsData.numOpenFits, this, sub.runNumber, cRunNumber->runFitsFile.get()))
1858 {
1859 GoToRunWriteErrorState();
1860 return;
1861 }
1862 }
1863 else
1864 {
1865 if (!sub.runFile.Open(partialName, serviceName, &fNumSubAndFitsData.numOpenFits, this, sub.runNumber))
1866 {
1867 GoToRunWriteErrorState();
1868 return;
1869 }
1870 }
1871
1872 ostringstream str;
1873 str << "Opened: " << partialName << " (Nfits=" << fNumSubAndFitsData.numOpenFits << ")";
1874 Info(str);
1875
1876 if (fNumSubAndFitsIsOn)
1877 fNumSubAndFits->Update();
1878 }
1879}
1880// --------------------------------------------------------------------------
1881//
1882//! Allocates the required memory for a given pair of fits files (nightly and run)
1883//! @param sub the subscription of interest.
1884//
1885void DataLogger::AllocateFITSBuffers(SubscriptionType& sub)
1886{
1887 //Init the time columns of the file
1888 Description dateDesc(string("Time"), string("Modified Julian Date"), string("MJD"));
1889 sub.nightlyFile.AddStandardColumn(dateDesc, "1D", &fMjD, sizeof(double));
1890 sub.runFile.AddStandardColumn(dateDesc, "1D", &fMjD, sizeof(double));
1891
1892 Description QoSDesc("QoS", "Quality of service", "");
1893 sub.nightlyFile.AddStandardColumn(QoSDesc, "1J", &fQuality, sizeof(int));
1894 sub.runFile.AddStandardColumn(QoSDesc, "1J", &fQuality, sizeof(int));
1895
1896 // Compilation failed
1897 if (!sub.fConv->valid())
1898 {
1899 Error("Compilation of format string failed.");
1900 return;
1901 }
1902
1903 //we've got a nice structure describing the format of this service's messages.
1904 //Let's create the appropriate FITS columns
1905 const vector<string> dataFormatsLocal = sub.fConv->GetFitsFormat();
1906
1907 sub.nightlyFile.InitDataColumns(GetDescription(sub.server, sub.service), dataFormatsLocal, sub.dimInfo->getData(), this);
1908 sub.runFile.InitDataColumns(GetDescription(sub.server, sub.service), dataFormatsLocal, sub.dimInfo->getData(), this);
1909 sub.fitsBufferAllocated = true;
1910}
1911// --------------------------------------------------------------------------
1912//
1913//! write a dimInfo data to its corresponding FITS files
1914//
1915void DataLogger::WriteToFITS(SubscriptionType& sub)
1916{
1917 //nightly File status (open or not) already checked
1918 if (sub.nightlyFile.IsOpen())
1919 {
1920 if (!sub.nightlyFile.Write(*sub.fConv.get()))
1921 {
1922 //sub.nightlyFile.Close();
1923 RemoveService(sub.server, sub.service, false);
1924 GoToNightlyWriteErrorState();
1925 return;
1926 }
1927 // sub.nightlyFile.Flush();
1928 }
1929
1930 if (sub.runFile.IsOpen())
1931 {
1932 if (!sub.runFile.Write(*sub.fConv.get()))
1933 {
1934 sub.runFile.Close();
1935 RemoveService(sub.server, sub.service, false);
1936 GoToRunWriteErrorState();
1937 return;
1938 }
1939 }
1940}
1941#endif //if has_fits
1942// --------------------------------------------------------------------------
1943//
1944//! Go to Run Write Error State
1945// A write error has occurred. Checks what is the current state and take appropriate action
1946void DataLogger::GoToRunWriteErrorState()
1947{
1948 if ((GetCurrentState() != kSM_RunWriteError) &&
1949 (GetCurrentState() != kSM_DailyWriteError))
1950 SetCurrentState(kSM_RunWriteError);
1951}
1952// --------------------------------------------------------------------------
1953//
1954//! Go to Nightly Write Error State
1955// A write error has occurred. Checks what is the current state and take appropriate action
1956void DataLogger::GoToNightlyWriteErrorState()
1957{
1958 if (GetCurrentState() != kSM_DailyWriteError)
1959 SetCurrentState(kSM_DailyWriteError);
1960}
1961
1962/*
1963// --------------------------------------------------------------------------
1964//
1965//! Implements the StartRun transition.
1966//! Concatenates the given path for the run file and the filename itself (based on the run number),
1967//! and tries to open it.
1968//! @returns
1969//! kSM_Logging if success, kSM_BadRunConfig if failure.
1970int DataLogger::StartRunPlease()
1971{
1972 if (fDebugIsOn)
1973 {
1974 Debug("Starting Run Logging...");
1975 }
1976 //open all the relevant run-files. i.e. all the files associated with run numbers.
1977 for (list<RunNumberType>::iterator it=fRunNumber.begin(); it != fRunNumber.end(); it++)
1978 if (OpenRunFile(*it) != 0)
1979 {
1980 StopRunPlease();
1981 return kSM_BadRunConfig;
1982 }
1983
1984 return kSM_Logging;
1985}
1986*/
1987#ifdef HAVE_FITS
1988// --------------------------------------------------------------------------
1989//
1990//! Create a fits group file with all the run-fits that were written (either daily or run)
1991//! @param filesToGroup a map of filenames mapping to table names to be grouped (i.e. a
1992//! single file can contain several tables to group
1993//! @param runNumber the run number that should be used for grouping. 0 means nightly group
1994//
1995void DataLogger::CreateFitsGrouping(map<string, vector<string> > & filesToGroup, int runNumber)
1996{
1997 if (fDebugIsOn)
1998 {
1999 ostringstream str;
2000 str << "Creating fits group for ";
2001 if (runNumber != 0)
2002 str << "run files";
2003 else
2004 str << "nightly files";
2005 Debug(str);
2006 }
2007 //create the FITS group corresponding to the ending run.
2008 CCfits::FITS* groupFile;
2009 unsigned int numFilesToGroup = 0;
2010 unsigned int maxCharLength = 0;
2011 for (map<string, vector<string> >::const_iterator it=filesToGroup.begin(); it != filesToGroup.end(); it++)
2012 {
2013 //add the number of tables in this file to the total number to group
2014 numFilesToGroup += it->second.size();
2015 //check the length of all the strings to be written, to determine the max string length to write
2016 if (it->first.size() > maxCharLength)
2017 maxCharLength = it->first.size();
2018 for (vector<string>::const_iterator jt=it->second.begin(); jt != it->second.end(); jt++)
2019 if (jt->size() > maxCharLength)
2020 maxCharLength = jt->size();
2021 }
2022
2023 if (fDebugIsOn)
2024 {
2025 ostringstream str;
2026 str << "There are " << numFilesToGroup << " tables to group";
2027 Debug(str);
2028 }
2029 if (numFilesToGroup <= 1)
2030 {
2031 filesToGroup.clear();
2032 return;
2033 }
2034 const string groupName = CompileFileNameWithPath(fFilePath, "", "fits", runNumber);
2035
2036 Info("Creating FITS group in: "+groupName);
2037
2038 CCfits::Table* groupTable;
2039// const int maxCharLength = FILENAME_MAX;
2040 try
2041 {
2042 groupFile = new CCfits::FITS(groupName, CCfits::RWmode::Write);
2043 //setup the column names
2044 ostringstream pathTypeName;
2045 pathTypeName << maxCharLength << "A";
2046 vector<string> names;
2047 vector<string> dataTypes;
2048 names.push_back("MEMBER_XTENSION");
2049 dataTypes.push_back("8A");
2050 names.push_back("MEMBER_URI_TYPE");
2051 dataTypes.push_back("3A");
2052 names.push_back("MEMBER_LOCATION");
2053 dataTypes.push_back(pathTypeName.str());
2054 names.push_back("MEMBER_NAME");
2055 dataTypes.push_back(pathTypeName.str());
2056
2057 groupTable = groupFile->addTable("GROUPING", numFilesToGroup, names, dataTypes);
2058//TODO handle the case when the logger was stopped and restarted during the same day, i.e. the grouping file must be updated
2059 }
2060 catch (CCfits::FitsException e)
2061 {
2062 ostringstream str;
2063 str << "Creating FITS table GROUPING in " << groupName << ": " << e.message();
2064 Error(str);
2065 return;
2066 }
2067
2068 //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.
2069 //use cfitsio routines instead
2070 groupTable->makeThisCurrent();
2071 //create appropriate buffer.
2072 const unsigned int n = 8 + 3 + 2*maxCharLength + 1; //+1 for trailling character
2073
2074 vector<unsigned char> realBuffer;
2075 realBuffer.resize(n);
2076 unsigned char* fitsBuffer = &realBuffer[0];
2077 memset(fitsBuffer, 0, n);
2078
2079 char* startOfExtension = reinterpret_cast<char*>(fitsBuffer);
2080 char* startOfURI = reinterpret_cast<char*>(&fitsBuffer[8]);
2081 char* startOfLocation = reinterpret_cast<char*>(&fitsBuffer[8 + 3]);
2082 char* startOfName = reinterpret_cast<char*>(&fitsBuffer[8+3+maxCharLength]);
2083
2084 strcpy(startOfExtension, "BINTABLE");
2085 strcpy(startOfURI, "URL");
2086
2087 int i=1;
2088 for (map<string, vector<string> >::const_iterator it=filesToGroup.begin(); it!=filesToGroup.end(); it++)
2089 for (vector<string>::const_iterator jt=it->second.begin(); jt != it->second.end(); jt++, i++)
2090 {
2091 strcpy(startOfLocation, it->first.c_str());
2092 strcpy(startOfName, jt->c_str());
2093
2094 if (fDebugIsOn)
2095 {
2096 ostringstream str;
2097 str << "Grouping " << it->first << " " << *jt;
2098 Debug(str);
2099 }
2100
2101 int status = 0;
2102 fits_write_tblbytes(groupFile->fitsPointer(), i, 1, 8+3+2*maxCharLength, fitsBuffer, &status);
2103 if (status)
2104 {
2105 char text[30];//max length of cfitsio error strings (from doc)
2106 fits_get_errstatus(status, text);
2107 ostringstream str;
2108 str << "Writing FITS row " << i << " in " << groupName << ": " << text << " (file_write_tblbytes, rc=" << status << ")";
2109 Error(str);
2110 GoToRunWriteErrorState();
2111 delete groupFile;
2112 return;
2113 }
2114 }
2115
2116 filesToGroup.clear();
2117 delete groupFile;
2118}
2119#endif //HAVE_FITS
2120
2121// --------------------------------------------------------------------------
2122//
2123//! Implements the StopRun transition.
2124//! Attempts to close the run file.
2125//! @returns
2126//! kSM_WaitingRun if success, kSM_FatalError otherwise
2127int DataLogger::StopRunLogging()
2128{
2129
2130 if (fDebugIsOn)
2131 {
2132 Debug("Stopping Run Logging...");
2133 }
2134 //it may be that dim tries to write a dimInfo at the same time as we're closing files. Prevent this
2135
2136// dim_lock();
2137 for (list<RunNumberType>::const_iterator it=fRunNumber.begin(); it != fRunNumber.end(); it++)
2138 {
2139#ifdef RUN_LOGS
2140 if (!it->logFile->is_open() || !it->reportFile->is_open())
2141#else
2142 if (!it->reportFile->is_open())
2143#endif
2144 return kSM_FatalError;
2145#ifdef RUN_LOGS
2146 it->logFile->close();
2147 Info("Closed: "+it->logName);
2148
2149#endif
2150 it->reportFile->close();
2151 Info("Closed: "+it->reportName);
2152 }
2153
2154#ifdef HAVE_FITS
2155 for (SubscriptionsListType::iterator i = fServiceSubscriptions.begin(); i != fServiceSubscriptions.end(); i++)
2156 for (map<string, SubscriptionType>::iterator j = i->second.begin(); j != i->second.end(); j++)
2157 {
2158 if (j->second.runFile.IsOpen())
2159 j->second.runFile.Close();
2160 }
2161#endif
2162 NotifyOpenedFile("", 0, fOpenedRunFiles);
2163 if (fNumSubAndFitsIsOn)
2164 fNumSubAndFits->Update();
2165
2166 while (fRunNumber.size() > 0)
2167 {
2168 RemoveOldestRunNumber();
2169 }
2170// dim_unlock();
2171 return kSM_WaitingRun;
2172}
2173// --------------------------------------------------------------------------
2174//
2175//! Implements the Stop and Reset transitions.
2176//! Attempts to close any openned file.
2177//! @returns
2178//! kSM_Ready
2179int DataLogger::GoToReadyPlease()
2180{
2181 if (fDebugIsOn)
2182 {
2183 Debug("Going to the Ready state...");
2184 }
2185 if (GetCurrentState() == kSM_Logging || GetCurrentState() == kSM_WaitingRun)
2186 StopRunLogging();
2187
2188 //it may be that dim tries to write a dimInfo while we're closing files. Prevent that
2189 const string baseFileName = CompileFileNameWithPath(fFilePath, "", "");
2190
2191 if (fNightlyReportFile.is_open())
2192 {
2193 fNightlyReportFile.close();
2194 Info("Closed: "+baseFileName+".rep");
2195 }
2196#ifdef HAVE_FITS
2197 for (SubscriptionsListType::iterator i = fServiceSubscriptions.begin(); i != fServiceSubscriptions.end(); i++)
2198 for (map<string, SubscriptionType>::iterator j = i->second.begin(); j != i->second.end(); j++)
2199 {
2200 if (j->second.nightlyFile.IsOpen())
2201 j->second.nightlyFile.Close();
2202 }
2203#endif
2204 if (GetCurrentState() == kSM_Logging ||
2205 GetCurrentState() == kSM_WaitingRun ||
2206 GetCurrentState() == kSM_NightlyOpen)
2207 {
2208 NotifyOpenedFile("", 0, fOpenedNightlyFiles);
2209 if (fNumSubAndFitsIsOn)
2210 fNumSubAndFits->Update();
2211 }
2212#ifdef HAVE_FITS
2213 CreateFitsGrouping(fOpenedNightlyFits, 0);
2214#endif
2215 return kSM_Ready;
2216}
2217
2218// --------------------------------------------------------------------------
2219//
2220//! Implements the transition towards kSM_WaitingRun
2221//! If current state is kSM_Ready, then tries to go to nightlyOpen state first.
2222//! @returns
2223//! kSM_WaitingRun or kSM_BadFolder
2224int DataLogger::NightlyToWaitRunPlease()
2225{
2226 int cState = GetCurrentState();
2227
2228// if (cState == kSM_Logging)
2229// cState = kSM_NightlyOpen;
2230
2231 if (cState == kSM_Ready)
2232 cState = StartPlease();
2233
2234 if (cState != kSM_NightlyOpen)
2235 return GetCurrentState();
2236
2237 if (fDebugIsOn)
2238 {
2239 Debug("Going to Wait Run Number state...");
2240 }
2241 return kSM_WaitingRun;
2242}
2243// --------------------------------------------------------------------------
2244//
2245//! Implements the transition from wait for run number to nightly open
2246//! Does nothing really.
2247//! @returns
2248//! kSM_WaitingRun
2249int DataLogger::BackToNightlyOpenPlease()
2250{
2251 if (GetCurrentState()==kSM_Logging)
2252 StopRunLogging();
2253
2254 if (fDebugIsOn)
2255 {
2256 Debug("Going to NightlyOpen state...");
2257 }
2258 return kSM_NightlyOpen;
2259}
2260// --------------------------------------------------------------------------
2261//
2262//! Setup Logger's configuration from a Configuration object
2263//! @param conf the configuration object that should be used
2264//!
2265int DataLogger::EvalOptions(Configuration& conf)
2266{
2267 fDebugIsOn = conf.Get<bool>("debug");
2268 fFilesStats.SetDebugMode(fDebugIsOn);
2269
2270 //Set the block or allow list
2271 fBlackList.clear();
2272 fWhiteList.clear();
2273
2274 //Adding entries that should ALWAYS be ignored
2275 fBlackList.insert("DATA_LOGGER/MESSAGE");
2276 fBlackList.insert("/SERVICE_LIST");
2277 fBlackList.insert("DIS_DNS/");
2278
2279 //set the black list, white list and the goruping
2280 const vector<string> vec1 = conf.Vec<string>("block");
2281 const vector<string> vec2 = conf.Vec<string>("allow");
2282 const vector<string> vec3 = conf.Vec<string>("group");
2283
2284 fBlackList.insert(vec1.begin(), vec1.end());
2285 fWhiteList.insert(vec2.begin(), vec2.end());
2286 fGrouping.insert( vec3.begin(), vec3.end());
2287
2288 //set the old run numbers timeout delay
2289 if (conf.Has("run-timeout"))
2290 {
2291 const uint32_t timeout = conf.Get<uint32_t>("run-timeout");
2292 if (timeout == 0)
2293 {
2294 Error("Time out delay for old run numbers must not be 0.");
2295 return 1;
2296 }
2297 fRunNumberTimeout = timeout;
2298 }
2299
2300 //configure the run files directory
2301 if (conf.Has("destination-folder"))
2302 {
2303 const string folder = conf.Get<string>("destination-folder");
2304 if (!fFilesStats.SetCurrentFolder(folder))
2305 return 2;
2306
2307 fFilePath = folder;
2308 fFullNightlyLogFileName = CompileFileNameWithPath(fFilePath, "", "log");
2309 if (!OpenTextFilePlease(fNightlyLogFile, fFullNightlyLogFileName))
2310 return 3;
2311
2312 fNightlyLogFile << endl;
2313 NotifyOpenedFile(fFullNightlyLogFileName, 1, fOpenedNightlyFiles);
2314 for (vector<string>::iterator it=backLogBuffer.begin();it!=backLogBuffer.end();it++)
2315 fNightlyLogFile << *it;
2316 }
2317
2318 shouldBackLog = false;
2319 backLogBuffer.clear();
2320
2321 //configure the interval between statistics updates
2322 if (conf.Has("stats-interval"))
2323 fFilesStats.SetUpdateInterval(conf.Get<int16_t>("stats-interval"));
2324
2325 //configure if the filenames service is on or off
2326 fOpenedFilesIsOn = !conf.Get<bool>("no-filename-service");
2327
2328 //configure if the number of subscriptions and fits files is on or off.
2329 fNumSubAndFitsIsOn = !conf.Get<bool>("no-numsubs-service");
2330 //should we open the daily files at startup ?
2331 if (conf.Has("start-daily-files"))
2332 if (conf.Get<bool>("start-daily-files"))
2333 {
2334 fShouldAutoStart = true;
2335 }
2336 return -1;
2337}
2338
2339
2340#include "Main.h"
2341
2342// --------------------------------------------------------------------------
2343template<class T>
2344int RunShell(Configuration &conf)
2345{
2346 return Main::execute<T, DataLogger>(conf, true);
2347}
2348
2349/*
2350 Extract usage clause(s) [if any] for SYNOPSIS.
2351 Translators: "Usage" and "or" here are patterns (regular expressions) which
2352 are used to match the usage synopsis in program output. An example from cp
2353 (GNU coreutils) which contains both strings:
2354 Usage: cp [OPTION]... [-T] SOURCE DEST
2355 or: cp [OPTION]... SOURCE... DIRECTORY
2356 or: cp [OPTION]... -t DIRECTORY SOURCE...
2357 */
2358void PrintUsage()
2359{
2360 cout << "\n"
2361 "The data logger connects to all available Dim services and "
2362 "writes them to ascii and fits files.\n"
2363 "\n"
2364 "The default is that the program is started without user interaction. "
2365 "All actions are supposed to arrive as DimCommands. Using the -c "
2366 "option, a local shell can be initialized. With h or help a short "
2367 "help message about the usage can be brought to the screen.\n"
2368 "\n"
2369 "Usage: datalogger [-c type] [OPTIONS]\n"
2370 " or: datalogger [OPTIONS]\n";
2371 cout << endl;
2372
2373}
2374// --------------------------------------------------------------------------
2375void PrintHelp()
2376{
2377 /* Additional help text which is printed after the configuration
2378 options goes here */
2379 cout <<
2380 "\n"
2381 "If the allow list has any element, only the servers and/or services "
2382 "specified in the list will be used for subscription. The black list "
2383 "will disable service subscription and has higher priority than the "
2384 "allow list. If the allow list is not present by default all services "
2385 "will be subscribed."
2386 "\n"
2387 "For example, block=DIS_DNS/ will skip all the services offered by "
2388 "the DIS_DNS server, while block=/SERVICE_LIST will skip all the "
2389 "SERVICE_LIST services offered by any server and DIS_DNS/SERVICE_LIST "
2390 "will skip DIS_DNS/SERVICE_LIST.\n"
2391 << endl;
2392
2393 Main::PrintHelp<DataLogger>();
2394}
2395
2396// --------------------------------------------------------------------------
2397void SetupConfiguration(Configuration &conf)
2398{
2399 po::options_description configs("DataLogger options");
2400 configs.add_options()
2401 ("block,b", vars<string>(), "Black-list to block services")
2402 ("allow,a", vars<string>(), "White-list to only allowe certain services")
2403 ("debug,d", po_bool(), "Debug mode. Print clear text of received service reports.")
2404 ("group,g", vars<string>(), "Grouping of services into a single run-Fits")
2405 ("run-timeout", var<uint32_t>(), "Time out delay for old run numbers in milliseconds.")
2406 ("destination-folder", var<string>(), "Base path for the nightly and run files")
2407 ("stats-interval", var<int16_t>(), "Interval in milliseconds for write statistics update")
2408 ("no-filename-service", po_bool(), "Disable update of filename service")
2409 ("no-numsubs-service", po_bool(), "Disable update of number-of-subscriptions service")
2410 ("start-daily-files", po_bool(), "Starts the logger in DailyFileOpen instead of Ready")
2411 ;
2412
2413 conf.AddOptions(configs);
2414}
2415// --------------------------------------------------------------------------
2416int main(int argc, const char* argv[])
2417{
2418 Configuration conf(argv[0]);
2419 conf.SetPrintUsage(PrintUsage);
2420 Main::SetupConfiguration(conf);
2421 SetupConfiguration(conf);
2422
2423 if (!conf.DoParse(argc, argv, PrintHelp))
2424 return -1;
2425
2426// try
2427 {
2428 // No console access at all
2429 if (!conf.Has("console"))
2430 return RunShell<LocalStream>(conf);
2431
2432 // Console access w/ and w/o Dim
2433 if (conf.Get<int>("console")==0)
2434 return RunShell<LocalShell>(conf);
2435 else
2436 return RunShell<LocalConsole>(conf);
2437 }
2438/* catch (exception& e)
2439 {
2440 cerr << "Exception: " << e.what() << endl;
2441 return -1;
2442 }*/
2443
2444 return 0;
2445}
Note: See TracBrowser for help on using the repository browser.