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:
Geir Horn 2024-01-26 19:14:15 +01:00
parent 313cf335dd
commit 36af1a2605
6 changed files with 167 additions and 53 deletions

View File

@ -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"

View File

@ -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, &AMPLSolver::DataFileUpdate );

View File

@ -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

View File

@ -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 );
}

View File

@ -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

View File

@ -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()