Skip to content

Commit

Permalink
Merge pull request #4 from Contegix/metric_process_thread_count
Browse files Browse the repository at this point in the history
Process/Threads count metrics and check
  • Loading branch information
mattyjones committed Mar 2, 2015
2 parents 38a1005 + 23a33b4 commit 31a4b0c
Show file tree
Hide file tree
Showing 7 changed files with 414 additions and 1 deletion.
96 changes: 96 additions & 0 deletions bin/check-threads-count.rb
@@ -0,0 +1,96 @@
#! /usr/bin/env ruby
#
# check-threads-count.rb
#
# DESCRIPTION:
# Counts the number of threads running on the system and alerts if that number is greater than the warning or critical values.
# The default warning and critical count thresholds come from the ~32000 thread limit in older Linux kernels.
#
# OUTPUT:
# check
#
# PLATFORMS:
# Linux, Windows
#
# DEPENDENCIES:
# gem: sensu-plugin
# gem: sys-proctable
#
# USAGE:
# The check will return an UNKNOWN if the sys-proctable version is not new enough to support it.
#
# NOTES:
# sys-proctable > 0.9.5 is required for counting threads
#
# LICENSE:
# Copyright (c) 2015 Contegix LLC
# Richard Chatteron richard.chatterton@contegix.com
# Released under the same terms as Sensu (the MIT license); see LICENSE
# for details.
#

require 'sensu-plugin/check/cli'
require 'sys/proctable'

#
# Check Threads Count
#
class ThreadsCount < Sensu::Plugin::Check::CLI
option :warn,
description: 'Produce a warning if the total number of threads is greater than this value.',
short: '-w WARN',
default: 30_000,
proc: proc(&:to_i)

option :crit,
description: 'Produce a critical if the total number of threads is greater than this value.',
short: '-c CRIT',
default: 32_000,
proc: proc(&:to_i)

PROCTABLE_MSG = 'sys-proctable version newer than 0.9.5 is required for counting threads with -t or --threads'

# Exit with an unknown if sys-proctable is not high enough to support counting threads.
def check_proctable_version
Gem.loaded_specs['sys-proctable'].version > Gem::Version.create('0.9.5')
end

# Takes a value to be tested as an integer. If a new Integer instance cannot be created from it, return 1.
# See the comments on get_process_threads() for why 1 is returned.
def test_int(i)
return Integer(i) rescue return 1
end

# Takes a process struct from Sys::ProcTable.ps() as an argument
# Attempts to use the Linux thread count field :nlwp first, then tries the Windows field :thread_count.
# Returns the number of processes in those fields.
# Otherwise, returns 1 as all processes are assumed to have at least one thread.
def get_process_threads(p)
if p.respond_to?(:nlwp)
return test_int(p.nlwp)
elsif p.respond_to?(:thread_count)
return test_int(p.thread_count)
else
return 1
end
end

def count_threads
ps_table = Sys::ProcTable.ps
ps_table.reduce(0) do |sum, p|
sum + get_process_threads(p)
end
end

# Main function
def run
if !check_proctable_version
unknown PROCTABLE_MSG unless check_proctable_version
else
threads = count_threads
critical "#{threads} threads running, over threshold #{config[:crit]}" if threads > config[:crit]
warning "#{threads} threads running, over threshold #{config[:warn]}" if threads > config[:warn]
ok "#{threads} threads running"
end
end
end
102 changes: 102 additions & 0 deletions bin/metrics-processes-threads-count.rb
@@ -0,0 +1,102 @@
#! /usr/bin/env ruby
#
# metric-processes-threads-count.rb
#
# DESCRIPTION:
# Counts the number of processes running on the system (and optionally, the number of running threads) and outputs it in metric format.
# Can alternatively count the number of processes/threads matching a certain substring.
#
# OUTPUT:
# metric data
#
# PLATFORMS:
# Linux, Windows
#
# DEPENDENCIES:
# gem: sensu-plugin
# gem: sys-proctable
#
# USAGE:
# Pass [-t|--threads] to count the number of running threads in addition to processes.
# The check will return an UNKNOWN if the sys-proctable version is not new enough to support counting threads.
#
# NOTES:
# sys-proctable > 0.9.5 is required for counting threads (-t, --threads)
#
# LICENSE:
# Copyright (c) 2015 Contegix LLC
# Richard Chatteron richard.chatterton@contegix.com
# Released under the same terms as Sensu (the MIT license); see LICENSE
# for details.
#

require 'sensu-plugin/metric/cli'
require 'sys/proctable'

#
# Processes and Threads Count Metrics
#
class ProcessesThreadsCount < Sensu::Plugin::Metric::CLI::Graphite
option :scheme,
description: 'Scheme for metric output',
short: '-s SCHEME',
long: '--scheme SCHEME',
default: 'system'

option :threads,
description: 'If specified, count the number of threads running on the system in addition to processes. Note: Requires sys-proctables > 0.9.5',
short: '-t',
long: '--threads',
boolean: true,
default: false

PROCTABLE_MSG = 'sys-proctable version newer than 0.9.5 is required for counting threads with -t or --threads'

# Exit with an unknown if sys-proctable is not high enough to support counting threads.
def check_proctable_version
Gem.loaded_specs['sys-proctable'].version > Gem::Version.create('0.9.5')
end

