Skip to content

Commit

Permalink
Feature/eventlog improvements (#45)
Browse files Browse the repository at this point in the history
* wip

* faster switching by allocating a new reducer queue

* make mermaid part of parsers

* recursive eventlogging

* example of gantt

* prettier

* wip improv
  • Loading branch information
thorstink authored Jul 29, 2023
1 parent a9eb608 commit b52d0a2
Show file tree
Hide file tree
Showing 24 changed files with 532 additions and 362 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ build
install
docs/html
.vscode
examples/flight/mermaid.html

# Prerequisites
*.d
Expand Down
11 changes: 5 additions & 6 deletions examples/flight/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
#an app
# an app
find_package(spdlog REQUIRED)

if(ASAN_BUILD AND NOT TSAN_BUILD)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address,undefined -fno-omit-frame-pointer -O0")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address,undefined -fno-omit-frame-pointer -O0")
elseif(TSAN_BUILD AND NOT ASAN_BUILD)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=thread -fno-omit-frame-pointer -O0")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=thread -fno-omit-frame-pointer -O0")
endif()

add_executable(${PROJECT_NAME}_flight flight.cc)
target_link_libraries(${PROJECT_NAME}_flight symmetri spdlog::spdlog)

#install
install(TARGETS ${PROJECT_NAME}_flight DESTINATION ${PROJECT_SOURCE_DIR}/install)
89 changes: 56 additions & 33 deletions examples/flight/flight.cc
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
#include <spdlog/spdlog.h>

#include <fstream>
#include <iostream>

#include "symmetri/parsers.h"
#include "symmetri/symmetri.h"
#include "transition.hpp"

