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

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