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

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