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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21354f28529b35ce4d5897120e0f0b37f3ace979705c9d80f746fc2f55e55444
4
- data.tar.gz: 8bf865f98151f931e009bf9c8ee0c19397a7cdc3c5c3406c8ac5273f9743963f
3
+ metadata.gz: bac5cf8138ded782d5114cf84c9d05f3f06e748328024aaa2c99783187c734ac
4
+ data.tar.gz: a8959e5ff6b66c568b0c5d7ead18abdb5c1df9305155e4e9f29ca0c449074617
5
5
  SHA512:
6
- metadata.gz: b23ae54f7a5d19b065c172ea24d9a9df6e81c048ff07a56b770f8d6abe1765cd78971e8604c5f05179d27b9410d8b23997076c79fc56360f69ca2c76bfb72f80
7
- data.tar.gz: dd542f7f462a637e909dd3d1f84e59f2adcc3a184fd0a67287d56228f4a5fd4c757a0950e8294825642a6f26462ffefef7ed51435acbb833f19b31d35ae20791
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
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/sunburst`. To experiment with that code, run `bin/console` for an interactive prompt.
7
+ For example:
4
8
 
5
- TODO: Delete this and the text above, and describe your gem
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
- ## Installation
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
- $ bundle install
47
+ ```
48
+ $ bundle install
49
+ ```
18
50
 
19
51
  Or install it yourself as:
20
-
21
- $ gem install sunburst
52
+ ```
53
+ $ gem install sunburst
54
+ ```
22
55
 
23
56
  ## Usage
57
+ Run sunbust -h for instruction.
24
58
 
25
- TODO: Write usage instructions here
26
-
27
- ## Development
28
-
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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
- require 'sunburst'
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
- puts ?- * Sunburst.win_width
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 ?- * Sunburst.win_width
49
- puts ":: Total Execution Time: #{data[:execution_time]} seconds"
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 "$!.full_message"
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 ps_times(VALUE obj, VALUE pid) {
30
+ VALUE ps_stat(volatile VALUE obj, volatile VALUE pid) {
30
31
  int _pid = FIX2INT(pid) ;
31
- if (_pid < 0) return Qnil ;
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
- unsigned long utime, stime ;
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 != 2) return Qnil ;
45
- float total_time = (utime + stime) / (float)TICKS ;
57
+ if (status != 4) return rb_ary_new() ;
46
58
 
47
- return rb_float_new(total_time) ;
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
- long double time = tv.tv_sec + tv.tv_nsec / 1000000000.0 ;
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
  }
@@ -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 = {execution_time: nil, cpu_time: nil, memory: nil}
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 CPU Time
27
- cpu_time = Sunburst.get_times(pid).truncate(5)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sunburst
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
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.1.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-08 00:00:00.000000000 Z
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