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

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