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

Last change on this file since 11837 was 11837, checked in by tbretz, 13 years ago
Changed Dim services to ensure they send the time.
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->Update();
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->Update();
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->Update();
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->Update();
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->Update();
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.