munin2graphite 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +19 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +123 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/bin/munin2graphite +16 -0
- data/bin/munin2graphite-daemon +20 -0
- data/conf/munin2graphite.conf.example +35 -0
- data/etc/munin2graphite/munin2graphite.conf.example +35 -0
- data/lib/ast_node.rb +344 -0
- data/lib/carbon.rb +36 -0
- data/lib/graphite/base.rb +30 -0
- data/lib/graphite/graph.rb +40 -0
- data/lib/graphite/metric.rb +19 -0
- data/lib/graphite/my_graph.rb +23 -0
- data/lib/graphite/user_graph.rb +4 -0
- data/lib/graphite.rb +8 -0
- data/lib/munin2graphite/config.rb +117 -0
- data/lib/munin2graphite/scheduler.rb +169 -0
- data/lib/munin2graphite.rb +8 -0
- data/lib/munin_graph.rb +136 -0
- data/munin2graphite.gemspec +96 -0
- data/test/munin2graphite/config_test.rb +53 -0
- data/test/test_config.rb +27 -0
- data/test/test_init.rb +13 -0
- data/test/test_munin.rb +43 -0
- data/test/test_munin_graph.rb +286 -0
- data/test/test_my_graph.rb +28 -0
- data/test/test_scheduler.rb +19 -0
- metadata +167 -0
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
|
7
|
+
gem "rufus-scheduler", "2.0.10"
|
8
|
+
gem "daemons", "1.1.4"
|
9
|
+
gem "parseconfig"
|
10
|
+
gem "munin-ruby", "~> 0.2.1"
|
11
|
+
|
12
|
+
|
13
|
+
# Add dependencies to develop your gem here.
|
14
|
+
# Include everything needed to run rake, tests, features, etc.
|
15
|
+
group :development do
|
16
|
+
gem "bundler", "~> 1.0.0"
|
17
|
+
gem "jeweler", "~> 1.5.2"
|
18
|
+
gem "yard", "~> 0.6.0"
|
19
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Jose Fernandez (magec)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
munin2graphite
|
2
|
+
===============
|
3
|
+
|
4
|
+
Munin2graphite is a munin-node to graphite translator. It works as a daemon that connects to a [munin-node](http://munin-monitoring.org/wiki/munin-node), and translates the data and the graphics into [carbon/graphite](http://graphite.wikidot.com/).
|
5
|
+
|
6
|
+
Installing
|
7
|
+
----------
|
8
|
+
|
9
|
+
To install munin2graphite you need a working ruby virtual machine with rubygems installed. Once you've got that, It's as easy as
|
10
|
+
|
11
|
+
gem install munin2graphite
|
12
|
+
|
13
|
+
Configuring
|
14
|
+
------------
|
15
|
+
|
16
|
+
Munin2graphite can be used to post data to graphite from any number of munin nodes you want. The idea is simple, there is bunch of workers that periodically ask for metrics to their munin-nodes and then post that data to carbon. Also, every time the daemon is run, the available graphs from the munin nodes are read, translated and posted into graphite as well.
|
17
|
+
|
18
|
+
## Workers
|
19
|
+
You can either choose to use workers with different configuration or configure everything as global in the config file. A worker implies a new thread of execution with different configuration. Note that if you don't rewrite a given value on the worker, the one in the global config will be used.
|
20
|
+
|
21
|
+
## Config Example
|
22
|
+
Imagine, for example, that we have two munin-nodes in two different servers (munin-node1.example.com,munin-node2.example.com). Let's say that one munin node has, in turn 2 nodes configured on it and the other just one (node1.munin-node1.example.com and node2.munin-node1.example.com). We also have one graphite server and one carbon server (carbon.example.com and graphite.example.com), they can be on the same machine on in a different one. A valid config file for this would be:
|
23
|
+
|
24
|
+
# Log config
|
25
|
+
# log: The logfile, STDOUT if stdout is needed
|
26
|
+
# log_level: Either DEBUG, INFO or WARN
|
27
|
+
log=/var/log/munin2graphite
|
28
|
+
log_level=INFO
|
29
|
+
|
30
|
+
# Carbon backend
|
31
|
+
# This has to point to the carbon backend to submit metrics
|
32
|
+
carbon_hostname=carbon.example.com
|
33
|
+
carbon_port=2003
|
34
|
+
|
35
|
+
# Graphite endpoint
|
36
|
+
# This is needed to send graph data to graphite
|
37
|
+
graphite_endpoint=http://graphite.example.com/
|
38
|
+
|
39
|
+
# User and password of the graphite web UI
|
40
|
+
graphite_user=test
|
41
|
+
graphite_password=secret
|
42
|
+
|
43
|
+
# This is the prefix you want the on metrics
|
44
|
+
graphite_metric_prefix=test.server
|
45
|
+
|
46
|
+
# The prefix you want in the graphics, note that in the UI the grapichs are shown under the user name, so the user name is also added prfixed
|
47
|
+
# to this prefix. That's why conveniently, I used 'test' (the user name) in the metric prefix
|
48
|
+
graphite_graph_prefix=server
|
49
|
+
|
50
|
+
|
51
|
+
# The period for sending the metrics
|
52
|
+
# its format is the one of rufus-scheduler
|
53
|
+
scheduler_metrics_period=1m
|
54
|
+
|
55
|
+
# The munin node hostname and its port
|
56
|
+
munin_hostname=localhost
|
57
|
+
munin_port=4949
|
58
|
+
|
59
|
+
# Apart from the global configuration, you can define workers so a new thread is opened with the new configuration,
|
60
|
+
# this is particulary useful when you have a single munin-node with several nodes configured and want to send different graphs
|
61
|
+
# with different prefixes
|
62
|
+
[node1.munin-node1]
|
63
|
+
munin_hostname=munin-node1.example.com
|
64
|
+
nodes=node1
|
65
|
+
|
66
|
+
[node2.munin-node1]
|
67
|
+
munin_hostname=munin-node1.example.com
|
68
|
+
nodes=node2
|
69
|
+
|
70
|
+
[munin-node2]
|
71
|
+
munin_hostname=munin-node1.example.com
|
72
|
+
|
73
|
+
Running it
|
74
|
+
-----------
|
75
|
+
You can run it either as a daemon or as an executable. To run it as an executable, just call it with the config file.
|
76
|
+
|
77
|
+
munin2graphite config.conf
|
78
|
+
|
79
|
+
There is also a daemon version, bassically the same thing but wrapped up with the daemons gem. Its usage is as follows. By default the config file location will be /etc/munin2graphite/munin2graphite.conf. This daemon also supports to be run from /etc/init.d by means of a symbolic link.
|
80
|
+
|
81
|
+
|
82
|
+
Usage: munin-graphite.rb <command> <options> -- <application options>
|
83
|
+
|
84
|
+
* where <command> is one of:
|
85
|
+
start start an instance of the application
|
86
|
+
stop stop all instances of the application
|
87
|
+
restart stop all instances and restart them afterwards
|
88
|
+
reload send a SIGHUP to all instances of the application
|
89
|
+
run start the application and stay on top
|
90
|
+
zap set the application to a stopped state
|
91
|
+
status show status (PID) of application instances
|
92
|
+
|
93
|
+
* and where <options> may contain several of the following:
|
94
|
+
|
95
|
+
-t, --ontop Stay on top (does not daemonize)
|
96
|
+
-f, --force Force operation
|
97
|
+
-n, --no_wait Do not wait for processes to stop
|
98
|
+
|
99
|
+
Common options:
|
100
|
+
-h, --help Show this message
|
101
|
+
--version Show version
|
102
|
+
|
103
|
+
|
104
|
+
Troubleshooting
|
105
|
+
-----------------
|
106
|
+
You better start testing the conf with the not daemonized mode and then swap to the daemon, another aproarch is to use the daemon but adding a -t (stay on top) when running.
|
107
|
+
|
108
|
+
Contributing to munin2graphite
|
109
|
+
-------------------------------
|
110
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
111
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
112
|
+
* Fork the project
|
113
|
+
* Start a feature/bugfix branch
|
114
|
+
* Commit and push until you are happy with your contribution
|
115
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
116
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
117
|
+
|
118
|
+
Copyright
|
119
|
+
-----------
|
120
|
+
|
121
|
+
Copyright (c) 2011 Jose Fernandez (magec). See LICENSE.txt for
|
122
|
+
further details.
|
123
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "munin2graphite"
|
18
|
+
gem.homepage = "http://github.com/magec/munin2graphite"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Allows to post both data and graphic info from munin to graphite (https://launchpad.net/graphite)}
|
21
|
+
gem.description = %Q{This gem will install as a daemon and can be used to connect to a graphite and a carbon backend. It will not only post the data for the metrics but also create graphs into graphite, by means of a translation from munin-node.}
|
22
|
+
gem.email = "jfernandezperez@gmail.com"
|
23
|
+
gem.authors = ["Jose Fernandez (magec)"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :test
|
36
|
+
|
37
|
+
require 'yard'
|
38
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/munin2graphite
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__),"..","lib")))
|
3
|
+
require 'rubygems'
|
4
|
+
require 'munin2graphite'
|
5
|
+
|
6
|
+
if ARGV.last && File.stat(ARGV.last)
|
7
|
+
Munin2Graphite::Config.config_file = ARGV.last
|
8
|
+
else
|
9
|
+
Munin2Graphite::Config.config_file = "/etc/conf/munin2graphite.conf"
|
10
|
+
end
|
11
|
+
|
12
|
+
Thread.abort_on_exception = true
|
13
|
+
|
14
|
+
scheduler = Munin2Graphite::Scheduler.new(Munin2Graphite::Config)
|
15
|
+
scheduler.start
|
16
|
+
scheduler.scheduler.join
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
### BEGIN INIT INFO
|
4
|
+
# Provides: munin2graphite
|
5
|
+
# Required-Start: $network $remote_fs $munin-node
|
6
|
+
# Required-Stop: $null
|
7
|
+
# Default-Start: 3 5
|
8
|
+
# Default-Stop: 0 1 2 6
|
9
|
+
# Short-Description: Populates carbon agents with munin-node data
|
10
|
+
# Description: munin graphs to graphite servers
|
11
|
+
### END INIT INFO
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'daemons'
|
15
|
+
|
16
|
+
THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
17
|
+
|
18
|
+
DAEMON=File.dirname(THIS_FILE)+ "/munin2graphite"
|
19
|
+
Daemons.run(DAEMON,{:dir_mode => :system, :monitor => true})
|
20
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Log config
|
2
|
+
# log: The logfile, STDOUT if stdout is needed
|
3
|
+
# log_level: Either DEBUG, INFO or WARN
|
4
|
+
log=/var/log/munin2graphite.log
|
5
|
+
log_level=INFO
|
6
|
+
|
7
|
+
# Carbon backend
|
8
|
+
# This has to point to the carbon backend to submit metrics
|
9
|
+
carbon_hostname=localhost
|
10
|
+
carbon_port=2003
|
11
|
+
|
12
|
+
# Graphite endpoint
|
13
|
+
# This is needed to send graph data to graphite
|
14
|
+
graphite_endpoint=http://graphite/
|
15
|
+
|
16
|
+
# prefix for the metrics usually, the user name have to be put as a prefix
|
17
|
+
graphite_metric_prefix=
|
18
|
+
graphite_graph_prefix=
|
19
|
+
graphite_user=test
|
20
|
+
graphite_password=
|
21
|
+
|
22
|
+
# The period for sending the metrics and the graph info
|
23
|
+
# its format is the one of rufus-scheduler
|
24
|
+
scheduler_metrics_period=1m
|
25
|
+
scheduler_graphs_period=10m
|
26
|
+
|
27
|
+
# The munin node hostname and its port
|
28
|
+
munin_hostname=localhost
|
29
|
+
munin_port=4949
|
30
|
+
|
31
|
+
# Apart from the global configuration, you can define workers so a new thread is opened with the new configuration,
|
32
|
+
# this is particulary useful when you have a single munin-node with several nodes configured and want to send different graphs
|
33
|
+
# with different prefixes
|
34
|
+
#[test_worker1]
|
35
|
+
#munin_hostname=127.0.0.1
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Log config
|
2
|
+
# log: The logfile, STDOUT if stdout is needed
|
3
|
+
# log_level: Either DEBUG, INFO or WARN
|
4
|
+
log=/var/log/munin2graphite.log
|
5
|
+
log_level=INFO
|
6
|
+
|
7
|
+
# Carbon backend
|
8
|
+
# This has to point to the carbon backend to submit metrics
|
9
|
+
carbon_hostname=
|
10
|
+
carbon_port=2003
|
11
|
+
|
12
|
+
# Graphite endpoint
|
13
|
+
# This is needed to send graph data to graphite
|
14
|
+
graphite_endpoint=http://graphite/
|
15
|
+
|
16
|
+
# prefix for the metrics usually, the user name have to be put as a prefix
|
17
|
+
graphite_metric_prefix=
|
18
|
+
graphite_graph_prefix=
|
19
|
+
graphite_user=test
|
20
|
+
graphite_password=
|
21
|
+
|
22
|
+
# The period for sending the metrics and the graph info
|
23
|
+
# its format is the one of rufus-scheduler
|
24
|
+
scheduler_metrics_period=1m
|
25
|
+
scheduler_graphs_period=10m
|
26
|
+
|
27
|
+
# The munin node hostname and its port
|
28
|
+
munin_hostname=localhost
|
29
|
+
munin_port=4949
|
30
|
+
|
31
|
+
# Apart from the global configuration, you can define workers so a new thread is opened with the new configuration,
|
32
|
+
# this is particulary useful when you have a single munin-node with several nodes configured and want to send different graphs
|
33
|
+
# with different prefixes
|
34
|
+
#[test_worker1]
|
35
|
+
#munin_hostname=127.0.0.1
|
data/lib/ast_node.rb
ADDED
@@ -0,0 +1,344 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
class ASTNode
|
4
|
+
|
5
|
+
attr_accessor :properties, :children, :parent , :root_node, :graph_properties
|
6
|
+
|
7
|
+
def default_colors
|
8
|
+
%w(#00CC00 #0066B3 #FF8000 #FFCC00 #330099 #990099 #CCFF00 #FF0000 #808080
|
9
|
+
#008F00 #00487D #B35A00 #B38F00 #6B006B #8FB300 #B30000 #BEBEBE
|
10
|
+
#80FF80 #80C9FF #FFC080 #FFE680 #AA80FF #EE00CC #FF8080
|
11
|
+
#666600 #FFBFFF #00FFCC #CC6699 #999900)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(raw_data)
|
15
|
+
@root_node = nil
|
16
|
+
@raw_data = raw_data
|
17
|
+
@children = []
|
18
|
+
@properties = {'graph_period' => "seconds","category" => "other"}
|
19
|
+
@graph_properties = {}
|
20
|
+
@graph_properties[:colorList] = default_colors
|
21
|
+
@parent = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def config=(config)
|
25
|
+
self.properties.merge!(config)
|
26
|
+
end
|
27
|
+
|
28
|
+
def root?
|
29
|
+
@root_node == nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def children_of_class(klass)
|
33
|
+
children.select { |i| i.is_a? klass }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add a child to the node
|
37
|
+
def add_child(child)
|
38
|
+
if self.root?
|
39
|
+
child.root_node = self
|
40
|
+
else
|
41
|
+
child.root_node = self.root_node
|
42
|
+
end
|
43
|
+
|
44
|
+
child.parent = self
|
45
|
+
children << child
|
46
|
+
end
|
47
|
+
|
48
|
+
def compile
|
49
|
+
# The compilation is done twice cause there are certain cases where is necessary, a better implementation would control whether this is needed
|
50
|
+
# or not, but given the small impact I just do it twice
|
51
|
+
children.map{|i| i.compile} if children
|
52
|
+
children.map{|i| i.compile} if children
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the FieldDeclaration Nodes
|
56
|
+
def targets
|
57
|
+
children_of_class(FieldDeclarationNode)
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_variables(properties)
|
61
|
+
[:vtitle,:title].each do |key|
|
62
|
+
aux = properties[key]
|
63
|
+
properties[key].scan(/\$\{(.*)\}/).each do
|
64
|
+
if self.properties.has_key? $1
|
65
|
+
aux.gsub!(/\$\{#{$1}\}/,self.properties[$1])
|
66
|
+
end
|
67
|
+
end if properties[key]
|
68
|
+
properties[key] = aux
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the global properties as url values
|
74
|
+
def properties_to_url
|
75
|
+
|
76
|
+
# Color List initialization
|
77
|
+
aux_graph_properties = self.graph_properties.clone
|
78
|
+
process_variables(aux_graph_properties)
|
79
|
+
aux_graph_properties[:colorList] = aux_graph_properties[:colorList].join(",") if aux_graph_properties[:colorList]
|
80
|
+
|
81
|
+
# Change of the base stuff
|
82
|
+
if self.properties[:base]
|
83
|
+
aux_graph_properties[:yMax] = aux_graph_properties[:yMax].to_f / self.properties[:base]
|
84
|
+
aux_graph_properties[:yMin] = aux_graph_properties[:yMin].to_f / self.properties[:base]
|
85
|
+
aux_graph_properties.delete :yMax
|
86
|
+
aux_graph_properties.delete :yMin
|
87
|
+
end
|
88
|
+
|
89
|
+
aux = aux_graph_properties.map{|i,j| "#{i}=#{URI.escape(j.to_s.gsub('%','percent'))}"}.join("&")
|
90
|
+
return aux
|
91
|
+
end
|
92
|
+
|
93
|
+
# This returns the url field of the graph after compiling it
|
94
|
+
def url
|
95
|
+
self.compile
|
96
|
+
url = "#{properties[:endpoint]}/render/?width=586&height=308&#{properties_to_url}&target=" + URI.escape(targets.map{|i| i.compile}.compact.join("&target="))
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
class GlobalDeclarationNode < ASTNode
|
103
|
+
def initialize(line)
|
104
|
+
super
|
105
|
+
line =~ /^([\w_]*)\ (.*)/
|
106
|
+
@value = $2
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def string_to_ansi(string)
|
111
|
+
string.unpack("U*").map{|c|c.chr}.join
|
112
|
+
end
|
113
|
+
|
114
|
+
class GraphTitleGlobalDeclarationNode < GlobalDeclarationNode
|
115
|
+
def compile
|
116
|
+
root_node.graph_properties[:title] = @value
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class GraphVLabelGlobalDeclarationNode < GlobalDeclarationNode
|
121
|
+
def compile
|
122
|
+
root_node.graph_properties[:vtitle] = @value
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class CreateArgsGlobalDeclarationNode < GlobalDeclarationNode
|
127
|
+
end
|
128
|
+
|
129
|
+
class GraphArgsGlobalDeclarationNode < GlobalDeclarationNode
|
130
|
+
def compile
|
131
|
+
if @raw_data =~ /--base\ (\d+)/
|
132
|
+
self.root_node.properties[:base] = $1.to_i
|
133
|
+
end
|
134
|
+
if @raw_data =~ /.*logarithmic.*/
|
135
|
+
self.root_node.properties[:logarithmic] = true
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class GraphCategoryGlobalDeclarationNode < GlobalDeclarationNode
|
141
|
+
def compile
|
142
|
+
root_node.properties["category"] = @value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class GraphInfoGlobalDeclarationNode < GlobalDeclarationNode; end
|
147
|
+
class GraphOrderGlobalDeclarationNode < GlobalDeclarationNode; end
|
148
|
+
class GraphTotalGlobalDeclarationNode < GlobalDeclarationNode; end
|
149
|
+
class GraphScaleGlobalDeclarationNode < GlobalDeclarationNode; end
|
150
|
+
class GraphGlobalDeclarationNode < GlobalDeclarationNode; end
|
151
|
+
class HostNameGlobalDeclarationNode < GlobalDeclarationNode
|
152
|
+
def compile
|
153
|
+
if @raw_data =~ /host_name (.*)$/
|
154
|
+
root_node.properties['hostname'] = $1
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
class UpdateGlobalDeclarationNode < GlobalDeclarationNode; end
|
159
|
+
class GraphPeriodGlobalDeclarationNode < GlobalDeclarationNode;
|
160
|
+
def compile
|
161
|
+
if @raw_data =~ /graph_period (.*)$/
|
162
|
+
root_node.properties['graph_period'] = $1
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
class GraphVTitleGlobalDeclarationNode < GlobalDeclarationNode; end
|
167
|
+
class ServiceOrderGlobalDeclarationNode < GlobalDeclarationNode; end
|
168
|
+
class GraphWidthGlobalDeclarationNode < GlobalDeclarationNode; end
|
169
|
+
class GraphHeightGlobalDeclarationNode < GlobalDeclarationNode; end
|
170
|
+
class GraphPrintFormatGlobalDeclarationNode < GlobalDeclarationNode; end
|
171
|
+
|
172
|
+
|
173
|
+
class FieldDeclarationNode < ASTNode
|
174
|
+
|
175
|
+
def metric
|
176
|
+
"#{root_node.properties['graphite_metric_prefix']}.#{root_node.properties['hostname'].split('.').first}.#{root_node.properties['category']}.#{root_node.properties['metric']}.#{children.first.metric}"
|
177
|
+
end
|
178
|
+
|
179
|
+
def compile
|
180
|
+
aux = children.first.apply_function(metric.gsub("-","_"))
|
181
|
+
children[1..-1].each do |i|
|
182
|
+
aux = i.apply_function(aux)
|
183
|
+
end if children[1..-1]
|
184
|
+
if self.root_node.properties[:logarithmic]
|
185
|
+
# NOT IMPLEMENTED the logarithmic means that a logarithmic scale is to be used not that a log function has to be implemented aux = "log(#{aux},10)"
|
186
|
+
end
|
187
|
+
if self.properties[:stacked]
|
188
|
+
aux = "stacked(#{aux})"
|
189
|
+
end
|
190
|
+
if self.properties[:is_negative]
|
191
|
+
aux = "scale(#{aux},-1)"
|
192
|
+
end
|
193
|
+
if self.properties[:alias]
|
194
|
+
aux = "alias(#{aux},'#{self.properties[:alias]}')"
|
195
|
+
end
|
196
|
+
if self.properties[:hide]
|
197
|
+
return nil
|
198
|
+
else
|
199
|
+
return aux
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def index
|
204
|
+
return parent.children_of_class(FieldDeclarationNode).index(self)
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
class FieldPropertyNode < ASTNode
|
210
|
+
attr_accessor :metric
|
211
|
+
|
212
|
+
def initialize(line)
|
213
|
+
super
|
214
|
+
line =~ /([\w_]+)\.(\w+)\ (.*)$/
|
215
|
+
@metric = $1
|
216
|
+
@function = $2
|
217
|
+
@value = $3
|
218
|
+
end
|
219
|
+
|
220
|
+
def apply_function(operand)
|
221
|
+
return "FUNCION(#{operand})"
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
class LabelFieldPropertyNode < FieldPropertyNode;
|
227
|
+
def apply_function(operand)
|
228
|
+
parent.properties[:alias] = @value
|
229
|
+
return operand
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
class ColourFieldPropertyNode < FieldPropertyNode
|
234
|
+
|
235
|
+
def apply_function(operand)
|
236
|
+
# In this case a function can't be applied cause graphite does not allow this
|
237
|
+
# instead we modify a given
|
238
|
+
aux = @value
|
239
|
+
aux = "##{@value}" if @value =~ /[0-9A-Fa-f]{3,6}/
|
240
|
+
|
241
|
+
self.root_node.graph_properties[:colorList] ||= Array.new
|
242
|
+
self.root_node.graph_properties[:colorList][parent.index] = aux
|
243
|
+
return operand
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
class TypeFieldPropertyNode < FieldPropertyNode
|
248
|
+
|
249
|
+
def apply_function(operand)
|
250
|
+
if @value == "DERIVE" || @value == "COUNTER"
|
251
|
+
# The scaling is because of the minutes/seconds"
|
252
|
+
return "scale(nonNegativeDerivative(#{operand}),0.0166666666666667)"
|
253
|
+
end
|
254
|
+
operand
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
class DrawFieldPropertyNode < FieldPropertyNode
|
259
|
+
def apply_function(operand)
|
260
|
+
if @value == "STACK" || @value == "AREA"
|
261
|
+
parent.properties[:stacked] = true
|
262
|
+
end
|
263
|
+
return operand
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
class MinFieldPropertyNode < FieldPropertyNode
|
269
|
+
def apply_function(operand)
|
270
|
+
self.root_node.graph_properties[:yMin] ||= @value.to_i
|
271
|
+
self.root_node.graph_properties[:yMin] = @value.to_i if self.root_node.graph_properties[:yMin] > @value.to_i
|
272
|
+
return operand
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class MaxFieldPropertyNode < FieldPropertyNode
|
277
|
+
def apply_function(operand)
|
278
|
+
self.root_node.graph_properties[:yMax] ||= @value.to_i
|
279
|
+
self.root_node.graph_properties[:yMax] = @value.to_i if self.root_node.graph_properties[:yMax] < @value.to_i
|
280
|
+
return operand
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
class InfoFieldPropertyNode < FieldPropertyNode
|
285
|
+
def apply_function(operand)
|
286
|
+
# puts "Info tag is currently ignored"
|
287
|
+
return operand
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class WarningFieldPropertyNode < FieldPropertyNode
|
292
|
+
# Ignored
|
293
|
+
def apply_function(operand)
|
294
|
+
operand
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
class CriticalFieldPropertyNode < FieldPropertyNode
|
299
|
+
# Ignored
|
300
|
+
def apply_function(operand)
|
301
|
+
operand
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
class CDefFieldPropertyNode < FieldPropertyNode
|
307
|
+
def apply_function(operand)
|
308
|
+
if @raw_data =~ /(\w+),(\d+),\*/
|
309
|
+
return "scale(#{operand},#{$2})"
|
310
|
+
elsif @raw_data =~ /(\w+),(\d+),\//
|
311
|
+
return "scale(#{operand},#{1.0/$2.to_i})"
|
312
|
+
end
|
313
|
+
"FUNCTION(#{operand})"
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
class GraphFieldPropertyNode < FieldPropertyNode
|
318
|
+
def apply_function(operand)
|
319
|
+
operand
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class ExtInfoFieldPropertyNode < FieldPropertyNode; end
|
324
|
+
class NegativeFieldPropertyNode < FieldPropertyNode
|
325
|
+
def apply_function(operand)
|
326
|
+
# We have to mark the other node as negative (note that for this to work we have to compile twice
|
327
|
+
node = self.root_node.targets.find { |i| i.properties[:field_name] == @value }
|
328
|
+
if node
|
329
|
+
node.properties[:is_negative] = true
|
330
|
+
# We also use the same color
|
331
|
+
# config.log.info("Begin getting metrics negative (node : #{node.index} with : #{parent.index} parent Color = root_node.graph_properties[:colorList][parent.index] ")
|
332
|
+
|
333
|
+
root_node.graph_properties[:colorList][node.index] = root_node.graph_properties[:colorList][parent.index] if root_node.graph_properties[:colorList][parent.index]
|
334
|
+
end
|
335
|
+
return operand
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
class SkipDrawFieldPropertyNode < FieldPropertyNode; end
|
340
|
+
class SumFieldPropertyNode < FieldPropertyNode; end
|
341
|
+
class StackFieldPropertyNode < FieldPropertyNode; end
|
342
|
+
class LineValueFieldPropertyNode < FieldPropertyNode; end
|
343
|
+
class OldNameFieldPropertyNode < FieldPropertyNode; end
|
344
|
+
class ValueFieldPropertyNode < FieldPropertyNode; end
|
data/lib/carbon.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'socket'
|
2
|
+
#
|
3
|
+
# Author:: Adam Jacob (<adam@hjksolutions.com>)
|
4
|
+
# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
|
5
|
+
# License:: GNU General Public License version 2 or later
|
6
|
+
#
|
7
|
+
# This program and entire repository is free software; you can
|
8
|
+
# redistribute it and/or modify it under the terms of the GNU
|
9
|
+
# General Public License as published by the Free Software
|
10
|
+
# Foundation; either version 2 of the License, or any later version.
|
11
|
+
#
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with this program; if not, write to the Free Software
|
19
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
20
|
+
|
21
|
+
class Carbon
|
22
|
+
def initialize(chost='localhost', port=2003)
|
23
|
+
@carbon = TCPSocket.new(chost, port)
|
24
|
+
end
|
25
|
+
|
26
|
+
def send(msg)
|
27
|
+
@carbon.write(msg)
|
28
|
+
end
|
29
|
+
|
30
|
+
def flush
|
31
|
+
@carbon.flush
|
32
|
+
end
|
33
|
+
def close
|
34
|
+
@carbon.close
|
35
|
+
end
|
36
|
+
end
|