/**
* @brief We want to use the Foo class with Symmetri; Foo has nice functionality
* such as Pause and Resume, and it can also get preempted/cancelled. We need to
* define functions to let Symmetri use these functionalities. It is as simple
* by creating specialized version of the fire/cancel/isDirect/pause/resume
* functions. One does not need to implement all - if nothing is defined, a
* default version is used.
* @brief We want to use the Foo class with Symmetri; Foo has nice
* functionalities such as Pause and Resume and it can also get
* preempted/cancelled. We need to define functions to let Symmetri use these
* functionalities. It is as simple by creating specialized version of the
* fire/cancel/isDirect/pause/resume functions. One does not need to implement
* all - if nothing is defined, a default version is used.
*
*/
namespace symmetri {

template <>
Result fire(const Foo &f) {
return f.fire() ? Result{{}, symmetri::State::Error}
: Result{{}, symmetri::State::Completed};
return f.fire() ? Result{{}, State::Error} : Result{{}, State::Completed};
}

template <>
Result cancel(const Foo &f) {
f.cancel();
return {{}, symmetri::State::UserExit};
return {{}, State::UserExit};
}

template <>
bool isDirect(const Foo &) {
return false;
}

template <>
void pause(const Foo &f) {
f.pause();
}

template <>
void resume(const Foo &f) {
f.resume();
Expand All @@ -51,6 +57,15 @@ void printLog(const symmetri::Eventlog &eventlog) {
}
}

void writeMermaidHtmlToFile(const std::string &mermaid) {
std::ofstream mermaid_file;
mermaid_file.open("examples/flight/mermaid.html");
mermaid_file << "<div class=\"mermaid\">" + mermaid + "</div>";
mermaid_file.close();

return;
}

int main(int, char *argv[]) {
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%f] [%^%l%$] [thread %t] %v");

Expand All @@ -59,33 +74,45 @@ int main(int, char *argv[]) {
const auto pnml2 = std::string(argv[2]);
const auto pnml3 = std::string(argv[3]);

// We create a threadpool to which transitions can be dispatched. In this case
// 4 is too many; in theory you can deduce the maximum amount of parallel
// transitions from your petri net.
// We create a threadpool to which transitions can be dispatched. In this
// case 4 is too many; in theory you can deduce the maximum amount of
// parallel transitions from your petri net.
auto pool = std::make_shared<symmetri::TaskSystem>(4);

// Here we create the first PetriNet based on composing pnml1 and pnml2 using
// flat composition. The associated transitions are two instance of the
// Foo-class.
// Here we create the first PetriNet based on composing pnml1 and pnml2
// using flat composition. The associated transitions are two instance of
// the Foo-class.
symmetri::PetriNet subnet({pnml1, pnml2}, {{"P2", 1}},
{{"T0", Foo("SubFoo")}, {"T1", Foo("SubBar")}}, {},
"SubNet", pool);

// We create another PetriNet by flatly composing all three petri nets. Again
// we have 2 Foo-transitions, and the third transition is the subnet. This
// show how you can also nest PetriNets.
// We create another PetriNet by flatly composing all three petri nets.
// Again we have 2 Foo-transitions, and the first transition (T0) is the
// subnet. This show how you can also nest PetriNets.
symmetri::PetriNet bignet(
{pnml1, pnml2, pnml3}, {{"P3", 5}},
{{"T0", subnet}, {"T1", Foo("Bar")}, {"T2", Foo("Foo")}}, {}, "RootNet",
pool);

// Parallel to the PetriNet execution, we run a thread through which we can
// get some keyboard input for interaction
auto t = std::thread([bignet] {
bool is_running = true;
while (is_running) {
// a flag to check if we are running
std::atomic<bool> running(true);

// a thread that polls the eventlog and writes it to a file
auto gantt = std::thread([&] {
while (running) {
writeMermaidHtmlToFile(
symmetri::mermaidFromEventlog(bignet.getEventLog()));
std::this_thread::sleep_for(std::chrono::seconds(3));
}
writeMermaidHtmlToFile(symmetri::mermaidFromEventlog(bignet.getEventLog()));
});

// Parallel to the PetriNet execution, we run a thread through which we
// can get some keyboard input for interaction
auto t = std::thread([&] {
while (running) {
std::cout << "input options: \n [p] - pause\n [r] - resume\n [x] - "
"exit\n [l] - print log\n";
"exit\n";
char input;
std::cin >> input;
switch (input) {
Expand All @@ -95,13 +122,9 @@ int main(int, char *argv[]) {
case 'r':
resume(bignet);
break;
case 'l': {
printLog(bignet.getEventLog());
break;
}
case 'x': {
cancel(bignet);
is_running = false;
running = false;
break;
}
default:
Expand All @@ -114,11 +137,11 @@ int main(int, char *argv[]) {
// this is where we call the blocking fire-function that executes the petri
// net
auto [el, result] = fire(bignet);

running = false;
// print the results and eventlog
spdlog::info("Result of this net: {0}", printState(result));
printLog(el);

t.join(); // clean up
return result == symmetri::State::Completed ? 0 : -1;
t.join(); // clean up
gantt.join(); // clean up
return 0;
}
22 changes: 22 additions & 0 deletions examples/flight/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div id="gantt">
</div>
<script src="https://code.jquery.com/jquery-3.7.0.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>
$("#gantt").load("mermaid.html", function () {
mermaid.initialize({
mermaid: {
startOnLoad: false
}
});
window.mermaid.init(undefined, document.querySelectorAll('.mermaid'));
});
</script>
</body>
</html>
Empty file added examples/flight/mermaid.html
Empty file.
54 changes: 54 additions & 0 deletions examples/flight/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Flight

This is a little application that parses three Petri nets and uses multiple ways of composing to demonstrate the capabilities. It also demonstrates how to implement custom behavior for *fire*, *pause* and *cancel* using a user supplied class `Foo` in `transitions.hpp`.

## installing

You can build and install to a local `install`-directory:

```bash
mkdir build
cd build
cmake -DBUILD_EXAMPLES=ON -DBUILD_TESTING=OFF ..
make
```

You will also need a little local webserver if you want to visualize the Gantt-chart online. See below how it could look. In this example [live-server](https://github.com/tapio/live-server) is used because it has fancy hot reloading.

You can run it like this:

```bash
live-server --port=8000 --open=./examples/flight/index.html --watch=./examples/flight & \
./build/examples/flight/symmetri_flight nets/PT1.pnml nets/PT2.pnml nets/PT3.pnml && \
fg
```

And you should be able to watch [http://127.0.0.1:8000/examples/flight/](http://127.0.0.1:8000/examples/flight/)

You can interact with the application through simple keys followed by an [enter]

```bash
input options:
[p] - pause
[r] - resume
[x] - exit
```

# Mermaid example

```mermaid
---
displayMode : compact
---
gantt
title A Gantt Diagram
dateFormat x
axisFormat %H:%M:%S
section RootNet
T0 :done, 2606109959,2606119999
T1 :done, 2606119999,2606125016
T2 :active, 2606125016,2606129933
section SubNet
T0 :done, 2606109959,2606114979
T1 :done, 2606114980,2606119999
```
3 changes: 0 additions & 3 deletions examples/hello_world/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,3 @@ find_package(spdlog REQUIRED)

add_executable(${PROJECT_NAME}_hello_world main.cc)
target_link_libraries(${PROJECT_NAME}_hello_world symmetri spdlog::spdlog)

#install
install(TARGETS ${PROJECT_NAME}_hello_world DESTINATION ${PROJECT_SOURCE_DIR}/install)
4 changes: 2 additions & 2 deletions examples/hello_world/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ int main(int, char *argv[]) {
// becomes false.
});

auto [el, result] = net.fire(); // This function blocks until either the
// net completes, deadlocks
auto [el, result] = symmetri::fire(net); // This function blocks until either
// the net completes, deadlocks
// or user requests exit (ctrl-c)
running.store(false); // We set this to false so the thread that we launched
// gets interrupted.
Expand Down
3 changes: 0 additions & 3 deletions examples/model_benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,3 @@ find_package(spdlog REQUIRED)

add_executable(${PROJECT_NAME}_benchmark model_benchmark.cc)
target_link_libraries(${PROJECT_NAME}_benchmark symmetri spdlog::spdlog)

#install
install(TARGETS ${PROJECT_NAME}_benchmark DESTINATION ${PROJECT_SOURCE_DIR}/install)
2 changes: 1 addition & 1 deletion examples/model_benchmark/model_benchmark.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ int main(int, char *argv[]) {
symmetri::PetriNet bignet(net, m0, {}, store, {}, "pluto", pool);
spdlog::info("start!");
const auto start_time = symmetri::Clock::now();
auto [el, result] = bignet.fire(); // infinite loop
auto [el, result] = symmetri::fire(bignet); // infinite loop
const auto end_time = symmetri::Clock::now();
auto trans_count = el.size() / 2;
auto delta_t = (double((end_time - start_time).count()) / 1e9);
Expand Down
1 change: 1 addition & 0 deletions symmetri/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ add_library(${PROJECT_NAME} SHARED
model_utilities.cc
pnml_parser.cc
grml_parser.cc
eventlog_parsers.cc
types.cpp
submodules/tinyxml2/tinyxml2.cpp
)
Expand Down
85 changes: 85 additions & 0 deletions symmetri/eventlog_parsers.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <algorithm>
#include <sstream>

#include "symmetri/parsers.h"

namespace symmetri {

std::string mermaidFromEventlog(symmetri::Eventlog el) {
auto now_timestamp = Clock::now();
el.erase(std::remove_if(el.begin(), el.end(),
[](const auto &x) {
return x.state == symmetri::State::Scheduled;
}),
el.end());
std::sort(el.begin(), el.end(), [](const auto &a, const auto &b) {
if (a.case_id != b.case_id) {
return a.case_id < b.case_id;
}
if (a.stamp != b.stamp) {
return a.stamp < b.stamp;
}
if (a.transition != b.transition) {
return a.transition < b.transition;
}
return false;
});

if (el.empty()) {
return "";
}

std::stringstream mermaid;
mermaid << "\n---\ndisplayMode : compact\n---\ngantt\ntitle A Gantt "
"Diagram\ndateFormat x\naxisFormat \%H:\%M:\%S\n";
std::string current_section("");
for (auto it = el.begin(); std::next(it) != el.end(); it = std::next(it)) {
const auto &start = *it;
const auto &end = *std::next(it);
auto id1 = start.case_id + start.transition;
auto id2 = end.case_id + end.transition;
if (start.state == symmetri::State::Started) {
if (current_section != start.case_id) {
current_section = start.case_id;
mermaid << "section " << start.case_id << "\n";
}
auto result = (end.state == symmetri::State::Error ||
end.state == symmetri::State::Deadlock)
? "crit"
: (end.state == symmetri::State::UserExit // Paused
? "active"
: "done"); // Completed

mermaid
<< start.transition << " :" << (id1 == id2 ? result : "active")
<< ", "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
start.stamp.time_since_epoch())
.count()
<< ','
<< std::chrono::duration_cast<std::chrono::milliseconds>(
(id1 == id2 ? end.stamp : now_timestamp).time_since_epoch())
.count()
<< '\n';
}
}

// and check if the latest is an active transition
const auto &start = el.back();
if (start.state == symmetri::State::Started) {
mermaid << start.transition << " :"
<< "active"
<< ", "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
start.stamp.time_since_epoch())
.count()
<< ','
<< std::chrono::duration_cast<std::chrono::milliseconds>(
now_timestamp.time_since_epoch())
.count()
<< '\n';
}

return mermaid.str();
}
} // namespace symmetri
Loading

0 comments on commit b52d0a2

Please sign in to comment.