├── .gitignore ├── .travis.yml ├── Dockerfile.glibc-2.12 ├── Dockerfile.glibc-2.14 ├── README.md ├── Rakefile ├── Vagrantfile ├── bintest └── rcon.rb ├── build_config.rb ├── docker-compose.yml ├── misc ├── provision.sh └── rcon.gif ├── mrbgem.rake ├── mrblib ├── rcon.rb └── rcon │ ├── usage.rb │ └── version.rb ├── test ├── cgconfig.conf └── test_rcon.rb └── tools └── rcon └── rcon.c /.gitignore: -------------------------------------------------------------------------------- 1 | mruby/ 2 | pkg/ 3 | .vagrant/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | compiler: 3 | - gcc 4 | - clang 5 | before_install: 6 | - sudo apt-get -qq update 7 | - sudo mkdir -p /cgroup 8 | - sudo cp test/cgconfig.conf /etc/. 9 | install: 10 | - sudo apt-get -qq install rake bison git gperf libpam0g-dev autotools-dev cgroup-lite 11 | - gem install rake 12 | script: 13 | - rake all test 14 | -------------------------------------------------------------------------------- /Dockerfile.glibc-2.12: -------------------------------------------------------------------------------- 1 | FROM centos:6 2 | MAINTAINER matsumotory 3 | 4 | RUN yum -y install --enablerepo=extras epel-release 5 | RUN yum -y groupinstall "Development Tools" 6 | #RUN yum -y install wget tar libcgroup-devel 7 | RUN yum -y install wget tar pam-devel 8 | RUN yum -y install openssl-devel 9 | RUN yum -y install flex 10 | 11 | RUN mkdir -p /opt/ruby-2.2.3/ && \ 12 | curl -s https://s3.amazonaws.com/pkgr-buildpack-ruby/current/centos-6/ruby-2.2.3.tgz | tar xzC /opt/ruby-2.2.3/ 13 | ENV PATH /opt/ruby-2.2.3/bin:$PATH 14 | 15 | WORKDIR /home/mruby/code 16 | ENV GEM_HOME /home/mruby/code/.gem/ 17 | 18 | ENV PATH $GEM_HOME/bin/:$PATH 19 | ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/ 20 | -------------------------------------------------------------------------------- /Dockerfile.glibc-2.14: -------------------------------------------------------------------------------- 1 | FROM hone/mruby-cli 2 | RUN apt-get -y --no-install-recommends install libpam0g-dev flex 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rcon [![Build Status](https://travis-ci.org/matsumotory/rcon.svg?branch=master)](https://travis-ci.org/matsumotory/rcon) 2 | 3 | rcon is a lightweight resource virtualization tool for linux processes. rcon is one-binary. 4 | 5 | ## demo 6 | 7 | ![](misc/rcon.gif) 8 | 9 | ## build 10 | 11 | ### build on vagrant 12 | 13 | ``` 14 | vagrant up 15 | vagrant ssh 16 | cd rcon/ 17 | rake 18 | cp -p mruby/bin/rcon /path/to/bin-dir/. 19 | ``` 20 | 21 | ## build packages and release 22 | 23 | Create release binary considering glibc versions using Docker. 24 | 25 | __Require: golang docker__ 26 | 27 | I build and release rcon on macOS. 28 | 29 | ``` 30 | rake package 31 | ls -l pkg/ 32 | GITHUB_TOKEN=`cat ~/.github_token` rake release 33 | ``` 34 | 35 | ## usage 36 | ``` 37 | ./rcon --help 38 | Usage: rcon [options] --user username --command "yes >> /dev/null" 39 | --cpu VAL 40 | default: 30 (%) 41 | --memory VAL 42 | default: 512000000 (Byte) 43 | --read VAL 44 | default: 10485760 (Byte/sec) 45 | --write VAL 46 | default: 10485760 (Byte/sec) 47 | --group VAL 48 | default: rcon 49 | --dev VAL 50 | default: 8:0 51 | --pids VAL 52 | default: nothing 53 | --version 54 | ``` 55 | 56 | ## cpu example 57 | 58 | #### no limit 59 | 60 | - command 61 | ``` 62 | yes >> /dev/null 63 | ``` 64 | 65 | - cpu usage 66 | ``` 67 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 68 | 27577 matsumot 20 0 98.5m 608 520 R 100.0 0.0 0:01.95 yes 69 | ``` 70 | 71 | #### limitting cpu 10% 72 | 73 | - command 74 | ``` 75 | sudo ./rcon --user matsumotory --command "yes >> /dev/null" --cpu 10 76 | ``` 77 | 78 | - cpu usage limitted 10% by rcon 79 | ``` 80 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 81 | 23941 matsumot 20 0 98.5m 604 520 R 9.6 0.0 0:00.63 yes 82 | ``` 83 | 84 | ### limitting already running process to cpu 30% 85 | 86 | - command 87 | ``` 88 | yes >> /dev/null & 89 | yes >> /dev/null & 90 | sudo ./rcon --pids "`pgrep yes`" 91 | ``` 92 | 93 | - cpu usage 94 | ``` 95 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 96 | 22531 vagrant 20 0 98.5m 612 524 R 15.6 0.0 0:16.16 yes 97 | 22532 vagrant 20 0 98.5m 612 524 R 14.3 0.0 0:15.47 yes 98 | ``` 99 | 100 | __Notice: pids optsion don't delete groups after running process was finished__ 101 | 102 | ## io example 103 | 104 | #### no limit 105 | 106 | - command 107 | ``` 108 | dd if=/dev/zero of=tempfile bs=1M count=1000 oflag=direct 109 | ``` 110 | 111 | - io usage 112 | ``` 113 | TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 114 | 27569 be/4 matsumot 0.00 B/s 22.09 M/s 0.00 % 95.93 % dd if=/dev/zero of=tempfile bs=1M count=1000 oflag=direct 115 | ``` 116 | 117 | #### limitting write io 1MByte/sec 118 | 119 | - command 120 | ``` 121 | sudo ./rcon --user matsumotory --command "dd if=/dev/zero of=tempfile bs=1M count=1000 oflag=direct" --write 1024000 122 | ``` 123 | 124 | - io usage limitted 1MByte/sec by rcon 125 | ``` 126 | TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 127 | 24676 be/4 matsumot 0.00 B/s 995.77 K/s 0.00 % 99.99 % dd if=/dev/zero of=tempfile bs=1M count=1000 oflag=direct 128 | ``` 129 | 130 | - find io dev id (--dev) 131 | ``` 132 | $ ls -l /dev/xvda | awk '{print $5 $6}' | sed 's/,/:/' 133 | 202:0 134 | ``` 135 | 136 | for `--dev` option. default `8:0`. 137 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | MRUBY_VERSION=ENV["MRUBY_VERSION"] || "c3188cac431225fda48718f309ad3d9318a6e44f" 4 | file :mruby do 5 | cmd = "git clone git://github.com/mruby/mruby.git" 6 | case MRUBY_VERSION 7 | when /\A[a-fA-F0-9]+\z/ 8 | cmd << " && cd mruby" 9 | cmd << " && git fetch && git checkout #{MRUBY_VERSION}" 10 | when /\A\d\.\d\.\d\z/ 11 | cmd << " && cd mruby" 12 | cmd << " && git fetch --tags && git checkout $(git rev-parse #{MRUBY_VERSION})" 13 | when "master" 14 | # skip 15 | else 16 | fail "Invalid MRUBY_VERSION spec: #{MRUBY_VERSION}" 17 | end 18 | sh cmd 19 | end 20 | 21 | APP_NAME=ENV["APP_NAME"] || "rcon" 22 | APP_ROOT=ENV["APP_ROOT"] || Dir.pwd 23 | # avoid redefining constants in mruby Rakefile 24 | mruby_root=File.expand_path(ENV["MRUBY_ROOT"] || "#{APP_ROOT}/mruby") 25 | mruby_config=File.expand_path(ENV["MRUBY_CONFIG"] || "build_config.rb") 26 | ENV['MRUBY_ROOT'] = mruby_root 27 | ENV['MRUBY_CONFIG'] = mruby_config 28 | Rake::Task[:mruby].invoke unless Dir.exist?(mruby_root) 29 | Dir.chdir(mruby_root) 30 | load "#{mruby_root}/Rakefile" 31 | 32 | desc "compile binary" 33 | task :compile => [:all] do 34 | 35 | MRuby.each_target do |target| 36 | `#{target.cc.command} --version` 37 | abort("Command #{target.cc.command} for #{target.name} is missing.") unless $?.success? 38 | end 39 | %W(#{mruby_root}/build/x86_64-pc-linux-gnu/bin/#{APP_NAME} #{mruby_root}/build/i686-pc-linux-gnu/#{APP_NAME}").each do |bin| 40 | sh "strip --strip-unneeded #{bin}" if File.exist?(bin) 41 | end 42 | end 43 | 44 | namespace :test do 45 | desc "run mruby & unit tests" 46 | # only build mtest for host 47 | task :mtest => :compile do 48 | # in order to get mruby/test/t/synatx.rb __FILE__ to pass, 49 | # we need to make sure the tests are built relative from mruby_root 50 | MRuby.each_target do |target| 51 | # only run unit tests here 52 | target.enable_bintest = true 53 | run_test if target.test_enabled? 54 | end 55 | end 56 | 57 | def clean_env(envs) 58 | old_env = {} 59 | envs.each do |key| 60 | old_env[key] = ENV[key] 61 | ENV[key] = nil 62 | end 63 | yield 64 | envs.each do |key| 65 | ENV[key] = old_env[key] 66 | end 67 | end 68 | 69 | desc "run integration tests" 70 | task :bintest => :compile do 71 | MRuby.each_target do |target| 72 | clean_env(%w(MRUBY_ROOT MRUBY_CONFIG)) do 73 | run_bintest if target.bintest_enabled? 74 | end 75 | end 76 | end 77 | end 78 | 79 | desc "run all tests" 80 | Rake::Task['test'].clear 81 | task :test => ["test:mtest", "test:bintest"] 82 | 83 | desc "package" 84 | task :package do 85 | require_relative 'mrblib/rcon/version' 86 | 87 | FileUtils.rm_rf "../pkg" 88 | FileUtils.mkdir_p "../pkg" unless File.exist? "pkg" 89 | 90 | %w[glibc-2.12 glibc-2.14].each do |dist| 91 | Rake::Task["clean"].execute 92 | # build linux_amd64 93 | sh "docker-compose build #{dist}" 94 | sh "docker-compose run #{dist}" 95 | 96 | %w[linux_amd64].each do |target| 97 | Dir.chdir "../mruby/build/#{target}/bin" do 98 | sh "zip #{APP_NAME}.zip #{APP_NAME}" unless File.exist? "#{APP_NAME}.zip" 99 | FileUtils.mv "#{APP_NAME}.zip", "../../../../pkg/#{APP_NAME}_#{Rconner::VERSION}_#{target}_#{dist}.zip" 100 | end 101 | end 102 | end 103 | end 104 | 105 | task :ghr do 106 | sh "go get -u github.com/tcnksm/ghr" if `type ghr`.empty? 107 | end 108 | 109 | desc "release" 110 | task :release => [:ghr] do 111 | Dir.chdir(APP_ROOT) 112 | require_relative 'mrblib/rcon/version' 113 | sh "ghr -u matsumotory --replace v#{Rconner::VERSION} pkg/" 114 | end 115 | 116 | desc "cleanup" 117 | task :clean do 118 | sh "rake deep_clean" 119 | end 120 | 121 | namespace :local do 122 | desc "show help" 123 | task :version do 124 | require_relative 'mrblib/mruby-cli/version' 125 | puts "mruby-cli #{MRubyCLI::Version::VERSION}" 126 | end 127 | end 128 | 129 | def is_in_a_docker_container? 130 | `grep -q docker /proc/self/cgroup` 131 | $?.success? 132 | end 133 | 134 | Rake.application.tasks.each do |task| 135 | next 136 | unless task.name.start_with?("local:") 137 | # Inspired by rake-hooks 138 | # https://github.com/guillermo/rake-hooks 139 | old_task = Rake.application.instance_variable_get('@tasks').delete(task.name) 140 | desc old_task.full_comment 141 | task old_task.name => old_task.prerequisites do 142 | abort("Not running in docker, you should type \"docker-compose run \".") unless is_in_a_docker_container? 143 | old_task.invoke 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "bento/ubuntu-16.04" 3 | config.vm.provision "shell", :path => "misc/provision.sh", :privileged => false 4 | end 5 | -------------------------------------------------------------------------------- /bintest/rcon.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | 3 | BIN_PATH = File.join(File.dirname(__FILE__), "../mruby/bin/rcon") 4 | 5 | assert('argv') do 6 | output, status = Open3.capture2("sudo", BIN_PATH, "--user", "daemon", "--command", "id -u", "--dry-run") 7 | assert_true status.success?, "Process did not exit cleanly" 8 | assert_include output, "{:user=>\"daemon\", :command=>\"id -u\", :\"dry-run\"=>\"\", :cpu=>\"30\", :memory=>\"536870912\", :read=>\"10485760\", :write=>\"10485760\", :group=>\"rcon\", :pids=>\"\", :dev=>\"8:0\"}\n" 9 | end 10 | 11 | assert('argv all') do 12 | output, status = Open3.capture2("sudo", BIN_PATH, "--user", "daemon", "--command", "id -u", "--dry-run", "--cpu", "20", "--memory", "512", "--write", "1024", "--read", "1024", "--group", "hoge", "--dev", "9:0") 13 | assert_true status.success?, "Process did not exit cleanly" 14 | assert_include output, "{:user=>\"daemon\", :command=>\"id -u\", :\"dry-run\"=>\"\", :cpu=>\"20\", :memory=>\"512\", :write=>\"1024\", :read=>\"1024\", :group=>\"hoge\", :dev=>\"9:0\", :pids=>\"\"}\n" 15 | end 16 | -------------------------------------------------------------------------------- /build_config.rb: -------------------------------------------------------------------------------- 1 | def gem_config(conf) 2 | conf.gembox 'full-core' 3 | conf.gem :mgem => "mruby-rcon" 4 | conf.gem :mgem => "mruby-env" 5 | conf.gem :mgem => "mruby-getopts" 6 | 7 | conf.gem File.expand_path(File.dirname(__FILE__)) 8 | end 9 | 10 | MRuby::Build.new do |conf| 11 | toolchain :gcc 12 | 13 | conf.enable_bintest 14 | conf.enable_debug 15 | conf.enable_test 16 | 17 | gem_config(conf) 18 | end 19 | 20 | MRuby::Build.new('linux_amd64') do |conf| 21 | toolchain :gcc 22 | 23 | gem_config(conf) 24 | end 25 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # build on centos6 for glibc 2.12 or earlier 2 | glibc-2.12: 3 | dockerfile: Dockerfile.glibc-2.12 4 | build: . 5 | volumes: 6 | - .:/home/mruby/code:rw 7 | command: rake compile 8 | 9 | # build on ubuntu 14_04 for glibc 2.14 or later binary like centos7 10 | glibc-2.14: 11 | dockerfile: Dockerfile.glibc-2.14 12 | build: . 13 | volumes: 14 | - .:/home/mruby/code:rw 15 | command: rake compile 16 | -------------------------------------------------------------------------------- /misc/provision.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | 3 | sudo apt-get update 4 | sudo apt-get -y install build-essential rake bison git gperf automake m4 \ 5 | autoconf libtool cmake pkg-config libcunit1-dev ragel flex libpam0g-dev 6 | 7 | git clone https://github.com/matsumotory/rcon 8 | -------------------------------------------------------------------------------- /misc/rcon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsumotory/rcon/3a7fe8049f75d336c46e4797fbc9052e4ad6eb45/misc/rcon.gif -------------------------------------------------------------------------------- /mrbgem.rake: -------------------------------------------------------------------------------- 1 | MRuby::Gem::Specification.new('rcon') do |spec| 2 | spec.license = 'MIT' 3 | spec.author = 'MRuby Developer' 4 | spec.summary = 'rcon' 5 | spec.bins = ['rcon'] 6 | 7 | spec.add_dependency 'mruby-print', :core => 'mruby-print' 8 | spec.add_dependency 'mruby-mtest', :mgem => 'mruby-mtest' 9 | spec.add_dependency "mruby-rcon", :github => "matsumoto-r/mruby-rcon" 10 | spec.add_dependency "mruby-env", :mgem => "mruby-env" 11 | spec.add_dependency "mruby-getopts", :mgem => "mruby-getopts" 12 | 13 | end 14 | -------------------------------------------------------------------------------- /mrblib/rcon.rb: -------------------------------------------------------------------------------- 1 | def __main__(argv) 2 | opts = Getopts.getopts("", 3 | "cpu:30", 4 | "memory:#{512 * 1024 * 1024}", 5 | "read:#{10 * 1024 * 1024}", 6 | "write:#{10 * 1024 * 1024}", 7 | "group:rcon", 8 | "user:", 9 | "pids:", 10 | "command:", 11 | "dev:8:0", 12 | "version", 13 | "help", 14 | "dry-run").each_with_object({}) {|ary,opts| opts[ary[0].to_sym] = ary[1] } 15 | 16 | raise ArgumentError, "\n\n#{Rconner::USAGE}\n" if opts.has_key?(:"?") 17 | 18 | if opts.has_key? :version 19 | puts "v#{Rconner::VERSION}" 20 | exit 21 | end 22 | 23 | if opts.has_key? :help 24 | puts Rconner::USAGE 25 | exit 26 | end 27 | 28 | if opts.has_key? :"dry-run" 29 | p opts 30 | exit 31 | end 32 | 33 | raise ArgumentError, "command or pids not found: --command or --pids" if opts[:command].empty? && opts[:pids].empty? 34 | raise ArgumentError, "don't use both --command and --pids" if ! opts[:command].empty? && ! opts[:pids].empty? 35 | raise ArgumentError, "don't use both --user and --pids" if ! opts[:user].empty? && ! opts[:pids].empty? 36 | raise ArgumentError, "user not found: --user" if opts[:user].empty? && opts[:pids].empty? 37 | 38 | Rcon.new({ 39 | :command => opts[:pids].empty? ? opts[:command] : nil, 40 | :pids => opts[:pids].empty? ? nil : opts[:pids].split(" ").map(&:to_i), 41 | :user => opts[:pids].empty? ? opts[:user] : nil, 42 | :resource => { 43 | # if you use oom event callbakc, you should set your cgroup root dir. 44 | # since libcgroup don't support oom event callback. 45 | :root => "/cgroup", 46 | :group => opts[:group], 47 | :cpu_quota => opts[:cpu].to_i * 1000, 48 | :blk_dvnd => opts[:dev], 49 | :blk_rbps => opts[:read].to_i, 50 | :blk_wbps => opts[:write].to_i, 51 | :mem => opts[:memory].to_f, 52 | :oom => true, 53 | }, 54 | }).run 55 | opts 56 | end 57 | -------------------------------------------------------------------------------- /mrblib/rcon/usage.rb: -------------------------------------------------------------------------------- 1 | module Rconner 2 | USAGE =<> /dev/null" 4 | --cpu VAL 5 | default: 30 (%) 6 | --memory VAL 7 | default: 512000000 (Byte) 8 | --read VAL 9 | default: 10000000 (Byte/sec) 10 | --write VAL 11 | default: 10000000 (Byte/sec) 12 | --group VAL 13 | default: rcon 14 | --dev VAL 15 | default: 8:0 16 | --pids VAL 17 | default: nothing 18 | --version 19 | USAGE 20 | end 21 | -------------------------------------------------------------------------------- /mrblib/rcon/version.rb: -------------------------------------------------------------------------------- 1 | module Rconner 2 | VERSION = "0.0.4" 3 | end 4 | -------------------------------------------------------------------------------- /test/cgconfig.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright IBM Corporation. 2007 3 | # 4 | # Authors: Balbir Singh 5 | # This program is free software; you can redistribute it and/or modify it 6 | # under the terms of version 2.1 of the GNU Lesser General Public License 7 | # as published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it would be useful, but 10 | # WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | # 13 | # See man cgconfig.conf for further details. 14 | # 15 | # By default, mount all controllers to /cgroup/ 16 | 17 | mount { 18 | cpuset = /cgroup/cpuset; 19 | cpu = /cgroup/cpu; 20 | cpuacct = /cgroup/cpuacct; 21 | memory = /cgroup/memory; 22 | devices = /cgroup/devices; 23 | freezer = /cgroup/freezer; 24 | net_cls = /cgroup/net_cls; 25 | blkio = /cgroup/blkio; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /test/test_rcon.rb: -------------------------------------------------------------------------------- 1 | class TestRcon < MTest::Unit::TestCase 2 | end 3 | 4 | MTest::Unit.new.run 5 | -------------------------------------------------------------------------------- /tools/rcon/rcon.c: -------------------------------------------------------------------------------- 1 | // This file is generated by mruby-cli. Do not touch. 2 | #include 3 | #include 4 | 5 | /* Include the mruby header */ 6 | #include 7 | #include 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | mrb_state *mrb = mrb_open(); 12 | mrb_value ARGV = mrb_ary_new_capa(mrb, argc); 13 | int i; 14 | int return_value; 15 | 16 | for (i = 0; i < argc; i++) { 17 | mrb_ary_push(mrb, ARGV, mrb_str_new_cstr(mrb, argv[i])); 18 | } 19 | mrb_define_global_const(mrb, "ARGV", ARGV); 20 | 21 | // call __main__(ARGV) 22 | mrb_funcall(mrb, mrb_top_self(mrb), "__main__", 1, ARGV); 23 | 24 | return_value = EXIT_SUCCESS; 25 | 26 | if (mrb->exc) { 27 | mrb_print_error(mrb); 28 | return_value = EXIT_FAILURE; 29 | } 30 | mrb_close(mrb); 31 | 32 | return return_value; 33 | } 34 | --------------------------------------------------------------------------------