# Takes a value to be tested as an integer. If a new Integer instance cannot be created from it, return 1.
# See the comments on get_process_threads() for why 1 is returned.
def test_int(i)
return Integer(i) rescue return 1
end

# Takes a process struct from Sys::ProcTable.ps() as an argument
# Attempts to use the Linux thread count field :nlwp first, then tries the Windows field :thread_count.
# Returns the number of processes in those fields.
# Otherwise, returns 1 as all processes are assumed to have at least one thread.
def get_process_threads(p)
if p.respond_to?(:nlwp)
return test_int(p.nlwp)
elsif p.respond_to?(:thread_count)
return test_int(p.thread_count)
else
return 1
end
end

def count_threads(ps_table)
ps_table.reduce(0) do |sum, p|
sum + get_process_threads(p)
end
end

# Main function
def run
if config[:threads]
unknown PROCTABLE_MSG unless check_proctable_version
end
ps_table = Sys::ProcTable.ps
processes = ps_table.length
threads = count_threads(ps_table) if config[:threads]

timestamp = Time.now.to_i
output "#{[config[:scheme], 'process_count'].join('.')} #{processes} #{timestamp}"
if config[:threads]
output "#{[config[:scheme], 'thread_count'].join('.')} #{threads} #{timestamp}"
end
ok
end
end
1 change: 0 additions & 1 deletion lib/sensu-plugins-process-checks.rb
Expand Up @@ -4,5 +4,4 @@
module SensuPluginsProcessChecks
# Gem version
VERSION = '0.0.1.alpha.3'

end
1 change: 1 addition & 0 deletions sensu-plugins-process-checks.gemspec
Expand Up @@ -32,6 +32,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'english', '0.6.3'
s.add_runtime_dependency 'sensu-plugin', '1.1.0'
s.add_runtime_dependency 'json', '1.8.2'
s.add_runtime_dependency 'sys-proctable', '0.9.6'

s.add_development_dependency 'codeclimate-test-reporter'
s.add_development_dependency 'rubocop', '~> 0.17.0'
Expand Down
92 changes: 92 additions & 0 deletions test/check-threads-count_spec.rb
@@ -0,0 +1,92 @@
#!/usr/bin/env ruby
#
# check-threads-count_spec
#
# DESCRIPTION:
# Tests for check-threads-count.rb
#
# OUTPUT:
#
# PLATFORMS:
#
# DEPENDENCIES:
#
# USAGE:
# bundle install
# rake spec
#
# NOTES:
# This test suite mocks up a process table to be returned by Sys::ProcTable.ps()
#
# LICENSE:
# Copyright 2015 Contegix, LLC.
# Released under the same terms as Sensu (the MIT license); see LICENSE for details.
#
require_relative './plugin_stub.rb'
require_relative './spec_helper.rb'
require_relative '../bin/check-threads-count.rb'

RSpec.configure do |c|
c.before { allow($stdout).to receive(:puts) }
c.before { allow($stderr).to receive(:puts) }
end

describe ThreadsCount, 'count_threads' do
# Here, we mock Sys::ProcTable.ps() to return an array of structs with only the property this class cares about.
#
it 'should be able to count threads from ProcTable structs with :nlwp fields' do
nlwp_entry = Struct.new(:nlwp)
table = [nlwp_entry.new(3), nlwp_entry.new(1), nlwp_entry.new(6)]
allow(Sys::ProcTable).to receive(:ps).and_return(table)
threadscount = ThreadsCount.new
expect(threadscount.count_threads).to eq(10)
end

it 'should be able to count threads from ProcTable structs with :thread_count fields' do
tc_entry = Struct.new(:thread_count)
table = [tc_entry.new(3), tc_entry.new(1), tc_entry.new(6)]
allow(Sys::ProcTable).to receive(:ps).and_return(table)
threadscount = ThreadsCount.new
expect(threadscount.count_threads).to eq(10)
end

end

describe ThreadsCount, 'run' do

it 'returns unknown if check_proctable_version returns false' do
threadscount = ThreadsCount.new
allow(threadscount).to receive(:count_threads).and_return(0)
allow(threadscount).to receive(:check_proctable_version).and_return(false)
expect(threadscount).to receive(:unknown)
threadscount.run
end

it 'returns critical if count_threads returns more than the critical threshold' do
threadscount = ThreadsCount.new
threadscount.config[:warn] = 10
threadscount.config[:crit] = 15
allow(threadscount).to receive(:count_threads).and_return(20)
expect(threadscount).to receive(:critical)
expect(-> { threadscount.run }).to raise_error SystemExit
end

it 'returns warning if count_threads returns more than the warning threshold' do
threadscount = ThreadsCount.new
threadscount.config[:warn] = 10
threadscount.config[:crit] = 30
allow(threadscount).to receive(:count_threads).and_return(20)
expect(threadscount).to receive(:warning)
expect(-> { threadscount.run }).to raise_error SystemExit
end

it 'returns ok if count_threads returns less than the critical or warning thresholds' do
threadscount = ThreadsCount.new
threadscount.config[:warn] = 10
threadscount.config[:crit] = 20
allow(threadscount).to receive(:count_threads).and_return(5)
expect(threadscount).to receive(:ok)
threadscount.run
end

end

0 comments on commit 31a4b0c

Please sign in to comment.