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

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