Skip to content

Commit

Permalink
Add guiding texts to examples and cleanup some things
Browse files Browse the repository at this point in the history
  • Loading branch information
Oipo committed Nov 24, 2023
1 parent 7b718ed commit b68bc9e
Show file tree
Hide file tree
Showing 30 changed files with 218 additions and 127 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ TL;DR: Node.js-style event loops with coroutines and dependency injection, excep

Ichor borrows from the concept of [Fearless Concurrency](https://doc.rust-lang.org/book/ch16-00-concurrency.html) and offers thread confinement.

# Boost supports event loops, dependency injection, networking, can do all of that, and more. Any reason i'd want to use your lib instead?
### Boost supports event loops, dependency injection, networking, can do all of that, and more. Any reason i'd want to use your lib instead?

Excellent question! Please see [this page](./docs/04-WhyUseIchor.md) to get a more in-depth answer.

Expand Down
106 changes: 28 additions & 78 deletions benchmarks/serializer_benchmark/ichor_serializer_benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include <thread>
#include <array>
#include "../../examples/common/TestMsgRapidJsonSerializer.h"
#include "../../examples/common/TestMsgBoostJsonSerializer.h"
#include "../../examples/common/lyra.hpp"

uint64_t sizeof_test{};
Expand All @@ -19,14 +18,8 @@ int main(int argc, char *argv[]) {

bool showHelp{};
bool singleOnly{};
bool rapidjsonOnly{};
[[maybe_unused]] bool boostjsonOnly{};

auto cli = lyra::help(showHelp)
| lyra::opt(rapidjsonOnly)["-r"]["--rapid-json"]("enable only rapid json")
#ifdef ICHOR_USE_BOOST_JSON
| lyra::opt(boostjsonOnly)["-b"]["--boost-json"]("enable only boost json")
#endif
| lyra::opt(singleOnly)["-s"]["--single"]("Single core only");

auto result = cli.parse( { argc, argv } );
Expand All @@ -40,83 +33,40 @@ int main(int argc, char *argv[]) {
return 0;
}

if((!rapidjsonOnly && !boostjsonOnly) || rapidjsonOnly)
{
{
auto start = std::chrono::steady_clock::now();
auto queue = std::make_unique<MultimapQueue>();
auto &dm = queue->createManager();
dm.createServiceManager<LoggerFactory<NullLogger>, ILoggerFactory>();
dm.createServiceManager<TestMsgRapidJsonSerializer, ISerializer<TestMsg>>();
dm.createServiceManager<TestService>();
queue->start(CaptureSigInt);
auto end = std::chrono::steady_clock::now();
std::cout << fmt::format("{} single threaded rapidjson ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n", argv[0], std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(), getPeakRSS(),
std::floor(1'000'000. / static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()) * SERDE_COUNT * static_cast<double>(sizeof_test) / 1'000'000.));
}

if(!singleOnly) {
auto start = std::chrono::steady_clock::now();
std::array<std::thread, threadCount> threads{};
std::array<MultimapQueue, threadCount> queues{};
for (uint_fast32_t i = 0, j = 0; i < threadCount; i++, j += 2) {
threads[i] = std::thread([&queues, i] {
auto &dm = queues[i].createManager();
dm.createServiceManager<LoggerFactory<NullLogger>, ILoggerFactory>();
dm.createServiceManager<TestMsgRapidJsonSerializer, ISerializer<TestMsg>>();
dm.createServiceManager<TestService>();
queues[i].start(CaptureSigInt);
});
}
for (uint_fast32_t i = 0; i < threadCount; i++) {
threads[i].join();
}
auto end = std::chrono::steady_clock::now();
std::cout << fmt::format("{} multi threaded rapidjson ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n",
argv[0], std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(), getPeakRSS(),
std::floor(1'000'000. / static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()) * SERDE_COUNT * threadCount * static_cast<double>(sizeof_test) / 1'000'000.));
}
auto start = std::chrono::steady_clock::now();
auto queue = std::make_unique<MultimapQueue>();
auto &dm = queue->createManager();
dm.createServiceManager<LoggerFactory<NullLogger>, ILoggerFactory>();
dm.createServiceManager<TestMsgRapidJsonSerializer, ISerializer<TestMsg>>();
dm.createServiceManager<TestService>();
queue->start(CaptureSigInt);
auto end = std::chrono::steady_clock::now();
std::cout << fmt::format("{} single threaded rapidjson ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n", argv[0], std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(), getPeakRSS(),
std::floor(1'000'000. / static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()) * SERDE_COUNT * static_cast<double>(sizeof_test) / 1'000'000.));
}

