actuator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 459b67293c89bd2854e96adbaf484ec950774fc2
4
+ data.tar.gz: cdea3e412329800ae7d1553fae14dd509a0c03cc
5
+ SHA512:
6
+ metadata.gz: 0b25766398cac34c1ec62b3ac959381850517afcae1cbbd4b59ef7cd8a67b1c0e3de2bcae698007023a47cec6711a1e523c8b1824a0fc000147c42efb6a981b8
7
+ data.tar.gz: c9972bc77b4e58b94ca2c74367aa1cf116629cf5b916c7a41de5623320ffb2c76dabcff0f856f01296410e87dd609839c143a1e4b6b144e9db7f1ff418a6ed75
@@ -0,0 +1,13 @@
1
+ # -*- ruby -*-
2
+
3
+ require "autotest/restart"
4
+
5
+ Autotest.add_hook :initialize do |at|
6
+ at.add_mapping(/.*\.c/) do |f, _|
7
+ at.files_matching(/test_.*rb$/)
8
+ end
9
+ end
10
+
11
+ Autotest.add_hook :run_command do |at|
12
+ system "rake clean compile"
13
+ end
@@ -0,0 +1,3 @@
1
+ === 0.0.1 / 2018-01-23
2
+
3
+ * Initial alpha release. Simple and efficient high precision timer scheduling implemented in C++.
@@ -0,0 +1,28 @@
1
+ .autotest
2
+ README.md
3
+ History.txt
4
+ Manifest.txt
5
+ Rakefile
6
+ lib/actuator.rb
7
+ lib/actuator/fiber.rb
8
+ lib/actuator/fiber_pool.rb
9
+ lib/actuator/job.rb
10
+ lib/actuator/mutex.rb
11
+ lib/actuator/mutex/replace.rb
12
+ ext/actuator/extconf.rb
13
+ ext/actuator/actuator.h
14
+ ext/actuator/actuator.c
15
+ ext/actuator/clock.h
16
+ ext/actuator/clock.c
17
+ ext/actuator/debug.h
18
+ ext/actuator/debug.c
19
+ ext/actuator/ruby_helpers.h
20
+ ext/actuator/ruby_helpers.c
21
+ ext/actuator/reactor.h
22
+ ext/actuator/reactor.cpp
23
+ ext/actuator/timer.h
24
+ ext/actuator/timer.cpp
25
+ ext/actuator/log.h
26
+ ext/actuator/log.cpp
27
+ test/setup_test.rb
28
+ test/test_actuator.rb
@@ -0,0 +1,105 @@
1
+ ## Welcome to Actuator
2
+
3
+ Code: https://github.com/bawNg/actuator \
4
+ Bugs: https://github.com/bawNg/actuator/issues
5
+
6
+ Actuator provides high precision scheduling of light weight timers for async and fibered real-time Ruby applications.
7
+ Even on Windows where kernel precision is lower, average timer accuracy is ~2 us on modern high end hardware (subject to system load).
8
+
9
+ This C++ Ruby extension allows time-sensitive applications to replace native threads with jobs that run in pooled fibers.
10
+
11
+ This is an alpha release, not recommended for production use. While fairly well tested on Windows and Linux, minimal safety
12
+ and error handling has been implemented in order to minimize overhead. Using the API wrong may result in a segfault.
13
+
14
+ #### Features
15
+
16
+ * Provides a high precision float representing the current reactor time
17
+ * High precision single threaded timer callback scheduling
18
+ * Light weight jobs can be used to replace threads with pooled fibers
19
+ * Job-based implementation of sleep, join, kill, Mutex and ConditionVariable
20
+ * Job-aware sample-based CPU profiling API and execution time warnings
21
+ * Warnings for timers that fire later than the configured threshold
22
+ * Low overhead timestamped logging API which is thread-safe
23
+
24
+ #### Supported platforms
25
+
26
+ * MRI Ruby 2.x (1.9 is probably compatible but has not been tested).
27
+ * High precision timer support is implemented for Windows, Linux and OSX.
28
+
29
+ #### Getting started
30
+
31
+ Install the gem with `gem install actuator` or by adding it to your bundle.
32
+
33
+ ```ruby
34
+ require 'actuator'
35
+
36
+ Actuator.run do
37
+ # Schedule a once off timer which fires after 500 us delay
38
+ Timer.in 0.0005 do
39
+ Log.puts "Reactor time: #{Actuator.now}"
40
+ end
41
+ # Schedule a repeating timer which fires every 50ms
42
+ Timer.every 0.05 do
43
+ Log.puts "50ms have passed"
44
+ end
45
+ # Schedule a timer which we will cancel before it expires
46
+ timer = Timer.in 0.005 do
47
+ Log.warn "This should never be printed"
48
+ end
49
+ # Create a new job which executes inside a pooled fiber
50
+ job1 = Actuator.defer do
51
+ begin
52
+ Log.puts "Job has started"
53
+ # Yield from the job fiber for 200ms
54
+ Job.sleep 0.2
55
+ Log.warn "Job 1 finished sleeping even though it should have been killed"
56
+ ensure
57
+ # The stack is unwound when a job is killed so ensure blocks are executed
58
+ Log.puts "Job 1 is ending"
59
+ end
60
+ end
61
+ # Create another new job
62
+ job2 = Actuator.defer do
63
+ # Yield from job fiber until job1 ends
64
+ job1.join
65
+ Log.puts "Job 2 finished waiting for Job 1"
66
+ end
67
+ Job.sleep 0.001
68
+ # Cancel the 5ms timer that we scheduled above
69
+ timer.destroy
70
+ Job.sleep 0.1
71
+ # Kill job1 before it finishes sleeping
72
+ job1.kill
73
+ # Wait for job2 to end naturally
74
+ job2.join
75
+ Log.puts "Job 2 has ended"
76
+ # Shutdown the reactor
77
+ Actuator.stop
78
+ end
79
+ ```
80
+
81
+ #### Known issues
82
+ - Memory for active timers will not be freed when calling Actuator.stop
83
+ - The profiling API will include time spent yielded from the job.
84
+ The job-aware implementation has been commented out to reduce overhead until the profiling has been rewritten in C++.
85
+ - Minimal safety checks and error handling has been implemented in order to minimize overhead. Using the API wrong may result in a segfault.
86
+ Feel free to open a bug report for any such cases that you may come across.
87
+
88
+
89
+ #### Contributing
90
+
91
+ Actuator is an open source project and any contributions which improve the project are encouraged.
92
+ Feel free to make a pull request for any contributions that you would like to see merged into the project.
93
+
94
+ After cloning the source from this repo, run `rake test` to build the C++ extension and run the reactor and precision tests.
95
+
96
+ Some ways that you can contribute include:
97
+ - Create new [bug reports](https://github.com/bawNg/actuator/issues/new)
98
+ - Reviewing and providing detailed feedback on existing [issues](https://github.com/bawNg/actuator/issues/new)
99
+ - Writing and improving documentation
100
+ - Writing additional tests
101
+ - Implementing new features
102
+
103
+ #### License
104
+
105
+ Actuator is released under the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require "rubygems"
4
+ require "hoe"
5
+ require 'rake/extensiontask'
6
+
7
+ Hoe.plugin :clean
8
+
9
+ Hoe.spec "actuator" do
10
+ developer 'bawNg', 'bawng@intoxicated.co.za'
11
+ license 'MIT'
12
+ self.urls = 'https://github.com/bawNg/actuator'
13
+
14
+ self.extra_dev_deps << ['rake-compiler', '>= 0']
15
+ self.extra_dev_deps << ['minitest', '>= 0']
16
+ self.spec_extras = { extensions: ['ext/actuator/extconf.rb'] }
17
+
18
+ Rake::ExtensionTask.new('actuator', spec) do |ext|
19
+ ext.lib_dir = File.join('lib', 'actuator')
20
+ end
21
+ end
22
+
23
+ Rake::Task[:test].prerequisites << :clean << :compile
@@ -0,0 +1,2 @@
1
+ #include <ruby.h>
2
+ #include "actuator.h"
@@ -0,0 +1,16 @@
1
+ #ifndef ACTUATOR_H
2
+ #define ACTUATOR_H
3
+
4
+ #include "clock.h"
5
+
6
+ #ifdef __cplusplus
7
+ extern "C" {
8
+ #endif
9
+
10
+ extern VALUE ActuatorClass;
11
+
12
+ #ifdef __cplusplus
13
+ }
14
+ #endif
15
+
16
+ #endif
@@ -0,0 +1,81 @@
1
+ #include <stdint.h>
2
+ #include "clock.h"
3
+ #include "debug.h"
4
+
5
+ #if !defined(__APPLE__) && !defined(_WIN32)
6
+ #define HAVE_POSIX_TIMER
7
+ #include <time.h>
8
+ #ifdef CLOCK_MONOTONIC
9
+ #define CLOCKID CLOCK_MONOTONIC
10
+ #else
11
+ #define CLOCKID CLOCK_REALTIME
12
+ #endif
13
+ #endif
14
+
15
+ #ifdef __APPLE__
16
+ #define HAVE_MACH_TIMER
17
+ #include <mach/mach_time.h>
18
+ #endif
19
+
20
+ #if defined(_WIN32) || defined(_MSC_VER)
21
+ #define HAVE_WIN32_TIMER
22
+ #include <windows.h>
23
+
24
+ #ifdef _MSC_VER
25
+ #include <Strsafe.h>
26
+ #endif
27
+ #endif
28
+
29
+ static uint64_t last_count;
30
+ static uint64_t frequency;
31
+
32
+ void clock_init()
33
+ {
34
+ #ifdef HAVE_POSIX_TIMER
35
+ struct timespec freq, time;
36
+ clock_getres(CLOCKID, &freq);
37
+ clock_gettime(CLOCKID, &time);
38
+ frequency = 1000000000LL * freq.tv_nsec;
39
+ last_count = time.tv_sec * 1000000000LL + time.tv_nsec;
40
+ #endif
41
+ #ifdef HAVE_MACH_TIMER
42
+ mach_timebase_info_data_t frequency_nsec;
43
+ mach_timebase_info(&frequency_nsec);
44
+ frequency = 1000000000LL * frequency_nsec.numer / frequency_nsec.denom;
45
+ last_count = mach_absolute_time();
46
+ #endif
47
+ #ifdef HAVE_WIN32_TIMER
48
+ {
49
+ LARGE_INTEGER tmp;
50
+ QueryPerformanceFrequency(&tmp);
51
+ frequency = tmp.QuadPart;
52
+ QueryPerformanceCounter(&tmp);
53
+ last_count = tmp.QuadPart;
54
+ }
55
+ #endif
56
+ //puts("Timer resolution: %g ns", 1e9 / (double)frequency);
57
+ }
58
+
59
+ double clock_time()
60
+ {
61
+ uint64_t now;
62
+ double delta;
63
+ #ifdef HAVE_POSIX_TIMER
64
+ struct timespec time;
65
+ clock_gettime(CLOCKID, &time);
66
+ now = time.tv_sec * 1000000000LL + time.tv_nsec;
67
+ delta = (now - last_count) / (double)frequency;
68
+ #endif
69
+ #ifdef HAVE_MACH_TIMER
70
+ now = mach_absolute_time();
71
+ #endif
72
+ #ifdef HAVE_WIN32_TIMER
73
+ {
74
+ LARGE_INTEGER tmp;
75
+ QueryPerformanceCounter(&tmp);
76
+ now = tmp.QuadPart;
77
+ delta = (now - last_count) / (double)frequency;
78
+ }
79
+ #endif
80
+ return delta;
81
+ }
@@ -0,0 +1,15 @@
1
+ #ifndef ACTUATOR_CLOCK_H
2
+ #define ACTUATOR_CLOCK_H
3
+
4
+ #ifdef __cplusplus
5
+ extern "C"{
6
+ #endif
7
+
8
+ void clock_init();
9
+ double clock_time();
10
+
11
+ #ifdef __cplusplus
12
+ }
13
+ #endif
14
+
15
+ #endif
@@ -0,0 +1,4 @@
1
+ #include "debug.h"
2
+
3
+ FILE *debug_file = 0;
4
+ FILE *log_file = 0;
@@ -0,0 +1,56 @@
1
+ #ifndef ACTUATOR_DEBUG_H
2
+ #define ACTUATOR_DEBUG_H
3
+
4
+ #include <stdio.h>
5
+
6
+ #ifdef __cplusplus
7
+ extern "C" {
8
+ #endif
9
+
10
+ #define DEBUG 0
11
+
12
+ #ifdef DEBUG
13
+ #define puts(...) do { \
14
+ fprintf(stdout, __VA_ARGS__); \
15
+ fprintf(stdout, "\n"); \
16
+ fflush(stdout); \
17
+ } while (0)
18
+ #else
19
+ #define puts(...)
20
+ #endif
21
+
22
+ #ifdef DEBUG
23
+ #define debug(...) do { \
24
+ if (!debug_file) debug_file = fopen("debug.txt", "w"); \
25
+ fprintf(debug_file, __VA_ARGS__); \
26
+ fflush(debug_file); \
27
+ } while (0)
28
+ #define DEBUG_LOG(...) do { \
29
+ if (!debug_file) debug_file = fopen("debug.txt", "w"); \
30
+ fprintf(debug_file, __VA_ARGS__); \
31
+ fflush(debug_file); \
32
+ } while (0)
33
+ #else
34
+ #define debug(...)
35
+ #define DEBUG_LOG(...)
36
+ #endif
37
+
38
+ #define INFO(...) do { \
39
+ fprintf(log_file, "%010.3f INFO ", clock_time() * 1000); \
40
+ fprintf(log_file, __VA_ARGS__); \
41
+ fprintf(log_file, "\n"); \
42
+ fflush(log_file); \
43
+ } while (0)
44
+
45
+ #define WARN(...) do { \
46
+ fprintf(log_file, "%010.3f WARN ", clock_time() * 1000); \
47
+ fprintf(log_file, __VA_ARGS__); \
48
+ fprintf(log_file, "\n"); \
49
+ fflush(log_file); \
50
+ } while (0)
51
+
52
+ #ifdef __cplusplus
53
+ }
54
+ #endif
55
+
56
+ #endif
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+
3
+ $CXXFLAGS += " -std=c++11 "
4
+
5
+ create_makefile 'actuator/actuator'
@@ -0,0 +1,134 @@
1
+ #include <ruby.h>
2
+ #include "reactor.h"
3
+
4
+ enum class LogLevel
5
+ {
6
+ None,
7
+ Error,
8
+ Warn,
9
+ Info,
10
+ Debug
11
+ };
12
+
13
+ FILE *Log::debug_file = 0;
14
+ FILE *Log::log_file = 0;
15
+
16
+ static VALUE LogClass;
17
+ static LogLevel Level = LogLevel::Info;
18
+
19
+ static void Print(const char *tag, const char *format, va_list args)
20
+ {
21
+ if (!Log::log_file) return;
22
+ fprintf(Log::log_file, "%010.3f %s ", clock_time() * 1000, tag);
23
+ vfprintf(Log::log_file, format, args);
24
+ fprintf(Log::log_file, "\n");
25
+ fflush(Log::log_file);
26
+ }
27
+
28
+ static VALUE Log_Debug(VALUE self, VALUE message)
29
+ {
30
+ Log::Debug("%s", StringValueCStr(message));
31
+ }
32
+
33
+ static VALUE Log_Info(VALUE self, VALUE message)
34
+ {
35
+ Log::Info("%s", StringValueCStr(message));
36
+ }
37
+
38
+ static VALUE Log_Warn(VALUE self, VALUE message)
39
+ {
40
+ Log::Warn("%s", StringValueCStr(message));
41
+ }
42
+
43
+ static VALUE Log_Error(VALUE self, VALUE message)
44
+ {
45
+ Log::Error("%s", StringValueCStr(message));
46
+ }
47
+
48
+ static VALUE Log_SetFilePath(VALUE self, VALUE path)
49
+ {
50
+ if (NIL_P(path)) {
51
+ Log::log_file = 0;
52
+ } else if (SYMBOL_P(path) && SYM2ID(path) == rb_intern("stdout")) {
53
+ Log::log_file = stdout;
54
+ } else if (RB_TYPE_P(path, T_STRING) && CLASS_OF(path) == rb_cString) {
55
+ Log::log_file = fopen(rb_id2name(SYM2ID(path)), "w");
56
+ } else {
57
+ rb_raise(rb_eRuntimeError, "path must be a string, :stdout or nil");
58
+ }
59
+ return Qnil;
60
+ }
61
+
62
+ static VALUE Log_SetLevel(VALUE self, VALUE level)
63
+ {
64
+ if (SYMBOL_P(level)) {
65
+ if (SYM2ID(level) == rb_intern("debug")) {
66
+ Level = LogLevel::Debug;
67
+ return Qnil;
68
+ }
69
+ if (SYM2ID(level) == rb_intern("info")) {
70
+ Level = LogLevel::Info;
71
+ return Qnil;
72
+ }
73
+ if (SYM2ID(level) == rb_intern("warn")) {
74
+ Level = LogLevel::Warn;
75
+ return Qnil;
76
+ }
77
+ if (SYM2ID(level) == rb_intern("error")) {
78
+ Level = LogLevel::Error;
79
+ return Qnil;
80
+ }
81
+ }
82
+ rb_raise(rb_eRuntimeError, "level must be :debug, :info, :warn or :error");
83
+ return Qnil;
84
+ }
85
+
86
+ void Log::Setup()
87
+ {
88
+ log_file = stdout;
89
+
90
+ LogClass = rb_define_module("Log");
91
+
92
+ rb_define_singleton_method(LogClass, "debug", RUBY_METHOD_FUNC(Log_Debug), 1);
93
+ rb_define_singleton_method(LogClass, "puts", RUBY_METHOD_FUNC(Log_Info), 1);
94
+ rb_define_singleton_method(LogClass, "warn", RUBY_METHOD_FUNC(Log_Warn), 1);
95
+ rb_define_singleton_method(LogClass, "error", RUBY_METHOD_FUNC(Log_Error), 1);
96
+ rb_define_singleton_method(LogClass, "file_path=", RUBY_METHOD_FUNC(Log_SetFilePath), 1);
97
+ rb_define_singleton_method(LogClass, "level=", RUBY_METHOD_FUNC(Log_SetLevel), 1);
98
+ }
99
+
100
+ void Log::Debug(const char *format, ...)
101
+ {
102
+ if (Level < LogLevel::Debug) return;
103
+ va_list args;
104
+ va_start(args, format);
105
+ Print("DEBUG", format, args);
106
+ va_end(args);
107
+ }
108
+
109
+ void Log::Info(const char *format, ...)
110
+ {
111
+ if (Level < LogLevel::Info) return;
112
+ va_list args;
113
+ va_start(args, format);
114
+ Print("INFO", format, args);
115
+ va_end(args);
116
+ }
117
+
118
+ void Log::Warn(const char *format, ...)
119
+ {
120
+ if (Level < LogLevel::Warn) return;
121
+ va_list args;
122
+ va_start(args, format);
123
+ Print("WARN", format, args);
124
+ va_end(args);
125
+ }
126
+
127
+ void Log::Error(const char *format, ...)
128
+ {
129
+ if (Level < LogLevel::Error) return;
130
+ va_list args;
131
+ va_start(args, format);
132
+ Print("ERROR", format, args);
133
+ va_end(args);
134
+ }