hakuban 0.5.2 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,275 @@
1
+ require 'hakuban/hakuban'
2
+ require 'ostruct'
3
+
4
+ #TODO: prevent usage of old object?
5
+ #TODO: destructors
6
+
7
+ module Hakuban
8
+
9
+ class ObjectManager
10
+
11
+ attr_reader :contract
12
+
13
+ class Event < OpenStruct; end
14
+
15
+ def initialize(contract, object_class, block)
16
+ @contract = contract
17
+ @objects_mutex = Mutex.new
18
+ @objects = {}
19
+ @running_handlers = {}
20
+ @event_queue = Queue.new
21
+
22
+ # This callback gets called from a separate thread, with no async reactor running.
23
+ # So, we only use it to forward actions to the main thread.
24
+ @ffi_callback = proc { |descriptor, action|
25
+ @event_queue << Event.new(action: action, descriptor: descriptor)
26
+ }
27
+ @ffi_events = @contract.new_callback_event_queue
28
+ @ffi_events.callback_register(&@ffi_callback)
29
+
30
+ @async = async_run {
31
+ while @event_queue and event = @event_queue.shift
32
+ @objects_mutex.synchronize {
33
+ case event.action
34
+ when :insert
35
+ raise if @objects[event.descriptor]
36
+ @objects[event.descriptor] = object_class.new(@contract, event.descriptor)
37
+ @event_queue << Event.new(action: :handler_start, descriptor: event.descriptor) if block
38
+ when :change
39
+ raise if not @objects[event.descriptor]
40
+ @objects[event.descriptor].do_change(:change)
41
+ when :remove
42
+ raise if not @objects[event.descriptor]
43
+ @objects[event.descriptor].do_change(:remove)
44
+ @objects.delete(event.descriptor)
45
+ when :handler_start
46
+ if (object = @objects[event.descriptor]) and !object.handler_already_run and !@running_handlers[event.descriptor]
47
+ descriptor_for_lambda = event.descriptor
48
+ @running_handlers[event.descriptor] = async_run {
49
+ object.run(block)
50
+ @event_queue << Event.new(action: :handler_finished, descriptor: descriptor_for_lambda) if @event_queue
51
+ }
52
+ end
53
+ when :handler_finished
54
+ raise if not @running_handlers[event.descriptor]
55
+ @running_handlers.delete(event.descriptor)
56
+ @event_queue << Event.new(action: :handler_start, descriptor: event.descriptor) if @objects[event.descriptor]
57
+ when :drop
58
+ @ffi_events.callback_unregister
59
+ @objects.values.each { |object| object.do_change(:remove) }
60
+ while @running_handlers.size > 0
61
+ event = @event_queue.shift
62
+ @running_handlers.delete(event.descriptor) if event.action == :handler_finished
63
+ end
64
+ @event_queue.clear
65
+ @objects.clear
66
+ @contract.drop
67
+ @contract, @event_queue = nil, nil
68
+ break
69
+ end
70
+ }
71
+ end
72
+ }
73
+ end
74
+
75
+
76
+ def objects
77
+ @objects_mutex.synchronize {
78
+ @objects.dup
79
+ }
80
+ end
81
+
82
+
83
+ def object
84
+ @objects_mutex.synchronize {
85
+ @objects.values.first
86
+ }
87
+ end
88
+
89
+
90
+ def drop
91
+ drop_nonblock
92
+ async_join(@async)
93
+ end
94
+
95
+
96
+ def drop_nonblock
97
+ if @contract
98
+ @event_queue << Event.new(action: :drop)
99
+ end
100
+ end
101
+ end
102
+
103
+
104
+
105
+ class ManagedObject
106
+
107
+ attr_reader :descriptor, :async, :handler_already_run
108
+
109
+
110
+ def initialize(contract, descriptor)
111
+ @contract,@descriptor = contract, descriptor
112
+ @changes = Queue.new
113
+ @handler_already_run = false
114
+ end
115
+
116
+
117
+ def run(handler)
118
+ @handler_already_run = true
119
+ handler.call(self)
120
+ end
121
+
122
+
123
+ def next_event; next_change; end
124
+ def next_change
125
+ loop {
126
+ case @changes.shift
127
+ when :change then return true
128
+ when :remove then return false
129
+ end
130
+ }
131
+ end
132
+
133
+
134
+ def do_change(change)
135
+ @changes.push(change)
136
+ end
137
+
138
+ end
139
+
140
+
141
+
142
+ class ObjectObserve
143
+
144
+ class ObservedObject < ManagedObject
145
+
146
+ def state
147
+ @contract.object_state
148
+ end
149
+
150
+ def data
151
+ @contract.object_state&.data
152
+ end
153
+
154
+ end
155
+
156
+ end
157
+
158
+
159
+ class ObjectExpose
160
+
161
+ class ExposedObject < ManagedObject
162
+
163
+ def initialize(contract, descriptor)
164
+ super(contract, descriptor)
165
+ @assignment = contract.assignment
166
+ end
167
+
168
+ def run(handler)
169
+ super
170
+ @contract.desynchronize(@assignment)
171
+ end
172
+
173
+ def do_change(change)
174
+ @assignment = @contract.assignment
175
+ super(change)
176
+ end
177
+
178
+ def assignement
179
+ @assignment = @contract.assignment()
180
+ end
181
+
182
+ def assigned?
183
+ @contract.assigned?()
184
+ end
185
+
186
+ def set_state(*state, **kwargs)
187
+ kwargs[:assignment] ||= @assignment
188
+ @contract.set_object_state(*state, **kwargs)
189
+ end
190
+
191
+ def state=(state)
192
+ set_state(*state)
193
+ end
194
+
195
+ def set_data(value)
196
+ timestamp = Time.new.to_f
197
+ set_state([1, timestamp.floor, ((timestamp - timestamp.floor)*1000000000).floor, 0],value)
198
+ end
199
+
200
+ def data=(value)
201
+ set_data(value)
202
+ end
203
+
204
+ end
205
+
206
+ end
207
+
208
+
209
+ class TagObserve
210
+
211
+ class ObservedObject < ManagedObject
212
+
213
+ def state
214
+ @contract.object_state(@descriptor)
215
+ end
216
+
217
+ def data
218
+ @contract.object_state(@descriptor)&.data
219
+ end
220
+
221
+ end
222
+
223
+ end
224
+
225
+
226
+ class TagExpose
227
+
228
+ class ExposedObject < ManagedObject
229
+
230
+ def initialize(contract, descriptor)
231
+ super(contract, descriptor)
232
+ @assignment = contract.assignment(@descriptor)
233
+ end
234
+
235
+ def run(handler)
236
+ super
237
+ @contract.desynchronize(@descriptor, @assignment)
238
+ end
239
+
240
+ def do_change(change)
241
+ @assignment = @contract.assignment(@descriptor)
242
+ super(change)
243
+ end
244
+
245
+ def assignement
246
+ @assignment = @contract.assignment(@descriptor)
247
+ end
248
+
249
+ def assigned?
250
+ @contract.assigned?(@descriptor)
251
+ end
252
+
253
+ def set_state(*args, **kwargs)
254
+ kwargs[:assignment] ||= @assignment
255
+ @contract.set_object_state(@descriptor, *args, **kwargs)
256
+ end
257
+
258
+ def state=(state)
259
+ set_state(*state)
260
+ end
261
+
262
+ def set_data(value)
263
+ timestamp = Time.new.to_f
264
+ set_state([1, timestamp.floor, ((timestamp - timestamp.floor)*1000000000).floor, 0],value)
265
+ end
266
+
267
+ def data=(value)
268
+ set_data(value)
269
+ end
270
+
271
+ end
272
+
273
+ end
274
+
275
+ end
@@ -0,0 +1,44 @@
1
+ require 'hakuban'
2
+ require 'hakuban/manager'
3
+
4
+ module Hakuban
5
+
6
+ class ThreadObjectManager < ObjectManager
7
+ def async_run
8
+ Thread.new { yield }
9
+ end
10
+
11
+ def async_join(thread)
12
+ thread.join
13
+ end
14
+ end
15
+
16
+
17
+ class ObjectObserve
18
+ def thread(&block)
19
+ ThreadObjectManager.new(self, ObservedObject, block)
20
+ end
21
+ end
22
+
23
+
24
+ class ObjectExpose
25
+ def thread(&block)
26
+ ThreadObjectManager.new(self, ExposedObject, block)
27
+ end
28
+ end
29
+
30
+
31
+ class TagObserve
32
+ def thread(&block)
33
+ ThreadObjectManager.new(self, ObservedObject, block)
34
+ end
35
+ end
36
+
37
+
38
+ class TagExpose
39
+ def thread(&block)
40
+ ThreadObjectManager.new(self, ExposedObject, block)
41
+ end
42
+ end
43
+
44
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hakuban
4
- VERSION = "0.5.2"
4
+ VERSION = "0.6.2"
5
5
  end
