librato-metrics-taps 0.3.6
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.
- data/.document +5 -0
- data/.gitignore +42 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +46 -0
- data/LICENSE.txt +20 -0
- data/README.md +145 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/bin/librato-metrics-tap-jmxbeans +209 -0
- data/examples/cassandra/cfstats-0_7_6-2.yaml +18 -0
- data/examples/cassandra/cfstats-0_8_1.yaml +20 -0
- data/examples/cassandra/cfstats-0_8_8.yaml +20 -0
- data/examples/cassandra/cfstats-1_1_6.yaml +29 -0
- data/examples/cassandra/tpstats-0_7_6-2.yaml +70 -0
- data/examples/cassandra/tpstats-0_8_1.yaml +62 -0
- data/examples/cassandra/tpstats-0_8_8.yaml +82 -0
- data/examples/cassandra/tpstats-1_1_6.yaml +137 -0
- data/examples/java-lang.yaml +21 -0
- data/lib/librato-metrics-taps.rb +0 -0
- data/lib/librato/metrics/taps.rb +24 -0
- data/lib/librato/metrics/taps/jmx.rb +141 -0
- data/lib/librato/metrics/taps/publisher.rb +88 -0
- data/librato-metrics-taps.gemspec +30 -0
- data/temp_sensors/check_em01.c +657 -0
- data/temp_sensors/report-em01b.rb +90 -0
- data/test/helper.rb +18 -0
- data/test/test_librato-metrics-taps.rb +7 -0
- metadata +199 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
java.lang:type=ClassLoading:
|
2
|
+
LoadedClassCount:
|
3
|
+
TotalLoadedClassCount: counter
|
4
|
+
UnloadedClassCount:
|
5
|
+
java.lang:type=GarbageCollector,name=ConcurrentMarkSweep:
|
6
|
+
CollectionCount: counter
|
7
|
+
CollectionTime: counter
|
8
|
+
java.lang:type=GarbageCollector,name=ParNew:
|
9
|
+
CollectionCount: counter
|
10
|
+
CollectionTime: counter
|
11
|
+
java.lang:type=Memory:
|
12
|
+
HeapMemoryUsage:
|
13
|
+
NonHeapMemoryUsage:
|
14
|
+
java.lang:type=MemoryPool,name=CMS Old Gen:
|
15
|
+
CollectionUsage:
|
16
|
+
PeakUsage:
|
17
|
+
Usage:
|
18
|
+
java.lang:type=MemoryPool,name=CMS Perm Gen:
|
19
|
+
CollectionUsage:
|
20
|
+
PeakUsage:
|
21
|
+
Usage:
|
File without changes
|
@@ -0,0 +1,24 @@
|
|
1
|
+
__LIB_DIR__ = File.expand_path(File.join(File.dirname(__FILE__), '../..'))
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift __LIB_DIR__ unless
|
4
|
+
$LOAD_PATH.include?(__LIB_DIR__) ||
|
5
|
+
$LOAD_PATH.include?(File.expand_path(__LIB_DIR__))
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'jmx4r'
|
9
|
+
require 'librato/metrics'
|
10
|
+
require 'trollop'
|
11
|
+
|
12
|
+
module Librato
|
13
|
+
module Metrics
|
14
|
+
module Taps
|
15
|
+
|
16
|
+
def self.version
|
17
|
+
File.read(File.join(File.dirname(__FILE__), '../../../VERSION')).chomp
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'librato/metrics/taps/jmx.rb'
|
24
|
+
require 'librato/metrics/taps/publisher.rb'
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Librato
|
2
|
+
module Metrics
|
3
|
+
module Taps
|
4
|
+
module JMX
|
5
|
+
class << self
|
6
|
+
def connect!(host = 'localhost', port = 3000, username = nil, password = nil)
|
7
|
+
begin
|
8
|
+
::JMX::MBean.establish_connection :host => host, :port => port, :username => username, :pasword => password
|
9
|
+
rescue => err
|
10
|
+
return false
|
11
|
+
end
|
12
|
+
@connected = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def match_beans(beans)
|
16
|
+
raise "Not connected" unless @connected || connect!
|
17
|
+
|
18
|
+
begin
|
19
|
+
b = ::JMX::MBean.find_all_by_name(beans.to_s)
|
20
|
+
rescue => err
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
|
24
|
+
b.collect { |bean| bean.object_name.to_s }
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Retrieves a list of JMX attributes converted to
|
29
|
+
# gauges and counters.
|
30
|
+
#
|
31
|
+
# Argument options:
|
32
|
+
#
|
33
|
+
# beans = ['bean1', 'bean2']
|
34
|
+
# Will retrieve each attribute of each beanname as
|
35
|
+
# a gauge with a count value of 1.
|
36
|
+
#
|
37
|
+
# beans = {'bean1' => ['attr1', 'attr2'], 'bean2' => true}
|
38
|
+
# Will retrieve only the specified attributes for each
|
39
|
+
# bean name. To retrieve all attributes, simply set
|
40
|
+
# to true instead of a list.
|
41
|
+
#
|
42
|
+
# beans = {'bean1' => {'attr' => 'gauge',
|
43
|
+
# 'attr2' => 'counter'}}
|
44
|
+
# Will retrieve the specified beans and their
|
45
|
+
# attributes like the example above, but with the
|
46
|
+
# ability to specify how each attribute is returned. By
|
47
|
+
# default, all attributes are returned as
|
48
|
+
# gauges. However, if you set the attribute name to
|
49
|
+
# 'counter', it will return the attribute as a counter.
|
50
|
+
#
|
51
|
+
#
|
52
|
+
def retrieve(bean_names, ignore_missing = false)
|
53
|
+
raise "Not connected" unless @connected || connect!
|
54
|
+
|
55
|
+
gauges = {}
|
56
|
+
counters = {}
|
57
|
+
(bean_names.keys rescue bean_names).each do |bean|
|
58
|
+
begin
|
59
|
+
b = ::JMX::MBean.find_by_name(bean.to_s)
|
60
|
+
rescue
|
61
|
+
if ignore_missing
|
62
|
+
next
|
63
|
+
else
|
64
|
+
raise "No such bean: #{bean}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
if bean_names.respond_to?(:keys) &&
|
69
|
+
( bean_names[bean].class == Array ||
|
70
|
+
bean_names[bean].class == Hash)
|
71
|
+
attrs = bean_names[bean]
|
72
|
+
else
|
73
|
+
attrs = b.attributes.keys
|
74
|
+
end
|
75
|
+
|
76
|
+
attrs.each do |attr|
|
77
|
+
attrname = attr.first
|
78
|
+
begin
|
79
|
+
value = b.send(snake_case(attrname.to_s))
|
80
|
+
rescue
|
81
|
+
if ignore_missing
|
82
|
+
value = nil
|
83
|
+
else
|
84
|
+
raise "Bean #{bean} has no such attribute: #{attrname}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Skip attributes without a value
|
89
|
+
next unless value
|
90
|
+
|
91
|
+
# Take a look at the type and only handle types that we know how to handle
|
92
|
+
if value.kind_of? Java::JavaxManagementOpenmbean::CompositeDataSupport
|
93
|
+
# If this is a composit data type loop through the elements assuming they are numbers
|
94
|
+
value.each do |value_subkey, value_subvalue|
|
95
|
+
if value_subvalue.kind_of? Numeric
|
96
|
+
if attrs.respond_to?(:keys) && "#{attr.last}".downcase == 'counter'
|
97
|
+
counters[metric_name(bean, attrname + "." + value_subkey)] = value_subvalue.to_i
|
98
|
+
else
|
99
|
+
gauges[metric_name(bean, attrname + "." + value_subkey)] = value_subvalue
|
100
|
+
end
|
101
|
+
else
|
102
|
+
raise "The subvalue \"#{value_subkey}\" of value \"#{value}\" in Bean \"#{bean}\" is of type \"#{value_subvalue.class}\" and I only know how to handle numbers.\n"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
elsif value.kind_of? Numeric
|
106
|
+
# Skip bogus values
|
107
|
+
next if value.to_f.nan? || value.to_f.infinite?
|
108
|
+
|
109
|
+
# If this is a number go ahead and submit it as either a counter or gauge
|
110
|
+
# depending on what we set in the attributes
|
111
|
+
if attrs.respond_to?(:keys) && "#{attr.last}".downcase == 'counter'
|
112
|
+
counters[metric_name(bean, attrname)] = value.to_i
|
113
|
+
else
|
114
|
+
gauges[metric_name(bean, attrname)] = value
|
115
|
+
end
|
116
|
+
else
|
117
|
+
raise "The value \"#{value}\" of Bean \"#{bean}\" is of type \"#{value.class}\" and I do not know how to handle that type.\n"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
[counters, gauges]
|
123
|
+
end
|
124
|
+
|
125
|
+
# From ActiveSupport
|
126
|
+
def snake_case(camel_cased_word)
|
127
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
128
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
129
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
130
|
+
tr("-", "_").
|
131
|
+
downcase
|
132
|
+
end
|
133
|
+
|
134
|
+
def metric_name(bean_name, attr_name)
|
135
|
+
"#{bean_name.gsub('=', ':').gsub(/[, ]/, '_')}::#{attr_name}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Librato
|
2
|
+
module Metrics
|
3
|
+
module Taps
|
4
|
+
class Publisher
|
5
|
+
SOURCE_NAME_REGEX = /^[-A-Za-z0-9_.]{1,255}$/
|
6
|
+
|
7
|
+
@url = nil
|
8
|
+
@user = nil
|
9
|
+
@passwd = nil
|
10
|
+
|
11
|
+
DEFAULT_BATCH_SIZE = 300
|
12
|
+
|
13
|
+
def initialize(opts)
|
14
|
+
unless !opts[:source] || opts[:source] =~ SOURCE_NAME_REGEX
|
15
|
+
raise "Invalid source"
|
16
|
+
end
|
17
|
+
|
18
|
+
unless !opts[:prefix] || opts[:prefix] =~ SOURCE_NAME_REGEX
|
19
|
+
raise "Invalid prefix"
|
20
|
+
end
|
21
|
+
|
22
|
+
@client = Librato::Metrics::Client.new
|
23
|
+
@client.authenticate(opts[:email], opts[:token])
|
24
|
+
@client.api_endpoint = opts[:metrics_url]
|
25
|
+
@client.agent_identifier("librato-metrics-tap-jmxbeans/%s" %
|
26
|
+
[Taps.version])
|
27
|
+
|
28
|
+
qparams = {
|
29
|
+
:autosubmit_count => DEFAULT_BATCH_SIZE
|
30
|
+
}
|
31
|
+
|
32
|
+
[:prefix, :source].each do |o|
|
33
|
+
qparams[o] = opts[o] if opts[o]
|
34
|
+
end
|
35
|
+
|
36
|
+
@queue_params = qparams
|
37
|
+
end
|
38
|
+
|
39
|
+
# Post metrics to metrics service
|
40
|
+
#
|
41
|
+
# counters:
|
42
|
+
# {:my-counter-1 => 4, :my-counter-2 => 6, ...}
|
43
|
+
#
|
44
|
+
# gauges:
|
45
|
+
# {:my-gauge-1 => 4, ...}
|
46
|
+
# or
|
47
|
+
# {:my-gauge-2 => {:value => 5, :count => 4,
|
48
|
+
# :min => 3, :max => 6, ...}}
|
49
|
+
# or mix-match.
|
50
|
+
def post(counters, gauges, params = {})
|
51
|
+
mt = params[:measure_time] || now_secs
|
52
|
+
q = @client.new_queue(@queue_params)
|
53
|
+
|
54
|
+
if counters.length > 0
|
55
|
+
counters.each_pair do |k, v|
|
56
|
+
q.add k => {:type => :counter, :value => v, :measure_time => mt}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if gauges.length > 0
|
61
|
+
params[:gauges] = {}
|
62
|
+
gauges.each_pair do |k, v|
|
63
|
+
if v.respond_to?(:keys)
|
64
|
+
q.add k => v.merge(:measure_time => mt)
|
65
|
+
else
|
66
|
+
q.add k => {:value => Float(v), :measure_time => mt}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
begin
|
72
|
+
q.submit
|
73
|
+
rescue => err
|
74
|
+
# XXX: ever get here? what about auto-submit?
|
75
|
+
puts "Failed to publish stats (unknown exception): #{err.inspect}"
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
def now_secs
|
83
|
+
Time.now.tv_sec
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{librato-metrics-taps}
|
5
|
+
s.version = File.read(File.join(File.dirname(__FILE__), 'VERSION')).chomp
|
6
|
+
|
7
|
+
s.authors = [%q{Librato, Inc.}]
|
8
|
+
s.date = %q{2012-05-15}
|
9
|
+
s.description = %q{Taps for extracting metrics data and publishing to Librato Metrics}
|
10
|
+
s.email = %q{mike@librato.com}
|
11
|
+
s.summary = %q{Librato Metrics Taps}
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.homepage = %q{http://github.com/librato/librato-metrics-taps}
|
19
|
+
s.licenses = [%q{MIT}]
|
20
|
+
|
21
|
+
s.add_runtime_dependency(%q<jmx4r>, ["= 0.1.4"])
|
22
|
+
s.add_runtime_dependency(%q<librato-metrics>, ["~> 1.0.4"])
|
23
|
+
s.add_runtime_dependency(%q<trollop>, ["= 1.16.2"])
|
24
|
+
s.add_runtime_dependency(%q<jruby-openssl>, ["= 0.8.5"])
|
25
|
+
|
26
|
+
s.add_development_dependency(%q<rake>, [">= 0"])
|
27
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
28
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,657 @@
|
|
1
|
+
/* Print Debug Info. Uncomment the #define below to print debugging to stderr */
|
2
|
+
|
3
|
+
//#define DEBUG
|
4
|
+
|
5
|
+
/* Number of seconds to timeout */
|
6
|
+
#define CONNECT_TIMEOUT 5
|
7
|
+
#define MAX_RETRIES 5
|
8
|
+
/******************************************************************************
|
9
|
+
|
10
|
+
Esensors EM01 Plugin.
|
11
|
+
Description: This plugin is written mainly for Nagios, but can be
|
12
|
+
easily used for other software too.
|
13
|
+
|
14
|
+
This program is free software; you can redistribute it and/or modify
|
15
|
+
it under the terms of the GNU General Public License as published by
|
16
|
+
the Free Software Foundation; either version 2 of the License, or
|
17
|
+
(at your option) any later version.
|
18
|
+
|
19
|
+
This program is distributed in the hope that it will be useful,
|
20
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
21
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
22
|
+
GNU General Public License for more details.
|
23
|
+
|
24
|
+
You should have received a copy of the GNU General Public License
|
25
|
+
along with this program; if not, write to the Free Software
|
26
|
+
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
27
|
+
|
28
|
+
Credits:
|
29
|
+
Duncan Robertson [Duncan.Robertson@vsl.com.au] - 64bits fix
|
30
|
+
Ali Rahimi [ali@XCF.Berkeley.EDU] - Sockets code
|
31
|
+
David W. Deley [deleyd@cox.net] - Random numbers
|
32
|
+
|
33
|
+
$Id: check_em01.c,v 2.1 3:47 PM 1/26/2009 $
|
34
|
+
|
35
|
+
******************************************************************************/
|
36
|
+
/** This value is to multiple the Voltage value detected by a fixed constant. (
|
37
|
+
* i.e. if you are using a regulator supply to measure AC voltage)
|
38
|
+
*/
|
39
|
+
const float VOLTAGE_MULTIPLIER = 1.0;
|
40
|
+
const float VOLTAGE_OFFSET = 0.0;
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Tells the plugin to reset contact closure on trigger. Change to 0 to turn this off.
|
44
|
+
*/
|
45
|
+
const int RESETCONTACT = 1;
|
46
|
+
|
47
|
+
|
48
|
+
const int DEFAULTPORT = 80;
|
49
|
+
const char *progname = "check_em01";
|
50
|
+
const char *revision = "$Revision: 2.1 $";
|
51
|
+
const char *copyright = "2009";
|
52
|
+
const char *email = "techhelp@eEsensors.com";
|
53
|
+
|
54
|
+
typedef int SOCKET;
|
55
|
+
typedef enum {E_NET_ERRNO=-1, E_NET_OK=0} NetErrnoType;
|
56
|
+
|
57
|
+
#include <stdio.h>
|
58
|
+
#include <stdlib.h>
|
59
|
+
#include <sys/types.h>
|
60
|
+
#include <sys/socket.h>
|
61
|
+
#include <netinet/in.h>
|
62
|
+
#include <arpa/inet.h>
|
63
|
+
#include <netdb.h>
|
64
|
+
#include <sys/ioctl.h>
|
65
|
+
#include <string.h>
|
66
|
+
#include <errno.h>
|
67
|
+
#include <signal.h>
|
68
|
+
#include <time.h>
|
69
|
+
|
70
|
+
void INThandler(int);
|
71
|
+
|
72
|
+
static NetErrnoType net_errno = E_NET_OK;
|
73
|
+
static int saved_errno = 0;
|
74
|
+
static SOCKET s;
|
75
|
+
|
76
|
+
|
77
|
+
/* Translations between ``net_errno'' values and human readable strings.
|
78
|
+
*/
|
79
|
+
static const char *net_syserrlist[] = {
|
80
|
+
"All was chill"
|
81
|
+
};
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
#ifdef STRERROR_NOT_DEFINED
|
87
|
+
const char *strerror(int errno) { return sys_errlist[errno]; }
|
88
|
+
#endif
|
89
|
+
|
90
|
+
static void NetSetErrno(NetErrnoType e)
|
91
|
+
{
|
92
|
+
if(e == E_NET_ERRNO)saved_errno = errno;
|
93
|
+
net_errno = e;
|
94
|
+
}
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
/* NetErrStr()
|
99
|
+
*--------------------------------------------------------------------
|
100
|
+
* Returns a diagnostic message for the last failure.
|
101
|
+
*/
|
102
|
+
const char *NetErrStr()
|
103
|
+
{
|
104
|
+
return net_errno==E_NET_ERRNO ? strerror(saved_errno) :
|
105
|
+
net_syserrlist[net_errno];
|
106
|
+
}
|
107
|
+
|
108
|
+
/* NetErrNo()
|
109
|
+
*--------------------------------------------------------------------
|
110
|
+
* Returns a diagnostic number for the last failure.
|
111
|
+
*/
|
112
|
+
NetErrnoType NetErrNo()
|
113
|
+
{
|
114
|
+
return net_errno;
|
115
|
+
}
|
116
|
+
|
117
|
+
/* NetMakeContact()
|
118
|
+
*--------------------------------------------------------------------
|
119
|
+
* Makes a tcp connection to a host:port pair.
|
120
|
+
*--------------------------------------------------------------------
|
121
|
+
* ``Hostname'' can either be in the form of a hostname or an IP address
|
122
|
+
* represented as a string. If the hostname is not found as it is,
|
123
|
+
* ``hostname'' is assumed to be an IP address, and it is treated as such.
|
124
|
+
*
|
125
|
+
* If the lookup succeeds, a TCP connection is established with the
|
126
|
+
* specified ``port'' number on the remote host and a stream socket is
|
127
|
+
* returned.
|
128
|
+
*
|
129
|
+
* On any sort of error, an error code can be obtained with @NetErrNo()
|
130
|
+
* and a message with @NetErrStr().
|
131
|
+
*/
|
132
|
+
SOCKET
|
133
|
+
NetMakeContact(const char *hname, int port)
|
134
|
+
{
|
135
|
+
int fd;
|
136
|
+
struct sockaddr_in addr;
|
137
|
+
struct hostent *hent;
|
138
|
+
|
139
|
+
fd = socket(AF_INET, SOCK_STREAM, 0);
|
140
|
+
if(fd == -1)
|
141
|
+
{
|
142
|
+
NetSetErrno(E_NET_ERRNO);
|
143
|
+
return -1;
|
144
|
+
}
|
145
|
+
|
146
|
+
|
147
|
+
hent = gethostbyname(hname);
|
148
|
+
if(hent == NULL)
|
149
|
+
addr.sin_addr.s_addr = inet_addr(hname);
|
150
|
+
else
|
151
|
+
memcpy(&addr.sin_addr, hent->h_addr, hent->h_length);
|
152
|
+
addr.sin_family = AF_INET;
|
153
|
+
addr.sin_port = htons(port);
|
154
|
+
|
155
|
+
#ifdef DEBUG
|
156
|
+
fprintf(stderr, "Creating Connection... ");
|
157
|
+
#endif
|
158
|
+
|
159
|
+
if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)))
|
160
|
+
{
|
161
|
+
NetSetErrno(E_NET_ERRNO);
|
162
|
+
return -1;
|
163
|
+
}
|
164
|
+
|
165
|
+
NetSetErrno(E_NET_OK);
|
166
|
+
return fd;
|
167
|
+
}
|
168
|
+
|
169
|
+
|
170
|
+
|
171
|
+
/*********************************************************************************/
|
172
|
+
|
173
|
+
float Exp10(int n)
|
174
|
+
{
|
175
|
+
int i;
|
176
|
+
float result = 1;
|
177
|
+
for(i =n; i; i--)
|
178
|
+
result *= 10;
|
179
|
+
return result;
|
180
|
+
}
|
181
|
+
|
182
|
+
float myatof(const char *s)
|
183
|
+
{
|
184
|
+
float result;
|
185
|
+
int val = 0, dec = 0, n = 0;;
|
186
|
+
int neg = 0;
|
187
|
+
|
188
|
+
/* skip white space */
|
189
|
+
while (*s == ' ' || *s == '\t') {
|
190
|
+
s++;
|
191
|
+
}
|
192
|
+
|
193
|
+
if (*s == '-') {
|
194
|
+
neg = 1;
|
195
|
+
s++;
|
196
|
+
} else if (*s == '+') {
|
197
|
+
s++;
|
198
|
+
}
|
199
|
+
while (*s >= '0' && *s <= '9') {
|
200
|
+
val *= 10;
|
201
|
+
val += *s++ - '0';
|
202
|
+
}
|
203
|
+
result = val;
|
204
|
+
|
205
|
+
if(*s == '.') {
|
206
|
+
*s++;
|
207
|
+
while (*s >= '0' && *s <= '9') {
|
208
|
+
dec *= 10;
|
209
|
+
dec += *s++ - '0';
|
210
|
+
n++;
|
211
|
+
}
|
212
|
+
|
213
|
+
if(n)
|
214
|
+
result += dec/Exp10(n);
|
215
|
+
}
|
216
|
+
|
217
|
+
if (neg) {
|
218
|
+
result = -result;
|
219
|
+
}
|
220
|
+
|
221
|
+
return result;
|
222
|
+
}
|
223
|
+
|
224
|
+
/*
|
225
|
+
This function is an abstraction layer between app and sockets.
|
226
|
+
Currently, it only passes down all the arguments it receives from
|
227
|
+
app. However, in the future, it will be easier to change
|
228
|
+
sockets library and not affect the main app code at all.
|
229
|
+
*/
|
230
|
+
SOCKET connectWebsensor (char* hostname, int port){
|
231
|
+
//SOCKET s;
|
232
|
+
fd_set readfds;
|
233
|
+
|
234
|
+
int i;
|
235
|
+
s = NetMakeContact(hostname,port);
|
236
|
+
if(s==-1) {
|
237
|
+
return -1;
|
238
|
+
}
|
239
|
+
else{
|
240
|
+
/* Make the socket non-blocking */
|
241
|
+
ioctl(s, FIONBIO, 1);
|
242
|
+
FD_ZERO(&readfds);
|
243
|
+
FD_SET(0, &readfds);
|
244
|
+
FD_SET(s, &readfds);
|
245
|
+
return s;
|
246
|
+
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
/*
|
251
|
+
This function is called when a timeout has occurred.
|
252
|
+
It shutsdown the socket and allows the main loop to continue.
|
253
|
+
*/
|
254
|
+
void INThandler(int sig)
|
255
|
+
{
|
256
|
+
signal(SIGALRM, SIG_IGN);
|
257
|
+
shutdown(s, SHUT_RDWR);
|
258
|
+
alarm(CONNECT_TIMEOUT);
|
259
|
+
signal(SIGALRM, INThandler);
|
260
|
+
}
|
261
|
+
|
262
|
+
|
263
|
+
main(int argc, char **argv)
|
264
|
+
{
|
265
|
+
int l, retry, i, random_backoff;
|
266
|
+
long ms;
|
267
|
+
float data;
|
268
|
+
char iobuf[1024], timestr[32], datachar[7];
|
269
|
+
char* pos;
|
270
|
+
char rcvd_checksum, calc_checksum;
|
271
|
+
time_t start_time, cur_time;
|
272
|
+
double r, x;
|
273
|
+
SOCKET contacts;
|
274
|
+
|
275
|
+
progname = argv[0];
|
276
|
+
if(argc < 2 || strcmp(argv[1],"--help") == 0) {
|
277
|
+
print_help();
|
278
|
+
return(3);
|
279
|
+
}
|
280
|
+
|
281
|
+
time(&cur_time);
|
282
|
+
sprintf(timestr, "%s", ctime(&cur_time));
|
283
|
+
|
284
|
+
signal(SIGALRM, INThandler);
|
285
|
+
alarm(CONNECT_TIMEOUT);
|
286
|
+
|
287
|
+
for(retry = 0; retry < MAX_RETRIES; retry++){
|
288
|
+
|
289
|
+
srand((unsigned int)time(NULL));
|
290
|
+
|
291
|
+
r = ( (double)rand() / ((double)(RAND_MAX)+(double)(1)) );
|
292
|
+
x = (r * 20);
|
293
|
+
random_backoff = (int) x;
|
294
|
+
|
295
|
+
#ifdef DEBUG
|
296
|
+
fprintf(stderr, "Sleeping: %d ", 100+random_backoff);
|
297
|
+
#endif
|
298
|
+
for(ms=0; ms < 100+random_backoff; ms++){
|
299
|
+
usleep(5000);
|
300
|
+
}
|
301
|
+
|
302
|
+
|
303
|
+
/* make connection to websensor */
|
304
|
+
s = connectWebsensor(argv[1], DEFAULTPORT);
|
305
|
+
#ifdef DEBUG
|
306
|
+
fprintf(stderr, "Socket created ");
|
307
|
+
#endif
|
308
|
+
|
309
|
+
if(NetErrNo() != 0){
|
310
|
+
#ifdef DEBUG
|
311
|
+
fprintf(stderr, "Could not connect to Websensor because %s, will retry %d more times.\n", NetErrStr(), 10-retry);
|
312
|
+
#endif
|
313
|
+
shutdown(s, SHUT_RDWR);
|
314
|
+
continue;
|
315
|
+
}
|
316
|
+
|
317
|
+
|
318
|
+
/* send HTTP GET request to obtain data */
|
319
|
+
if(argc>2){
|
320
|
+
|
321
|
+
switch (toupper(argv[2][0])){
|
322
|
+
case 'R':
|
323
|
+
write(s, "GET /index.html?eR HTTP/1.1\r\nUser-Agent: EsensorsPlugin\r\nHost: localhost\r\n\r\n", 77);
|
324
|
+
break;
|
325
|
+
|
326
|
+
case 'V':
|
327
|
+
write(s, "GET /index.html?eV HTTP/1.1\r\nUser-Agent: EsensorsPlugin\r\nHost: localhost\r\n\r\n", 77);
|
328
|
+
break;
|
329
|
+
|
330
|
+
default:
|
331
|
+
write(s, "GET /index.html?em123456 HTTP/1.1\r\nUser-Agent: EsensorsPlugin\r\nHost: localhost\r\n\r\n", 83);
|
332
|
+
break;
|
333
|
+
}
|
334
|
+
}
|
335
|
+
|
336
|
+
else{ // Not enough arguments from command line. Use default websensor command.
|
337
|
+
write(s, "GET /index.html?em123456 HTTP/1.1\r\nUser-Agent: EsensorsPlugin\r\nHost: localhost\r\n\r\n", 83);
|
338
|
+
}
|
339
|
+
|
340
|
+
#ifdef DEBUG
|
341
|
+
fprintf(stderr, "Wrote to Socket ");
|
342
|
+
#endif
|
343
|
+
|
344
|
+
l = read(s, iobuf, sizeof(iobuf));
|
345
|
+
|
346
|
+
#ifdef DEBUG
|
347
|
+
fprintf(stderr, "Read from socket ");
|
348
|
+
#endif
|
349
|
+
|
350
|
+
/* No data returned from websensor. Will retry again. */
|
351
|
+
|
352
|
+
if(l<=0){
|
353
|
+
#ifdef DEBUG
|
354
|
+
fprintf(stderr, "No Data Read, will retry %d more times.\n", MAX_RETRIES-retry);
|
355
|
+
#endif
|
356
|
+
shutdown(s, SHUT_RDWR);
|
357
|
+
continue;
|
358
|
+
}
|
359
|
+
|
360
|
+
pos = strstr(iobuf, "<body>");
|
361
|
+
if(pos == 0){
|
362
|
+
printf("Invalid data received.\n");
|
363
|
+
return(3);
|
364
|
+
}
|
365
|
+
|
366
|
+
|
367
|
+
|
368
|
+
//Search for the sensor data string
|
369
|
+
pos = strstr(iobuf, "TF:");
|
370
|
+
if(pos == 0){
|
371
|
+
pos = strstr(iobuf, "TC:");
|
372
|
+
if(pos == 0){
|
373
|
+
#ifdef DEBUG
|
374
|
+
fprintf(stderr, "Using default parsing parameters.\n");
|
375
|
+
#endif
|
376
|
+
pos=&iobuf[167];
|
377
|
+
}
|
378
|
+
}
|
379
|
+
|
380
|
+
if(argc>2 && toupper(argv[2][0]) == 'V'){ //If the command was for voltage measurement, try looking for the string 'RV' instead.
|
381
|
+
pos = strstr(iobuf, "RV");
|
382
|
+
if(pos == 0 || pos[1] != 'V'){
|
383
|
+
#ifdef DEBUG
|
384
|
+
fprintf(stderr, "Invalid Data Received. Will retry %d more times.\n", MAX_RETRIES-retry);
|
385
|
+
#endif
|
386
|
+
shutdown(s, SHUT_RDWR);
|
387
|
+
continue;
|
388
|
+
}
|
389
|
+
else{
|
390
|
+
pos = pos - 2;
|
391
|
+
break; //Voltage data looks good
|
392
|
+
}
|
393
|
+
}
|
394
|
+
|
395
|
+
pos = pos - 2;
|
396
|
+
/* Unsupported command. OBSOLETE CODE */
|
397
|
+
if(pos[0] == '#'){
|
398
|
+
printf("Invalid Command. Option %c selected may not be available for this websensor.\n", argv[2][0]);
|
399
|
+
return 3;
|
400
|
+
}
|
401
|
+
|
402
|
+
/* The http data is not properly formatted. */
|
403
|
+
if(pos[2] != 'T' && pos[2] != 'R' && pos[1] != 'v'){
|
404
|
+
#ifdef DEBUG
|
405
|
+
fprintf(stderr, "Data input incorrect, will retry %d more times.\n", MAX_RETRIES-retry);
|
406
|
+
#endif
|
407
|
+
shutdown(s, SHUT_RDWR);
|
408
|
+
continue;
|
409
|
+
}
|
410
|
+
else{
|
411
|
+
break; /* All data looks good. Break out of loop. */
|
412
|
+
}
|
413
|
+
}
|
414
|
+
|
415
|
+
time(&cur_time);
|
416
|
+
sprintf(timestr, "%s", ctime(&cur_time));
|
417
|
+
|
418
|
+
/* Retried 3 times earlier and still no good data. Time to exit */
|
419
|
+
if(retry >= MAX_RETRIES){
|
420
|
+
printf("NO DATA\n");
|
421
|
+
return 3;
|
422
|
+
}
|
423
|
+
else{
|
424
|
+
shutdown(s, SHUT_RDWR);
|
425
|
+
}
|
426
|
+
|
427
|
+
if(argc > 2){
|
428
|
+
switch(toupper(argv[2][0])){
|
429
|
+
|
430
|
+
|
431
|
+
case 'G': //Cacti/RRDTool Output TempF:** Humid:**
|
432
|
+
{
|
433
|
+
strncpy(datachar, pos+5, 5);
|
434
|
+
datachar[5] = '\0';
|
435
|
+
data = myatof(datachar);
|
436
|
+
data += 0.01;
|
437
|
+
printf("Temp:%3.2f ", data);
|
438
|
+
//printf("TempUnit:%c ", pos[3]); //prints the temperature unit
|
439
|
+
|
440
|
+
strncpy(datachar, pos+13, 4);
|
441
|
+
datachar[4] = '\0';
|
442
|
+
data = myatof(datachar);
|
443
|
+
data += 0.01;
|
444
|
+
printf("Humid:%3.2f ", data);
|
445
|
+
|
446
|
+
strncpy(datachar, pos+21, 5);
|
447
|
+
datachar[5] = '\0';
|
448
|
+
data = myatof(datachar);
|
449
|
+
data += 0.01;
|
450
|
+
printf("Illum:%3.2f\n", data);
|
451
|
+
|
452
|
+
return(0);
|
453
|
+
}
|
454
|
+
break;
|
455
|
+
|
456
|
+
|
457
|
+
|
458
|
+
case 'T':
|
459
|
+
{
|
460
|
+
strncpy(datachar, pos+5, 5);
|
461
|
+
datachar[5] = '\0';
|
462
|
+
data = myatof(datachar);
|
463
|
+
data += 0.01;
|
464
|
+
if(argc != 7){
|
465
|
+
printf("(No limits specified) Temperature: %s %c | Temp%c=%3.2f\n", datachar, pos[3], pos[3], data);
|
466
|
+
return(0);
|
467
|
+
}
|
468
|
+
if(data < myatof(argv[5]) || data > myatof(argv[6])){
|
469
|
+
printf("CRITICAL ( %s< or >%s ) Temperature: %s %c | Temp%c=%3.2f\n", argv[5], argv[6], datachar, pos[3], pos[3], data);
|
470
|
+
return(2);
|
471
|
+
}
|
472
|
+
else if(data < myatof(argv[3]) || data > myatof(argv[4])){
|
473
|
+
printf("WARNING ( %s< or >%s ) Temperature: %s %c | Temp%c=%3.2f\n", argv[3], argv[4], datachar, pos[3], pos[3], data);
|
474
|
+
return(1);
|
475
|
+
}
|
476
|
+
else{
|
477
|
+
printf("OK Temperature: %s %c | Temp%c=%3.2f\n", datachar, pos[3], pos[3], data);
|
478
|
+
return(0);
|
479
|
+
}
|
480
|
+
}
|
481
|
+
break;
|
482
|
+
|
483
|
+
case 'H':
|
484
|
+
{
|
485
|
+
strncpy(datachar, pos+13, 4);
|
486
|
+
datachar[4] = '\0';
|
487
|
+
data = myatof(datachar);
|
488
|
+
data += 0.01;
|
489
|
+
if(argc != 7){
|
490
|
+
printf("(No limits specified) %s% | Humid=%3.2f\n", datachar, data);
|
491
|
+
return(0);
|
492
|
+
}
|
493
|
+
if(data < myatof(argv[5]) || data > myatof(argv[6])){
|
494
|
+
printf("CRITICAL ( %s< or >%s ) Humidity: %s% | Humid=%3.2f\n", argv[5], argv[6], datachar, data);
|
495
|
+
return(2);
|
496
|
+
}
|
497
|
+
else if(data < myatof(argv[3]) || data > myatof(argv[4])){
|
498
|
+
printf("WARNING ( %s< or >%s ) Humidity: %s% | Humid=%3.2f\n", argv[3], argv[4], datachar, data);
|
499
|
+
return(1);
|
500
|
+
}
|
501
|
+
else{
|
502
|
+
printf("OK Humidity: %s% | Humid=%3.2f\n", datachar, data);
|
503
|
+
return(0);
|
504
|
+
}
|
505
|
+
}
|
506
|
+
break;
|
507
|
+
|
508
|
+
case 'I':
|
509
|
+
{
|
510
|
+
strncpy(datachar, pos+21, 5);
|
511
|
+
datachar[5] = '\0';
|
512
|
+
data = myatof(datachar);
|
513
|
+
data += 0.01;
|
514
|
+
if(argc != 7){
|
515
|
+
printf("(No limits specified) Illumination: %s | Illum=%3.2f\n", datachar, data);
|
516
|
+
return(0);
|
517
|
+
}
|
518
|
+
if(data < myatof(argv[5]) || data > myatof(argv[6])){
|
519
|
+
printf("CRITICAL ( %s< or >%s ) Illumination: %s | Illum=%3.2f\n", argv[5], argv[6], datachar, data);
|
520
|
+
return(2);
|
521
|
+
}
|
522
|
+
else if(data < myatof(argv[3]) || data > myatof(argv[4])){
|
523
|
+
printf("WARNING ( %s< or >%s ) Illumination: %s | Illum=%3.2f\n", argv[3], argv[4], datachar, data);
|
524
|
+
return(1);
|
525
|
+
}
|
526
|
+
else{
|
527
|
+
printf("OK Illumination: %s | Illum=%3.2f\n", datachar,data);
|
528
|
+
return(0);
|
529
|
+
}
|
530
|
+
}
|
531
|
+
break;
|
532
|
+
|
533
|
+
case 'C':
|
534
|
+
{
|
535
|
+
if(iobuf[160] == 'W'){
|
536
|
+
printf("OK Contacts Close. | Contacts=0\n");
|
537
|
+
return(0);
|
538
|
+
}
|
539
|
+
else if(iobuf[160] == 'N'){
|
540
|
+
printf("CRITICAL Contacts Open! | Contacts=1\n");
|
541
|
+
|
542
|
+
/* Reset the Contact Closure back to Closed */
|
543
|
+
if(RESETCONTACT == 1){
|
544
|
+
contacts = connectWebsensor(argv[1], DEFAULTPORT);
|
545
|
+
if(NetErrNo() != 0){
|
546
|
+
fprintf(stderr, "Could not reset contact closure", NetErrStr());
|
547
|
+
shutdown(contacts, SHUT_RDWR);
|
548
|
+
return(2);
|
549
|
+
}
|
550
|
+
write(contacts, "GET /index.html?eL HTTP/1.1\r\nUser-Agent: EsensorsPlugin\r\nHost: localhost\r\n\r\n", 77);
|
551
|
+
l = read(contacts, iobuf, sizeof(iobuf));
|
552
|
+
//printf("%s", iobuf);
|
553
|
+
shutdown(contacts, SHUT_RDWR);
|
554
|
+
}
|
555
|
+
return(2);
|
556
|
+
}
|
557
|
+
else{
|
558
|
+
printf("WARNING Unknown status. Try Reset Device.\n");
|
559
|
+
return(1);
|
560
|
+
}
|
561
|
+
}
|
562
|
+
break;
|
563
|
+
|
564
|
+
case 'R':
|
565
|
+
{
|
566
|
+
strncpy(datachar, pos+4, 6);
|
567
|
+
datachar[6] = '\0';
|
568
|
+
data = myatof(datachar);
|
569
|
+
data += 0.01;
|
570
|
+
if(argc != 7){
|
571
|
+
printf("(No limits specified) Ext. Temperature: %s %c | XTemp%c=%3.2f\n", datachar, pos[3], pos[3], data);
|
572
|
+
return(0);
|
573
|
+
}
|
574
|
+
if(data < myatof(argv[5]) || data > myatof(argv[6])){
|
575
|
+
printf("CRITICAL ( %s< or >%s ) Ext. Temperature: %s %c | XTemp%c=%3.2f\n", argv[5], argv[6], datachar, pos[3], pos[3], data);
|
576
|
+
return(2);
|
577
|
+
}
|
578
|
+
else if(data < myatof(argv[3]) || data > myatof(argv[4])){
|
579
|
+
printf("WARNING ( %s< or >%s ) Ext. Temperature: %s %c | XTemp%c=%3.2f\n", argv[3], argv[4], datachar, pos[3], pos[3], data);
|
580
|
+
return(1);
|
581
|
+
}
|
582
|
+
else{
|
583
|
+
printf("OK Ext. Temperature: %s %c | XTemp%c=%3.2f\n", datachar, pos[3], pos[3], data);
|
584
|
+
return(0);
|
585
|
+
}
|
586
|
+
}
|
587
|
+
break;
|
588
|
+
|
589
|
+
case 'V':
|
590
|
+
{
|
591
|
+
strncpy(datachar, pos+21, 5);
|
592
|
+
datachar[5] = '\0';
|
593
|
+
data = myatof(datachar);
|
594
|
+
data = (data * VOLTAGE_MULTIPLIER) + VOLTAGE_OFFSET;
|
595
|
+
data += 0.01;
|
596
|
+
if(data <= VOLTAGE_OFFSET+(0.5*VOLTAGE_MULTIPLIER)){ //Anything less than VOLTAGE_OFFSET AC should be considered as zero
|
597
|
+
data = 0.00;
|
598
|
+
}
|
599
|
+
|
600
|
+
if(argc != 7){
|
601
|
+
printf("(No limits specified) Voltage: %3.2f V | Voltage=%3.2f\n", data,data);
|
602
|
+
return(0);
|
603
|
+
}
|
604
|
+
if(data < myatof(argv[5]) || data > myatof(argv[6])){
|
605
|
+
printf("CRITICAL ( %s< or >%s ) Voltage: %3.2f V | Voltage=%3.2f\n", argv[5], argv[6], data, data);
|
606
|
+
return(2);
|
607
|
+
}
|
608
|
+
else if(data < myatof(argv[3]) || data > myatof(argv[4])){
|
609
|
+
printf("WARNING ( %s< or >%s ) Voltage: %3.2f V | Voltage=%3.2f\n", argv[3], argv[4], data, data);
|
610
|
+
return(1);
|
611
|
+
}
|
612
|
+
|
613
|
+
else{
|
614
|
+
printf("OK Voltage: %3.2f V | Voltage=%3.2f\n", data,data);
|
615
|
+
return(0);
|
616
|
+
}
|
617
|
+
}
|
618
|
+
|
619
|
+
default :
|
620
|
+
printf("Please choose only 'T', 'H', 'I', 'R', 'V' or 'C'.\n Please refer to README for further instructions.\n");
|
621
|
+
break;
|
622
|
+
}
|
623
|
+
}
|
624
|
+
else{
|
625
|
+
iobuf[195] = 0;
|
626
|
+
fprintf(stderr, "2");
|
627
|
+
printf("%s\t%s", pos+2, timestr);
|
628
|
+
fprintf(stderr, "3");
|
629
|
+
}
|
630
|
+
|
631
|
+
return 0;
|
632
|
+
}
|
633
|
+
|
634
|
+
int print_help (void)
|
635
|
+
{
|
636
|
+
fprintf (stdout, "Copyright (c) 2005-2009 Esensors, Inc <TechHelp@eEsensors.com>\n");
|
637
|
+
fprintf (stdout,("This plugin is written mainly for Nagios/Cacti/RRDTool, but can work standalone too. \nIt returns the HVAC data from the EM01 Websensor\n\n"));
|
638
|
+
print_usage ();
|
639
|
+
return 0;
|
640
|
+
}
|
641
|
+
|
642
|
+
|
643
|
+
|
644
|
+
int print_usage (void)
|
645
|
+
{
|
646
|
+
fprintf(stdout, "usage: \n %s [hostname] [T/H/I] [LowWarning HighWarning LowCritical HighCritical]\n\n", progname);
|
647
|
+
fprintf(stdout, "Only the hostname is mandatory. The rest of the arguments are optional\n");
|
648
|
+
fprintf(stdout, "T is for Temperature data, H for Humidity Data and I for Illumination data\nExamples:\n");
|
649
|
+
fprintf(stdout, "This will return all HVAC data: \n %s 192.168.0.2\n\n", progname);
|
650
|
+
fprintf(stdout, "This will return only Illumination data: \n %s 192.168.0.2 I\n\n", progname);
|
651
|
+
fprintf(stdout, "This will return temperature data with status: \n %s 192.168.0.2 T 65 85 60 90\n\n", progname);
|
652
|
+
fprintf(stdout, "This will return humidity data with status: \n %s 192.168.0.2 H 25.5 50 15 70.5\n\n", progname);
|
653
|
+
fprintf(stdout, "This will return Cacti/RRDTool format: \n %s 192.168.0.2 G\n\n", progname);
|
654
|
+
fprintf(stdout, "For further information, please refer to the README file included with the package\n");
|
655
|
+
fprintf(stdout, "or available for download from http://www.eesensors.com\n");
|
656
|
+
return 0;
|
657
|
+
}
|