uringmachine 0.28.3 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbdc1ca412e20436a209c2e4f77af29873d5c8bac02f88c35562333f1e5328e6
4
- data.tar.gz: d426661ce83c278041042c5829a23b4bb5c0c65cf99d1cb3cb96f5706b464859
3
+ metadata.gz: 6a6a6d78b632e43c1ac15e8745ede898e531ff9a731822fd22c5991ca0222d69
4
+ data.tar.gz: 0acd35ee028ea519c2788c720336dbcb1c2fa8317912459b7a81f4afbe6b3747
5
5
  SHA512:
6
- metadata.gz: 9908398a698bf7a2771a88b369023e27c3669bd205eb86a8132fee154709d68b21cd05a4fcd8b0bacb0f8e10140f2a2ef002f01cd7ac8177bf678c4f8e8574b5
7
- data.tar.gz: afb606febf25d41fdf99b00e8cdeaa97523608445cc842cf49acbdf5af95f726276d443a574363b23b9b6c8287fad560780913f4bf1fd09240cf5df29185a9ac
6
+ metadata.gz: ed9f7684132ee1e1aae9c7783131fd927e88604444d44bc889cdf8c362b0a5dd85b3e887873c0512946af2c08dd8189e6c3bf972f19f90a7f12daee4c44c5ccd
7
+ data.tar.gz: f5bff069de535e906543a679266700bacbb715d0e72b14d4bf7cd042ab8d77e58d8c69feb1dbd65f16026aa4bcec20d024acffd2e2f18ac255f5b70190c25788
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ # 0.29.0 2026-03-11
2
+
3
+ - Reimplement streams based on buffer pool
4
+ - Add buffer pool with automatic buffer commit
5
+ - Add support for procs in `#await`, `#join`
6
+ - Accept splat fibers in `#await`
7
+ - Rename `#await_fibers` to `#await`
8
+ - Fiber scheduler: make `blocking_operation_wait` optional
9
+
1
10
  # 0.28.3 2026-02-22
2
11
 
3
12
  - Accept array in `#terminate` and `#join`
@@ -67,7 +76,7 @@
67
76
  - Add `UM#metrics` for getting metrics
68
77
  - Add `UM#pending_fibers` for detecting leaking fibers in tests
69
78
  - More tests and benchmarks
70
- - Add `UM#await_fibers` for awaiting fibers
79
+ - Add `UM#await` for awaiting fibers
71
80
  - Add `UM.socketpair` for creating a socket pair
72
81
  - Fix segfault caused by waiting fibers not being marked
73
82
  - Fiber scheduler:
data/TODO.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ## immediate
2
2
 
3
3
  - Add support for exception instances in `#timeout`.
4
- - Add support for returning a value on timeout:
4
+ - Add support for returning a value on timeout
5
5
 
6
6
  Since to do this safely we need to actually raise an exception that wraps the
7
7
  value, rescue it and return the value, we might want a separate method that
@@ -15,19 +15,22 @@
15
15
  timeout(interval, timeout_error, &block)
16
16
  rescue TimeoutValueError => e
17
17
  raise if e != timeout_error
18
-
18
+
19
19
  value
20
20
  end
21
21
  ```
22
22
 
23
- - Add tests for support for Set in `machine#await_fibers`
23
+ - Add tests for support for Set in `machine#await`
24
24
  - Add tests for support for Set, Array in `machine#join`
25
25
  - Add `#read_file` for reading entire file
26
26
  - Add `#write_file` for writing entire file
27
27
 
28
28
  - (?) Fix all futex value (Queue, Mutex) to be properly aligned
29
29
 
30
+ <<<<<<< HEAD
31
+ =======
30
32
  ## Buffer rings - automatic management
33
+
31
34
  - Take the buffer_pool branch, rewrite it
32
35
  - Allow multiple stream modes:
33
36
  - :buffer_pool - uses buffer rings
@@ -39,13 +42,27 @@ The API will look something like:
39
42
  ```ruby
40
43
  # The mode is selected automatically according to the given target
41
44
 
42
- stream = UM::Stream.new(fd) # buffer_pool mode
45
+ stream = UM::Stream.new(machine, fd) # buffer_pool mode (read)
46
+ stream = UM::Stream.new(machine, fd, :recv) # buffer_pool mode (recv)
47
+ stream = UM::Stream.new(machine, ssl_sock) # SSLSocket mode
48
+ stream = UM::Stream.new(machine, conn) # IO mode
49
+ stream = UM::Stream.new(machine, str) # string mode
50
+ stream = UM::Stream.new(machine, io_buf) # IO:Buffer mode
51
+ ```
43
52
 