#ifdef ICHOR_USE_BOOST_JSON
if((!rapidjsonOnly && !boostjsonOnly) || boostjsonOnly)
{
{
auto start = std::chrono::steady_clock::now();
auto queue = std::make_unique<MultimapQueue>();
auto &dm = queue->createManager();
dm.createServiceManager<LoggerFactory<NullLogger>, ILoggerFactory>();
dm.createServiceManager<TestMsgBoostJsonSerializer, ISerializer<TestMsg>>();
dm.createServiceManager<TestService>();
queue->start(CaptureSigInt);
auto end = std::chrono::steady_clock::now();
std::cout << fmt::format("{} single threaded boost.JSON ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n", argv[0], std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(), getPeakRSS(),
std::floor(1'000'000. / static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()) * SERDE_COUNT * static_cast<double>(sizeof_test) / 1'000'000.));
if(!singleOnly) {
auto start = std::chrono::steady_clock::now();
std::array<std::thread, threadCount> threads{};
std::array<MultimapQueue, threadCount> queues{};
for (uint_fast32_t i = 0, j = 0; i < threadCount; i++, j += 2) {
threads[i] = std::thread([&queues, i] {
auto &dm = queues[i].createManager();
dm.createServiceManager<LoggerFactory<NullLogger>, ILoggerFactory>();
dm.createServiceManager<TestMsgRapidJsonSerializer, ISerializer<TestMsg>>();
dm.createServiceManager<TestService>();
queues[i].start(CaptureSigInt);
});
}

if(!singleOnly) {
auto start = std::chrono::steady_clock::now();
std::array<std::thread, threadCount> threads{};
std::array<MultimapQueue, threadCount> queues{};
for (uint_fast32_t i = 0, j = 0; i < threadCount; i++, j += 2) {
threads[i] = std::thread([&queues, i] {
auto &dm = queues[i].createManager();
dm.createServiceManager<LoggerFactory<NullLogger>, ILoggerFactory>();
dm.createServiceManager<TestMsgBoostJsonSerializer, ISerializer<TestMsg>>();
dm.createServiceManager<TestService>();
queues[i].start(CaptureSigInt);
});
}
for (uint_fast32_t i = 0; i < threadCount; i++) {
threads[i].join();
}
auto end = std::chrono::steady_clock::now();
std::cout << fmt::format("{} multi threaded boost.JSON ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n",
argv[0], std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(), getPeakRSS(),
std::floor(1'000'000. / static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()) * SERDE_COUNT * threadCount * static_cast<double>(sizeof_test) / 1'000'000.));
for (uint_fast32_t i = 0; i < threadCount; i++) {
threads[i].join();
}
auto end = std::chrono::steady_clock::now();
std::cout << fmt::format("{} multi threaded rapidjson ran for {:L} µs with {:L} peak memory usage {:L} MB/s\n",
argv[0], std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(), getPeakRSS(),
std::floor(1'000'000. / static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()) * SERDE_COUNT * threadCount * static_cast<double>(sizeof_test) / 1'000'000.));
}
#endif

return 0;
}
14 changes: 8 additions & 6 deletions docs/04-WhyUseIchor.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ Here's a short hand comparison between Ichor and various DI libraries:
||Ichor|Hypodermic|Boost.DI|Google.Fruit|CppMicroservices|
|:-|:-|:-|:-|:-|:-|
|Runtime/compile-time|Runtime|Runtime|Compile time (with some runtime)|Compile time (with some runtime)|Runtime|
|Constructor Injection|Yes|Yes|Yes|Yes|No|
|Per-instance resolving|Yes|No|No|No|Yes|
|Factories|Yes|No?|Partial|Partial|Yes|
|Coroutines|Yes|No|No|No|No|
|Optional dependencies|Yes|No|No|No|Yes?|
|Per-instance lifetime|Yes|No|No|No|Yes|
|Constructor Injection||||||
|Per-instance resolving||||||
|Factories||?|Partial|Partial||
|Coroutines||||||
|Optional dependencies|||||?|
|Per-instance lifetime||||||
|Minimum c++|20|11|14|11|17|

