sunburst 0.1.0 → 0.2.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 +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
|