44
- stream = UM::Stream.new(ssl_sock) # ssl mode
53
+ This can be very useful in testing of stuff such as protocol implementations:
45
54
 
46
- stream = UM::Stream.new(conn) # io mode
55
+ ```ruby
56
+ stream = UM::Stream.new(machine, "GET /foo HTTP/1.1\r\nHost: bar.com\r\n")
47
57
  ```
48
58
 
59
+ So basically the stream is tied to a machine, and that means it can only be used
60
+ on the thread with which the machine is associated. It is not thread-safe. (This
61
+ is incidentally true also for most of the UringMachine instance methods!)
62
+
63
+ Continued discussion in docs/design/buffer_pool.md
64
+
65
+ >>>>>>> 04d9eb7 (Docs)
49
66
  ## Balancing I/O with the runqueue
50
67
 
51
68
  - in some cases where there are many entries in the runqueue, this can
@@ -133,37 +150,14 @@ stream.read_to_eof(buf)
133
150
  stream.read_to_eof(nil)
134
151
  ```
135
152
 
136
- ## Syntax / pattern for launching multiple operations
153
+ ## Syntax / pattern for launching/supervising multiple operations
154
+
155
+ Select (see above):
137
156
 
138
157
  ```ruby
139
- results = machine.concurrently(
140
- -> { machine.read(fd1, ...) },
141
- -> { machine.read(fd2, ...) }
142
- -> { ... }
143
- )
144
-
145
- # or maybe:
146
- jobs = (1..100).map { |i| -> { machine.read_file("/file_#{i}.csv") } }
147
- machine.join(jobs)
148
-
149
- # or maybe:
150
- jobs = (1..100).map { |i|
151
- -> {
152
- pipe { machine.read_file("/file_#{i}.csv") }
153
- > { csv_to_pdf(it) }
154
- > { machine.write_file("/file_#{i}.pdf", it) }
155
- }
156
- }
157
-
158
- # or otherwise
159
- jobs = (1..100).map { |i|
160
- -> {
161
- csv = machine.read_file("/file_#{i}.csv")
162
- pdf = csv_to_pdf(csv)
163
- machine.write_file("/file_#{i}.pdf", pdf)
164
- }
165
- }
166
- machine.join(jobs)
158
+ # select
159
+ machine.join_select(*fibers) #=> [result, fiber]
160
+ machine.shift_select(*queues) #=> [result, queue]
167
161
  ```
168
162
 
169
163
  ## Other abstractions
data/benchmark/common.rb CHANGED
@@ -200,7 +200,7 @@ class UMBenchmark
200
200
  fibers = []
201
201
  fds = []
202
202
  do_um(machine, fibers, fds)
203
- machine.await_fibers(fibers)
203
+ machine.await(fibers)
204
204
  fds.each { machine.close(it) }
205
205
  end
206
206
 
@@ -209,7 +209,7 @@ class UMBenchmark
209
209
  fibers = []
210
210
  fds = []
211
211
  do_um(machine, fibers, fds)
212
- machine.await_fibers(fibers)
212
+ machine.await(fibers)
213
213
  fds.each { machine.close(it) }
214
214
  end
215
215
 
@@ -218,7 +218,7 @@ class UMBenchmark
218
218
  fibers = []
219
219
  fds = []
220
220
  do_um(machine, fibers, fds)
221
- machine.await_fibers(fibers)
221
+ machine.await(fibers)
222
222
  fds.each { machine.close(it) }
223
223
  end
224
224
 
@@ -229,7 +229,7 @@ class UMBenchmark
229
229
  fibers = []
230
230
  fds = []
231
231
  do_um_x(2, machine, fibers, fds)
232
- machine.await_fibers(fibers)
232
+ machine.await(fibers)
233
233
  fds.each { machine.close(it) }
234
234
  end
235
235
  end
@@ -243,7 +243,7 @@ class UMBenchmark
243
243
  fibers = []
244
244
  fds = []
245
245
  do_um_x(4, machine, fibers, fds)
246
- machine.await_fibers(fibers)
246
+ machine.await(fibers)
247
247
  fds.each { machine.close(it) }
248
248
  end
249
249
  end
@@ -257,7 +257,7 @@ class UMBenchmark
257
257
  fibers = []
258
258
  fds = []
259
259
  do_um_x(8, machine, fibers, fds)
260
- machine.await_fibers(fibers)
260
+ machine.await(fibers)
261
261
  fds.each { machine.close(it) }
262
262
  end
263
263
  end
data/benchmark/gets.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'uringmachine', path: '..'
8
+ gem 'benchmark'
9
+ gem 'benchmark-ips'
10
+ end
11
+
12
+ require 'benchmark/ips'
13
+ require 'uringmachine'
14
+
15
+ @machine = UM.new
16
+
17
+ @io = File.open('/dev/random', 'r')
18
+ def io_gets
19
+ @io.gets
20
+ end
21
+
22
+ @fd = @machine.open('/dev/random', UM::O_RDONLY)
23
+ @buffer = +''.encode(Encoding::US_ASCII)
24
+ def um_read
25
+ while true
26
+ idx = @buffer.byteindex("\n")
27
+ if idx
28
+ line = @buffer[0..(idx - 1)]
29
+
30
+ @buffer = @buffer[(idx + 1)..-1]
31
+ return line
32
+ end
33
+ @machine.read(@fd, @buffer, 65536, -1)
34
+ end
35
+ end
36
+
37
+ @fd_stream = @machine.open('/dev/random', UM::O_RDONLY)
38
+ @stream = UM::Stream.new(@machine, @fd_stream)
39
+ def um_stream_get_line
40
+ @stream.get_line(0)
41
+ end
42
+
43
+ Benchmark.ips do |x|
44
+ x.report('IO#gets') { io_gets }
45
+ x.report('UM#read+buf') { um_read }
46
+ x.report('UM::Stream') { um_stream_get_line }
47
+
48
+ x.compare!(order: :baseline)
49
+ end
@@ -13,42 +13,31 @@ require 'uringmachine'
13
13
 
14
14
  @machine = UM.new
15
15
 
16
- make_socket_pair = -> do
17
- port = 10000 + rand(30000)
18
- server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
19
- @machine.setsockopt(server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
20
- @machine.bind(server_fd, '127.0.0.1', port)
21
- @machine.listen(server_fd, UM::SOMAXCONN)
22
-
23
- client_conn_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
24
- @machine.connect(client_conn_fd, '127.0.0.1', port)
25
-
26
- server_conn_fd = @machine.accept(server_fd)
27
-
28
- @machine.close(server_fd)
29
- [client_conn_fd, server_conn_fd]
30
- end
31
-
32
- @client_fd, @server_fd = make_socket_pair.()
16
+ @client_fd, @server_fd = UM.socketpair(UM::AF_UNIX, UM::SOCK_STREAM, 0)
33
17
 
34
18
  @read_buf = +''
35
19
  @read_fiber = @machine.spin do
36
20
  while true
37
21
  @machine.read(@client_fd, @read_buf, 65536, 0)
38
22
  end
23
+ rescue Exception => e
24
+ p e
25
+ p e.backtrace.join
26
+ exit!
39
27
  end
40
28
 
41
29
  STR_COUNT = ARGV[0]&.to_i || 3
42
30
  STR_SIZE = ARGV[1]&.to_i || 100
43
31
 
44
32
  @parts = ['*' * STR_SIZE] * STR_COUNT
33
+ p @parts
45
34
 
46
- @server_io = IO.new(@server_fd)
47
- @server_io.sync = true
48
- def io_write
49
- @server_io.write(*@parts)
50
- @machine.snooze
51
- end
35
+ # @server_io = IO.new(@server_fd)
36
+ # @server_io.sync = true
37
+ # def io_write
38
+ # @server_io.write(*@parts)
39
+ # 10.times { @machine.snooze }
40
+ # end
52
41
 
53
42
  def um_write
54
43
  str = @parts.join
@@ -61,6 +50,17 @@ def um_write
61
50
  end
62
51
  end
63
52
 
53
+ # def um_writev
54
+ # str = @parts.join
55
+ # len = str.bytesize
56
+
57
+ # while len > 0
58
+ # ret = @machine.write(@server_fd, str, len)
59
+ # len -= ret
60
+ # str = str[ret..-1] if len > 0
61
+ # end
62
+ # end
63
+
64
64
  def um_send
65
65
  str = @parts.join
66
66
  @machine.send(@server_fd, str, str.bytesize, UM::MSG_WAITALL)
@@ -73,8 +73,11 @@ end
73
73
 
74
74
  p(STR_COUNT:, STR_SIZE:)
75
75
 
76
+ # 10.times { io_write }
77
+ # @machine.sleep(0.5)
78
+
76
79
  Benchmark.ips do |x|
77
- x.report('IO#write') { io_write }
80
+ # x.report('IO#write') { io_write }
78
81
  x.report('UM#write') { um_write }
79
82
  x.report('UM#send') { um_send }
80
83
  x.report('UM#send_bundle') { um_send_bundle }
@@ -20,9 +20,44 @@ buffers, to using managed buffers from the buffer pool.
20
20
 
21
21
  ## Design
22
22
 
23
+ ### Goals
24
+
25
+ - We want an abstract design that will work with both multishot read/recv and
26
+ automatic buffer management, *and* other readable sources, such as an SSL
27
+ socket, an `IO` instance, or even a string or a `IO::Buffer`.
28
+
29
+ ### The Stream Data Model
30
+
31
+ - A stream is a series of `segments` (containing a `ptr` and `len`), held in a
32
+ linked list. A segment also has a `type` and a potential backing store
33
+ reference. For example, a segment with a `SEGMENT_STRING` type points to a
34
+ `String` object. A segment with a `SEGMENT_BUFFER_POOL` has a reference to a
35
+ managed buffer from the buffer pool with the given id.
36
+ - The buffer pool is linked list of buffer descriptors, each referencing a piece
37
+ of memory of a certain length, along with some metadata.
38
+ - Buffer descriptors are allocated in batches as needed and added to the buffer
39
+ pool.
40
+ - A buffer group describes a buffer ring. It has a certain size, which limits
41
+ the number of buffers that can be added to the buffer ring.
42
+ - When a buffer group is used for a multishot read/recv, buffers will be
43
+ automatically checked out from the buffer pool and added to the buffer ring
44
+ and commited to the kernel. The buffer group allocates and tracks a buffer id
45
+ for each buffer descriptor.
46
+
47
+ ### How buffer groups are checked out
48
+
49
+ - We need a freelist of buffer groups
50
+
51
+ - Once a segment is completely consumed, some cleanup code might be needed (for
52
+ example to decrement a ref_count on a managed buffer, or unlocking a string
53
+ backing store.)
54
+
55
+
23
56
  ### The API
24
57
 
25
58
  - The buffer pool is created and managed automatically. No API is involved.
59
+ -
60
+ -
26
61
 
27
62
  - To use the buffer pool, two dedicated APIs are added:
28
63
 
data/docs/um_api.md CHANGED
@@ -22,6 +22,7 @@
22
22
  - `accept_into_queue(sockfd, queue)` - accepts incoming connections to the given
23
23
  server socket in an infinite loop, pushing each one to the given queue.
24
24
  - `accept(sockfd)` - accepts an incoming connection, returning its fd.
25
+ - `await(*fibers)` - awaits fiber termination
25
26
  - `bind(sockfd, host, port)` - binds the given socket to the given address.
26
27
  - `close_async(fd)` - closes the given fd asynchronously, i.e. <w>ithout waiting
27
28
  for the operation to complete.
@@ -29,6 +30,7 @@
29
30
  - `connect(sockfd, host, port)` - connects the given socket to the given
30
31
  address.
31
32
  - `getsockopt(sockfd, level, opt)` - returns a socket option value.
33
+ - `join(*fibers)` - waits for fibers to terminate and returns return values.
32
34
  - `listen(sockfd)` - starts listening on the given socket.
33
35
  - `metrics` - returns metrics for the machine.
34
36
  - `open(pathname, flags)` - opens the given path and returns an fd.
data/ext/um/extconf.rb CHANGED
@@ -30,9 +30,7 @@ def get_config
30
30
  end
31
31
 
32
32
  config = get_config
33
- puts "Building UringMachine (\n#{config.map { |(k, v)| " #{k}: #{v}\n"}.join})"
34
-
35
- # require_relative 'zlib_conf'
33
+ STDERR.puts "Building UringMachine (\n#{config.map { |(k, v)| " #{k}: #{v}\n"}.join})"
36
34
 
37
35
  liburing_path = File.expand_path('../../vendor/liburing', __dir__)
38
36
  FileUtils.cd liburing_path do
@@ -76,8 +74,11 @@ $defs << '-DHAVE_IO_URING_PREP_BIND' if config[:prep_bind]
76
74
  $defs << '-DHAVE_IO_URING_PREP_LISTEN' if config[:prep_listen]
77
75
  $defs << '-DHAVE_IO_URING_SEND_VECTORIZED' if config[:send_vectoized]
78
76
 
79
- $CFLAGS << ' -Wno-pointer-arith'
77
+ $CFLAGS << ' -Werror -Wall -Wextra'
80
78
 
81
- CONFIG['optflags'] << ' -fno-strict-aliasing'
79
+ if ENV['SANITIZE']
80
+ $CFLAGS << ' -fsanitize=undefined,address -lasan'
81
+ $LDFLAGS << ' -fsanitize=undefined,address -lasan'
82
+ end
82
83
 
83
84
  create_makefile 'um_ext'
data/ext/um/um.c CHANGED
@@ -38,6 +38,8 @@ void um_setup(VALUE self, struct um *machine, uint size, uint sqpoll_timeout_mse
38
38
  if (ret) rb_syserr_fail(-ret, strerror(-ret));
39
39
  machine->ring_initialized = 1;
40
40
 
41
+ bp_setup(machine);
42
+
41
43
  if (machine->sidecar_mode) um_sidecar_setup(machine);
42
44
  }
43
45
 
@@ -56,7 +58,8 @@ inline void um_teardown(struct um *machine) {
56
58
  io_uring_queue_exit(&machine->ring);
57
59
  machine->ring_initialized = 0;
58
60
 
59
- um_free_buffer_linked_list(machine);
61
+ bp_discard_buffer_freelist(machine);
62
+ bp_teardown(machine);
60
63
  }
61
64
 
62
65
  inline struct io_uring_sqe *um_get_sqe(struct um *machine, struct um_op *op) {
@@ -981,7 +984,7 @@ struct send_recv_fd_ctx {
981
984
  struct msghdr msgh;
982
985
  char iobuf[1];
983
986
  struct iovec iov;
984
- union { // Ancillary data buffer, wrapped in a union for alignment
987
+ union { // Ancillary data buffer, wrapped in a union for alignment
985
988
  char buf[CMSG_SPACE(sizeof(int))];
986
989
  struct cmsghdr align;
987
990
  } u;
@@ -1296,6 +1299,13 @@ VALUE accept_each_start(VALUE arg) {
1296
1299
  struct um_op_result *result = &ctx->op->result;
1297
1300
  while (result) {
1298
1301
  if (unlikely(result->res < 0)) {
1302
+ if (result->res == -EINVAL) {
1303
+ // An EINVAL indicates the socket was shutdown, so we just break of of
1304
+ // the loop. We also need to prevent raising error in
1305
+ // um_verify_op_completion.
1306
+ result->res = 0;
1307
+ break;
1308
+ }
1299
1309
  rb_syserr_fail(-result->res, strerror(-result->res));
1300
1310
  }
1301
1311
  rb_yield(INT2NUM(result->res));
@@ -1326,6 +1336,13 @@ VALUE accept_into_queue_start(VALUE arg) {
1326
1336
  struct um_op_result *result = &ctx->op->result;
1327
1337
  while (result) {
1328
1338
  if (unlikely(result->res < 0)) {
1339
+ if (result->res == -EINVAL) {
1340
+ // An EINVAL indicates the socket was shutdown, so we just break of of
1341
+ // the loop. We also need to prevent raising error in
1342
+ // um_verify_op_completion.
1343
+ result->res = 0;
1344
+ break;
1345
+ }
1329
1346
  rb_syserr_fail(-result->res, strerror(-result->res));
1330
1347
  }
1331
1348
  um_queue_push(ctx->machine, ctx->queue, INT2NUM(result->res));
@@ -1533,26 +1550,38 @@ extern VALUE SYM_ops_free;
1533
1550
  extern VALUE SYM_ops_transient;
1534
1551
  extern VALUE SYM_time_total_cpu;
1535
1552
  extern VALUE SYM_time_total_wait;
1553
+ extern VALUE SYM_buffer_groups;
1554
+ extern VALUE SYM_buffers_allocated;
1555
+ extern VALUE SYM_buffers_free;
1556
+ extern VALUE SYM_segments_free;
1557
+ extern VALUE SYM_buffer_space_allocated;
1558
+ extern VALUE SYM_buffer_space_commited;
1536
1559
 
1537
1560
  VALUE um_metrics(struct um *machine, struct um_metrics *metrics) {
1538
1561
  VALUE hash = rb_hash_new();
1539
1562
 
1540
- rb_hash_aset(hash, SYM_size, UINT2NUM(machine->size));
1563
+ rb_hash_aset(hash, SYM_size, UINT2NUM(machine->size));
1564
+
1565
+ rb_hash_aset(hash, SYM_total_ops, ULONG2NUM(metrics->total_ops));
1566
+ rb_hash_aset(hash, SYM_total_switches, ULONG2NUM(metrics->total_switches));
1567
+ rb_hash_aset(hash, SYM_total_waits, ULONG2NUM(metrics->total_waits));
1541
1568
 
1542
- rb_hash_aset(hash, SYM_total_ops, ULONG2NUM(metrics->total_ops));
1543
- rb_hash_aset(hash, SYM_total_switches, ULONG2NUM(metrics->total_switches));
1544
- rb_hash_aset(hash, SYM_total_waits, ULONG2NUM(metrics->total_waits));
1569
+ rb_hash_aset(hash, SYM_ops_pending, UINT2NUM(metrics->ops_pending));
1570
+ rb_hash_aset(hash, SYM_ops_unsubmitted, UINT2NUM(metrics->ops_unsubmitted));
1571
+ rb_hash_aset(hash, SYM_ops_runqueue, UINT2NUM(metrics->ops_runqueue));
1572
+ rb_hash_aset(hash, SYM_ops_free, UINT2NUM(metrics->ops_free));
1573
+ rb_hash_aset(hash, SYM_ops_transient, UINT2NUM(metrics->ops_transient));
1545
1574
 
1546
- rb_hash_aset(hash, SYM_ops_pending, UINT2NUM(metrics->ops_pending));
1547
- rb_hash_aset(hash, SYM_ops_unsubmitted, UINT2NUM(metrics->ops_unsubmitted));
1548
- rb_hash_aset(hash, SYM_ops_runqueue, UINT2NUM(metrics->ops_runqueue));
1549
- rb_hash_aset(hash, SYM_ops_free, UINT2NUM(metrics->ops_free));
1550
- rb_hash_aset(hash, SYM_ops_transient, UINT2NUM(metrics->ops_transient));
1575
+ rb_hash_aset(hash, SYM_buffers_allocated, UINT2NUM(metrics->buffers_allocated));
1576
+ rb_hash_aset(hash, SYM_buffers_free, UINT2NUM(metrics->buffers_free));
1577
+ rb_hash_aset(hash, SYM_segments_free, UINT2NUM(metrics->segments_free));
1578
+ rb_hash_aset(hash, SYM_buffer_space_allocated, ULONG2NUM(metrics->buffer_space_allocated));
1579
+ rb_hash_aset(hash, SYM_buffer_space_commited, ULONG2NUM(metrics->buffer_space_commited));
1551
1580
 
1552
1581
  if (machine->profile_mode) {
1553
1582
  double total_cpu = um_get_time_cpu() - metrics->time_first_cpu;
1554
- rb_hash_aset(hash, SYM_time_total_cpu, DBL2NUM(total_cpu));
1555
- rb_hash_aset(hash, SYM_time_total_wait, DBL2NUM(metrics->time_total_wait));
1583
+ rb_hash_aset(hash, SYM_time_total_cpu, DBL2NUM(total_cpu));
1584
+ rb_hash_aset(hash, SYM_time_total_wait, DBL2NUM(metrics->time_total_wait));
1556
1585
  }
1557
1586
 
1558
1587
  return hash;