sunburst 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +54 -19
- data/exe/sunburst +66 -10
- data/ext/stats/stats.c +46 -13
- data/lib/sunburst/sunburst.rb +31 -7
- data/lib/sunburst/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bac5cf8138ded782d5114cf84c9d05f3f06e748328024aaa2c99783187c734ac
|
4
|
+
data.tar.gz: a8959e5ff6b66c568b0c5d7ead18abdb5c1df9305155e4e9f29ca0c449074617
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bad636441bee759ab7783226a3ea6e0e86b5cfae584b1ddc81f2d181df354101c58c43092f38f80136d00e22846641199b5256bc8c80da5f398e29d32a990432
|
7
|
+
data.tar.gz: 8846d22b978e865380622bd7139d0260c7d0f9a3b6c2e43c01230bedeb5f7f473b24136ac1a9dc1a08b85e7efa7f7029a33f439630cd24fa49e6876dd17626c5
|
data/README.md
CHANGED
@@ -1,11 +1,41 @@
|
|
1
1
|
# Sunburst
|
2
|
+
Sunburst lets you run a command for a given time.
|
3
|
+
When the time expires, the program will be SIGKILLed.
|
4
|
+
Sunburst will then report the total CPU time and last known memory usage
|
5
|
+
of the program.
|
2
6
|
|
3
|
-
|
7
|
+
For example:
|
4
8
|
|
5
|
-
|
9
|
+
```
|
10
|
+
$ sunburst "while : ; do echo hi ; sleep 1 ; done" --time=3
|
11
|
+
:: Running "while : ; do echo hi ; sleep 1 ; done" for 3.0 seconds
|
12
|
+
Logging Standard Output and Error
|
13
|
+
hi
|
14
|
+
hi
|
15
|
+
hi
|
16
|
+
|
17
|
+
----------------------------------------------------------------------
|
18
|
+
:: Total Execution Time: 3.00097 seconds
|
19
|
+
:: CPU Time: 0.0 seconds (0.0% exec time)
|
20
|
+
:: Memory usage: 577536 bytes (0.007% system mem)
|
21
|
+
:: Max Threads: 1
|
22
|
+
```
|
6
23
|
|
7
|
-
|
24
|
+
Or
|
25
|
+
|
26
|
+
```
|
27
|
+
$ sunburst "while : ; do : ; done" --time=3
|
28
|
+
:: Running "while : ; do : ; done" for 3.0 seconds
|
29
|
+
Logging Standard Output and Error
|
30
|
+
|
31
|
+
----------------------------------------------------------------------
|
32
|
+
:: Total Execution Time: 3.00097 seconds
|
33
|
+
:: CPU Time: 2.97 seconds (98.968% exec time)
|
34
|
+
:: Memory usage: 360448 bytes (0.004% system mem)
|
35
|
+
:: Max Threads: 1
|
36
|
+
```
|
8
37
|
|
38
|
+
## Installation
|
9
39
|
Add this line to your application's Gemfile:
|
10
40
|
|
11
41
|
```ruby
|
@@ -14,30 +44,35 @@ gem 'sunburst'
|
|
14
44
|
|
15
45
|
And then execute:
|
16
46
|
|
17
|
-
|
47
|
+
```
|
48
|
+
$ bundle install
|
49
|
+
```
|
18
50
|
|
19
51
|
Or install it yourself as:
|
20
|
-
|
21
|
-
|
52
|
+
```
|
53
|
+
$ gem install sunburst
|
54
|
+
```
|
22
55
|
|
23
56
|
## Usage
|
57
|
+
Run sunbust -h for instruction.
|
24
58
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
59
|
+
```
|
60
|
+
Arguments:
|
61
|
+
--time=N Run the program for N seconds
|
62
|
+
-h | --help Show this help section
|
63
|
+
--humanize Human readable memory units
|
64
|
+
|
65
|
+
Example:
|
66
|
+
sunburst echo hello world --time=0.05 --humanize
|
67
|
+
sunburst "echo hello world" --time=0.05 --humanize
|
68
|
+
sunburst "ruby -e 'while true do end'" --time=3 --humanize
|
69
|
+
sunburst "ruby -e 'p :Hello'" --time=3 --humanize
|
70
|
+
```
|
30
71
|
|
31
|
-
|
72
|
+
If no time is specified, it will run until the command exits.
|
32
73
|
|
33
74
|
## Contributing
|
34
|
-
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sunburst. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/sunburst/blob/master/CODE_OF_CONDUCT.md).
|
75
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Souravgoswami/sunburst.
|
36
76
|
|
37
77
|
## License
|
38
|
-
|
39
78
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
40
|
-
|
41
|
-
## Code of Conduct
|
42
|
-
|
43
|
-
Everyone interacting in the Sunburst project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/sunburst/blob/master/CODE_OF_CONDUCT.md).
|
data/exe/sunburst
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$-v = nil
|
2
3
|
STDOUT.sync = STDIN.sync = true
|
4
|
+
require 'sunburst'
|
3
5
|
|
4
6
|
def help
|
5
7
|
puts <<~EOF
|
@@ -25,6 +27,11 @@ def help
|
|
25
27
|
exit 0
|
26
28
|
end
|
27
29
|
|
30
|
+
def splitter(sub = 8)
|
31
|
+
width = Sunburst.win_width
|
32
|
+
puts ?\n, ?-.*(width - sub).center(width)
|
33
|
+
end
|
34
|
+
|
28
35
|
help if ARGV.any? { |x| x[/^\-(\-help|h)$/] }
|
29
36
|
|
30
37
|
time_arg = ARGV.find { |x| x[/^\-\-time=[0-9]+\.?[0-9]*$/] }
|
@@ -40,18 +47,52 @@ command = ARGV.join(' ')
|
|
40
47
|
help if command.empty?
|
41
48
|
|
42
49
|
puts %Q(:: Running "#{command}" for #{time || 'infinite'} seconds)
|
43
|
-
|
50
|
+
|
44
51
|
|
45
52
|
begin
|
53
|
+
print ""
|
54
|
+
|
55
|
+
message = "\e[4mLogging Standard Output and Error\e[0m"
|
56
|
+
puts "\e[38;2;243;156;18m#{message.center(Sunburst.win_width + 6)}\e[0m"
|
57
|
+
|
46
58
|
data = Sunburst.measure(command: command, time: time, sleep_time: 0.0001)
|
59
|
+
print "\e[38;2;243;156;18m"
|
60
|
+
splitter()
|
61
|
+
print "\e[0m"
|
62
|
+
|
63
|
+
exec_time = data[:execution_time]
|
64
|
+
cpu_time = data[:cpu_time]
|
65
|
+
percent_time = cpu_time.*(100).fdiv(exec_time)
|
66
|
+
|
67
|
+
style = "\e[1;"
|
68
|
+
style << if percent_time > 75
|
69
|
+
"38;2;230;80;70m"
|
70
|
+
elsif percent_time > 50
|
71
|
+
"38;2;45;125;255m"
|
72
|
+
elsif percent_time > 25
|
73
|
+
"38;2;255;225;0m"
|
74
|
+
else
|
75
|
+
"38;2;40;175;95m"
|
76
|
+
end
|
47
77
|
|
48
|
-
puts
|
49
|
-
puts "::
|
50
|
-
puts ":: CPU Time: #{data[:cpu_time]} second#{?s if data[:cpu_time] != 1}"
|
78
|
+
puts ":: Total Execution Time: #{data[:execution_time]} seconds\e[0m"
|
79
|
+
puts ":: CPU Time: #{style}#{data[:cpu_time]}\e[0m second#{?s if data[:cpu_time] != 1} (#{percent_time.round(3)}% exec time)"
|
51
80
|
|
52
81
|
mem = data[:memory]
|
53
|
-
|
54
82
|
if mem
|
83
|
+
percent_mem = mem.*(100).fdiv(Sunburst.total_ram)
|
84
|
+
|
85
|
+
style = "\e[1;"
|
86
|
+
style << if percent_mem > 50
|
87
|
+
"38;2;230;80;70m"
|
88
|
+
elsif percent_mem > 30
|
89
|
+
"38;2;45;125;255m"
|
90
|
+
elsif percent_mem > 10
|
91
|
+
"38;2;255;225;0m"
|
92
|
+
else
|
93
|
+
"38;2;40;175;95m"
|
94
|
+
end
|
95
|
+
|
55
96
|
if human_readable
|
56
97
|
mem_text = if mem >= 10 ** 12
|
57
98
|
"#{mem.fdiv(10 ** 12).round(3)} TB"
|
@@ -65,16 +106,31 @@ begin
|
|
65
106
|
"#{mem} Bytes"
|
66
107
|
end
|
67
108
|
|
68
|
-
puts ":: Memory usage: #{mem_text}"
|
109
|
+
puts ":: Memory usage: #{style}#{mem_text}\e[0m (#{percent_mem.round(3)}% system mem)"
|
69
110
|
else
|
70
|
-
puts ":: Memory usage: #{mem} bytes"
|
111
|
+
puts ":: Memory usage: #{style}#{mem} bytes\e[0m (#{percent_mem.round(3)}% system mem)"
|
71
112
|
end
|
72
113
|
else
|
73
114
|
puts ":: The memory usage can't be logged."
|
74
115
|
end
|
116
|
+
|
117
|
+
max_threads = data[:max_threads]
|
118
|
+
style = "\e[1;"
|
119
|
+
style << if max_threads > 16
|
120
|
+
"38;2;230;80;70m"
|
121
|
+
elsif max_threads > 8
|
122
|
+
"38;2;45;125;255m"
|
123
|
+
elsif max_threads > 4
|
124
|
+
"38;2;255;225;0m"
|
125
|
+
else
|
126
|
+
"38;2;40;175;95m"
|
127
|
+
end
|
128
|
+
|
129
|
+
puts ":: Max Threads: #{style}#{max_threads}\e[0m"
|
130
|
+
|
75
131
|
rescue Errno::ENOENT
|
76
132
|
puts "sunburst: #{command}: command not found"
|
77
|
-
puts ?- * Sunburst.win_width
|
133
|
+
puts ?\n, ?- * Sunburst.win_width
|
78
134
|
rescue StandardError
|
79
|
-
puts
|
135
|
+
puts $!.full_message
|
80
136
|
end
|
data/ext/stats/stats.c
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
#include <unistd.h>
|
3
3
|
#include <time.h>
|
4
4
|
#include <sys/ioctl.h>
|
5
|
+
#include <sys/sysinfo.h>
|
5
6
|
|
6
7
|
unsigned int PAGESIZE ;
|
7
8
|
unsigned int TICKS ;
|
8
9
|
|
9
|
-
VALUE statm_memory(VALUE obj, VALUE pid) {
|
10
|
+
VALUE statm_memory(volatile VALUE obj, volatile VALUE pid) {
|
10
11
|
int _pid = FIX2INT(pid) ;
|
11
12
|
if (_pid < 0) return Qnil ;
|
12
13
|
|
@@ -26,41 +27,71 @@ VALUE statm_memory(VALUE obj, VALUE pid) {
|
|
26
27
|
return UINT2NUM(v) ;
|
27
28
|
}
|
28
29
|
|
29
|
-
VALUE
|
30
|
+
VALUE ps_stat(volatile VALUE obj, volatile VALUE pid) {
|
30
31
|
int _pid = FIX2INT(pid) ;
|
31
|
-
if (_pid < 0) return
|
32
|
+
if (_pid < 0) return rb_str_new_cstr("") ;
|
32
33
|
|
33
34
|
char _path[22] ;
|
34
35
|
sprintf(_path, "/proc/%d/stat", _pid) ;
|
35
36
|
|
36
37
|
FILE *f = fopen(_path, "r") ;
|
37
|
-
if (!f) return Qnil ;
|
38
38
|
|
39
|
-
|
39
|
+
if (!f) return rb_ary_new() ;
|
40
|
+
|
41
|
+
// For this info
|
42
|
+
// follow https://man7.org/linux/man-pages/man5/proc.5.html
|
43
|
+
int ppid, processor ;
|
44
|
+
long unsigned utime, stime ;
|
45
|
+
long num_threads ;
|
46
|
+
|
47
|
+
char status = fscanf(
|
48
|
+
f, "%*llu (%*[^)]%*[)] %*c "
|
49
|
+
"%d %*d %*d %*d %*d %*u "
|
50
|
+
"%*lu %*lu %*lu %*lu %lu %lu "
|
51
|
+
"%*ld %*ld %*ld %*ld %ld",
|
52
|
+
&ppid, &utime, &stime, &num_threads
|
53
|
+
) ;
|
40
54
|
|
41
|
-
char status = fscanf(f, "%*llu (%*[^)]%*[)] %*c %*d %*d %*d %*d %*d %*u %*lu %*lu %*lu %*lu %lu %lu", &utime, &stime) ;
|
42
55
|
fclose(f) ;
|
43
56
|
|
44
|
-
if (status !=
|
45
|
-
float total_time = (utime + stime) / (float)TICKS ;
|
57
|
+
if (status != 4) return rb_ary_new() ;
|
46
58
|
|
47
|
-
return
|
59
|
+
return rb_ary_new_from_args(4,
|
60
|
+
INT2NUM(ppid),
|
61
|
+
ULONG2NUM(utime),
|
62
|
+
ULONG2NUM(stime),
|
63
|
+
LONG2NUM(num_threads)
|
64
|
+
) ;
|
48
65
|
}
|
49
66
|
|
50
|
-
VALUE clock_monotonic(VALUE obj) {
|
67
|
+
VALUE clock_monotonic(volatile VALUE obj) {
|
51
68
|
struct timespec tv ;
|
52
69
|
clock_gettime(CLOCK_MONOTONIC, &tv) ;
|
53
|
-
|
70
|
+
float time = tv.tv_sec + tv.tv_nsec / 1000000000.0 ;
|
54
71
|
|
55
72
|
return rb_float_new(time) ;
|
56
73
|
}
|
57
74
|
|
58
|
-
VALUE winWidth(VALUE obj) {
|
75
|
+
VALUE winWidth(volatile VALUE obj) {
|
59
76
|
struct winsize w ;
|
60
77
|
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) ;
|
61
78
|
return INT2NUM(w.ws_col) ;
|
62
79
|
}
|
63
80
|
|
81
|
+
VALUE totalRAM(volatile VALUE obj) {
|
82
|
+
struct sysinfo buf ;
|
83
|
+
char status = sysinfo(&buf) ;
|
84
|
+
|
85
|
+
if (status != 0) return Qnil ;
|
86
|
+
|
87
|
+
return rb_funcall(
|
88
|
+
ULONG2NUM(buf.totalram),
|
89
|
+
rb_intern("*"),
|
90
|
+
1,
|
91
|
+
ULONG2NUM(buf.mem_unit)
|
92
|
+
) ;
|
93
|
+
}
|
94
|
+
|
64
95
|
void Init_stats() {
|
65
96
|
PAGESIZE = sysconf(_SC_PAGESIZE) ;
|
66
97
|
TICKS = sysconf(_SC_CLK_TCK) ;
|
@@ -70,8 +101,10 @@ void Init_stats() {
|
|
70
101
|
rb_define_const(sunburst, "TICKS", UINT2NUM(TICKS)) ;
|
71
102
|
|
72
103
|
rb_define_module_function(sunburst, "get_mem", statm_memory, 1) ;
|
73
|
-
rb_define_module_function(sunburst, "get_times", ps_times, 1) ;
|
74
104
|
rb_define_module_function(sunburst, "clock_monotonic", clock_monotonic, 0) ;
|
75
105
|
|
76
106
|
rb_define_module_function(sunburst, "win_width", winWidth, 0) ;
|
107
|
+
rb_define_module_function(sunburst, "ps_stat", ps_stat, 1) ;
|
108
|
+
|
109
|
+
rb_define_module_function(sunburst, "total_ram", totalRAM, 0) ;
|
77
110
|
}
|
data/lib/sunburst/sunburst.rb
CHANGED
@@ -1,16 +1,29 @@
|
|
1
1
|
module Sunburst
|
2
|
+
def self.get_stats(pid)
|
3
|
+
stats = Sunburst.ps_stat(pid)
|
4
|
+
|
5
|
+
if stats.empty?
|
6
|
+
Process.kill(9, pid)
|
7
|
+
fail RuntimeError, 'Something horribly wrong happened! Exiting.'
|
8
|
+
end
|
9
|
+
|
10
|
+
stats
|
11
|
+
end
|
12
|
+
|
2
13
|
def self.measure(command:, time: nil, sleep_time: 0.001)
|
3
|
-
r = {
|
14
|
+
r = {
|
15
|
+
execution_time: nil, cpu_time: nil,
|
16
|
+
memory: nil, max_threads: nil
|
17
|
+
}
|
4
18
|
|
5
19
|
IO.popen(command) { |x|
|
6
20
|
time1 = Sunburst.clock_monotonic
|
7
21
|
pid = x.pid
|
8
22
|
|
9
|
-
t = Thread.new {
|
10
|
-
print x.readpartial(4096) until x.eof?
|
11
|
-
}
|
23
|
+
t = Thread.new { print x.readpartial(4096) until x.eof? }
|
12
24
|
|
13
25
|
last_mem = 0
|
26
|
+
max_threads = 0
|
14
27
|
|
15
28
|
while true
|
16
29
|
_last_mem = Sunburst.get_mem(pid)
|
@@ -18,13 +31,22 @@ module Sunburst
|
|
18
31
|
break if (time && Sunburst.clock_monotonic - time1 > time) || _last_mem == 0
|
19
32
|
last_mem = _last_mem
|
20
33
|
|
34
|
+
# Get stats
|
35
|
+
stats = get_stats(pid)
|
36
|
+
_threads = stats[3]
|
37
|
+
max_threads = _threads if max_threads < _threads
|
38
|
+
|
21
39
|
sleep(sleep_time)
|
22
40
|
end
|
23
41
|
|
24
42
|
time2 = Sunburst.clock_monotonic
|
25
43
|
|
26
|
-
# Get
|
27
|
-
|
44
|
+
# Get Stats
|
45
|
+
stats = get_stats(pid)
|
46
|
+
cpu_time = stats[1].+(stats[2]).fdiv(Sunburst::TICKS)
|
47
|
+
|
48
|
+
_threads = stats[3]
|
49
|
+
max_threads = _threads if max_threads < _threads
|
28
50
|
|
29
51
|
# Get Memory Usage
|
30
52
|
_last_mem = Sunburst.get_mem(pid)
|
@@ -33,9 +55,11 @@ module Sunburst
|
|
33
55
|
t.kill
|
34
56
|
Process.kill(9, pid)
|
35
57
|
|
36
|
-
r[:execution_time] = time2.-(time1).truncate(5)
|
37
58
|
r[:cpu_time] = cpu_time
|
59
|
+
r[:max_threads] = max_threads unless max_threads == 0
|
38
60
|
r[:memory] = last_mem * Sunburst::PAGESIZE if last_mem > 0
|
61
|
+
|
62
|
+
r[:execution_time] = time2.-(time1).truncate(5)
|
39
63
|
}
|
40
64
|
|
41
65
|
r
|
data/lib/sunburst/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sunburst
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sourav Goswami
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-04-
|
11
|
+
date: 2021-04-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Run a process for a given time, kill it with SIGKILL, report CPU time
|
14
14
|
and memory usage
|