zold 0.14.17 → 0.14.18
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 +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +1 -1
- data/bin/zold +6 -1
- data/fixtures/scripts/_head.sh +33 -13
- data/fixtures/scripts/calculate-scores.sh +1 -1
- data/fixtures/scripts/distribute-wallet.sh +2 -2
- data/fixtures/scripts/push-and-pull.sh +2 -2
- data/fixtures/scripts/redeploy-on-upgrade.sh +3 -3
- data/fixtures/scripts/sigdump.sh +1 -1
- data/fixtures/scripts/spread-wallets.sh +2 -2
- data/lib/zold/commands/next.rb +57 -0
- data/lib/zold/commands/node.rb +23 -8
- data/lib/zold/commands/remote.rb +7 -4
- data/lib/zold/metronome.rb +0 -1
- data/lib/zold/node/farm.rb +3 -2
- data/lib/zold/node/front.rb +11 -0
- data/lib/zold/node/spread_entrance.rb +0 -1
- data/lib/zold/remotes.rb +68 -75
- data/lib/zold/type.rb +5 -4
- data/lib/zold/version.rb +1 -1
- data/test/commands/routines/test_reconnect.rb +2 -2
- data/test/commands/test_create.rb +1 -1
- data/test/commands/test_invoice.rb +1 -1
- data/test/commands/test_list.rb +1 -1
- data/test/commands/test_merge.rb +1 -1
- data/test/commands/test_pull.rb +49 -0
- data/test/commands/test_remote.rb +11 -12
- data/test/commands/test_show.rb +1 -1
- data/test/fake_home.rb +26 -4
- data/test/node/fake_node.rb +1 -0
- data/test/node/test_farm.rb +10 -10
- data/test/test_atomic_file.rb +2 -2
- data/test/test_copies.rb +8 -8
- data/test/test_key.rb +1 -1
- data/test/test_remotes.rb +60 -21
- data/test/test_zold.rb +3 -2
- metadata +5 -4
- data/.zoldata/remotes +0 -1
- data/farm +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ecca17ec572da83d30874c74ad40b383e22855e
|
4
|
+
data.tar.gz: 501cc2ef09710e3bd6d578d3f8cae99895ae25e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 584cfab45f2f97b6d5bccaee7e285d1637ca546357bb9b08e7a7e30f85789a31ca69e59ec96e72177b6c64568284e0ab982248bc25c0ada8a13ad86580c9496b
|
7
|
+
data.tar.gz: '09553f0034eee231dbba5c6d1eccd3c2d4e8e120bb70505f741d1b20f21cd7d103baeca54259c0e5bfad66b97e4b64b62ea3fb536b5eef411b9344faf83fbbfa'
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/bin/zold
CHANGED
@@ -95,6 +95,8 @@ Available commands:
|
|
95
95
|
Run node at the given TCP port
|
96
96
|
#{Rainbow('alias').green} [alias] [wallet ID]
|
97
97
|
Set an alias for a wallet
|
98
|
+
#{Rainbow('next').green} score
|
99
|
+
Generate next score from the provided one
|
98
100
|
#{Rainbow('score').green} [options]
|
99
101
|
Generate score for the given host and port
|
100
102
|
Available options:"
|
@@ -149,7 +151,7 @@ Available options:"
|
|
149
151
|
Zold::RenameForeignWallets.new(Dir.pwd, opts['network'], log).exec
|
150
152
|
|
151
153
|
wallets = Zold::Wallets.new('.')
|
152
|
-
remotes = Zold::Remotes.new('./.zoldata/remotes', network: opts['network'])
|
154
|
+
remotes = Zold::Remotes.new(file: './.zoldata/remotes', network: opts['network'])
|
153
155
|
copies = './.zoldata/copies'
|
154
156
|
|
155
157
|
case command
|
@@ -201,6 +203,9 @@ Available options:"
|
|
201
203
|
when 'score'
|
202
204
|
require_relative '../lib/zold/commands/calculate'
|
203
205
|
Zold::Calculate.new(log: log).run(args)
|
206
|
+
when 'next'
|
207
|
+
require_relative '../lib/zold/commands/next'
|
208
|
+
Zold::Next.new(log: log).run(args)
|
204
209
|
else
|
205
210
|
raise "Command '#{command}' is not supported"
|
206
211
|
end
|
data/fixtures/scripts/_head.sh
CHANGED
@@ -5,7 +5,7 @@ shopt -s expand_aliases
|
|
5
5
|
|
6
6
|
export RUBYOPT="-W0"
|
7
7
|
|
8
|
-
alias zold="$1 --ignore-this-stupid-option --ignore-global-config --trace --network=test --no-colors"
|
8
|
+
alias zold="$1 --ignore-this-stupid-option --halt-code=test --ignore-global-config --trace --network=test --no-colors --dump-errors"
|
9
9
|
|
10
10
|
function reserve_port {
|
11
11
|
python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'
|
@@ -13,33 +13,53 @@ function reserve_port {
|
|
13
13
|
|
14
14
|
function wait_for_url {
|
15
15
|
while ! curl --silent --fail $1 > /dev/null; do
|
16
|
-
((p++)) || sleep
|
16
|
+
((p++)) || sleep 5
|
17
17
|
if ((p==30)); then
|
18
|
-
echo URL $1 is not available after $p seconds of waiting
|
19
|
-
exit
|
18
|
+
echo "URL $1 is not available after $p seconds of waiting"
|
19
|
+
exit 12
|
20
20
|
fi
|
21
|
-
sleep
|
21
|
+
sleep 5
|
22
22
|
done
|
23
23
|
}
|
24
24
|
|
25
25
|
function wait_for_port {
|
26
26
|
while ! nc -z localhost $1; do
|
27
|
-
((p++)) || sleep
|
27
|
+
((p++)) || sleep 5
|
28
28
|
if ((p==30)); then
|
29
|
-
echo Port $1 is not available after $p seconds of waiting
|
30
|
-
exit
|
29
|
+
echo "Port $1 is not available after $p seconds of waiting"
|
30
|
+
exit 13
|
31
31
|
fi
|
32
|
-
sleep
|
32
|
+
sleep 5
|
33
33
|
done
|
34
34
|
}
|
35
35
|
|
36
36
|
function wait_for_file {
|
37
37
|
while [ ! -f $1 ]; do
|
38
|
-
((c++)) || sleep
|
38
|
+
((c++)) || sleep 5
|
39
39
|
if ((c==30)); then
|
40
|
-
echo File $1 not found, giving up after $c seconds of waiting
|
41
|
-
exit
|
40
|
+
echo "File $1 not found, giving up after $c seconds of waiting"
|
41
|
+
exit 14
|
42
42
|
fi
|
43
|
-
sleep
|
43
|
+
sleep 5
|
44
44
|
done
|
45
45
|
}
|
46
|
+
|
47
|
+
function halt_nodes {
|
48
|
+
for p in "$@"; do
|
49
|
+
pid=$(curl --silent "http://localhost:$p/pid?halt=test" || echo 'absent')
|
50
|
+
if [[ "${pid}" =~ ^[0-9]+$ ]]; then
|
51
|
+
while kill -0 ${pid}; do
|
52
|
+
((c++)) || sleep 5
|
53
|
+
if ((c==30)); then
|
54
|
+
echo "Waiting for process ${pid} to die"
|
55
|
+
exit 15
|
56
|
+
fi
|
57
|
+
echo "Still waiting for process ${pid} to die, cycle no.${c}"
|
58
|
+
sleep 5
|
59
|
+
done
|
60
|
+
echo "Process ${pid} is dead!"
|
61
|
+
fi
|
62
|
+
echo "Node at TCP port ${p} stopped!"
|
63
|
+
done
|
64
|
+
}
|
65
|
+
|
@@ -4,7 +4,7 @@ function start_node {
|
|
4
4
|
port=$(reserve_port)
|
5
5
|
mkdir ${port}
|
6
6
|
cd ${port}
|
7
|
-
zold node --trace --invoice=
|
7
|
+
zold node --trace --invoice=DISTRWALLET@ffffffffffffffff \
|
8
8
|
--host=localhost --port=${port} --bind-port=${port} \
|
9
9
|
--threads=0 --routine-immediately > log.txt &
|
10
10
|
pid=$!
|
@@ -18,7 +18,7 @@ function start_node {
|
|
18
18
|
# don't do the TRAP for killing, the test will never end.
|
19
19
|
first=$(start_node)
|
20
20
|
second=$(start_node)
|
21
|
-
trap "
|
21
|
+
trap "halt_nodes ${first} ${second}" EXIT
|
22
22
|
|
23
23
|
# The first node is linked to the second one and the second one
|
24
24
|
# is linked to the first one. The --home argument specifies their
|
@@ -4,11 +4,11 @@ port=$(reserve_port)
|
|
4
4
|
|
5
5
|
mkdir server
|
6
6
|
cd server
|
7
|
-
zold node --trace --invoice=
|
7
|
+
zold node --trace --invoice=PUSHNPULL@ffffffffffffffff \
|
8
8
|
--host=localhost --port=${port} --bind-port=${port} \
|
9
9
|
--threads=0 --standalone &
|
10
10
|
pid=$!
|
11
|
-
trap "
|
11
|
+
trap "halt_nodes ${port}" EXIT
|
12
12
|
cd ..
|
13
13
|
|
14
14
|
wait_for_port ${port}
|
@@ -4,9 +4,9 @@ function start_node {
|
|
4
4
|
mkdir $1
|
5
5
|
cd $1
|
6
6
|
zold remote clean
|
7
|
-
zold node $3 --nohup --nohup-command='touch restarted' --nohup-log=log \
|
7
|
+
zold node $3 --nohup --nohup-command='touch restarted' --nohup-log=log --nohup-max-cycles=0 \
|
8
8
|
--expose-version=$2 --save-pid=pid --routine-immediately \
|
9
|
-
--verbose --trace --invoice=
|
9
|
+
--verbose --trace --invoice=REDEPLOY@ffffffffffffffff \
|
10
10
|
--host=localhost --port=$1 --bind-port=$1 --threads=0 > /dev/null 2>&1
|
11
11
|
wait_for_port $1
|
12
12
|
cat pid
|
@@ -20,7 +20,7 @@ low=$(reserve_port)
|
|
20
20
|
secondary=$(start_node ${low} 1.1.1)
|
21
21
|
zold remote add localhost ${high} --home=${low} --skip-ping
|
22
22
|
|
23
|
-
trap "
|
23
|
+
trap "halt_nodes ${high}" EXIT
|
24
24
|
|
25
25
|
wait_for_file ${low}/restarted
|
26
26
|
|
data/fixtures/scripts/sigdump.sh
CHANGED
@@ -4,7 +4,7 @@ function start_node {
|
|
4
4
|
port=$(reserve_port)
|
5
5
|
mkdir ${port}
|
6
6
|
cd ${port}
|
7
|
-
zold node --trace --invoice=
|
7
|
+
zold node --trace --invoice=SPREADWALLETS@ffffffffffffffff \
|
8
8
|
--host=localhost --port=${port} --bind-port=${port} \
|
9
9
|
--threads=0 > log.txt &
|
10
10
|
pid=$!
|
@@ -16,7 +16,7 @@ function start_node {
|
|
16
16
|
|
17
17
|
first=$(start_node)
|
18
18
|
second=$(start_node)
|
19
|
-
trap "
|
19
|
+
trap "halt_nodes ${first} ${second}" EXIT
|
20
20
|
|
21
21
|
zold --home=${first} remote clean
|
22
22
|
zold --home=${first} remote add localhost ${second}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018 Yegor Bugayenko
|
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 all
|
13
|
+
# 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 NONINFINGEMENT. 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 THE
|
21
|
+
# SOFTWARE.
|
22
|
+
|
23
|
+
require 'slop'
|
24
|
+
require_relative '../log'
|
25
|
+
require_relative '../score'
|
26
|
+
|
27
|
+
# NEXT command.
|
28
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
29
|
+
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
|
30
|
+
# License:: MIT
|
31
|
+
module Zold
|
32
|
+
# Calculate next score
|
33
|
+
class Next
|
34
|
+
def initialize(log: Log::Quiet.new)
|
35
|
+
@log = log
|
36
|
+
end
|
37
|
+
|
38
|
+
def run(args = [])
|
39
|
+
opts = Slop.parse(args, help: true, suppress_errors: true) do |o|
|
40
|
+
o.banner = "Usage: zold next [options] score
|
41
|
+
Available options:"
|
42
|
+
o.bool '--help', 'Print instructions'
|
43
|
+
end
|
44
|
+
if opts.help?
|
45
|
+
@log.info(opts.to_s)
|
46
|
+
return
|
47
|
+
end
|
48
|
+
calculate(opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def calculate(opts)
|
54
|
+
@log.info(Score.parse(opts.arguments[1]).next.to_s)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/zold/commands/node.rb
CHANGED
@@ -93,6 +93,9 @@ module Zold
|
|
93
93
|
o.string '--nohup-log',
|
94
94
|
'The file to log output into (default: zold.log)',
|
95
95
|
default: 'zold.log'
|
96
|
+
o.string '--halt-code',
|
97
|
+
'The value of HTTP query parameter "halt," which will cause the front-end immediate termination',
|
98
|
+
default: ''
|
96
99
|
o.string '--save-pid',
|
97
100
|
'The file to save process ID into right after start (only in NOHUP mode)'
|
98
101
|
o.bool '--never-reboot',
|
@@ -111,6 +114,10 @@ module Zold
|
|
111
114
|
"The name of the network (default: #{Wallet::MAIN_NETWORK})",
|
112
115
|
require: true,
|
113
116
|
default: Wallet::MAIN_NETWORK
|
117
|
+
o.integer '--nohup-max-cycles',
|
118
|
+
'Maximum amount of nohup re-starts (-1 by default, which means forever)',
|
119
|
+
require: true,
|
120
|
+
default: -1
|
114
121
|
o.bool '--help', 'Print instructions'
|
115
122
|
end
|
116
123
|
if opts.help?
|
@@ -129,6 +136,7 @@ module Zold
|
|
129
136
|
Front.set(:version, opts['expose-version'])
|
130
137
|
Front.set(:protocol, Zold::PROTOCOL)
|
131
138
|
Front.set(:logging, @log.debug?)
|
139
|
+
Front.set(:halt, opts['halt-code'])
|
132
140
|
Front.set(:home, Dir.pwd)
|
133
141
|
@log.info("Home directory: #{Dir.pwd}")
|
134
142
|
@log.info("Ruby version: #{RUBY_VERSION}")
|
@@ -145,7 +153,7 @@ module Zold
|
|
145
153
|
AccessLog: []
|
146
154
|
)
|
147
155
|
if opts['standalone']
|
148
|
-
@remotes = Remotes::Empty.new
|
156
|
+
@remotes = Zold::Remotes::Empty.new(file: '/tmp/standalone')
|
149
157
|
@log.debug('Running in standalone mode! (will never talk to other remotes)')
|
150
158
|
end
|
151
159
|
Front.set(:ignore_score_weakness, opts['ignore-score-weakness'])
|
@@ -196,10 +204,10 @@ module Zold
|
|
196
204
|
@log.info("Starting up the web front at http://#{host}:#{opts[:port]}...")
|
197
205
|
Front.run!
|
198
206
|
@log.info("The web front stopped at http://#{host}:#{opts[:port]}")
|
207
|
+
@log.info('Thanks for helping Zold network!')
|
199
208
|
end
|
200
209
|
end
|
201
210
|
end
|
202
|
-
@log.info("The node #{host}:#{opts[:port]} is shut down, thanks for helping Zold network!")
|
203
211
|
end
|
204
212
|
|
205
213
|
private
|
@@ -238,19 +246,26 @@ module Zold
|
|
238
246
|
end
|
239
247
|
myself = File.expand_path($PROGRAM_NAME)
|
240
248
|
args = ARGV.delete_if { |a| a.start_with?('--nohup', '--home') }
|
249
|
+
cycle = 0
|
241
250
|
loop do
|
242
251
|
begin
|
243
252
|
code = exec("#{myself} #{args.join(' ')}", nohup_log)
|
244
|
-
if code != 0
|
245
|
-
nohup_log.print("Let's wait for a minute, because of the failure...")
|
246
|
-
sleep(60)
|
247
|
-
end
|
253
|
+
raise "Exit code is #{code}" if code != 0
|
248
254
|
exec(opts['nohup-command'], nohup_log)
|
249
255
|
rescue StandardError => e
|
250
256
|
nohup_log.print(Backtrace.new(e).to_s)
|
251
|
-
|
252
|
-
|
257
|
+
if cycle < opts['nohup-max-cycles']
|
258
|
+
nohup_log.print("Let's wait for a minutes, because of the exception...")
|
259
|
+
sleep(60)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
next if opts['nohup-max-cycles'].negative?
|
263
|
+
cycle += 1
|
264
|
+
if cycle > opts['nohup-max-cycles']
|
265
|
+
nohup_log.print("There are no more nohup cycles left, after the cycle no.#{cycle}")
|
266
|
+
break
|
253
267
|
end
|
268
|
+
nohup_log.print("Going for nohup cycle no.#{cycle}")
|
254
269
|
end
|
255
270
|
end
|
256
271
|
Process.detach(pid)
|
data/lib/zold/commands/remote.rb
CHANGED
@@ -207,10 +207,13 @@ Available options:"
|
|
207
207
|
end
|
208
208
|
|
209
209
|
def trim(opts)
|
210
|
-
@remotes.all
|
211
|
-
|
210
|
+
all = @remotes.all
|
211
|
+
all.each do |r|
|
212
|
+
next if r[:errors] <= opts['tolerate']
|
213
|
+
remove(r[:host], r[:port], opts)
|
214
|
+
@log.info("#{r[:host]}:#{r[:port]} removed because of #{r[:errors]} errors (over #{opts['tolerate']})")
|
212
215
|
end
|
213
|
-
@log.info("The list of remotes trimmed, #{@remotes.all.count} nodes left there")
|
216
|
+
@log.info("The list of #{all.count} remotes trimmed, #{@remotes.all.count} nodes left there")
|
214
217
|
end
|
215
218
|
|
216
219
|
def update(opts, deep = true)
|
@@ -256,7 +259,7 @@ in #{(Time.now - start).round(2)}s")
|
|
256
259
|
end
|
257
260
|
|
258
261
|
def select(opts)
|
259
|
-
selected = @remotes.all.sort_by { |r| r[:score] }.first(opts['max-nodes'])
|
262
|
+
selected = @remotes.all.sort_by { |r| r[:score] }.reverse.first(opts['max-nodes'])
|
260
263
|
(@remotes.all - selected).each do |r|
|
261
264
|
@remotes.remove(r[:host], r[:port])
|
262
265
|
end
|
data/lib/zold/metronome.rb
CHANGED
data/lib/zold/node/farm.rb
CHANGED
@@ -78,7 +78,6 @@ module Zold
|
|
78
78
|
Thread.new do
|
79
79
|
Thread.current.abort_on_exception = true
|
80
80
|
Thread.current.name = "f#{t}"
|
81
|
-
Thread.current.priority = -100
|
82
81
|
loop do
|
83
82
|
VerboseThread.new(@log).run do
|
84
83
|
cycle(host, port, strength, threads)
|
@@ -90,7 +89,6 @@ module Zold
|
|
90
89
|
@cleanup = Thread.new do
|
91
90
|
Thread.current.abort_on_exception = true
|
92
91
|
Thread.current.name = 'cleanup'
|
93
|
-
Thread.current.priority = -100
|
94
92
|
while alive
|
95
93
|
sleep(60) unless strength == 1 # which will only happen in tests
|
96
94
|
VerboseThread.new(@log).run do
|
@@ -147,6 +145,9 @@ module Zold
|
|
147
145
|
return unless s.strength >= strength
|
148
146
|
Thread.current.name = s.to_mnemo
|
149
147
|
save(threads, [s.next])
|
148
|
+
# score = Score.parse(`ruby #{File.join(File.dirname(__FILE__), '../../../bin/zold')} next "#{s}"`)
|
149
|
+
# @log.debug("New score discovered: #{score}")
|
150
|
+
# save(threads, [score])
|
150
151
|
cleanup(host, port, strength, threads)
|
151
152
|
end
|
152
153
|
|
data/lib/zold/node/front.rb
CHANGED
@@ -49,6 +49,7 @@ module Zold
|
|
49
49
|
set :lock, false
|
50
50
|
set :show_exceptions, false
|
51
51
|
set :server, 'webrick'
|
52
|
+
set :halt, '' # to be injected at node.rb
|
52
53
|
set :dump_errors, false # to be injected at node.rb
|
53
54
|
set :version, VERSION # to be injected at node.rb
|
54
55
|
set :protocol, PROTOCOL # to be injected at node.rb
|
@@ -69,6 +70,10 @@ module Zold
|
|
69
70
|
use Rack::Deflater
|
70
71
|
|
71
72
|
before do
|
73
|
+
if !settings.halt.empty? && params[:halt] && params[:halt] == settings.halt
|
74
|
+
settings.log.error('Halt signal received, shutting the front end down...')
|
75
|
+
Front.stop!
|
76
|
+
end
|
72
77
|
check_header(Http::NETWORK_HEADER) do |header|
|
73
78
|
if header != settings.network
|
74
79
|
raise "Network name mismatch at #{request.url}, #{request.ip} is in '#{header}', \
|
@@ -119,6 +124,11 @@ while #{settings.address} is in '#{settings.network}'"
|
|
119
124
|
settings.version
|
120
125
|
end
|
121
126
|
|
127
|
+
get '/pid' do
|
128
|
+
content_type 'text/plain'
|
129
|
+
Process.pid.to_s
|
130
|
+
end
|
131
|
+
|
122
132
|
get '/score' do
|
123
133
|
content_type 'text/plain'
|
124
134
|
score.to_s
|
@@ -143,6 +153,7 @@ while #{settings.address} is in '#{settings.network}'"
|
|
143
153
|
score: score.to_h,
|
144
154
|
pid: Process.pid,
|
145
155
|
cpus: Concurrent.processor_count,
|
156
|
+
platform: RUBY_PLATFORM,
|
146
157
|
uptime: `uptime`.strip,
|
147
158
|
threads: "#{Thread.list.select { |t| t.status == 'run' }.count}/#{Thread.list.count}",
|
148
159
|
wallets: settings.wallets.all.count,
|