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

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