data/lib/hakuban.rb CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  require_relative "hakuban/version"
4
4
  require_relative "hakuban/hakuban"
5
+ require_relative "hakuban/engine"
6
+ #require_relative "hakuban/async"
7
+ #require_relative "hakuban/thread"
5
8
 
6
9
  module Hakuban
7
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hakuban
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - yunta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-11 00:00:00.000000000 Z
11
+ date: 2022-02-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -39,55 +39,55 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.15'
41
41
  - !ruby/object:Gem::Dependency
42
- name: msgpack
42
+ name: json
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.4'
47
+ version: '2.5'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.4'
54
+ version: '2.5'
55
55
  - !ruby/object:Gem::Dependency
56
- name: json
56
+ name: slop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '2.5'
61
+ version: '4.9'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '2.5'
68
+ version: '4.9'
69
69
  description: Ruby binding for convenient data-object sharing library - Hakuban.
70
70
  email:
71
71
  - maciej.blomberg@mikoton.com
72
- executables: []
72
+ executables:
73
+ - hakuban-observer
74
+ - hakuban-thread-engine
73
75
  extensions: []
74
76
  extra_rdoc_files: []
75
77
  files:
76
- - ".gitignore"
77
- - ".rspec"
78
- - Gemfile
79
- - LICENSE.txt
78
+ - MIT-LICENSE
80
79
  - README.md
