source: trunk/FACT++/src/dataLogger.cc@ 11024

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