Metric updater subscribes to SLO Violation detection messages and the default objective function is stated in the now structured AMPL file.
Change-Id: I8b1a7e2b5fde680f353d8cc8d8219f3e2d3e6691
This commit is contained in:
parent
313cf335dd
commit
36af1a2605
6
.vscode/c_cpp_properties.json
vendored
6
.vscode/c_cpp_properties.json
vendored
@ -3,8 +3,8 @@
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"/home/GHo/Documents/Code/Theron++/",
|
||||
"/home/GHo/Documents/Code/CxxOpts/include",
|
||||
"/home/GHo/Documents/Code/Theron++",
|
||||
"/opt/AMPL/amplapi/include/",
|
||||
"${workspaceFolder}/**",
|
||||
"/usr/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13"
|
||||
@ -15,7 +15,7 @@
|
||||
"compilerPath": "/usr/bin/g++",
|
||||
"compilerArgs": [
|
||||
"-std=c++23",
|
||||
"-I/home/GHo/Documents/Code/Theron++",
|
||||
"-I/home/GHo/Documents/Code/Theron++/",
|
||||
"-I/home/GHo/Documents/Code/CxxOpts/include",
|
||||
"-I/opt/AMPL/amplapi/include/"
|
||||
],
|
||||
@ -24,7 +24,7 @@
|
||||
"configurationProvider": "ms-vscode.makefile-tools",
|
||||
"browse": {
|
||||
"path": [
|
||||
"/home/GHo/Documents/Code/Theron++",
|
||||
"/home/GHo/Documents/Code/Theron++/",
|
||||
"/home/GHo/Documents/Code/CxxOpts/include",
|
||||
"/opt/AMPL/amplapi/include/",
|
||||
"/usr/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13"
|
||||
|
@ -32,23 +32,20 @@ std::string AMPLSolver::SaveFile( const JSON & TheMessage,
|
||||
{
|
||||
if( TheMessage.is_object() )
|
||||
{
|
||||
// Writing the problem file based on the message content that should be
|
||||
// only a single key-value pair. If the file could not be opened, a run
|
||||
// time exception is thrown.
|
||||
|
||||
std::string TheFileName
|
||||
= ProblemFileDirectory / TheMessage.begin().key();
|
||||
= ProblemFileDirectory / TheMessage.at( AMPLSolver::FileName );
|
||||
|
||||
std::fstream ProblemFile( TheFileName, std::ios::out );
|
||||
std::fstream ProblemFile( TheFileName, std::ios::out | std::ios::binary );
|
||||
|
||||
if( ProblemFile.is_open() )
|
||||
{
|
||||
ProblemFile << TheMessage.begin().value();
|
||||
ProblemFile << TheMessage.at( AMPLSolver::FileContent );
|
||||
ProblemFile.close();
|
||||
return TheFileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::source_location Location = std::source_location::current();
|
||||
std::ostringstream ErrorMessage;
|
||||
|
||||
ErrorMessage << "[" << Location.file_name() << " at line "
|
||||
@ -64,6 +61,7 @@ std::string AMPLSolver::SaveFile( const JSON & TheMessage,
|
||||
}
|
||||
else
|
||||
{
|
||||
std::source_location Location = std::source_location::current();
|
||||
std::ostringstream ErrorMessage;
|
||||
|
||||
ErrorMessage << "[" << Location.file_name() << " at line "
|
||||
@ -95,10 +93,30 @@ void AMPLSolver::DefineProblem(const Solver::OptimisationProblem & TheProblem,
|
||||
const Address TheOracle)
|
||||
{
|
||||
Theron::ConsoleOutput Output;
|
||||
Output << "AMPL Solver received the AMPL problem: " << TheProblem.dump(2)
|
||||
Output << "AMPL Solver received the AMPL problem: " << std::endl
|
||||
<< TheProblem.dump(2)
|
||||
<< std::endl;
|
||||
|
||||
//ProblemDefinition.read( SaveFile( TheProblem ) );
|
||||
|
||||
if( TheProblem.contains( Solver::ObjectiveFunctionLabel ) )
|
||||
DefaultObjectiveFunction = TheProblem.at( Solver::ObjectiveFunctionLabel );
|
||||
else
|
||||
{
|
||||
std::source_location Location = std::source_location::current();
|
||||
std::ostringstream ErrorMessage;
|
||||
|
||||
ErrorMessage << "[" << Location.file_name() << " at line "
|
||||
<< Location.line()
|
||||
<< "in function " << Location.function_name() <<"] "
|
||||
<< "The problem definition must contain a default objective "
|
||||
<< "function under the key ["
|
||||
<< Solver::ObjectiveFunctionLabel
|
||||
<< "]" << std::endl;
|
||||
|
||||
throw std::invalid_argument( ErrorMessage.str() );
|
||||
}
|
||||
|
||||
Output << "Problem loaded!" << std::endl;
|
||||
}
|
||||
|
||||
@ -168,16 +186,62 @@ void AMPLSolver::SolveProblem(
|
||||
// objective functions as 'dropped'. Note that this is experimental code
|
||||
// as the multi-objective possibilities in AMPL are not well documented.
|
||||
|
||||
std::string
|
||||
std::string OptimisationGoal;
|
||||
|
||||
if( TheContext.contains( Solver::ObjectiveFunctionLabel ) )
|
||||
OptimisationGoal = TheContext.at( Solver::ObjectiveFunctionLabel );
|
||||
else if( !DefaultObjectiveFunction.empty() )
|
||||
OptimisationGoal = DefaultObjectiveFunction;
|
||||
else
|
||||
{
|
||||
std::source_location Location = std::source_location::current();
|
||||
std::ostringstream ErrorMessage;
|
||||
|
||||
ErrorMessage << "[" << Location.file_name() << " at line "
|
||||
<< Location.line()
|
||||
<< "in function " << Location.function_name() <<"] "
|
||||
<< "No default objective function is defined and "
|
||||
<< "the Application Execution Context message did "
|
||||
<< "not define an objective function:"
|
||||
<< std::endl << TheContext.dump(2)
|
||||
<< std::endl;
|
||||
|
||||
throw std::invalid_argument( ErrorMessage.str() );
|
||||
}
|
||||
|
||||
// The objective function name given must correspond to a function
|
||||
// defined in the model, which implies that one function must be
|
||||
// activated.
|
||||
|
||||
bool ObjectiveFunctionActivated = false;
|
||||
|
||||
for( auto TheObjective : ProblemDefinition.getObjectives() )
|
||||
if( TheObjective.name() == OptimisationGoal )
|
||||
{
|
||||
TheObjective.restore();
|
||||
ObjectiveFunctionActivated = true;
|
||||
}
|
||||
else
|
||||
TheObjective.drop();
|
||||
|
||||
// The problem can then be solved.
|
||||
// An exception is thrown if there is no objective function activated
|
||||
|
||||
if( !ObjectiveFunctionActivated )
|
||||
{
|
||||
std::source_location Location = std::source_location::current();
|
||||
std::ostringstream ErrorMessage;
|
||||
|
||||
ErrorMessage << "[" << Location.file_name() << " at line "
|
||||
<< Location.line()
|
||||
<< "in function " << Location.function_name() <<"] "
|
||||
<< "The objective function label " << OptimisationGoal
|
||||
<< " does not correspond to any objective function in the "
|
||||
<< "model" << std::endl;
|
||||
|
||||
throw std::invalid_argument( ErrorMessage.str() );
|
||||
}
|
||||
|
||||
// The problem is valid and can then be solved.
|
||||
|
||||
Optimize();
|
||||
|
||||
@ -199,10 +263,10 @@ void AMPLSolver::SolveProblem(
|
||||
// The found solution can then be returned to the requesting actor or topic
|
||||
|
||||
Send( Solver::Solution(
|
||||
TheContext.at( Solver::ContextIdentifier ),
|
||||
TheContext.at( Solver::TimeStamp ).get< Solver::TimePointType >(),
|
||||
TheContext.at( Solver::ObjectiveFunctionLabel ),
|
||||
ObjectiveValues, VariableValues
|
||||
TheContext.at( Solver::ObjectiveFunctionLabel ), //TO DO: Where does this come from?
|
||||
ObjectiveValues, VariableValues,
|
||||
TheContext.at( DeploymentFlag ).get<bool>()
|
||||
), TheRequester );
|
||||
}
|
||||
|
||||
@ -229,7 +293,8 @@ AMPLSolver::AMPLSolver( const std::string & TheActorName,
|
||||
NetworkingActor( Actor::GetAddress().AsString() ),
|
||||
Solver( Actor::GetAddress().AsString() ),
|
||||
ProblemFileDirectory( ProblemPath ),
|
||||
ProblemDefinition( InstallationDirectory )
|
||||
ProblemDefinition( InstallationDirectory ),
|
||||
DefaultObjectiveFunction()
|
||||
{
|
||||
RegisterHandler( this, &LSolver::DataFileUpdate );
|
||||
|
||||
|
@ -127,7 +127,26 @@ protected:
|
||||
// constant string
|
||||
|
||||
static constexpr std::string_view AMPLProblemTopic
|
||||
= "AMPL::OptimisationProblem";
|
||||
= "eu.nebulouscloud.optimiser.solver.model";
|
||||
|
||||
// The JSON message received on this topic is supposed to contain three keys
|
||||
// 1) The filename of the problem file
|
||||
// 2) The file content as a single string
|
||||
// 3) The default objective function (defined in the Solver class)
|
||||
|
||||
static constexpr std::string_view FileName = "FileName",
|
||||
FileContent = "FileContent";
|
||||
|
||||
// The AMPL problem file can contain many objective functions, but can be
|
||||
// solved only for one objective function at the time. The name of the
|
||||
// default objective function is therefore stored together with the model
|
||||
// in the above Define Problem handler. If the default objective function
|
||||
// label is not provided with the optimisation problem message, an
|
||||
// invalid argument exception will be thrown.
|
||||
|
||||
private:
|
||||
|
||||
std::string DefaultObjectiveFunction;
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Data file updates
|
||||
|
@ -156,14 +156,15 @@ void MetricUpdater::SLOViolationHandler(
|
||||
// message provided that the size of the execution context equals the
|
||||
// number of metric values. It will be different if any of the metric
|
||||
// values has not been updated, and in this case the application execution
|
||||
// context is invalid and cannot be used for optimisation.
|
||||
// context is invalid and cannot be used for optimisation and the
|
||||
// SLO violation event will just be ignored. Finally, the flag indicating
|
||||
// that the corresponding solution found for this application execution
|
||||
// context should actually be enacted and deployed.
|
||||
|
||||
if( TheApplicationExecutionContext.size() == MetricValues.size() )
|
||||
Send( Solver::ApplicationExecutionContext(
|
||||
SeverityMessage[ NebulOuS::SLOIdentifier ],
|
||||
SeverityMessage[ NebulOuS::TimePoint ].get< Solver::TimePointType >(),
|
||||
SeverityMessage[ NebulOuS::ObjectiveFunctionName ],
|
||||
TheApplicationExecutionContext
|
||||
SeverityMessage.at( NebulOuS::TimePoint ).get< Solver::TimePointType >(),
|
||||
TheApplicationExecutionContext, true
|
||||
), TheSolverManager );
|
||||
}
|
||||
|
||||
|
@ -123,19 +123,11 @@ constexpr std::string_view MetricValueRootString
|
||||
// compared to a threshold, currently set to zero to ensure that every event
|
||||
// message will trigger a reconfiguration.
|
||||
//
|
||||
// However, the Metric updater will get this message from the Optimiser
|
||||
// Controller component only if an update must be made. The message must
|
||||
// contain a unique identifier, a time point for the solution, and the objective
|
||||
// function to be maximised.
|
||||
|
||||
constexpr std::string_view SLOIdentifier = "Identifier";
|
||||
constexpr std::string_view ObjectiveFunctionName = "ObjectiveFunction";
|
||||
|
||||
// The messages from the Optimizer Controller will be sent on a topic that
|
||||
// should follow some standard topic convention.
|
||||
|
||||
constexpr std::string_view SLOViolationTopic
|
||||
= "eu.nebulouscloud.optimiser.solver.slo";
|
||||
= "eu.nebulouscloud.monitoring.slo.severity_value";
|
||||
|
||||
/*==============================================================================
|
||||
|
||||
@ -265,12 +257,10 @@ private:
|
||||
// The SLO Violation detector publishes an event to indicate that at least
|
||||
// one of the constraints for the application deployment will be violated in
|
||||
// the predicted future, and that the search for a new solution should start.
|
||||
// This message is caught by the Optimisation Controller and republished
|
||||
// adding a unique event identifier enabling the Optimisation Controller to
|
||||
// match the produced solution with the event and deploy the right
|
||||
// configuration.The message must also contain the name of the objective
|
||||
// function to maximise. This name must match the name in the optimisation
|
||||
// model sent to the solver.
|
||||
// This will trigger the the publication of the Solver's Application Execution
|
||||
// context message. The context message will contain the current status of the
|
||||
// metric values, and trigger a solver to find a new, optimal variable
|
||||
// assignment to be deployed to resolve the identified problem.
|
||||
|
||||
class SLOViolation
|
||||
: public Theron::AMQ::JSONTopicMessage
|
||||
|
65
Solver.hpp
65
Solver.hpp
@ -101,6 +101,12 @@ public:
|
||||
// though all objective function values will be returned with the solution,
|
||||
// the solution will maximise only the objective function whose label is
|
||||
// given in the application execution context request message.
|
||||
//
|
||||
// The Application Execution Cntext message may contain the name of the
|
||||
// objective function to maximise. If so, this should be stored under the
|
||||
// key name indicated here. However, if the objective function name is not
|
||||
// given, the default objective function is used. The default objective
|
||||
// function will be named when defining the optimisation problem.
|
||||
|
||||
static constexpr std::string_view
|
||||
ObjectiveFunctionLabel = "ObjectiveFunction";
|
||||
@ -111,6 +117,19 @@ public:
|
||||
|
||||
static constexpr std::string_view ExecutionContext = "ExecutionContext";
|
||||
|
||||
// Finally, the execution context can come from the Metric Collector actor
|
||||
// as a consequence of an SLO Violation being detected. In this case the
|
||||
// optimised solution found by the solver should trigger a reconfiguration.
|
||||
// However, various application execution context can also be tried for
|
||||
// simulating future events and to investigate which configuration would be
|
||||
// the best for these situations. In this case the optimised solution should
|
||||
// not reconfigure the running application. For this reason there is a flag
|
||||
// in the message indicating whether the solution should be deployed, and
|
||||
// its default value is 'false' to prevent solutions form accidentially being
|
||||
// deployed.
|
||||
|
||||
static constexpr std::string_view DeploymentFlag = "DeploySolution";
|
||||
|
||||
// To ensure that the execution context is correctly provided by the senders
|
||||
// The expected metric value structure is defined as a type based on the
|
||||
// standard unsorted map based on a JSON value object since this can hold
|
||||
@ -144,25 +163,44 @@ public:
|
||||
static constexpr std::string_view MessageIdentifier
|
||||
= "eu.nebulouscloud.optimiser.solver.context";
|
||||
|
||||
ApplicationExecutionContext( const ContextIdentifierType & TheIdentifier,
|
||||
const TimePointType MicroSecondTimePoint,
|
||||
ApplicationExecutionContext( const TimePointType MicroSecondTimePoint,
|
||||
const std::string ObjectiveFunctionID,
|
||||
const MetricValueType & TheContext )
|
||||
const MetricValueType & TheContext,
|
||||
bool DeploySolution = false )
|
||||
: JSONTopicMessage( std::string( MessageIdentifier ),
|
||||
{ { std::string( ContextIdentifier ), TheIdentifier },
|
||||
{ std::string( TimeStamp ), MicroSecondTimePoint },
|
||||
{ { std::string( TimeStamp ), MicroSecondTimePoint },
|
||||
{ std::string( ObjectiveFunctionLabel ), ObjectiveFunctionID },
|
||||
{ std::string( ExecutionContext ), TheContext } }
|
||||
) {}
|
||||
{ std::string( ExecutionContext ), TheContext },
|
||||
{ std::string( DeploymentFlag ), DeploySolution }
|
||||
}) {}
|
||||
|
||||
// The constructor omitting the objective function identifier is similar
|
||||
// but without the objective function string.
|
||||
|
||||
ApplicationExecutionContext( const TimePointType MicroSecondTimePoint,
|
||||
const MetricValueType & TheContext,
|
||||
bool DeploySolution = false )
|
||||
: JSONTopicMessage( std::string( MessageIdentifier ),
|
||||
{ { std::string( TimeStamp ), MicroSecondTimePoint },
|
||||
{ std::string( ExecutionContext ), TheContext },
|
||||
{ std::string( DeploymentFlag ), DeploySolution }
|
||||
}) {}
|
||||
|
||||
// The copy constructor simply passes the job on to the JSON Topic
|
||||
// message for copying the message
|
||||
|
||||
ApplicationExecutionContext( const ApplicationExecutionContext & Other )
|
||||
: JSONTopicMessage( Other )
|
||||
{}
|
||||
|
||||
// The default constructor simply stores the message identifier
|
||||
|
||||
ApplicationExecutionContext()
|
||||
: JSONTopicMessage( std::string( MessageIdentifier ) )
|
||||
{}
|
||||
|
||||
// The default destrucor is used
|
||||
|
||||
virtual ~ApplicationExecutionContext() = default;
|
||||
};
|
||||
|
||||
@ -210,17 +248,18 @@ public:
|
||||
static constexpr std::string_view MessageIdentifier
|
||||
= "eu.nebulouscloud.optimiser.solver.solution";
|
||||
|
||||
Solution( const ContextIdentifierType & TheIdentifier,
|
||||
const TimePointType MicroSecondTimePoint,
|
||||
Solution( const TimePointType MicroSecondTimePoint,
|
||||
const std::string ObjectiveFunctionID,
|
||||
const ObjectiveValuesType & TheObjectiveValues,
|
||||
const VariableValuesType & TheVariables )
|
||||
const VariableValuesType & TheVariables,
|
||||
bool DeploySolution )
|
||||
: JSONTopicMessage( std::string( MessageIdentifier ) ,
|
||||
{ { std::string( ContextIdentifier ), TheIdentifier },
|
||||
{ std::string( TimeStamp ), MicroSecondTimePoint },
|
||||
{ { std::string( TimeStamp ), MicroSecondTimePoint },
|
||||
{ std::string( ObjectiveFunctionLabel ), ObjectiveFunctionID },
|
||||
{ std::string( ObjectiveValues ) , TheObjectiveValues },
|
||||
{ std::string( VariableValues ), TheVariables } } )
|
||||
{ std::string( VariableValues ), TheVariables },
|
||||
{ std::string( DeploymentFlag ), DeploySolution }
|
||||
} )
|
||||
{}
|
||||
|
||||
Solution()
|
||||
|
Loading…
x
Reference in New Issue
Block a user