81
80
  - Rakefile
82
- - bin/console
83
- - bin/setup
84
- - examples/all-top-managed.rb
85
- - examples/all-top-simple.rb
86
- - examples/cpu-usage.rb
87
- - hakuban.gemspec
81
+ - bin/hakuban-observer
82
+ - bin/hakuban-thread-engine
88
83
  - lib/hakuban.rb
84
+ - lib/hakuban/async.rb
85
+ - lib/hakuban/engine.rb
86
+ - lib/hakuban/event-queue.rb
89
87
  - lib/hakuban/ffi.rb
90
88
  - lib/hakuban/hakuban.rb
89
+ - lib/hakuban/manager.rb
90
+ - lib/hakuban/thread.rb
91
91
  - lib/hakuban/version.rb
92
92
  homepage: https://gitlab.com/yunta/hakuban-ruby
93
93
  licenses:
@@ -99,16 +99,16 @@ require_paths:
99
99
  - lib
100
100
  required_ruby_version: !ruby/object:Gem::Requirement
101
101
  requirements:
102
- - - ">="
102
+ - - "~>"
103
103
  - !ruby/object:Gem::Version
104
- version: 2.4.0
104
+ version: '3.0'
105
105
  required_rubygems_version: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - ">="
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
110
  requirements: []
111
- rubygems_version: 3.2.15
111
+ rubygems_version: 3.2.22
112
112
  signing_key:
113
113
  specification_version: 4
114
114
  summary: Ruby binding for Hakuban library
