Skip to content

Commit 3a412b6

Browse files
NathanielVolfangojenkins
authored andcommitted
QPR-10445 -- Add EventMessage and FileIO wrapper class that adds a retry logic to some commen file IO operations
1 parent c53ca4d commit 3a412b6

9 files changed

Lines changed: 239 additions & 8 deletions

File tree

OREAnalytics/orea/app/inputparameters.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ class InputParameters {
267267
const boost::shared_ptr<ore::data::EngineData>& pricingEngine() { return pricingEngine_; }
268268
const boost::shared_ptr<ore::data::TodaysMarketParameters>& todaysMarketParams() { return todaysMarketParams_; }
269269
const boost::shared_ptr<ore::data::Portfolio>& portfolio() { return portfolio_; }
270+
271+
QuantLib::Size maxRetries() const { return maxRetries_; }
270272
QuantLib::Size nThreads() const { return nThreads_; }
271273
bool entireMarket() { return entireMarket_; }
272274
bool allFixings() { return allFixings_; }
@@ -471,7 +473,9 @@ class InputParameters {
471473
boost::shared_ptr<ore::data::EngineData> pricingEngine_;
472474
boost::shared_ptr<ore::data::TodaysMarketParameters> todaysMarketParams_;
473475
boost::shared_ptr<ore::data::Portfolio> portfolio_;
474-
QuantLib::Size nThreads_ = 1;
476+
QuantLib::Size maxRetries_ = 7;
477+
QuantLib::Size nThreads_ = 1;
478+
475479
bool entireMarket_ = false;
476480
bool allFixings_ = false;
477481
bool eomInflationFixings_ = true;

OREData/ored/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ utilities/currencyconfig.cpp
259259
utilities/currencyhedgedequityindexdecomposition.cpp
260260
utilities/currencyparser.cpp
261261
utilities/dategrid.cpp
262+
utilities/fileio.cpp
262263
utilities/filteredbufferedlogger.cpp
263264
utilities/flowanalysis.cpp
264265
utilities/indexnametranslator.cpp
@@ -562,6 +563,7 @@ utilities/currencyconfig.hpp
562563
utilities/currencyhedgedequityindexdecomposition.hpp
563564
utilities/currencyparser.hpp
564565
utilities/dategrid.hpp
566+
utilities/fileio.hpp
565567
utilities/filteredbufferedlogger.hpp
566568
utilities/flowanalysis.hpp
567569
utilities/indexnametranslator.hpp

OREData/ored/ored.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@
289289
#include <ored/utilities/currencyhedgedequityindexdecomposition.hpp>
290290
#include <ored/utilities/currencyparser.hpp>
291291
#include <ored/utilities/dategrid.hpp>
292+
#include <ored/utilities/fileio.hpp>
292293
#include <ored/utilities/filteredbufferedlogger.hpp>
293294
#include <ored/utilities/flowanalysis.hpp>
294295
#include <ored/utilities/indexnametranslator.hpp>

OREData/ored/report/csvreport.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <boost/filesystem/path.hpp>
2121
#include <boost/filesystem/operations.hpp>
2222
#include <ored/report/csvreport.hpp>
23+
#include <ored/utilities/fileio.hpp>
2324
#include <ored/utilities/to_string.hpp>
2425
#include <ql/errors.hpp>
2526
#include <ql/math/comparison.hpp>
@@ -85,7 +86,8 @@ class ReportTypePrinter : public boost::static_visitor<> {
8586
};
8687

8788
CSVFileReport::CSVFileReport(const string& filename, const char sep, const bool commentCharacter, char quoteChar,
88-
const string& nullString, bool lowerHeader, QuantLib::Size rolloverSize)
89+
const string& nullString, bool lowerHeader,
90+
QuantLib::Size rolloverSize)
8991
: filename_(filename), sep_(sep), commentCharacter_(commentCharacter), quoteChar_(quoteChar),
9092
nullString_(nullString), lowerHeader_(lowerHeader), rolloverSize_(rolloverSize), i_(0), fp_(NULL) {
9193
baseFilename_ = filename_;
@@ -101,7 +103,7 @@ CSVFileReport::~CSVFileReport() {
101103

102104
void CSVFileReport::open() {
103105
LOG("Opening CSV file report '" << filename_ << "'");
104-
fp_ = fopen(filename_.c_str(), "w");
106+
fp_ = FileIO::fopen(filename_.c_str(), "w");
105107
QL_REQUIRE(fp_, "Error opening file '" << filename_ << "'");
106108
finalized_ = false;
107109
}

OREData/ored/utilities/fileio.cpp

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
Copyright (C) 2023 Quaternion Risk Management Ltd
3+
All rights reserved.
4+
5+
This file is part of ORE, a free-software/open-source library
6+
for transparent pricing and risk analysis - http://opensourcerisk.org
7+
8+
ORE is free software: you can redistribute it and/or modify it
9+
under the terms of the Modified BSD License. You should have received a
10+
copy of the license along with this program.
11+
The license is also available online at <http://opensourcerisk.org>
12+
13+
This program is distributed on the basis that it will form a useful
14+
contribution to risk analytics and model standardisation, but WITHOUT
15+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16+
FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17+
*/
18+
19+
/*! \file ored/utilties/fileio.cpp
20+
\brief Wrapper class for retrying file IO operations
21+
\ingroup
22+
*/
23+
24+
#include <boost/filesystem/operations.hpp>
25+
#include <chrono>
26+
#include <ored/utilities/fileio.hpp>
27+
#include <ored/utilities/log.hpp>
28+
#include <ored/utilities/to_string.hpp>
29+
#include <thread>
30+
#include <vector>
31+
32+
namespace ore {
33+
namespace data {
34+
35+
using QuantLib::Real;
36+
using QuantLib::Size;
37+
38+
// Defaults
39+
static Size _s_maxRetries = 7;
40+
static std::vector<Real> _s_backoff = {0.5, 1, 2, 4, 8, 16, 30};
41+
42+
Size FileIO::maxRetries() { return _s_maxRetries; }
43+
44+
void FileIO::setMaxRetries(Size numOfRetries) {
45+
LOG("Setting FileOpen max retries to " << numOfRetries);
46+
_s_maxRetries = numOfRetries;
47+
}
48+
49+
FILE* FileIO::fopen(const char* filename, const char* mode) {
50+
FILE* fp;
51+
int i = 0;
52+
53+
do {
54+
if (i > 0) {
55+
Real backoff = (i >= _s_backoff.size()) ? _s_backoff.back() : _s_backoff[i - 1];
56+
int backoffMillis = backoff * 1000;
57+
auto em = EventMessage("Error opening file '" + std::string(filename) + "'.");
58+
em.set("retry_count", i);
59+
em.set("retry_interval", backoffMillis);
60+
WLOG(em);
61+
std::this_thread::sleep_for(std::chrono::milliseconds(backoffMillis));
62+
}
63+
64+
fp = std::fopen(filename, mode);
65+
66+
i++;
67+
} while (fp == nullptr && i <= maxRetries());
68+
69+
return fp;
70+
}
71+
72+
bool FileIO::create_directories(const path& p) {
73+
bool res = false;
74+
int i = 0;
75+
76+
do {
77+
if (i > 0) {
78+
Real backoff = (i >= _s_backoff.size()) ? _s_backoff.back() : _s_backoff[i - 1];
79+
int backoffMillis = backoff * 1000;
80+
auto em = EventMessage("Error creating directory '" + p.string() + "'.");
81+
em.set("retry_count", i);
82+
em.set("retry_interval", backoffMillis);
83+
WLOG(em);
84+
std::this_thread::sleep_for(std::chrono::milliseconds(backoffMillis));
85+
}
86+
87+
try {
88+
res = boost::filesystem::create_directories(p);
89+
} catch (...) {
90+
}
91+
92+
i++;
93+
} while (res == false && i <= maxRetries());
94+
95+
return res;
96+
}
97+
98+
} // namespace data
99+
} // namespace ore

OREData/ored/utilities/fileio.hpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
Copyright (C) 2023 Quaternion Risk Management Ltd
3+
All rights reserved.
4+
5+
This file is part of ORE, a free-software/open-source library
6+
for transparent pricing and risk analysis - http://opensourcerisk.org
7+
8+
ORE is free software: you can redistribute it and/or modify it
9+
under the terms of the Modified BSD License. You should have received a
10+
copy of the license along with this program.
11+
The license is also available online at <http://opensourcerisk.org>
12+
13+
This program is distributed on the basis that it will form a useful
14+
contribution to risk analytics and model standardisation, but WITHOUT
15+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16+
FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17+
*/
18+
19+
/*! \file ored/utilities/fileio.hpp
20+
\brief Wrapper class for retrying file IO operations
21+
\ingroup utilities
22+
*/
23+
24+
#pragma once
25+
26+
#include <boost/filesystem/path.hpp>
27+
#include <ql/types.hpp>
28+
#include <stdio.h>
29+
30+
namespace ore {
31+
namespace data {
32+
33+
using boost::filesystem::path;
34+
35+
class FileIO {
36+
37+
public:
38+
FileIO() = delete;
39+
40+
//! The maximum number of retries, defaults to 7
41+
static QuantLib::Size maxRetries();
42+
static void setMaxRetries(QuantLib::Size);
43+
44+
//! Retry wrapper for std::fopen
45+
static FILE* fopen(const char*, const char*);
46+
47+
//! Retry wrapper for boost::filesystem::create_directories
48+
static bool create_directories(const path&);
49+
};
50+
51+
} // namespace data
52+
} // namespace ore

OREData/ored/utilities/filteredbufferedlogger.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ void FilteredBufferedLogger::log(unsigned lvl, const std::string& log) {
4141
if (n != string::npos) {
4242
// Strip it out
4343
string log_message = log.substr(n + strlen(ore::data::StructuredMessage::name));
44-
// check if we have already logged the mssage
45-
std::size_t message_hash = std::hash<std::string>{}(log_message);
44+
// check if we have already logged the mssage
45+
std::size_t message_hash = std::hash<std::string>{}(log_message);
4646
if (message_hash_history_.insert(message_hash).second) {
4747
// log it
4848
BufferLogger::log(lvl, log_message);

OREData/ored/utilities/log.cpp

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <boost/date_time/posix_time/posix_time.hpp>
2525
#include <iomanip>
2626
#include <ored/utilities/log.hpp>
27+
#include <ored/utilities/to_string.hpp>
2728
#include <ql/errors.hpp>
2829

2930
using namespace boost::posix_time;
@@ -219,11 +220,11 @@ LoggerStream::~LoggerStream() {
219220
}
220221

221222
string StructuredMessage::json() const {
222-
string msg =
223-
"{ \"category\":\"" + category_ + "\", \"group\":\"" + group_ + "\"," + " \"message\":\"" + jsonify(message_);
223+
string msg = "{ \"category\":\"" + category_ + "\", \"group\":\"" + group_ + "\"," + " \"message\":\"" +
224+
jsonify(message_) + "\"";
224225

225226
if (!subFields_.empty()) {
226-
msg += "\", \"sub_fields\": [ ";
227+
msg += ", \"sub_fields\": [ ";
227228
QuantLib::Size i = 0;
228229
for (const auto& p : subFields_) {
229230
// Only include subFields that are non-empty.
@@ -249,5 +250,50 @@ string StructuredMessage::jsonify(const string& s) const {
249250
boost::replace_all(str, "\n", "\\n");
250251
return str;
251252
}
253+
254+
string EventMessage::json() const {
255+
string msg = "{ \"exception_message\":\"" + jsonify(message_) + "\"";
256+
257+
if (!data_.empty()) {
258+
msg += ", ";
259+
QuantLib::Size i = 0;
260+
for (const auto& p : data_) {
261+
string value;
262+
if (p.second.type() == typeid(string)) {
263+
value = "\"" + boost::any_cast<string>(p.second) + "\"";
264+
} else if (p.second.type() == typeid(boost::posix_time::ptime)) {
265+
auto time = boost::any_cast<boost::posix_time::ptime>(p.second);
266+
value = boost::posix_time::to_iso_extended_string(time);
267+
} else if (p.second.type() == typeid(int)) {
268+
value = to_string(boost::any_cast<int>(p.second));
269+
} else if (p.second.type() == typeid(QuantLib::Real)) {
270+
value = to_string(boost::any_cast<QuantLib::Real>(p.second));
271+
} else if (p.second.type() == typeid(bool)) {
272+
value = to_string(boost::any_cast<bool>(p.second));
273+
} else {
274+
WLOG(StructuredMessage("Error", "Event Logging", "Unrecognised value type for key '" + p.first + "'",
275+
std::pair<string, string>()));
276+
}
277+
278+
if (i > 0)
279+
msg += ", ";
280+
msg += "\"" + p.first + "\": " + value;
281+
i++;
282+
}
283+
}
284+
msg += " }";
285+
286+
return msg;
287+
}
288+
289+
string EventMessage::jsonify(const string& s) const {
290+
string str = s;
291+
boost::replace_all(str, "\\", "\\\\"); // do this before the below otherwise we get \\"
292+
boost::replace_all(str, "\"", "\\\"");
293+
boost::replace_all(str, "\r", "\\r");
294+
boost::replace_all(str, "\n", "\\n");
295+
return str;
296+
}
297+
252298
} // namespace data
253299
} // namespace ore

OREData/ored/utilities/log.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include <ql/patterns/singleton.hpp>
5454
#include <sstream>
5555

56+
#include <boost/any.hpp>
5657
#include <boost/thread/shared_mutex.hpp>
5758
#include <boost/thread/lock_types.hpp>
5859

@@ -478,6 +479,30 @@ class StructuredMessage {
478479

479480
inline std::ostream& operator<<(std::ostream& out, const StructuredMessage& sm) { return out << sm.msg(); }
480481

482+
class EventMessage {
483+
public:
484+
EventMessage(const string& msg, const std::map<string, boost::any> data = {}) : message_(msg), data_(data) {}
485+
486+
487+
virtual ~EventMessage() {}
488+
489+
static constexpr const char* name = "EventMessage";
490+
491+
//! return a string for the log file
492+
std::string msg() const { return string(name) + string(" ") + json(); }
493+
void set(const std::string& key, const boost::any& value) { data_[key] = value; }
494+
495+
private:
496+
// utility function to delimate string for json, handles \" and \\ and control characters
497+
string jsonify(const string& s) const;
498+
string json() const;
499+
500+
string message_;
501+
std::map<string, boost::any> data_;
502+
};
503+
504+
inline std::ostream& operator<<(std::ostream& out, const EventMessage& em) { return out << em.msg(); }
505+
481506
//! Singleton to control console logging
482507
//
483508
class ConsoleLog : public QuantLib::Singleton<ConsoleLog, std::integral_constant<bool, true>> {

0 commit comments

Comments
 (0)