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

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