data/.gitignore DELETED
@@ -1,12 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
- /vendor/
10
-
11
- # rspec failure tracking
12
- .rspec_status
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gemspec
6
-
data/LICENSE.txt DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2021 yunta
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "hakuban"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require "irb"
15
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -1,46 +0,0 @@
1
- require 'hakuban'
2
- require_relative './cpu-usage.rb'
3
-
4
-
5
- class Utilization < Hakuban::ObjectManager::ManagedObject
6
- include Hakuban::ObjectManager::ObservedObject
7
- include Hakuban::ObjectManager::ExposedObject
8
-
9
- attr_reader :id
10
-
11
- def initialize(contract, descriptor)
12
- super
13
- @id = descriptor.json["id"]
14
- end
15
- end
16
-
17
-
18
- id = "#{$$}@#{`hostname`.strip}"
19
-
20
- hakuban = Hakuban::LocalNode.new
21
- connector = Hakuban::WebsocketConnector.new("ws://localhost:3001").start(hakuban)
22
- all_utilizations = hakuban.tag("utilizations").observe.manage(Utilization)
23
- my_utilization = hakuban.object(["utilizations"],{id: id}).expose.manage(Utilization)
24
-
25
- previous_cpu_usage_state = nil
26
- loop {
27
- previous_cpu_usage_state, cpu_usage = CPU::current_usage(previous_cpu_usage_state)
28
- my_utilization.data = [cpu_usage]
29
-
30
- utilizations = all_utilizations.objects.values.sort_by(&:id).map { |utilization|
31
- [utilization.id, utilization.data&.first]
32
- }.select { |id, utilization| utilization }
33
- next sleep 1 if utilizations.empty?
34
-
35
- widest_id_width = utilizations.transpose[0].map(&:size).max
36
- utilizations.each.with_index { |(id, utilization), i|
37
- bars = utilization.map { |percentage| " ▁▂▃▄▅▆▇█"[percentage*9/100] }.join("")
38
- bg_color = [0,0,180 + (i % 2)*64]
39
- fg_color = [255,255,0]
40
- puts "%s -> \e[48;2;%i;%i;%im\e[38;2;%i;%i;%im%s\e[49m\e[39m"%([id.ljust(widest_id_width)]+bg_color+fg_color+[bars])
41
- }
42
-
43
- sleep 1
44
- #TODO: make this more friendly, save state, etc.
45
- print "\e[2J\e[f"
46
- }
@@ -1,26 +0,0 @@
1
- require 'hakuban'
2
- require_relative './cpu-usage.rb'
3
-
4
-
5
- id = "#{$$}@#{`hostname`.strip}"
6
-
7
- hakuban = Hakuban::LocalNode.new
8
- connector = Hakuban::WebsocketConnector.new("ws://localhost:3001").start(hakuban)
9
- all_utilizations = hakuban.tag("utilizations").observe
10
- my_utilization = hakuban.object(["utilizations"],{id: id}).expose
11
-
12
- previous_cpu_usage_state = nil
13
-
14
- loop {
15
- previous_cpu_usage_state, cpu_usage = CPU::current_usage(previous_cpu_usage_state)
16
- my_utilization.set_object_state([Time.new.to_f],[],[cpu_usage]) if cpu_usage
17
-
18
- all_utilizations.object_descriptors.map { |descriptor|
19
- if data = all_utilizations.object_state(descriptor).data
20
- puts "#{descriptor.json["id"]} -> #{data.first}"
21
- end
22
- }
23
-
24
- sleep 1
25
- puts
26
- }
@@ -1,16 +0,0 @@
1
- module CPU
2
-
3
- def self.current_usage(previous_cpu_usage_state)
4
- current_uptime = open("/proc/uptime") { |file| file.read.split(" ")[0].to_f }
5
- current_cpu_ticks = open("/proc/stat") { |file|
6
- file.read.split("\n").select { |line| line.start_with?(/cpu[0-9]+/) }.map { |line| line.split(" ")[4].to_f }
7
- }
8
- cpu_usage = previous_cpu_usage_state&.dig(0)&.zip(current_cpu_ticks)&.map { |old_ticks, new_ticks|
9
- [0,(100 - (new_ticks - old_ticks) / (current_uptime - previous_cpu_usage_state[1]))].max.floor
10
- }
11
- [[current_cpu_ticks, current_uptime], cpu_usage]
12
- end
13
-
14
- end
15
-
16
-
data/hakuban.gemspec DELETED
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/hakuban/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "hakuban"
7
- spec.version = Hakuban::VERSION
8
- spec.authors = ["yunta"]
9
- spec.email = ["maciej.blomberg@mikoton.com"]
10
-
11
- spec.summary = "Ruby binding for Hakuban library"
12
- spec.description = "Ruby binding for convenient data-object sharing library - Hakuban."
13
- spec.homepage = "https://gitlab.com/yunta/hakuban-ruby"
14
- spec.license = "MIT"
15
- spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
16
-
17
- #spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
-
19
- #spec.metadata["homepage_uri"] = spec.homepage
20
- #spec.metadata["source_code_uri"] = "https://gitlab.com/yunta/hakuban-ruby"
21
- #spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
22
-
23
- # Specify which files should be added to the gem when it is released.
24
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
- end
28
- #spec.bindir = "exe"
29
- #spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
- spec.require_paths = ["lib"]
31
-
32
- spec.add_development_dependency "rspec", "~>3.10"
33
- spec.add_dependency "ffi", "~>1.15"
34
- spec.add_dependency "msgpack", "~>1.4"
35
- spec.add_dependency "json", "~>2.5"
36
- end