use of org.lflang.federated.FederateInstance in project lingua-franca by lf-lang.
the class GeneratorBase method analyzeFederates.
/**
* Analyze the AST to determine whether code is being mapped to
* single or to multiple target machines. If it is being mapped
* to multiple machines, then set the {@link #isFederated} field to true,
* create a FederateInstance for each federate, and record various
* properties of the federation
*
* In addition, for each top-level connection, add top-level reactions to the AST
* that send and receive messages over the network.
*
* This class is target independent, so the target code
* generator still has quite a bit of work to do.
* It needs to provide the body of the sending and
* receiving reactions. It also needs to provide the
* runtime infrastructure that uses the dependency
* information between federates. See the C target
* for a reference implementation.
*/
private void analyzeFederates(LFGeneratorContext context) {
// Next, if there actually are federates, analyze the topology
// interconnecting them and replace the connections between them
// with an action and two reactions.
Reactor mainReactor = mainDef != null ? ASTUtils.toDefinition(mainDef.getReactorClass()) : null;
if (mainDef == null || !mainReactor.isFederated()) {
// The program is not federated.
// Ensure federates is never empty.
FederateInstance federateInstance = new FederateInstance(null, 0, 0, this, errorReporter);
federates.add(federateInstance);
federateByID.put(0, federateInstance);
} else {
// The Lingua Franca program is federated
isFederated = true;
// If the "--rti" flag is given to the compiler, use the argument from the flag.
if (context.getArgs().containsKey("rti")) {
setFederationRTIProperties(context);
} else if (mainReactor.getHost() != null) {
// If not specified, this defaults to 'localhost'
if (mainReactor.getHost().getAddr() != null) {
federationRTIProperties.put("host", mainReactor.getHost().getAddr());
}
// If not specified, this defaults to 14045
if (mainReactor.getHost().getPort() != 0) {
federationRTIProperties.put("port", mainReactor.getHost().getPort());
}
// Get the user information, if specified.
if (mainReactor.getHost().getUser() != null) {
federationRTIProperties.put("user", mainReactor.getHost().getUser());
}
}
// Since federates are always within the main (federated) reactor,
// create a list containing just that one containing instantiation.
// This will be used to look up parameter values.
List<Instantiation> mainReactorContext = new ArrayList<>();
mainReactorContext.add(mainDef);
// Create a FederateInstance for each top-level reactor.
for (Instantiation instantiation : ASTUtils.allInstantiations(mainReactor)) {
int bankWidth = ASTUtils.width(instantiation.getWidthSpec(), mainReactorContext);
if (bankWidth < 0) {
errorReporter.reportError(instantiation, "Cannot determine bank width! Assuming width of 1.");
// Continue with a bank width of 1.
bankWidth = 1;
}
// Create one federate instance for each instance in a bank of reactors.
List<FederateInstance> federateInstances = new ArrayList<>(bankWidth);
for (int i = 0; i < bankWidth; i++) {
// Assign an integer ID to the federate.
int federateID = federates.size();
FederateInstance federateInstance = new FederateInstance(instantiation, federateID, i, this, errorReporter);
federateInstance.bankIndex = i;
federates.add(federateInstance);
federateInstances.add(federateInstance);
federateByID.put(federateID, federateInstance);
if (instantiation.getHost() != null) {
federateInstance.host = instantiation.getHost().getAddr();
// The following could be 0.
federateInstance.port = instantiation.getHost().getPort();
// The following could be null.
federateInstance.user = instantiation.getHost().getUser();
/* FIXME: The at keyword should support a directory component.
* federateInstance.dir = instantiation.getHost().dir
*/
if (federateInstance.host != null && federateInstance.host != "localhost" && federateInstance.host != "0.0.0.0") {
federateInstance.isRemote = true;
}
}
}
if (federatesByInstantiation == null) {
federatesByInstantiation = new LinkedHashMap<Instantiation, List<FederateInstance>>();
}
federatesByInstantiation.put(instantiation, federateInstances);
}
// In a federated execution, we need keepalive to be true,
// otherwise a federate could exit simply because it hasn't received
// any messages.
targetConfig.keepalive = true;
// Analyze the connection topology of federates.
// First, find all the connections between federates.
// For each connection between federates, replace it in the
// AST with an action (which inherits the delay) and two reactions.
// The action will be physical for physical connections and logical
// for logical connections.
replaceFederateConnectionsWithActions();
// Remove the connections at the top level
mainReactor.getConnections().clear();
}
}
use of org.lflang.federated.FederateInstance in project lingua-franca by lf-lang.
the class FedLauncher method createLauncher.
/**
* Create the launcher shell scripts. This will create one or two files
* in the output path (bin directory). The first has name equal to
* the filename of the source file without the ".lf" extension.
* This will be a shell script that launches the
* RTI and the federates. If, in addition, either the RTI or any
* federate is mapped to a particular machine (anything other than
* the default "localhost" or "0.0.0.0"), then this will generate
* a shell script in the bin directory with name filename_distribute.sh
* that copies the relevant source files to the remote host and compiles
* them so that they are ready to execute using the launcher.
*
* A precondition for this to work is that the user invoking this
* code generator can log into the remote host without supplying
* a password. Specifically, you have to have installed your
* public key (typically found in ~/.ssh/id_rsa.pub) in
* ~/.ssh/authorized_keys on the remote host. In addition, the
* remote host must be running an ssh service.
* On an Arch Linux system using systemd, for example, this means
* running:
*
* sudo systemctl <start|enable> ssh.service
*
* Enable means to always start the service at startup, whereas
* start means to just start it this once.
*
* On MacOS, open System Preferences from the Apple menu and
* click on the "Sharing" preference panel. Select the checkbox
* next to "Remote Login" to enable it.
*
* In addition, every host must have OpenSSL installed, with at least
* version 1.1.1a. You can check the version with
*
* openssl version
*
* @param coreFiles The files from the core directory that must be
* copied to the remote machines.
* @param federates A list of federate instances in the federation
* @param federationRTIProperties Contains relevant properties of the RTI.
* Can have values for 'host', 'dir', and 'user'
*/
public void createLauncher(ArrayList<String> coreFiles, List<FederateInstance> federates, LinkedHashMap<String, Object> federationRTIProperties) throws IOException {
// NOTE: It might be good to use screen when invoking the RTI
// or federates remotely so you can detach and the process keeps running.
// However, I was unable to get it working properly.
// What this means is that the shell that invokes the launcher
// needs to remain live for the duration of the federation.
// If that shell is killed, the federation will die.
// Hence, it is reasonable to launch the federation on a
// machine that participates in the federation, for example,
// on the machine that runs the RTI. The command I tried
// to get screen to work looks like this:
// ssh -t «target» cd «path»; screen -S «filename»_«federate.name» -L bin/«filename»_«federate.name» 2>&1
// var outPath = binGenPath
StringBuilder shCode = new StringBuilder();
StringBuilder distCode = new StringBuilder();
shCode.append(getSetupCode() + "\n");
String distHeader = getDistHeader();
Object host = federationRTIProperties.get("host");
Object target = host;
Object path = federationRTIProperties.get("dir");
if (path == null)
path = "LinguaFrancaRemote";
Object user = federationRTIProperties.get("user");
if (user != null) {
target = user + "@" + host;
}
String RTILaunchString = getRtiCommand(federates);
// Launch the RTI in the foreground.
if (host.equals("localhost") || host.equals("0.0.0.0")) {
// FIXME: the paths below will not work on Windows
shCode.append(getLaunchCode(RTILaunchString) + "\n");
} else {
// Copy the source code onto the remote machine and compile it there.
if (distCode.length() == 0)
distCode.append(distHeader + "\n");
String logFileName = String.format("log/%s_RTI.log", fileConfig.name);
// Launch the RTI on the remote machine using ssh and screen.
// The -t argument to ssh creates a virtual terminal, which is needed by screen.
// The -S gives the session a name.
// The -L option turns on logging. Unfortunately, the -Logfile giving the log file name
// is not standardized in screen. Logs go to screenlog.0 (or screenlog.n).
// FIXME: Remote errors are not reported back via ssh from screen.
// How to get them back to the local machine?
// Perhaps use -c and generate a screen command file to control the logfile name,
// but screen apparently doesn't write anything to the log file!
//
// The cryptic 2>&1 reroutes stderr to stdout so that both are returned.
// The sleep at the end prevents screen from exiting before outgoing messages from
// the federate have had time to go out to the RTI through the socket.
RTILaunchString = getRtiCommand(federates);
shCode.append(getRemoteLaunchCode(host, target, logFileName, RTILaunchString) + "\n");
}
// Index used for storing pids of federates
int federateIndex = 0;
for (FederateInstance federate : federates) {
if (federate.isRemote) {
FedFileConfig fedFileConfig = new FedFileConfig(fileConfig, federate.name);
Path fedRelSrcGenPath = fedFileConfig.getSrcGenBasePath().relativize(fedFileConfig.getSrcGenPath());
if (distCode.length() == 0)
distCode.append(distHeader + "\n");
String logFileName = String.format("log/%s_%s.log", fedFileConfig.name, federate.name);
String compileCommand = compileCommandForFederate(federate);
// '''«targetConfig.compiler» src-gen/«topLevelName»_«federate.name».c -o bin/«topLevelName»_«federate.name» -pthread «targetConfig.compilerFlags.join(" ")»'''
// FIXME: Should $FEDERATION_ID be used to ensure unique directories, executables, on the remote host?
distCode.append(getDistCode(path, federate, fedRelSrcGenPath, logFileName, fedFileConfig, compileCommand) + "\n");
String executeCommand = executeCommandForRemoteFederate(federate);
shCode.append(getFedRemoteLaunchCode(federate, path, logFileName, executeCommand, federateIndex++) + "\n");
} else {
String executeCommand = executeCommandForLocalFederate(fileConfig, federate);
shCode.append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++) + "\n");
}
}
if (host.equals("localhost") || host.equals("0.0.0.0")) {
// Local PID managements
shCode.append("echo \"#### Bringing the RTI back to foreground so it can receive Control-C.\"" + "\n");
shCode.append("fg %1" + "\n");
}
// Wait for launched processes to finish
shCode.append(String.join("\n", "echo \"RTI has exited. Wait for federates to exit.\"", "# Wait for launched processes to finish.", "# The errors are handled separately via trap.", "for pid in \"${pids[@]}\"", "do", " wait $pid", "done", "echo \"All done.\"") + "\n");
// Write the launcher file.
// Delete file previously produced, if any.
File file = fileConfig.binPath.resolve(fileConfig.name).toFile();
if (file.exists()) {
file.delete();
}
FileOutputStream fOut = new FileOutputStream(file);
fOut.write(shCode.toString().getBytes());
fOut.close();
if (!file.setExecutable(true, false)) {
errorReporter.reportWarning("Unable to make launcher script executable.");
}
// Write the distributor file.
// Delete the file even if it does not get generated.
file = fileConfig.binPath.resolve(fileConfig.name + "_distribute.sh").toFile();
if (file.exists()) {
file.delete();
}
if (distCode.length() > 0) {
fOut = new FileOutputStream(file);
fOut.write(distCode.toString().getBytes());
fOut.close();
if (!file.setExecutable(true, false)) {
errorReporter.reportWarning("Unable to make distributor script executable.");
}
}
}
use of org.lflang.federated.FederateInstance in project lingua-franca by lf-lang.
the class GeneratorBase method replaceConnectionFromFederate.
/**
* Replace the connections from the specified output port for the specified federate reactor.
* @param output The output port instance.
* @param srcFederate The federate for which this port is an output.
* @param federateReactor The reactor instance for that federate.
* @param mainInstance The main reactor instance.
*/
private void replaceConnectionFromFederate(PortInstance output, ReactorInstance federateReactor, ReactorInstance mainInstance) {
for (SendRange srcRange : output.dependentPorts) {
for (RuntimeRange<PortInstance> dstRange : srcRange.destinations) {
MixedRadixInt srcID = srcRange.startMR();
MixedRadixInt dstID = dstRange.startMR();
int dstCount = 0;
int srcCount = 0;
while (dstCount++ < dstRange.width) {
int srcChannel = srcID.getDigits().get(0);
int srcBank = srcID.get(1);
int dstChannel = dstID.getDigits().get(0);
int dstBank = dstID.get(1);
FederateInstance srcFederate = federatesByInstantiation.get(srcRange.instance.parent.definition).get(srcBank);
FederateInstance dstFederate = federatesByInstantiation.get(dstRange.instance.parent.definition).get(dstBank);
Connection connection = srcRange.connection;
if (connection == null) {
// This should not happen.
errorReporter.reportError(output.definition, "Unexpected error. Cannot find output connection for port");
} else {
if (srcFederate != dstFederate && !connection.isPhysical() && targetConfig.coordination != CoordinationType.DECENTRALIZED) {
// Map the delays on connections between federates.
// First see if the cache has been created.
Set<Delay> dependsOnDelays = dstFederate.dependsOn.get(srcFederate);
if (dependsOnDelays == null) {
// If not, create it.
dependsOnDelays = new LinkedHashSet<Delay>();
dstFederate.dependsOn.put(srcFederate, dependsOnDelays);
}
// Put the delay on the cache.
if (connection.getDelay() != null) {
dependsOnDelays.add(connection.getDelay());
} else {
// To indicate that at least one connection has no delay, add a null entry.
dependsOnDelays.add(null);
}
// Map the connections between federates.
Set<Delay> sendsToDelays = srcFederate.sendsTo.get(dstFederate);
if (sendsToDelays == null) {
sendsToDelays = new LinkedHashSet<Delay>();
srcFederate.sendsTo.put(dstFederate, sendsToDelays);
}
if (connection.getDelay() != null) {
sendsToDelays.add(connection.getDelay());
} else {
// To indicate that at least one connection has no delay, add a null entry.
sendsToDelays.add(null);
}
}
FedASTUtils.makeCommunication(srcRange.instance, dstRange.instance, connection, srcFederate, srcBank, srcChannel, dstFederate, dstBank, dstChannel, this, targetConfig.coordination);
}
dstID.increment();
srcID.increment();
srcCount++;
if (srcCount == srcRange.width) {
// Multicast. Start over.
srcID = srcRange.startMR();
}
}
}
}
}
use of org.lflang.federated.FederateInstance in project lingua-franca by lf-lang.
the class PythonGenerator method doGenerate.
/**
* Generate C code from the Lingua Franca model contained by the
* specified resource. This is the main entry point for code
* generation.
* @param resource The resource containing the source code.
* @param context Context relating to invocation of the code generator.
*/
@Override
public void doGenerate(Resource resource, LFGeneratorContext context) {
// If there are federates, assign the number of threads in the CGenerator to 1
if (isFederated) {
targetConfig.threads = 1;
}
// Prevent the CGenerator from compiling the C code.
// The PythonGenerator will compiler it.
boolean compileStatus = targetConfig.noCompile;
targetConfig.noCompile = true;
// Force disable the CMake because
targetConfig.useCmake = false;
// it interferes with the Python target functionality
int cGeneratedPercentProgress = (IntegratedBuilder.VALIDATED_PERCENT_PROGRESS + 100) / 2;
super.doGenerate(resource, new SubContext(context, IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, cGeneratedPercentProgress));
SubContext compilingFederatesContext = new SubContext(context, cGeneratedPercentProgress, 100);
targetConfig.noCompile = compileStatus;
if (errorsOccurred()) {
context.unsuccessfulFinish();
return;
}
String baseFileName = topLevelName;
// Keep a separate file config for each federate
FileConfig oldFileConfig = fileConfig;
var federateCount = 0;
Map<Path, CodeMap> codeMaps = new HashMap<>();
for (FederateInstance federate : federates) {
federateCount++;
if (isFederated) {
topLevelName = baseFileName + '_' + federate.name;
try {
fileConfig = new FedFileConfig(fileConfig, federate.name);
} catch (IOException e) {
throw Exceptions.sneakyThrow(e);
}
}
// Don't generate code if there is no main reactor
if (this.main != null) {
try {
Map<Path, CodeMap> codeMapsForFederate = generatePythonFiles(federate);
codeMaps.putAll(codeMapsForFederate);
PyUtil.copyTargetFiles(fileConfig);
if (!targetConfig.noCompile) {
compilingFederatesContext.reportProgress(String.format("Validating %d/%d sets of generated files...", federateCount, federates.size()), 100 * federateCount / federates.size());
// If there are no federates, compile and install the generated code
new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context);
if (!errorsOccurred() && !Objects.equal(context.getMode(), LFGeneratorContext.Mode.LSP_MEDIUM)) {
compilingFederatesContext.reportProgress(String.format("Validation complete. Compiling and installing %d/%d Python modules...", federateCount, federates.size()), 100 * federateCount / federates.size());
// Why is this invoked here if the current federate is not a parameter?
pythonCompileCode(context);
}
} else {
System.out.println(PythonInfoGenerator.generateSetupInfo(fileConfig));
}
} catch (Exception e) {
throw Exceptions.sneakyThrow(e);
}
if (!isFederated) {
System.out.println(PythonInfoGenerator.generateRunInfo(fileConfig, topLevelName));
}
}
fileConfig = oldFileConfig;
}
if (isFederated) {
System.out.println(PythonInfoGenerator.generateFedRunInfo(fileConfig));
}
// Restore filename
topLevelName = baseFileName;
if (errorReporter.getErrorsOccurred()) {
context.unsuccessfulFinish();
} else if (!isFederated) {
context.finish(GeneratorResult.Status.COMPILED, topLevelName + ".py", fileConfig.getSrcGenPath(), fileConfig, codeMaps, "python3");
} else {
context.finish(GeneratorResult.Status.COMPILED, fileConfig.name, fileConfig.binPath, fileConfig, codeMaps, "bash");
}
}
Aggregations