From a7d9dc4b6164f1bd1be00492693aa79a1c556f6a Mon Sep 17 00:00:00 2001 From: erling Date: Thu, 28 Nov 2024 14:08:47 -0800 Subject: [PATCH] Add very simple benchmarking against reactor-c (#140) * Remove any platform specific code in code-generated CMakeLists * Fix FlexPRET * Dump working changes * Minimum reaction and event queue size is 1 * Ironing out the final wrinkles * Add support for running in fast mode, simply return immediatly from any call to wait_until. * Typo * Update examples * Fix preamble handling * Add two simple benchmarks * Run benchmarks in CI * Report benchmark result units * Post benchmark results to the PR * Try to output something from a step * CI * Fix colorized output * Remove the need for dynamically sized array on the stack * Add a newline * Another newline --- .github/workflows/benchmark.yml | 49 ++++++ .github/workflows/memory.yml | 2 +- .gitignore | 1 + CMakeLists.txt | 2 +- benchmarks/runAll.sh | 50 ++++++ benchmarks/src/BenchmarkRunnerC.lf | 160 ++++++++++++++++++ benchmarks/src/BenchmarkRunnerUc.lf | 160 ++++++++++++++++++ benchmarks/src/PingPongC.lf | 88 ++++++++++ benchmarks/src/PingPongUc.lf | 84 +++++++++ benchmarks/src/ReactionLatencyC.lf | 47 +++++ benchmarks/src/ReactionLatencyUc.lf | 46 +++++ .../target/property/LoggingProperty.java | 45 +++++ .../lflang/generator/uc/UcCmakeGenerator.kt | 1 + .../generator/uc/UcPreambleGenerator.kt | 6 +- .../lflang/generator/uc/UcReactorGenerator.kt | 6 +- src/federated.c | 8 +- src/logging.c | 2 +- 17 files changed, 744 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/benchmark.yml create mode 100755 benchmarks/runAll.sh create mode 100644 benchmarks/src/BenchmarkRunnerC.lf create mode 100644 benchmarks/src/BenchmarkRunnerUc.lf create mode 100644 benchmarks/src/PingPongC.lf create mode 100644 benchmarks/src/PingPongUc.lf create mode 100644 benchmarks/src/ReactionLatencyC.lf create mode 100644 benchmarks/src/ReactionLatencyUc.lf create mode 100644 lfc/core/src/main/java/org/lflang/target/property/LoggingProperty.java diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..6fbe1032 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,49 @@ +name: "Benchmarks" + +permissions: + contents: write + pull-requests: write + +on: + pull_request: + +jobs: + ci: + name: Run benchmarks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Setup java and gradle for compiling lfc + uses: ./.github/actions/lingua-franca + + - name: Install lfc + run: curl -Ls https://install.lf-lang.org | bash -s cli + - name: Run benchmarks + id: run_benchmarks + run: | + source env.bash + cd benchmarks + ./runAll.sh + + # This in conjunction with create-or-update-comment allows us to only + # comment once and update it after + - name: Find Comment + uses: peter-evans/find-comment@v3 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Benchmark results + + - name: Create or update comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: benchmarks/benchmark_results.md + edit-mode: replace + \ No newline at end of file diff --git a/.github/workflows/memory.yml b/.github/workflows/memory.yml index 0716b35a..734fc2d9 100644 --- a/.github/workflows/memory.yml +++ b/.github/workflows/memory.yml @@ -50,7 +50,7 @@ jobs: with: issue-number: ${{ github.event.pull_request.number }} comment-author: 'github-actions[bot]' - body-includes: Memory report + body-includes: Memory usage after merging this PR - name: Create or update comment uses: peter-evans/create-or-update-comment@v4 diff --git a/.gitignore b/.gitignore index f87d063a..696ea802 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ **/build/ **/src-gen/ **/bin/ +benchmarks/include .vscode/ cmake-build-debug cmake-build-release diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ed090be..1e3b3826 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,7 +93,7 @@ target_compile_options(reactor-uc PRIVATE -Wall -Wextra -Werror) # Disable selected warnings if (CMAKE_C_COMPILER_ID STREQUAL "GNU") - target_compile_options(reactor-uc PUBLIC -Wno-zero-length-bounds -Wno-stack-usage) + target_compile_options(reactor-uc PRIVATE -Wno-zero-length-bounds) endif() add_compile_options (-fdiagnostics-color=always) diff --git a/benchmarks/runAll.sh b/benchmarks/runAll.sh new file mode 100755 index 00000000..cecdcbfe --- /dev/null +++ b/benchmarks/runAll.sh @@ -0,0 +1,50 @@ +#!/bin/env bash +set -e + +LFC=lfc +LFCG=${REACTOR_UC_PATH}/lfc/bin/lfc-dev + +$LFC src/PingPongC.lf +$LFCG src/PingPongUc.lf + +$LFC src/ReactionLatencyC.lf +$LFCG src/ReactionLatencyUc.lf + +echo "Running benchmarks..." + +ping_pong_c_result=$(bin/PingPongC | grep -E "Time: *.") +ping_pong_uc_result=$(bin/PingPongUc | grep -E "Time: *.") +latency_c_result=$(bin/ReactionLatencyC | grep -E " latency: *.") +latency_uc_result=$(bin/ReactionLatencyUc | grep -E "latency: *.") + + +# Create or clear the output file +output_file="benchmark_results.md" +: > "$output_file" + +# Print and dump the results into the file +echo "Benchmark results after merging this PR: " >> "$output_file" +echo "
Benchmark results" >> "$output_file" +echo "" >> "$output_file" +echo "## Performance:" >> "$output_file" +echo "" >> "$output_file" + +benchmarks=("PingPongUc" "PingPongC" "ReactionLatencyUc" "ReactionLatencyC") +results=("$ping_pong_uc_result" "$ping_pong_c_result" "$latency_uc_result" "$latency_c_result") +echo $latency_uc_result >> test.md + +for i in "${!benchmarks[@]}"; do + echo "${benchmarks[$i]}:" >> "$output_file" + echo "${results[$i]}" >> "$output_file" + echo "" >> "$output_file" +done + +echo "## Memory usage:" >> "$output_file" +for benchmark in PingPongUc PingPongC ReactionLatencyUc ReactionLatencyC; +do + echo "$benchmark:" >> "$output_file" + echo "$(size -d bin/$benchmark)" >> "$output_file" + echo "" >> "$output_file" +done + +cat "$output_file" diff --git a/benchmarks/src/BenchmarkRunnerC.lf b/benchmarks/src/BenchmarkRunnerC.lf new file mode 100644 index 00000000..62691a49 --- /dev/null +++ b/benchmarks/src/BenchmarkRunnerC.lf @@ -0,0 +1,160 @@ +target C + +/** + * Reactor that starts the kernel of a benchmark, measures its runtime and outputs the results for a + * given number of iterations. + * + * This reactor is instantiated by the main reactor of a benchmark and the startup reaction of this + * reactor is the starting point for that benchmark. The reactor runs a given number of iterations + * of the benchmark, measures the runtime of each iteration and outputs them. The benchmark itself + * is responsible to reset its state between the iterations. A benchmark can have an optional + * initialization phase that is run once before the first iteration and is not measured. A benchmark + * can have an optional cleanup phase after each iteration before the next iteration start which is + * not considered in the runtime measurement. + * + * How to use: + * - Instantiate this reactor in the main reactor of the benchmark. + * - Connect the ports start, finish with the appropriate reactors of the benchmark. + * - Create a startup reaction in the main reactor that calls printBenchmarkInfo(), + * + * Prototype startup reaction in the main reactor of a benchmark: runner = new + * BenchmarkRunner(num_iterations=num_iterations); reaction(startup) {= + * printBenchmarkInfo("ThreadRingReactorLFCppBenchmark"); printSystemInfo(); + * =} + * + * @param num_iterations How many times to execute the kernel of the benchmark to measure. + * + * @author Hannes Klein + * @author Shaokai Lin + * @author Matt Chorlian + * @author Arthur Deng + */ +preamble {= + #include +=} + +reactor BenchmarkRunner(num_iterations: size_t = 12) { + /** Signal to start execution. Set this input from a startup reaction in the main reactor. */ + input inStart: bool + + /** Signals for starting and finishing the kernel and runtime measurement. */ + output start: bool + input finish: bool + + /** Events to switch between the phases of running the iterations. */ + logical action nextIteration + logical action done + + /** Number of iterations already executed. */ + state count: unsigned = 0 + + /** Start time for runtime measurement. */ + state startTime: instant_t + + /** Runtime measurements. */ + state measuredTimes: interval_t* + + preamble {= + static double toMS(interval_t t) { + return t / 1000000.0; + } + + int comp (const void * elem1, const void * elem2) { + int f = *((double*)elem1); + int s = *((double*)elem2); + if (f > s) return 1; + if (f < s) return -1; + return 0; + } + + static double median(double* execTimes, int size) { + if (size == 0) { + return 0.0; + } + + int middle = size / 2; + if(size % 2 == 1) { + return execTimes[middle]; + } else { + return (execTimes[middle-1] + execTimes[middle]) / 2; + } + } + + static double* getMSMeasurements(interval_t* measured_times, int num_iterations) { + + double* msMeasurements = (double *) calloc(num_iterations, sizeof(double)); + for (int i = 0; i < num_iterations; i++) { + msMeasurements[i] = toMS(measured_times[i]); + } + + return msMeasurements; + } + =} + + preamble {= + void printBenchmarkInfo(char* benchmarkId) { + printf("Benchmark: %s\n", benchmarkId); + } + + void printSystemInfo() { + + printf("System information\n"); + printf("O/S Name: "); + + #ifdef _WIN32 + printf("Windows 32-bit"); + #elif _WIN64 + printf("Windows 64-bit"); + #elif __APPLE__ || __MACH__ + printf("Mac OSX"); + #elif __linux__ + printf("Linux"); + #elif __FreeBSD__ + printf("FreeBSD"); + #elif __unix || __unix__ + printf("Unix"); + #else + printf("Other"); + #endif + + printf("\n"); + } + =} + + reaction(startup) -> nextIteration {= + // Initialize an array of interval_t + self->measuredTimes = (interval_t *) calloc(self->num_iterations, sizeof(interval_t)); + lf_schedule(nextIteration, 0); + =} + + reaction(nextIteration) -> start, done {= + if (self->count < self->num_iterations) { + self->startTime = lf_time_physical(); + lf_set(start, true); + } else { + lf_schedule(done, 0); + } + =} + + reaction(finish) -> nextIteration {= + interval_t end_time = lf_time_physical(); + interval_t duration = end_time - self->startTime; + self->measuredTimes[self->count] = duration; + self->count += 1; + + printf("Iteration %d - %.3f ms\n", self->count, toMS(duration)); + + lf_schedule(nextIteration, 0); + =} + + reaction(done) {= + double* measuredMSTimes = getMSMeasurements(self->measuredTimes, self->num_iterations); + qsort(measuredMSTimes, self->num_iterations, sizeof(double), comp); + + printf("Execution - Summary:\n"); + printf("Best Time:\t %.3f msec\n", measuredMSTimes[0]); + printf("Worst Time:\t %.3f msec\n", measuredMSTimes[self->num_iterations - 1]); + printf("Median Time:\t %.3f msec\n", median(measuredMSTimes, self->num_iterations)); + lf_request_stop(); + =} +} \ No newline at end of file diff --git a/benchmarks/src/BenchmarkRunnerUc.lf b/benchmarks/src/BenchmarkRunnerUc.lf new file mode 100644 index 00000000..adcb2dcc --- /dev/null +++ b/benchmarks/src/BenchmarkRunnerUc.lf @@ -0,0 +1,160 @@ +target uC + +/** + * Reactor that starts the kernel of a benchmark, measures its runtime and outputs the results for a + * given number of iterations. + * + * This reactor is instantiated by the main reactor of a benchmark and the startup reaction of this + * reactor is the starting point for that benchmark. The reactor runs a given number of iterations + * of the benchmark, measures the runtime of each iteration and outputs them. The benchmark itself + * is responsible to reset its state between the iterations. A benchmark can have an optional + * initialization phase that is run once before the first iteration and is not measured. A benchmark + * can have an optional cleanup phase after each iteration before the next iteration start which is + * not considered in the runtime measurement. + * + * How to use: + * - Instantiate this reactor in the main reactor of the benchmark. + * - Connect the ports start, finish with the appropriate reactors of the benchmark. + * - Create a startup reaction in the main reactor that calls printBenchmarkInfo(), + * + * Prototype startup reaction in the main reactor of a benchmark: runner = new + * BenchmarkRunner(num_iterations=num_iterations); reaction(startup) {= + * printBenchmarkInfo("ThreadRingReactorLFCppBenchmark"); printSystemInfo(); + * =} + * + * @param num_iterations How many times to execute the kernel of the benchmark to measure. + * + * @author Hannes Klein + * @author Shaokai Lin + * @author Matt Chorlian + * @author Arthur Deng + */ +preamble {= + #include +=} + +reactor BenchmarkRunner(num_iterations: size_t = 12) { + /** Signal to start execution. Set this input from a startup reaction in the main reactor. */ + input inStart: bool + + /** Signals for starting and finishing the kernel and runtime measurement. */ + output start: bool + input finish: bool + + /** Events to switch between the phases of running the iterations. */ + logical action nextIteration + logical action done + + /** Number of iterations already executed. */ + state count: unsigned = 0 + + /** Start time for runtime measurement. */ + state startTime: instant_t + + /** Runtime measurements. */ + state measuredTimes: interval_t* + + preamble {= + static double toMS(interval_t t) { + return t / 1000000.0; + } + + int comp (const void * elem1, const void * elem2) { + int f = *((double*)elem1); + int s = *((double*)elem2); + if (f > s) return 1; + if (f < s) return -1; + return 0; + } + + static double median(double* execTimes, int size) { + if (size == 0) { + return 0.0; + } + + int middle = size / 2; + if(size % 2 == 1) { + return execTimes[middle]; + } else { + return (execTimes[middle-1] + execTimes[middle]) / 2; + } + } + + static double* getMSMeasurements(interval_t* measured_times, int num_iterations) { + + double* msMeasurements = (double *) calloc(num_iterations, sizeof(double)); + for (int i = 0; i < num_iterations; i++) { + msMeasurements[i] = toMS(measured_times[i]); + } + + return msMeasurements; + } + =} + + preamble {= + void printBenchmarkInfo(char* benchmarkId) { + printf("Benchmark: %s\n", benchmarkId); + } + + void printSystemInfo() { + + printf("System information\n"); + printf("O/S Name: "); + + #ifdef _WIN32 + printf("Windows 32-bit"); + #elif _WIN64 + printf("Windows 64-bit"); + #elif __APPLE__ || __MACH__ + printf("Mac OSX"); + #elif __linux__ + printf("Linux"); + #elif __FreeBSD__ + printf("FreeBSD"); + #elif __unix || __unix__ + printf("Unix"); + #else + printf("Other"); + #endif + + printf("\n"); + } + =} + + reaction(startup) -> nextIteration {= + // Initialize an array of interval_t + self->measuredTimes = (interval_t *) calloc(self->num_iterations, sizeof(interval_t)); + lf_schedule(nextIteration, 0); + =} + + reaction(nextIteration) -> start, done {= + if (self->count < self->num_iterations) { + self->startTime = env->get_physical_time(env); + lf_set(start, true); + } else { + lf_schedule(done, 0); + } + =} + + reaction(finish) -> nextIteration {= + interval_t end_time = env->get_physical_time(env); + interval_t duration = end_time - self->startTime; + self->measuredTimes[self->count] = duration; + self->count += 1; + + printf("Iteration %d - %.3f ms\n", self->count, toMS(duration)); + + lf_schedule(nextIteration, 0); + =} + + reaction(done) {= + double* measuredMSTimes = getMSMeasurements(self->measuredTimes, self->num_iterations); + qsort(measuredMSTimes, self->num_iterations, sizeof(double), comp); + + printf("Execution - Summary:\n"); + printf("Best Time:\t %.3f msec\n", measuredMSTimes[0]); + printf("Worst Time:\t %.3f msec\n", measuredMSTimes[self->num_iterations - 1]); + printf("Median Time:\t %.3f msec\n", median(measuredMSTimes, self->num_iterations)); + env->request_shutdown(env); + =} +} \ No newline at end of file diff --git a/benchmarks/src/PingPongC.lf b/benchmarks/src/PingPongC.lf new file mode 100644 index 00000000..42d2c72b --- /dev/null +++ b/benchmarks/src/PingPongC.lf @@ -0,0 +1,88 @@ +/** + * Basic benchmark from the Savina benchmark suite that is intended to measure message-passing + * overhead. See [Benchmarks wiki page](https://github.com/icyphy/lingua-franca/wiki/Benchmarks). + * This is based on https://www.scala-lang.org/old/node/54 See + * https://shamsimam.github.io/papers/2014-agere-savina.pdf. + * + * Ping introduces a microstep delay using a logical action to break the causality loop. + * + * To get a sense, some (informal) results for 1,000,000 ping-pongs on my Mac: + * + * Unthreaded: 97 msec Threaded: 265 msec + * + * There is no parallelism in this application, so it does not benefit from being being threaded, + * just some additional overhead. + * + * These measurements are total execution time, including startup and shutdown. These are about an + * order of magnitude faster than anything reported in the paper. + * + * @author Edward A. Lee + */ + target C { + fast: true, + build-type: Release, + single-threaded: true +} + +import BenchmarkRunner from "./BenchmarkRunnerC.lf" + +reactor Ping(count: size_t = 1000000) { + input receive: size_t + input start: bool + output send: size_t + output finished: bool + state pingsLeft: size_t = count + logical action serve + reaction(startup) {= + self->pingsLeft = self->count; + =} + + reaction(start, serve) -> send {= + lf_set(send, self->pingsLeft--); + =} + + reaction(receive) -> serve, finished {= + if (self->pingsLeft > 0) { + lf_schedule(serve, 0); + } else { + // reset pingsLeft for next iteration + self->pingsLeft = self->count; + lf_set(finished, true); + } + =} +} + +reactor Pong(expected: size_t = 1000000) { + input receive: size_t + output send: size_t + input finish: bool + state count: size_t = 0 + + reaction(receive) -> send {= + self->count++; + // printf("Received %zu\n", receive->value); + lf_set(send, receive->value); + =} + + reaction(finish) {= + if (self->count == self->expected) { + printf("Success.\n"); + self->count = 0; + } else { + lf_print_error_and_exit("Error: expected %zu but received %zu\n", self->expected, self->count); + } + =} +} + +main reactor +{ + runner = new BenchmarkRunner(num_iterations=12); + ping = new Ping(count=1000000); + pong = new Pong(expected=1000000); + + runner.start -> ping.start; + ping.finished -> runner.finish; + ping.finished -> pong.finish; + ping.send -> pong.receive; + pong.send -> ping.receive; +} \ No newline at end of file diff --git a/benchmarks/src/PingPongUc.lf b/benchmarks/src/PingPongUc.lf new file mode 100644 index 00000000..adca0500 --- /dev/null +++ b/benchmarks/src/PingPongUc.lf @@ -0,0 +1,84 @@ +/** + * Basic benchmark from the Savina benchmark suite that is intended to measure message-passing + * overhead. See [Benchmarks wiki page](https://github.com/icyphy/lingua-franca/wiki/Benchmarks). + * This is based on https://www.scala-lang.org/old/node/54 See + * https://shamsimam.github.io/papers/2014-agere-savina.pdf. + * + * Ping introduces a microstep delay using a logical action to break the causality loop. + * + * To get a sense, some (informal) results for 1,000,000 ping-pongs on my Mac: + * + * Unthreaded: 97 msec Threaded: 265 msec + * + * There is no parallelism in this application, so it does not benefit from being being threaded, + * just some additional overhead. + * + * These measurements are total execution time, including startup and shutdown. These are about an + * order of magnitude faster than anything reported in the paper. + * + * @author Edward A. Lee + */ + target uC { + fast: true, + build-type: Release +} + +import BenchmarkRunner from "./BenchmarkRunnerUc.lf" + +reactor Ping(count: size_t = 1000000) { + input receive: size_t + input start: bool + output send: size_t + output finished: bool + state pingsLeft: size_t = count + logical action serve + reaction(startup) {= + self->pingsLeft = self->count; + =} + + reaction(start, serve) -> send {= + lf_set(send, self->pingsLeft--); + =} + + reaction(receive) -> serve, finished {= + if (self->pingsLeft > 0) { + lf_schedule(serve, 0); + } else { + // reset pingsLeft for next iteration + self->pingsLeft = self->count; + lf_set(finished, true); + } + =} +} + +reactor Pong(expected: size_t = 1000000) { + input receive: size_t + output send: size_t + input finish: bool + state count: size_t = 0 + + reaction(receive) -> send {= + self->count++; + // printf("Received %zu\n", receive->value); + lf_set(send, receive->value); + =} + + reaction(finish) {= + validate(self->count == self->expected); + printf("Success.\n"); + self->count = 0; + =} +} + +main reactor +{ + runner = new BenchmarkRunner(num_iterations=12); + ping = new Ping(count=1000000); + pong = new Pong(expected=1000000); + + runner.start -> ping.start; + ping.finished -> runner.finish; + ping.finished -> pong.finish; + ping.send -> pong.receive; + pong.send -> ping.receive; +} \ No newline at end of file diff --git a/benchmarks/src/ReactionLatencyC.lf b/benchmarks/src/ReactionLatencyC.lf new file mode 100644 index 00000000..db98b55f --- /dev/null +++ b/benchmarks/src/ReactionLatencyC.lf @@ -0,0 +1,47 @@ +target C { + build-type: Release, + single-threaded: true +} + + +reactor LatencyProbe(num_iterations: size_t = 10000){ +preamble {= + int comp (const void * elem1, const void * elem2) { + interval_t f = *((interval_t*)elem1); + interval_t s = *((interval_t*)elem2); + if (f > s) return 1; + if (f < s) return -1; + return 0; + } + +=} + timer t(100 msec, 500 usec) + + state latencies: interval_t* + state cnt: int = 0 + + reaction(startup) {= + self->latencies = calloc(self->num_iterations, sizeof(interval_t)); + =} + + reaction(t) {= + interval_t start = lf_time_physical(); + self->latencies[self->cnt++] = start - lf_time_logical(); + + if (self->cnt == self->num_iterations) { + lf_request_stop(); + } + =} + + reaction(shutdown) {= + qsort(self->latencies, self->num_iterations, sizeof(interval_t), comp); + printf("Best latency:\t %ld nsec\n", self->latencies[0]); + printf("Median latency:\t %ld nsec\n", self->latencies[self->num_iterations / 2]); + printf("Worst latency:\t %ld nsec\n", self->latencies[self->num_iterations - 1]); + =} +} + + +main reactor { + probe = new LatencyProbe(num_iterations=10000); +} \ No newline at end of file diff --git a/benchmarks/src/ReactionLatencyUc.lf b/benchmarks/src/ReactionLatencyUc.lf new file mode 100644 index 00000000..ded4d0e9 --- /dev/null +++ b/benchmarks/src/ReactionLatencyUc.lf @@ -0,0 +1,46 @@ +target uC { + build-type: Release +} + + +reactor LatencyProbe(num_iterations: size_t = 10000){ +preamble {= + int comp (const void * elem1, const void * elem2) { + interval_t f = *((interval_t*)elem1); + interval_t s = *((interval_t*)elem2); + if (f > s) return 1; + if (f < s) return -1; + return 0; + } + +=} + timer t(100 msec, 500 usec) + + state latencies: interval_t* + state cnt: int = 0 + + reaction(startup) {= + self->latencies = calloc(self->num_iterations, sizeof(interval_t)); + =} + + reaction(t) {= + interval_t start = env->get_physical_time(env); + self->latencies[self->cnt++] = start - env->get_logical_time(env); + + if (self->cnt == self->num_iterations) { + env->request_shutdown(env); + } + =} + + reaction(shutdown) {= + qsort(self->latencies, self->num_iterations, sizeof(interval_t), comp); + printf("Best latency:\t %ld nsec\n", self->latencies[0]); + printf("Median latency:\t %ld nsec\n", self->latencies[self->num_iterations / 2]); + printf("Worst latency:\t %ld nsec\n", self->latencies[self->num_iterations - 1]); + =} +} + + +main reactor { + probe = new LatencyProbe(num_iterations=10000); +} \ No newline at end of file diff --git a/lfc/core/src/main/java/org/lflang/target/property/LoggingProperty.java b/lfc/core/src/main/java/org/lflang/target/property/LoggingProperty.java new file mode 100644 index 00000000..f47a7d7f --- /dev/null +++ b/lfc/core/src/main/java/org/lflang/target/property/LoggingProperty.java @@ -0,0 +1,45 @@ +package org.lflang.target.property; + +import org.lflang.MessageReporter; +import org.lflang.ast.ASTUtils; +import org.lflang.lf.Element; +import org.lflang.target.property.type.LoggingType; +import org.lflang.target.property.type.LoggingType.LogLevel; + +/** + * Directive to specify the grain at which to report log messages during execution. The default is + * INFO. + */ +public final class LoggingProperty extends TargetProperty { + + /** Singleton target property instance. */ + public static final LoggingProperty INSTANCE = new LoggingProperty(); + + private LoggingProperty() { + super(new LoggingType()); + } + + @Override + public LogLevel initialValue() { + return LogLevel.getDefault(); + } + + @Override + protected LogLevel fromAst(Element node, MessageReporter reporter) { + return fromString(ASTUtils.elementToSingleString(node), reporter); + } + + protected LogLevel fromString(String string, MessageReporter reporter) { + return LogLevel.valueOf(string.toUpperCase()); + } + + @Override + public Element toAstElement(LogLevel value) { + return ASTUtils.toElement(value.toString()); + } + + @Override + public String name() { + return "logging"; + } +} diff --git a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcCmakeGenerator.kt b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcCmakeGenerator.kt index e2324892..953bc354 100644 --- a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcCmakeGenerator.kt +++ b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcCmakeGenerator.kt @@ -52,6 +52,7 @@ class UcCmakeGenerator(private val main: Reactor, private val targetConfig: Targ |set(PLATFORM POSIX CACHE STRING "Target platform") |set(REACTION_QUEUE_SIZE ${max(main.getReactionQueueSize(), 1)} CACHE STRING "Size of the reaction queue") |set(EVENT_QUEUE_SIZE ${max(main.getEventQueueSize(), 1)} CACHE STRING "Size of the event queue") + |set(CMAKE_BUILD_TYPE ${targetConfig.getOrDefault(BuildTypeProperty.INSTANCE)}) | |set(LF_MAIN_TARGET ${fileConfig.name}) |set(SOURCES diff --git a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcPreambleGenerator.kt b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcPreambleGenerator.kt index 8aade9e4..33ec22f1 100644 --- a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcPreambleGenerator.kt +++ b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcPreambleGenerator.kt @@ -1,10 +1,14 @@ package org.lflang.generator.uc +import org.eclipse.emf.ecore.resource.Resource import org.lflang.* import org.lflang.lf.Reactor +import org.lflang.scoping.LFGlobalScopeProvider class UcPreambleGenerator( + private val resource: Resource, private val reactor: Reactor, ) { - fun generateReactorPreamble() = reactor.preambles.joinToString(prefix= "// Reactor preambles\n", separator = "\n", postfix = "\n") { it.code.toText()} + fun generateReactorPrivatePreamble() = reactor.preambles.joinToString(prefix= "// Private preambles\n", separator = "\n", postfix = "\n") { it.code.toText()} + fun generateReactorPublicPreamble() = resource.model.preambles.joinToString(prefix = "// Public preambles ", separator = "\n", postfix = "\n") {it.code.toText()} } diff --git a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcReactorGenerator.kt b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcReactorGenerator.kt index c2754537..671b4412 100644 --- a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcReactorGenerator.kt +++ b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcReactorGenerator.kt @@ -37,7 +37,7 @@ class UcReactorGenerator(private val reactor: Reactor, fileConfig: UcFileConfig, private val timers = UcTimerGenerator(reactor) private val actions = UcActionGenerator(reactor) private val reactions = UcReactionGenerator(reactor) - private val preambles = UcPreambleGenerator(reactor) + private val preambles = UcPreambleGenerator(fileConfig.resource, reactor) private val instances = UcInstanceGenerator(reactor, parameters, ports, connections, reactions, fileConfig, messageReporter) @@ -150,7 +150,7 @@ class UcReactorGenerator(private val reactor: Reactor, fileConfig: UcFileConfig, """ |#include "reactor-uc/reactor-uc.h" | - ${" |"..preambles.generateReactorPreamble()} + ${" |"..preambles.generateReactorPublicPreamble()} ${" |"..instances.generateIncludes()} ${" |"..reactions.generateSelfStructs()} ${" |"..timers.generateSelfStructs()} @@ -168,7 +168,7 @@ class UcReactorGenerator(private val reactor: Reactor, fileConfig: UcFileConfig, fun generateSource() = with(PrependOperator) { """ |#include "${headerFile}" - | + ${" |"..preambles.generateReactorPrivatePreamble()} ${" |"..reactions.generateReactionBodies()} ${" |"..reactions.generateReactionCtors()} ${" |"..actions.generateCtors()} diff --git a/src/federated.c b/src/federated.c index 48a0a99c..52b548dd 100644 --- a/src/federated.c +++ b/src/federated.c @@ -3,18 +3,14 @@ #include "reactor-uc/logging.h" #include "reactor-uc/platform.h" -// #pragma GCC diagnostic ignored "-Wstack-usage=" - // TODO: Refactor so this function is available void LogicalConnection_trigger_downstreams(Connection *self, const void *value, size_t value_size); void FederatedConnectionBundle_connect_to_peers(FederatedConnectionBundle **bundles, size_t bundles_size) { - bool connected[bundles_size]; lf_ret_t ret; Environment *env = bundles[0]->parent->env; for (size_t i = 0; i < bundles_size; i++) { - connected[i] = false; FederatedConnectionBundle *bundle = bundles[i]; NetworkChannel *chan = bundle->net_channel; ret = chan->open_connection(chan); @@ -29,11 +25,11 @@ void FederatedConnectionBundle_connect_to_peers(FederatedConnectionBundle **bund for (size_t i = 0; i < bundles_size; i++) { FederatedConnectionBundle *bundle = bundles[i]; NetworkChannel *chan = bundle->net_channel; - if (!connected[i]) { + NetworkChannelState state = chan->get_connection_state(chan); + if (state != NETWORK_CHANNEL_STATE_CONNECTED) { ret = chan->try_connect(chan); switch (ret) { case LF_OK: - connected[i] = true; bundle->network_channel_state_changed(bundle); break; case LF_IN_PROGRESS: diff --git a/src/logging.c b/src/logging.c index 526b278c..c99bdeec 100644 --- a/src/logging.c +++ b/src/logging.c @@ -62,9 +62,9 @@ void log_message(int level, const char *module, const char *fmt, ...) { #endif log_printf("[%s] [%s] ", level_str, module); Platform_vprintf(fmt, args); - log_printf("\n"); #ifdef LF_COLORIZE_LOGS log_printf(ANSI_COLOR_RESET); #endif + log_printf("\n"); va_end(args); } \ No newline at end of file