Question marks denote me being unsure

# But Boost.BEAST also provides https and websockets. Why would I use your lib?

If you need all the bells and whistles that Boost.BEAST provides, then by all means, use that. If you need performance, the techempowered benchmarks will probably tell you to use Drogon or something similar. Ichor has a gap here.
Expand Down
42 changes: 0 additions & 42 deletions examples/common/TestMsgBoostJsonSerializer.h

This file was deleted.

1 change: 1 addition & 0 deletions examples/etcd_example/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <ichor/services/logging/LoggerFactory.h>
#include <ichor/services/etcd/EtcdService.h>

// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it.
#ifdef ICHOR_USE_SPDLOG
#include <ichor/services/logging/SpdlogLogger.h>
#define LOGGER_TYPE SpdlogLogger
Expand Down
38 changes: 38 additions & 0 deletions examples/event_statistics_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Event Statistics Example

This example showcases Ichor's capability to hook into the event loop system. It uses the pre- and post-event hooks to determine how long each event took to resolve and prints out the min/max/avg per event type at exit.

It uses the `EventStatisticsService` provided by ichor, which in turn uses `preInterceptEvent` and `postInterceptEvent`.

This demonstrates the following concepts:
* Using the provided `EventStatisticsService` for performance measurements
* Using the Timer subsystem to generate some artificial load to measure
* How to implement ones own Event Interceptor service (see below)

Implementing such a service yourself is also possible by calling the `registerEventInterceptor<>` function on the Dependency Manager:

```c++
class MyClass {
public:
MyClass(DependencyManager *dm) {
// register the interceptor and keep a reference it, as the destructor automatically unregisters it.
_interceptorRegistration = dm->registerEventInterceptor<Event>(this, this);
}

void preInterceptEvent(Event const &evt) {
// We don't need to check for evt.id or evt.type here, as postInterceptEvent() is guaranteed to be called directly after processing the event
// The downside is then that each async execution point of an event is counted as a separate instance. I.e. every co_await is a new event with its own processing time.
_startProcessingTimestamp = std::chrono::steady_clock::now();
}

void postInterceptEvent(Event const &evt, bool processed) {
auto now = std::chrono::steady_clock::now();
auto processingTime = now - _startProcessingTimestamp;
fmt::print("Processing of event type {} took {} ns", evt.name, std::chrono::duration_cast<std::chrono::nanoseconds>(processingTime).count());
}

private:
EventInterceptorRegistration _interceptorRegistration{};
std::chrono::time_point<std::chrono::steady_clock> _startProcessingTimestamp{};
};
```
1 change: 1 addition & 0 deletions examples/event_statistics_example/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <ichor/services/timer/TimerFactoryFactory.h>
#include "UsingStatisticsService.h"

// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it.
#ifdef ICHOR_USE_SPDLOG
#include <ichor/services/logging/SpdlogLogger.h>
#define LOGGER_TYPE SpdlogLogger
Expand Down
20 changes: 20 additions & 0 deletions examples/http_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# HTTP Example

This example starts up an HTTP server and an HTTP client that connects to said server. The client sends a JSON message to which the server responds and then quits.

This demonstrates the following concepts:
* Defining and using serializers
* How to set up an HTTP server
* How to set up an HTTP client using the `ClientFactory`
* Using the advanced parameter `Spinlock` in the `MultimapQueue`.

## Command Line Arguments

```shell
-a, --address, "Address to bind to, e.g. 127.0.0.1"
-v, --verbosity, "Increase logging for each -v"
-t, --threads, "Number of threads to use for I/O, default: 1"
-p, --spinlock, "Spinlock 10ms before going to sleep, improves latency in high workload cases at the expense of CPU usage"
-s, --silent, "No output"
```

6 changes: 6 additions & 0 deletions examples/http_example/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <ichor/services/network/ClientFactory.h>
#include <ichor/services/serialization/ISerializer.h>

// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it.
#ifdef ICHOR_USE_SPDLOG
#include <ichor/services/logging/SpdlogFrameworkLogger.h>
#include <ichor/services/logging/SpdlogLogger.h>
Expand Down Expand Up @@ -82,10 +83,15 @@ int main(int argc, char *argv[]) {
} else {
dm.createServiceManager<LoggerFactory<LOGGER_TYPE>, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any<LogLevel>(level)}});
}
// Create the JSON serializer for the TestMsg class
dm.createServiceManager<TestMsgRapidJsonSerializer, ISerializer<TestMsg>>();
// Create the Event Thread for Boost.ASIO
dm.createServiceManager<AsioContextService, IAsioContextService>(Properties{{"Threads", Ichor::make_any<uint64_t>(threads)}});
// Create the HTTP server binding to the given address
dm.createServiceManager<HttpHostService, IHttpHostService>(Properties{{"Address", Ichor::make_any<std::string>(address)}, {"Port", Ichor::make_any<uint16_t>(static_cast<uint16_t>(8001))}});
// Setup a factory which creates HTTP clients for every class requesting an IHttpConnectionService
dm.createServiceManager<ClientFactory<HttpConnectionService, IHttpConnectionService>>();
// Create the class that we defined in this example, to setup a /test endpoint in the server, to request (and therefore create) an HTTP client and send a message to the /test endpoint using the serializer for TestMsg
dm.createServiceManager<UsingHttpService>(Properties{{"Address", Ichor::make_any<std::string>(address)}, {"Port", Ichor::make_any<uint16_t>(static_cast<uint16_t>(8001))}});
queue->start(CaptureSigInt);
auto end = std::chrono::steady_clock::now();
Expand Down
1 change: 1 addition & 0 deletions examples/http_ping_pong/ping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <ichor/services/logging/NullLogger.h>
#include <ichor/services/timer/TimerFactoryFactory.h>

// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it.
#ifdef ICHOR_USE_SPDLOG
#include <ichor/services/logging/SpdlogFrameworkLogger.h>
#include <ichor/services/logging/SpdlogLogger.h>
Expand Down
1 change: 1 addition & 0 deletions examples/http_ping_pong/pong.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <ichor/services/serialization/ISerializer.h>
#include <ichor/services/logging/NullLogger.h>

// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it.
#ifdef ICHOR_USE_SPDLOG
#include <ichor/services/logging/SpdlogFrameworkLogger.h>
#include <ichor/services/logging/SpdlogLogger.h>
Expand Down
9 changes: 9 additions & 0 deletions examples/minimal_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Minimal Example

This example shows how to set up and quit an Ichor program

This demonstrates the following concepts:
* How to create the Queue and DependencyManager
* How to register your own services
* How to use the Timer subsystem
* How to quit the program from within the event loop
8 changes: 8 additions & 0 deletions examples/multithreaded_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Multithreaded Example

This example shows how to use multiple DependencyManagers on multiple threads and how to communicate safely between the two.

This demonstrates the following concepts:
* Creating multiple queues and DependencyManagers
* How to use the `CommunicationChannel` to communicate between different queues
* How to tell a different DependencyManager/queue to quit
1 change: 1 addition & 0 deletions examples/multithreaded_example/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <ichor/event_queues/MultimapQueue.h>
#include <ichor/services/logging/LoggerFactory.h>

// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it.
#ifdef ICHOR_USE_SPDLOG
#include <ichor/services/logging/SpdlogLogger.h>
#define LOGGER_TYPE SpdlogLogger
Expand Down
8 changes: 8 additions & 0 deletions examples/optional_dependency_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Optional Dependency Example

This example shows how to use optional dependencies, including multiple of the same type.

This demonstrates the following concepts:
* Requesting optional dependencies, separate from the lifecycle of the requesting service
* Normally, a service does not start until all of its required dependencies are met. Optional dependencies can come and go without starting/stopping the requesting service
* How to handle multiple dependencies of the same type
1 change: 1 addition & 0 deletions examples/optional_dependency_example/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <ichor/event_queues/MultimapQueue.h>
#include <ichor/services/logging/LoggerFactory.h>

// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it.
#ifdef ICHOR_USE_SPDLOG
#include <ichor/services/logging/SpdlogLogger.h>
#define LOGGER_TYPE SpdlogLogger
Expand Down
3 changes: 3 additions & 0 deletions examples/realtime_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Realtime Example

This example shows how Ichor behaves in a more real-time environment. It also includes code for Linux to increase the chance of meeting execution deadlines.
Loading

0 comments on commit b68bc9e

Please sign in to comment.