| 1 | //************************************************************************************* | 
|---|
| 2 | /** @class DimWriteStatistics | 
|---|
| 3 |  | 
|---|
| 4 | @brief provides a statistics service telling the free space on disk and the total size written so far | 
|---|
| 5 |  | 
|---|
| 6 | */ | 
|---|
| 7 | //************************************************************************************* | 
|---|
| 8 | #include "DimWriteStatistics.h" | 
|---|
| 9 |  | 
|---|
| 10 | #include <sys/statvfs.h> //for getting disk free space | 
|---|
| 11 | #include <sys/stat.h>    //for getting files sizes | 
|---|
| 12 |  | 
|---|
| 13 | #include <boost/filesystem.hpp> | 
|---|
| 14 |  | 
|---|
| 15 | #include "Time.h" | 
|---|
| 16 |  | 
|---|
| 17 | using namespace std; | 
|---|
| 18 | using namespace boost::posix_time; | 
|---|
| 19 |  | 
|---|
| 20 | // -------------------------------------------------------------------------- | 
|---|
| 21 | // | 
|---|
| 22 | //! Constructor with correct service name. The state machine using this object should give it | 
|---|
| 23 | //! its own name as a parameter | 
|---|
| 24 | //! @param serverName the name of the server which created this object | 
|---|
| 25 | // | 
|---|
| 26 | DimWriteStatistics::DimWriteStatistics(const string& server, MessageImp &log) : | 
|---|
| 27 | fLog(log), | 
|---|
| 28 | fDimService(server + "/STATS",  "X:4", "Statistics about size written"), | 
|---|
| 29 | fCurrentFolder("."), | 
|---|
| 30 | fUpdateInterval(1000), | 
|---|
| 31 | fBaseSize(0), | 
|---|
| 32 | fDebug(false) | 
|---|
| 33 | { | 
|---|
| 34 | fThread = boost::thread(boost::bind(&DimWriteStatistics::UpdateService, this)); | 
|---|
| 35 | } | 
|---|
| 36 |  | 
|---|
| 37 | // -------------------------------------------------------------------------- | 
|---|
| 38 | // | 
|---|
| 39 | //! Destructor. Stop thread by setting fUpdateInterval to 0 and join the | 
|---|
| 40 | //! thread. | 
|---|
| 41 | // | 
|---|
| 42 | DimWriteStatistics::~DimWriteStatistics() | 
|---|
| 43 | { | 
|---|
| 44 | fUpdateInterval = 0; | 
|---|
| 45 |  | 
|---|
| 46 | // This blocks for fPeriod duration, but maybe canceling the thread | 
|---|
| 47 | // could be more dangerous leaving Dim in an undefined state. | 
|---|
| 48 | fThread.interrupt(); | 
|---|
| 49 | } | 
|---|
| 50 |  | 
|---|
| 51 | int DimWriteStatistics::Write(const Time &t, const string &txt, int qos) | 
|---|
| 52 | { | 
|---|
| 53 | return fLog.Write(t, txt, qos); | 
|---|
| 54 | } | 
|---|
| 55 |  | 
|---|
| 56 | // -------------------------------------------------------------------------- | 
|---|
| 57 | // | 
|---|
| 58 | //! Retrieves the free space of the current base path | 
|---|
| 59 | //! @return the available free space on disk, in bytes | 
|---|
| 60 | // | 
|---|
| 61 | int64_t DimWriteStatistics::GetFreeSpace() | 
|---|
| 62 | { | 
|---|
| 63 | struct statvfs vfs; | 
|---|
| 64 | if (statvfs(fCurrentFolder.c_str(), &vfs)) | 
|---|
| 65 | return -1; | 
|---|
| 66 |  | 
|---|
| 67 | return vfs.f_bsize*vfs.f_bavail; | 
|---|
| 68 | } | 
|---|
| 69 |  | 
|---|
| 70 | // -------------------------------------------------------------------------- | 
|---|
| 71 | // | 
|---|
| 72 | //! Retrieves the size on disk of a given file, in bytes | 
|---|
| 73 | //! @param file the filename for which the size should be retrieved | 
|---|
| 74 | //! @return the size of the file, in bytes | 
|---|
| 75 | // | 
|---|
| 76 | int64_t DimWriteStatistics::GetFileSizeOnDisk(const string& file, MessageImp &log) | 
|---|
| 77 | { | 
|---|
| 78 | errno = 0; | 
|---|
| 79 | struct stat st; | 
|---|
| 80 | if (!stat(file.c_str(), &st)) | 
|---|
| 81 | return st.st_size; | 
|---|
| 82 |  | 
|---|
| 83 | //ignoring error #2: no such file or directory is not an error for new files | 
|---|
| 84 | if (errno == 0 || errno == 2) | 
|---|
| 85 | return 0; | 
|---|
| 86 |  | 
|---|
| 87 | ostringstream str; | 
|---|
| 88 | str << "stat() failed for '" << file << "': " << strerror(errno) << " [errno=" << errno << "]"; | 
|---|
| 89 | log.Error(str); | 
|---|
| 90 |  | 
|---|
| 91 | return -1; | 
|---|
| 92 | } | 
|---|
| 93 |  | 
|---|
| 94 | // -------------------------------------------------------------------------- | 
|---|
| 95 | // | 
|---|
| 96 | //! Check if a given path exists | 
|---|
| 97 | //! @param path the path to be checked | 
|---|
| 98 | //! @return whether or not the given path exists | 
|---|
| 99 | // | 
|---|
| 100 | bool DimWriteStatistics::DoesPathExist(string path, MessageImp &log) | 
|---|
| 101 | { | 
|---|
| 102 | namespace fs = boost::filesystem; | 
|---|
| 103 |  | 
|---|
| 104 | if (path.empty()) | 
|---|
| 105 | path = "."; | 
|---|
| 106 |  | 
|---|
| 107 | const fs::path fullPath = fs::system_complete(fs::path(path)); | 
|---|
| 108 |  | 
|---|
| 109 | if (!fs::exists(fullPath)) | 
|---|
| 110 | return false; | 
|---|
| 111 |  | 
|---|
| 112 | if (!fs::is_directory(fullPath)) | 
|---|
| 113 | { | 
|---|
| 114 | log.Error("Path given for checking '" + path + "' designate a file name. Please provide a path name only"); | 
|---|
| 115 | return false; | 
|---|
| 116 | } | 
|---|
| 117 |  | 
|---|
| 118 | if (access(path.c_str(), R_OK|W_OK|X_OK) != 0) | 
|---|
| 119 | { | 
|---|
| 120 | log.Error("Missing read, write or execute permissions on directory '" + path + "'"); | 
|---|
| 121 | return false; | 
|---|
| 122 | } | 
|---|
| 123 |  | 
|---|
| 124 | return true; | 
|---|
| 125 | } | 
|---|
| 126 |  | 
|---|
| 127 | // -------------------------------------------------------------------------- | 
|---|
| 128 | // | 
|---|
| 129 | //! Check if a given path exists | 
|---|
| 130 | //! @param path the path to be checked | 
|---|
| 131 | //! @return whether or not the creation has been successfull | 
|---|
| 132 | // | 
|---|
| 133 | void DimWriteStatistics::CreateDirectory(string path) | 
|---|
| 134 | { | 
|---|
| 135 | namespace fs = boost::filesystem; | 
|---|
| 136 |  | 
|---|
| 137 | //remove last '/', if present | 
|---|
| 138 | if (path[path.size()-1] == '/') | 
|---|
| 139 | path = path.substr(0, path.size()-1); | 
|---|
| 140 |  | 
|---|
| 141 | //create boost path | 
|---|
| 142 | const fs::path fullPath = fs::system_complete(fs::path(path)); | 
|---|
| 143 |  | 
|---|
| 144 | //if path does not exist, check if upper levels exist already | 
|---|
| 145 | if (fs::exists(fullPath)) | 
|---|
| 146 | { | 
|---|
| 147 | //if path already exist, make sure it does not designate a file (filenames cannot be checked if they do not exist) | 
|---|
| 148 | if (fs::is_directory(fullPath)) | 
|---|
| 149 | return; | 
|---|
| 150 |  | 
|---|
| 151 | throw runtime_error("Path to be created is a file  '" + path + "'"); | 
|---|
| 152 | } | 
|---|
| 153 |  | 
|---|
| 154 | // we're hitting "/", which SHOULD have existed... | 
|---|
| 155 | if (path.size() <= 1) | 
|---|
| 156 | throw runtime_error("Path to be created looks suspicious '"+path+"'"); | 
|---|
| 157 |  | 
|---|
| 158 | if (path.find_last_of('/')!=string::npos) | 
|---|
| 159 | CreateDirectory(path.substr(0, path.find_last_of('/'))); | 
|---|
| 160 |  | 
|---|
| 161 | //path does not exist, and upper level have been created already by recusrion. | 
|---|
| 162 | const mode_t rightsMask = S_IRWXU | S_IXGRP | S_IRGRP | S_IXOTH | S_IROTH; //everybody read, owner writes | 
|---|
| 163 |  | 
|---|
| 164 | if (mkdir(path.c_str(), rightsMask)==0) | 
|---|
| 165 | return; | 
|---|
| 166 |  | 
|---|
| 167 | ostringstream str; | 
|---|
| 168 | str << "mkdir() failed for '" << path << "': " << strerror(errno) << " [errno=" << errno << "]"; | 
|---|
| 169 | throw runtime_error(str.str()); | 
|---|
| 170 | } | 
|---|
| 171 |  | 
|---|
| 172 | // -------------------------------------------------------------------------- | 
|---|
| 173 | // | 
|---|
| 174 | //! Sets the current folder | 
|---|
| 175 | //! @param folder the path to the folder | 
|---|
| 176 | // | 
|---|
| 177 | bool DimWriteStatistics::SetCurrentFolder(const string& folder) | 
|---|
| 178 | { | 
|---|
| 179 | struct statvfs vfs; | 
|---|
| 180 | if (statvfs(folder.empty()?".":folder.c_str(), &vfs)) | 
|---|
| 181 | { | 
|---|
| 182 | fLog.Error("statvfs() failed for '"+folder+"'... ignoring it."); | 
|---|
| 183 | return false; | 
|---|
| 184 | } | 
|---|
| 185 |  | 
|---|
| 186 | fCurrentFolder = folder.empty()?".":folder; | 
|---|
| 187 | return true; | 
|---|
| 188 | } | 
|---|
| 189 |  | 
|---|
| 190 | // -------------------------------------------------------------------------- | 
|---|
| 191 | // | 
|---|
| 192 | //! Updates the service. This is the function executed by the thread | 
|---|
| 193 | // | 
|---|
| 194 | void DimWriteStatistics::UpdateService() | 
|---|
| 195 | { | 
|---|
| 196 | Time previousTime; | 
|---|
| 197 | uint64_t previousSize = 0; | 
|---|
| 198 |  | 
|---|
| 199 | while (1) | 
|---|
| 200 | { | 
|---|
| 201 | if (fUpdateInterval==0) | 
|---|
| 202 | { | 
|---|
| 203 | boost::this_thread::interruption_point(); | 
|---|
| 204 | boost::this_thread::yield(); | 
|---|
| 205 | continue; | 
|---|
| 206 | } | 
|---|
| 207 |  | 
|---|
| 208 | Stats data; | 
|---|
| 209 |  | 
|---|
| 210 | for (set<string>::const_iterator it = fOpenedFiles.begin(); it != fOpenedFiles.end(); it++) | 
|---|
| 211 | data.sizeWritten += GetFileSizeOnDisk(*it); | 
|---|
| 212 | data.sizeWritten -= fBaseSize; | 
|---|
| 213 |  | 
|---|
| 214 | const Time cTime = Time(); | 
|---|
| 215 |  | 
|---|
| 216 | data.freeSpace   = GetFreeSpace(); | 
|---|
| 217 | data.rateWritten = data.sizeWritten-previousSize; | 
|---|
| 218 | data.timeElapsed = (cTime - previousTime).total_milliseconds(); | 
|---|
| 219 |  | 
|---|
| 220 | previousSize = data.sizeWritten; | 
|---|
| 221 | previousTime = cTime; | 
|---|
| 222 |  | 
|---|
| 223 | fDimService.Update(data); | 
|---|
| 224 |  | 
|---|
| 225 | fStats = data; | 
|---|
| 226 |  | 
|---|
| 227 | if (fDebug) | 
|---|
| 228 | { | 
|---|
| 229 | ostringstream str; | 
|---|
| 230 | str << "Written: " << fStats.sizeWritten/1000 << " kB; writing rate: "; | 
|---|
| 231 | str << fStats.rateWritten/fStats.timeElapsed << " kB/s; free space: "; | 
|---|
| 232 | str << fStats.freeSpace/1000000 << " MB"; | 
|---|
| 233 | fLog.Debug(str); | 
|---|
| 234 | } | 
|---|
| 235 |  | 
|---|
| 236 | boost::this_thread::sleep(milliseconds(fUpdateInterval)); | 
|---|
| 237 | } | 
|---|
| 238 | } | 
|---|
| 239 | // -------------------------------------------------------------------------- | 
|---|
| 240 | // | 
|---|
| 241 | //! Let the object know that a new file has been opened | 
|---|
| 242 | //! @param fileName the full name of the file newly opened | 
|---|
| 243 | //! @return whether this file could be stated or not | 
|---|
| 244 | // | 
|---|
| 245 | bool DimWriteStatistics::FileOpened(const string& fileName) | 
|---|
| 246 | { | 
|---|
| 247 | if (fOpenedFiles.find(fileName) != fOpenedFiles.end()) | 
|---|
| 248 | return false; | 
|---|
| 249 |  | 
|---|
| 250 | //Add a newly opened file, and remember its original size | 
|---|
| 251 | const int64_t newSize = GetFileSizeOnDisk(fileName); | 
|---|
| 252 | if (newSize == -1) | 
|---|
| 253 | return false; | 
|---|
| 254 |  | 
|---|
| 255 | fBaseSize += newSize; | 
|---|
| 256 | fOpenedFiles.insert(fileName); | 
|---|
| 257 |  | 
|---|
| 258 | return true; | 
|---|
| 259 | } | 
|---|
| 260 | // -------------------------------------------------------------------------- | 
|---|
| 261 | // | 
|---|
| 262 | //! Set the debug mode on and off | 
|---|
| 263 | //! @param debug the new mode (true or false) | 
|---|
| 264 | // | 
|---|
| 265 | void DimWriteStatistics::SetDebugMode(bool debug) | 
|---|
| 266 | { | 
|---|
| 267 | fDebug = debug; | 
|---|
| 268 |  | 
|---|
| 269 | if (fDebug) | 
|---|
| 270 | fLog.Debug("Debug mode is now on."); | 
|---|
| 271 | } | 
|---|
| 272 | // -------------------------------------------------------------------------- | 
|---|
| 273 | // | 
|---|
| 274 | //! Set the update of the service interval | 
|---|
| 275 | //! @param duration the duration between two services update, in second | 
|---|
| 276 | // | 
|---|
| 277 | void DimWriteStatistics::SetUpdateInterval(const int16_t duration) | 
|---|
| 278 | { | 
|---|
| 279 | if (!finite(duration)) | 
|---|
| 280 | { | 
|---|
| 281 | fLog.Error("Provided update interval is not a valid float... discarding."); | 
|---|
| 282 | return; | 
|---|
| 283 | } | 
|---|
| 284 | if (uint16_t(duration) == fUpdateInterval) | 
|---|
| 285 | { | 
|---|
| 286 | fLog.Warn("Statistics update interval not modified. Supplied value already in use."); | 
|---|
| 287 | return; | 
|---|
| 288 | } | 
|---|
| 289 |  | 
|---|
| 290 | if (duration <= 0) | 
|---|
| 291 | fLog.Message("Statistics are now OFF."); | 
|---|
| 292 | else | 
|---|
| 293 | { | 
|---|
| 294 | ostringstream str; | 
|---|
| 295 | str << "Statistics update interval is now " << duration << " seconds"; | 
|---|
| 296 | fLog.Message(str); | 
|---|
| 297 | } | 
|---|
| 298 |  | 
|---|
| 299 | fUpdateInterval = duration<0 ? 0 : duration; | 
|---|
| 300 | } | 
|---|