rrd-grapher 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/.gitignore +9 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +11 -0
  4. data/Guardfile +123 -0
  5. data/README.md +91 -0
  6. data/Rakefile +42 -0
  7. data/example_app/Gemfile +9 -0
  8. data/example_app/Guardfile +18 -0
  9. data/example_app/app.rb +38 -0
  10. data/example_app/assets/javascripts/available_rrds.coffee +13 -0
  11. data/example_app/assets/javascripts/collectd.coffee +48 -0
  12. data/example_app/config.ru +9 -0
  13. data/example_app/public/.gitignore +1 -0
  14. data/example_app/public/chosen/chosen-sprite.png +0 -0
  15. data/example_app/public/chosen/chosen.css +340 -0
  16. data/example_app/public/chosen/chosen.jquery.js +786 -0
  17. data/example_app/public/chosen/chosen.jquery.min.js +10 -0
  18. data/example_app/public/javascripts/available_rrds.js +16 -0
  19. data/example_app/public/javascripts/collectd.js +64 -0
  20. data/example_app/public/javascripts/jquery.timeago.js +148 -0
  21. data/example_app/public/stylesheets/Aristo/images/bg_fallback.png +0 -0
  22. data/example_app/public/stylesheets/Aristo/images/icon_sprite.png +0 -0
  23. data/example_app/public/stylesheets/Aristo/images/progress_bar.gif +0 -0
  24. data/example_app/public/stylesheets/Aristo/images/slider_handles.png +0 -0
  25. data/example_app/public/stylesheets/Aristo/images/ui-icons_222222_256x240.png +0 -0
  26. data/example_app/public/stylesheets/Aristo/images/ui-icons_454545_256x240.png +0 -0
  27. data/example_app/public/stylesheets/Aristo/theme.css +738 -0
  28. data/example_app/views/available_rrds.haml +26 -0
  29. data/example_app/views/collectd.haml +30 -0
  30. data/example_app/views/layout.haml +19 -0
  31. data/example_app/views/stylesheets/available_rrds.scss +7 -0
  32. data/example_notifier/Gemfile +2 -0
  33. data/example_notifier/notifier.rb +25 -0
  34. data/lib/rrd-grapher/assets/javascripts/app-dev.js +20 -0
  35. data/lib/rrd-grapher/assets/javascripts/app.js +20 -0
  36. data/lib/rrd-grapher/assets/javascripts/classes/format.coffee +35 -0
  37. data/lib/rrd-grapher/assets/javascripts/classes/graph.coffee +306 -0
  38. data/lib/rrd-grapher/assets/javascripts/classes/graph_definition.coffee +216 -0
  39. data/lib/rrd-grapher/assets/javascripts/classes/serie.coffee +13 -0
  40. data/lib/rrd-grapher/assets/javascripts/classes/size.coffee +5 -0
  41. data/lib/rrd-grapher/assets/javascripts/classes/static_line.coffee +44 -0
  42. data/lib/rrd-grapher/assets/javascripts/classes/time.coffee +17 -0
  43. data/lib/rrd-grapher/notifier/alarm_manager.rb +190 -0
  44. data/lib/rrd-grapher/notifier/alarm_trigger.rb +187 -0
  45. data/lib/rrd-grapher/notifier/alarms.rb +79 -0
  46. data/lib/rrd-grapher/notifier/collectdrb.rb +86 -0
  47. data/lib/rrd-grapher/notifier/data_struct.rb +46 -0
  48. data/lib/rrd-grapher/notifier/default_user_handler.rb +36 -0
  49. data/lib/rrd-grapher/notifier/parsers/bindata_parser.rb +144 -0
  50. data/lib/rrd-grapher/notifier/parsers/ruby_parser.rb +134 -0
  51. data/lib/rrd-grapher/notifier/structures.rb +80 -0
  52. data/lib/rrd-grapher/notifier.rb +87 -0
  53. data/lib/rrd-grapher/public/favicon.ico +0 -0
  54. data/lib/rrd-grapher/public/javascripts/app-dev.js +13709 -0
  55. data/lib/rrd-grapher/public/javascripts/app.js +4057 -0
  56. data/lib/rrd-grapher/public/javascripts/backbone/backbone.js +1155 -0
  57. data/lib/rrd-grapher/public/javascripts/backbone/backbone.min.js +32 -0
  58. data/lib/rrd-grapher/public/javascripts/backbone/underscore.js +841 -0
  59. data/lib/rrd-grapher/public/javascripts/backbone/underscore.min.js +27 -0
  60. data/lib/rrd-grapher/public/javascripts/classes/format.js +42 -0
  61. data/lib/rrd-grapher/public/javascripts/classes/graph.js +360 -0
  62. data/lib/rrd-grapher/public/javascripts/classes/graph_definition.js +298 -0
  63. data/lib/rrd-grapher/public/javascripts/classes/serie.js +32 -0
  64. data/lib/rrd-grapher/public/javascripts/classes/size.js +7 -0
  65. data/lib/rrd-grapher/public/javascripts/classes/static_line.js +48 -0
  66. data/lib/rrd-grapher/public/javascripts/classes/time.js +17 -0
  67. data/lib/rrd-grapher/public/javascripts/flot/.gitignore +4 -0
  68. data/lib/rrd-grapher/public/javascripts/flot/excanvas.min.js +1 -0
  69. data/lib/rrd-grapher/public/javascripts/flot/jquery.colorhelpers.min.js +1 -0
  70. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.crosshair.min.js +1 -0
  71. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.fillbetween.min.js +1 -0
  72. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.image.min.js +1 -0
  73. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.js +2604 -0
  74. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.navigate.min.js +1 -0
  75. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.pie.min.js +1 -0
  76. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.resize.min.js +1 -0
  77. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.selection.js +345 -0
  78. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.selection.min.js +1 -0
  79. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.stack.min.js +1 -0
  80. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.symbol.min.js +1 -0
  81. data/lib/rrd-grapher/public/javascripts/flot/jquery.flot.threshold.min.js +1 -0
  82. data/lib/rrd-grapher/public/javascripts/jquery/jquery-1.6.2.js +8981 -0
  83. data/lib/rrd-grapher/public/javascripts/jquery/jquery-1.6.2.min.js +18 -0
  84. data/lib/rrd-grapher/public/javascripts/jquery/jquery-ui-1.8.11.min.js +783 -0
  85. data/lib/rrd-grapher/public/javascripts/jquery/jquery.showtime.js +63 -0
  86. data/lib/rrd-grapher/public/javascripts/jquery/jquery.tpl.min.js +10 -0
  87. data/lib/rrd-grapher/resources.rb +14 -0
  88. data/lib/rrd-grapher/rrd.rb +238 -0
  89. data/lib/rrd-grapher/rrd_server.rb +78 -0
  90. data/lib/rrd-grapher/version.rb +4 -0
  91. data/lib/rrd-grapher/views/stylesheets/app.scss +111 -0
  92. data/lib/rrd-grapher.rb +12 -0
  93. data/rrd-grapher.gemspec +47 -0
  94. data/spec/common.rb +70 -0
  95. data/spec/data/myrouter.rrd +0 -0
  96. data/spec/data/subdata.rrd +0 -0
  97. data/spec/data/test.rrd +0 -0
  98. data/spec/factories.rb +23 -0
  99. data/spec/javascripts/helpers/jasmine-jquery-1.3.0.js +283 -0
  100. data/spec/javascripts/source/format_spec.coffee +18 -0
  101. data/spec/javascripts/source/graph_def_spec.coffee +27 -0
  102. data/spec/javascripts/source/graph_spec.coffee +63 -0
  103. data/spec/javascripts/source/serie_spec.coffee +28 -0
  104. data/spec/javascripts/source/static_line_spec.coffee +13 -0
  105. data/spec/javascripts/source/time_spec.coffee +26 -0
  106. data/spec/javascripts/support/jasmine.yml +78 -0
  107. data/spec/javascripts/support/jasmine_config.rb +23 -0
  108. data/spec/javascripts/support/jasmine_runner.rb +32 -0
  109. data/spec/unit/alarm_manager_spec.rb +252 -0
  110. data/spec/unit/alarm_trigger_spec.rb +26 -0
  111. data/spec/unit/data_struct_spec.rb +55 -0
  112. data/spec/unit/notifier_spec.rb +45 -0
  113. data/spec/unit/parsers/bindata_parser_spec.rb +184 -0
  114. data/spec/unit/parsers/ruby_parser_spec.rb +184 -0
  115. data/spec/unit/rrd_spec.rb +50 -0
  116. data/spec/unit/structures_spec.rb +28 -0
  117. data/tests/4series.rrd +0 -0
  118. data/tests/analyze_rrd.rb +62 -0
  119. data/tests/exact.rrd +0 -0
  120. data/tests/exact2.rrd +0 -0
  121. data/tests/filler.rb +46 -0
  122. metadata +414 -0
@@ -0,0 +1,63 @@
1
+ /*
2
+ * jQuery.showTime
3
+ * http://plugins.jquery.com/project/showTime
4
+ *
5
+ * Converts seconds to Hours, Minutes and Seconds with configurable
6
+ * dividers (":","h", ...).
7
+ *
8
+ * Usage:
9
+ * input: <p class=".secs">345</p>
10
+ * jQuery('.secs').showTime();
11
+ * output: <p class=".secs">5:45</p>
12
+ *
13
+ * input: <p class=".secs">345</p>
14
+ * jQuery('.secs').showTime( { div_hours: "h ", div_minutes: "m ", div_seconds: "s " } );
15
+ * output: <p class=".secs">5m 45s</p>
16
+ *
17
+ * @author Martin Fischer
18
+ * @date 21-02-2011
19
+ * @param int time in seconds
20
+ * @version 0.2
21
+ */
22
+ jQuery.fn.showTime = function(settings){
23
+ var conf = {
24
+ div_hours: ":",
25
+ div_minutes: ":",
26
+ div_seconds: "",
27
+ show_null: false
28
+ };
29
+
30
+ if (settings) {
31
+ $.extend(conf, settings);
32
+ } // END if
33
+
34
+ if(typeof settings === "object" || !settings) {
35
+ return this.each(function() {
36
+ var status = jQuery(this).data('showtime');
37
+ var time = parseInt(jQuery(this).text());
38
+ if( typeof time === "number" && !isNaN(time) && status != "done" ) {
39
+ var hours = Math.floor(time / 3600);
40
+ time = time - hours * 3600;
41
+ var minutes = Math.floor(time / 60);
42
+ var seconds = time - minutes * 60;
43
+ var div = conf.show_divider_empty_follower;
44
+ var h = conf.div_hours;
45
+ var m = conf.div_minutes;
46
+ var s = conf.div_seconds;
47
+
48
+ var timestring = (hours > 0) ? hours + "" + h : "";
49
+ if (hours > 0) {
50
+ timestring += (minutes < 10) ? "0"+minutes+m : minutes + "" + m ;
51
+ } else {
52
+ timestring += (minutes > 0) ? minutes + "" + m : "";
53
+ }
54
+ if (minutes > 0) {
55
+ timestring += (seconds < 10) ? "0"+seconds + s : seconds + "" + s;
56
+ } else {
57
+ timestring += (seconds > 0) ? seconds + "" + s : "";
58
+ }
59
+ jQuery(this).html(timestring).data('showtime','done');
60
+ } // END if
61
+ }); // END each
62
+ } // END if
63
+ }; // END jQuery.showTime
@@ -0,0 +1,10 @@
1
+ /*
2
+ * jQuery Templates Plugin 1.0.0pre
3
+ * http://github.com/jquery/jquery-tmpl
4
+ * Requires jQuery 1.4.2
5
+ *
6
+ * Copyright Software Freedom Conservancy, Inc.
7
+ * Dual licensed under the MIT or GPL Version 2 licenses.
8
+ * http://jquery.org/license
9
+ */
10
+ (function(a){var r=a.fn.domManip,d="_tmplitem",q=/^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,b={},f={},e,p={key:0,data:{}},i=0,c=0,l=[];function g(g,d,h,e){var c={data:e||(e===0||e===false)?e:d?d.data:{},_wrap:d?d._wrap:null,tmpl:null,parent:d||null,nodes:[],calls:u,nest:w,wrap:x,html:v,update:t};g&&a.extend(c,g,{nodes:[],parent:d});if(h){c.tmpl=h;c._ctnt=c._ctnt||c.tmpl(a,c);c.key=++i;(l.length?f:b)[i]=c}return c}a.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(f,d){a.fn[f]=function(n){var g=[],i=a(n),k,h,m,l,j=this.length===1&&this[0].parentNode;e=b||{};if(j&&j.nodeType===11&&j.childNodes.length===1&&i.length===1){i[d](this[0]);g=this}else{for(h=0,m=i.length;h<m;h++){c=h;k=(h>0?this.clone(true):this).get();a(i[h])[d](k);g=g.concat(k)}c=0;g=this.pushStack(g,f,i.selector)}l=e;e=null;a.tmpl.complete(l);return g}});a.fn.extend({tmpl:function(d,c,b){return a.tmpl(this[0],d,c,b)},tmplItem:function(){return a.tmplItem(this[0])},template:function(b){return a.template(b,this[0])},domManip:function(d,m,k){if(d[0]&&a.isArray(d[0])){var g=a.makeArray(arguments),h=d[0],j=h.length,i=0,f;while(i<j&&!(f=a.data(h[i++],"tmplItem")));if(f&&c)g[2]=function(b){a.tmpl.afterManip(this,b,k)};r.apply(this,g)}else r.apply(this,arguments);c=0;!e&&a.tmpl.complete(b);return this}});a.extend({tmpl:function(d,h,e,c){var i,k=!c;if(k){c=p;d=a.template[d]||a.template(null,d);f={}}else if(!d){d=c.tmpl;b[c.key]=c;c.nodes=[];c.wrapped&&n(c,c.wrapped);return a(j(c,null,c.tmpl(a,c)))}if(!d)return[];if(typeof h==="function")h=h.call(c||{});e&&e.wrapped&&n(e,e.wrapped);i=a.isArray(h)?a.map(h,function(a){return a?g(e,c,d,a):null}):[g(e,c,d,h)];return k?a(j(c,null,i)):i},tmplItem:function(b){var c;if(b instanceof a)b=b[0];while(b&&b.nodeType===1&&!(c=a.data(b,"tmplItem"))&&(b=b.parentNode));return c||p},template:function(c,b){if(b){if(typeof b==="string")b=o(b);else if(b instanceof a)b=b[0]||{};if(b.nodeType)b=a.data(b,"tmpl")||a.data(b,"tmpl",o(b.innerHTML));return typeof c==="string"?(a.template[c]=b):b}return c?typeof c!=="string"?a.template(null,c):a.template[c]||a.template(null,q.test(c)?c:a(c)):null},encode:function(a){return(""+a).split("<").join("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;")}});a.extend(a.tmpl,{tag:{tmpl:{_default:{$2:"null"},open:"if($notnull_1){__=__.concat($item.nest($1,$2));}"},wrap:{_default:{$2:"null"},open:"$item.calls(__,$1,$2);__=[];",close:"call=$item.calls();__=call._.concat($item.wrap(call,__));"},each:{_default:{$2:"$index, $value"},open:"if($notnull_1){$.each($1a,function($2){with(this){",close:"}});}"},"if":{open:"if(($notnull_1) && $1a){",close:"}"},"else":{_default:{$1:"true"},open:"}else if(($notnull_1) && $1a){"},html:{open:"if($notnull_1){__.push($1a);}"},"=":{_default:{$1:"$data"},open:"if($notnull_1){__.push($.encode($1a));}"},"!":{open:""}},complete:function(){b={}},afterManip:function(f,b,d){var e=b.nodeType===11?a.makeArray(b.childNodes):b.nodeType===1?[b]:[];d.call(f,b);m(e);c++}});function j(e,g,f){var b,c=f?a.map(f,function(a){return typeof a==="string"?e.key?a.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,"$1 "+d+'="'+e.key+'" $2'):a:j(a,e,a._ctnt)}):e;if(g)return c;c=c.join("");c.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,function(f,c,e,d){b=a(e).get();m(b);if(c)b=k(c).concat(b);if(d)b=b.concat(k(d))});return b?b:k(c)}function k(c){var b=document.createElement("div");b.innerHTML=c;return a.makeArray(b.childNodes)}function o(b){return new Function("jQuery","$item","var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('"+a.trim(b).replace(/([\\'])/g,"\\$1").replace(/[\r\t\n]/g," ").replace(/\$\{([^\}]*)\}/g,"{{= $1}}").replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,function(m,l,k,g,b,c,d){var j=a.tmpl.tag[k],i,e,f;if(!j)throw"Unknown template tag: "+k;i=j._default||[];if(c&&!/\w$/.test(b)){b+=c;c=""}if(b){b=h(b);d=d?","+h(d)+")":c?")":"";e=c?b.indexOf(".")>-1?b+h(c):"("+b+").call($item"+d:b;f=c?e:"(typeof("+b+")==='function'?("+b+").call($item):("+b+"))"}else f=e=i.$1||"null";g=h(g);return"');"+j[l?"close":"open"].split("$notnull_1").join(b?"typeof("+b+")!=='undefined' && ("+b+")!=null":"true").split("$1a").join(f).split("$1").join(e).split("$2").join(g||i.$2||"")+"__.push('"})+"');}return __;")}function n(c,b){c._wrap=j(c,true,a.isArray(b)?b:[q.test(b)?b:a(b).html()]).join("")}function h(a){return a?a.replace(/\\'/g,"'").replace(/\\\\/g,"\\"):null}function s(b){var a=document.createElement("div");a.appendChild(b.cloneNode(true));return a.innerHTML}function m(o){var n="_"+c,k,j,l={},e,p,h;for(e=0,p=o.length;e<p;e++){if((k=o[e]).nodeType!==1)continue;j=k.getElementsByTagName("*");for(h=j.length-1;h>=0;h--)m(j[h]);m(k)}function m(j){var p,h=j,k,e,m;if(m=j.getAttribute(d)){while(h.parentNode&&(h=h.parentNode).nodeType===1&&!(p=h.getAttribute(d)));if(p!==m){h=h.parentNode?h.nodeType===11?0:h.getAttribute(d)||0:0;if(!(e=b[m])){e=f[m];e=g(e,b[h]||f[h]);e.key=++i;b[i]=e}c&&o(m)}j.removeAttribute(d)}else if(c&&(e=a.data(j,"tmplItem"))){o(e.key);b[e.key]=e;h=a.data(j.parentNode,"tmplItem");h=h?h.key:0}if(e){k=e;while(k&&k.key!=h){k.nodes.push(j);k=k.parent}delete e._ctnt;delete e._wrap;a.data(j,"tmplItem",e)}function o(a){a=a+n;e=l[a]=l[a]||g(e,b[e.parent.key+n]||e.parent)}}}function u(a,d,c,b){if(!a)return l.pop();l.push({_:a,tmpl:d,item:this,data:c,options:b})}function w(d,c,b){return a.tmpl(a.template(d),c,b,this)}function x(b,d){var c=b.options||{};c.wrapped=d;return a.tmpl(a.template(b.tmpl),b.data,c,b.item)}function v(d,c){var b=this._wrap;return a.map(a(a.isArray(b)?b.join(""):b).filter(d||"*"),function(a){return c?a.innerText||a.textContent:a.outerHTML||s(a)})}function t(){var b=this.nodes;a.tmpl(null,null,null,this).insertBefore(b[0]);a(b).remove()}})(jQuery);
@@ -0,0 +1,14 @@
1
+
2
+ require 'sinatra/base'
3
+
4
+ module RRDGrapher
5
+ class ResourcesServer < Sinatra::Base
6
+ set :root, File.dirname(__FILE__)
7
+
8
+ get "/stylesheets/app.css" do
9
+ content_type :css
10
+ scss :"stylesheets/app"
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,238 @@
1
+ require 'rrd'
2
+
3
+ unless Float.const_defined?(:NAN)
4
+ class Float
5
+ NAN = (0/0.0)
6
+ end
7
+ end
8
+
9
+ module RRDReader
10
+
11
+ class InvalidRRARange < RuntimeError; end;
12
+
13
+ class RRA
14
+
15
+ attr_reader :cf, :rows, :current_row
16
+
17
+ # {"cf"=>"AVERAGE", "rows"=>24, "cur_row"=>1, "pdp_per_row"=>1, "xff"=>0.5, "cdp_prep"=>0},
18
+ def initialize(step, h)
19
+ @step = step
20
+
21
+ @cf = translate_function( h.delete('cf') )
22
+ @rows = h.delete('rows').to_i
23
+ @cur_row = h.delete('cur_row').to_i
24
+ @pdp_per_row = h.delete('pdp_per_row').to_i
25
+ @xff = h.delete('xff').to_f
26
+ @cdp_prep = h.delete('cdp_prep').to_f
27
+ end
28
+
29
+ def current_row; @cur_row; end
30
+
31
+ def translate_function(cf)
32
+ case cf
33
+ when "AVERAGE" then :average
34
+ when "MIN" then :min
35
+ when "MAX" then :max
36
+ else
37
+ raise "Unknown function: #{cf}"
38
+ end
39
+ end
40
+
41
+ # one point is saved every X seconds
42
+ def interval
43
+ @pdp_per_row * @step
44
+ end
45
+
46
+ # <rows> points are stored which translate to a duration of X
47
+ def duration
48
+ @rows * interval()
49
+ end
50
+
51
+ def to_json(*args)
52
+ ret = {
53
+ :function => @cf,
54
+ :row => @rows,
55
+ :current_row => @cur_row,
56
+ :interval => self.interval,
57
+ :duration => self.duration
58
+ }
59
+
60
+ ret.to_json(*args)
61
+ end
62
+
63
+ def clip_time(from, to, last_update)
64
+ # compute start/end time for this rra
65
+ # last
66
+ # first_slot_time = last_update
67
+ diff = to -from
68
+
69
+ raise InvalidRRARange if diff > duration
70
+
71
+ # from/to must be a multiple of <interval>
72
+ to = (to / interval).to_i * interval
73
+ from = to - diff
74
+
75
+ [from, to]
76
+ end
77
+
78
+ end
79
+
80
+ class File
81
+ attr_reader :short_name, :rrd_version, :step, :last_update
82
+
83
+ attr_reader :ds, :rra
84
+ alias :archives :rra
85
+
86
+ def initialize(path, base_path = nil)
87
+ if base_path
88
+ @path = ::File.join(base_path, path)
89
+ @short_name = path
90
+ else
91
+ @path = @short_name = path
92
+ end
93
+
94
+ unless ::File.exists?(@path)
95
+ puts "RRD Not found: '#{@path}'"
96
+ raise Sinatra::NotFound
97
+ end
98
+ parse_data(@path)
99
+ end
100
+
101
+ def to_json(*args)
102
+ ret = {
103
+ :path => @path,
104
+ :short_name => ::File.basename(@short_name, '.rrd'),
105
+ :step => @step,
106
+ :sources => self.ds.keys,
107
+ :last_update => @last_update.iso8601
108
+ }
109
+
110
+
111
+ ret.to_json(*args)
112
+ end
113
+
114
+
115
+
116
+ # options:
117
+ # - rra : clip from/to to ensure returned values come from this rra
118
+ #
119
+ def get_values(consolidation_function, from, to, opts = {})
120
+
121
+ options = {}
122
+
123
+ rra_id = opts[:rra]
124
+ if rra_id
125
+ options[:resolution] = @rra[rra_id].interval
126
+ from, to = @rra[rra_id].clip_time(from, to, @last_update)
127
+ end
128
+
129
+ options[:start] = from.to_i
130
+ options[:end] = to.to_i
131
+
132
+ line_params = RRD.to_line_parameters(options)
133
+ # RRD::Wrapper.xport("--start", "1266933600", "--end", "1266944400", "DEF:xx=#{RRD_FILE}:cpu0:AVERAGE", "XPORT:xx:Legend 0")
134
+ ds_name = @ds.keys[0]
135
+ ret = RRD::Wrapper.xport(*line_params, "DEF:v1=#{@path}:#{ds_name}:#{consolidation_function}", "XPORT:v1:v1")
136
+
137
+ if ret.is_a?(Array)
138
+ ret = ret[1..-1].inject({}) do |h, (t,v)|
139
+ h[t] = v.finite? ? v : nil
140
+ h
141
+ end
142
+ end
143
+
144
+ ret
145
+ end
146
+
147
+ def xport_values(consolidation_function, from, to, opts = {})
148
+ args = []
149
+ args += ["--start", from.to_i.to_s]
150
+ args += ["--end", to.to_i.to_s]
151
+
152
+ if opts[:maxrows]
153
+ rows = opts[:maxrows].to_i
154
+ raise ArgumentError, "maxrows needs to be greater or equal to 10" unless rows >= 10
155
+ args += ["--maxrows", rows.to_s]
156
+ end
157
+
158
+ if opts[:rra]
159
+ rra_id = opts[:rra].to_i
160
+ interval = @rra[rra_id].interval
161
+ args += ['--step', interval.to_s]
162
+ end
163
+
164
+ if opts[:rrdcached]
165
+ args += ['--daemon', opts[:rrdcached]]
166
+ end
167
+
168
+ if opts[:ds_name]
169
+ ds_name = opts[:ds_name]
170
+ else
171
+ ds_name = @ds.keys[0]
172
+ end
173
+
174
+ args += ["DEF:v1=#{@path}:#{ds_name}:#{consolidation_function}", "XPORT:v1:v1"]
175
+
176
+ ret = RRD::Wrapper.xport(*args)
177
+ if ret.is_a?(Array)
178
+ ret = ret[1..-1].inject({}) do |h, (t,v)|
179
+ h[t.to_i] = v.finite? ? v : nil
180
+ h
181
+ end
182
+ end
183
+
184
+ ret
185
+ end
186
+
187
+
188
+ # rrdtool graph /dev/null \
189
+ # DEF:min=exact.rrd:value:MIN \
190
+ # DEF:max=exact.rrd:value:MAX \
191
+ # PRINT:min:MAX:%.2lf \
192
+ # PRINT:max:MAX:%.2lf
193
+
194
+ # def get_minmax(from = nil, to = nil)
195
+ # args = []
196
+ # args += ["--start", from.to_i.to_s] if from
197
+ # args += ["--end", to.to_i.to_s] if to
198
+ #
199
+ # end
200
+
201
+ private
202
+ def parse_data(path)
203
+ data = RRD::Wrapper.info(path)
204
+
205
+ @rra = []
206
+ @ds = {}
207
+
208
+ # and now ds and rra
209
+ # TODO: parse these eventually
210
+ # "rra[0].cdp_prep[0].value"=>NaN,
211
+ # "rra[0].cdp_prep[0].unknown_datapoints"=>7,
212
+ data.each do |key, val|
213
+ case key
214
+ when 'step' then @step = val
215
+ when 'rrd_version' then @rrd_version = val
216
+ when 'filename' then @filename = val
217
+ when 'last_update' then @last_update = Time.at(val)
218
+
219
+ # ds[value2].unknown_sec
220
+ when %r{^ds\[([a-zA-Z0-9_]+)\]\.([a-zA-Z_]+)}
221
+ @ds[$1] ||= {}
222
+ @ds[$1][$2] = val
223
+
224
+ when %r{^rra\[([0-9]+)\]\.([a-zA-Z_]+)}
225
+ @rra[$1.to_i] ||= {}
226
+ @rra[$1.to_i][$2] = val
227
+
228
+ when 'header_size' # ignore it
229
+
230
+ else
231
+ puts "Unknown key: #{key}"
232
+ end
233
+ end
234
+
235
+ @rra.map!{|v| RRA.new(@step, v) }
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,78 @@
1
+
2
+ require 'sinatra/base'
3
+
4
+ require 'rrd'
5
+ require File.expand_path('../rrd', __FILE__)
6
+
7
+ module RRDGrapher
8
+ class RRDServer < Sinatra::Base
9
+
10
+ def initialize(app, opts = {})
11
+ @app = app
12
+ @root_path = opts.delete(:root_path)
13
+ # use rrdcached ?
14
+ # value must be the path to the unix socket
15
+ @rrdcached = opts.delete(:rrdcached)
16
+
17
+ unless opts.empty?
18
+ raise "Unknown options: #{opts.inspect}"
19
+ end
20
+ end
21
+
22
+ set :public, nil
23
+ set :views, nil
24
+ set :reload_templates, false
25
+
26
+ before do
27
+ content_type :json
28
+ end
29
+
30
+ get '/rrd' do
31
+ ret = []
32
+
33
+ Dir.chdir(@root_path) do
34
+ Dir["**/*.rrd"].each do |rrd_path|
35
+ ret << load_rrd(rrd_path)
36
+ end
37
+ end
38
+
39
+ ret.to_json(false)
40
+ end
41
+
42
+
43
+ get '/rrd/:path/rra' do
44
+ rrd = load_rrd( "#{params[:path]}.rrd" )
45
+ rrd.archives.to_json
46
+ end
47
+
48
+ get '/rrd/:path/ds' do
49
+ rrd = load_rrd( "#{params[:path]}.rrd" )
50
+ rrd.ds.to_json
51
+ end
52
+
53
+ # optional parameters:
54
+ # - rra: if present the range will be clipped wihin it
55
+ #
56
+ get %r{/rrd/(.+)/values/([0-9.]+)/([0-9.]+)} do
57
+ args = {}
58
+
59
+ path, from, to = params[:captures]
60
+
61
+ args[:rra] = params[:rra].to_i if params[:rra]
62
+ args[:maxrows] = params[:maxrows].to_i if params[:maxrows]
63
+ args[:ds_name] = params[:ds_name] if params[:ds_name]
64
+ args[:rrdcached] = @rrdcached
65
+
66
+ rrd = load_rrd( "#{path}.rrd" )
67
+ ret = rrd.xport_values("AVERAGE", from.to_i, to.to_i, args)
68
+
69
+ ret.to_json
70
+ end
71
+
72
+ private
73
+ def load_rrd(short_path)
74
+ RRDReader::File.new(short_path, @root_path)
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,4 @@
1
+
2
+ module RRDGRapher
3
+ VERSION = "1.0.0"
4
+ end
@@ -0,0 +1,111 @@
1
+ .hidden {
2
+ display : none;
3
+ }
4
+
5
+ .transparent {
6
+ filter:alpha(opacity=50);
7
+ -moz-opacity:0.5;
8
+ -khtml-opacity: 0.5;
9
+ opacity: 0.5;
10
+ }
11
+
12
+ #config_panel {
13
+ position: fixed;
14
+ left: 0;
15
+ top: 0;
16
+ width: 100%;
17
+ height: 40px;
18
+ border-bottom: 1px solid black;
19
+ background-color: white;
20
+ z-index: 40;
21
+ padding: 2px 30px;
22
+
23
+ #rangeselect {
24
+ width: 200px;
25
+ }
26
+
27
+ // http://www.1kbgrid.com/
28
+ /* ================ */
29
+ /* = The 1Kb Grid = */ /* 12 columns, 60 pixels each, with 20 pixel gutter */
30
+ /* ================ */
31
+
32
+ .grid_1 { width:60px; }
33
+ .grid_2 { width:140px; }
34
+ .grid_3 { width:220px; }
35
+ .grid_4 { width:300px; }
36
+ .grid_5 { width:380px; }
37
+ .grid_6 { width:460px; }
38
+ .grid_7 { width:540px; }
39
+ .grid_8 { width:620px; }
40
+ .grid_9 { width:700px; }
41
+ .grid_10 { width:780px; }
42
+ .grid_11 { width:860px; }
43
+ .grid_12 { width:940px; }
44
+
45
+ .column {
46
+ margin: 0 10px;
47
+ // overflow: hidden;
48
+ float: left;
49
+ display: inline;
50
+ }
51
+ .row {
52
+ width: 960px;
53
+ margin: 0 auto;
54
+ // overflow: hidden;
55
+ }
56
+ .row .row {
57
+ margin: 0 -10px;
58
+ width: auto;
59
+ display: inline-block;
60
+ }
61
+ }
62
+
63
+ DIV.graph_container {
64
+
65
+ margin-top: 50px;
66
+
67
+ .graph_title {
68
+ margin: 25px 0 5px 0;
69
+ text-align: center;
70
+ font-size: 1.2em;
71
+ }
72
+
73
+ .graph {
74
+ display: inline-block;
75
+ width: 700px;
76
+ height: 200px;
77
+ }
78
+
79
+ .legend {
80
+ display: inline-block;
81
+ margin: 2px 20px;
82
+ height: 100%;
83
+ width: 400px;
84
+ vertical-align: top;
85
+
86
+ TABLE {
87
+ width: 100%;
88
+ border-collapse: collapse;
89
+
90
+ TH {
91
+ border-bottom: 2px solid black;
92
+ }
93
+
94
+ TD {
95
+ text-align: right;
96
+ }
97
+
98
+ .legendColorBox .outerBorder {
99
+ padding: 1px;
100
+ border: 1px solid #aaa;
101
+ overflow: hidden;
102
+ width: 14px;
103
+ }
104
+
105
+ TH, TD {
106
+ padding: 1px 8px;
107
+ }
108
+
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'rack'
3
+ require 'sinatra'
4
+ require 'json'
5
+ require 'sass'
6
+
7
+ require File.expand_path('../rrd-grapher/rrd_server', __FILE__)
8
+ require File.expand_path('../rrd-grapher/resources', __FILE__)
9
+
10
+ module RRDGrapher
11
+
12
+ end
@@ -0,0 +1,47 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path('../lib/rrd-grapher/version', __FILE__)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rrd-grapher"
7
+ s.version = RRDGRapher::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Julien Ammous"]
10
+ s.email = []
11
+ s.homepage = ""
12
+ s.summary = %q{Graphing toolkit for RRD}
13
+ s.description = s.summary
14
+
15
+ s.rubyforge_project = "rrd-grapher"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("sinatra", "~> 1.2.6")
23
+ s.add_dependency("sass", "~> 3.1.5")
24
+ s.add_dependency("rrd-ffi", "~> 0.2.7")
25
+ s.add_dependency("rest-client", "~> 1.6.3")
26
+ s.add_dependency("i18n", "~> 0.6.0")
27
+ s.add_dependency("eventmachine", "~> 0.12.10")
28
+ s.add_dependency("fiber_pool", "~> 1.0.0")
29
+
30
+
31
+ # s.add_development_dependency("jasmine", "~> 1.0.2.1")
32
+ s.add_development_dependency("jasmine", "~> 1.1.0.rc3")
33
+ s.add_development_dependency("schmurfy-bacon", "~> 1.2.1")
34
+ s.add_development_dependency("mocha", "~> 0.9.12")
35
+ s.add_development_dependency("factory_girl", "~> 2.0.4")
36
+ s.add_development_dependency("simplecov", "~> 0.4.2")
37
+ s.add_development_dependency("coffee-script", "~> 2.2.0")
38
+ s.add_development_dependency("therubyracer", "~> 0.9.2")
39
+ s.add_development_dependency("guard-coffeescript", "~> 0.3.2")
40
+ s.add_development_dependency("guard-livereload", "~> 0.2.1")
41
+ s.add_development_dependency("guard-sprockets", "~> 0.1.4")
42
+ s.add_development_dependency("guard-yard", "~> 1.0.0")
43
+ s.add_development_dependency("rdiscount", "~> 1.6.8")
44
+ s.add_development_dependency("rb-fsevent")
45
+ s.add_development_dependency("growl")
46
+ s.add_development_dependency("rake")
47
+ end
data/spec/common.rb ADDED
@@ -0,0 +1,70 @@
1
+ $:.reject! { |e| e.include? 'TextMate' }
2
+ require 'rubygems'
3
+
4
+ puts "Testing with ruby #{RUBY_VERSION} and rubygems #{Gem::VERSION}"
5
+ puts "Working path: #{Dir.pwd}"
6
+
7
+ require 'bundler/setup'
8
+ require 'bacon'
9
+ require 'mocha'
10
+ require 'factory_girl'
11
+
12
+
13
+ require File.expand_path('../factories', __FILE__)
14
+
15
+ ROOT = File.expand_path('../../', __FILE__)
16
+
17
+ if (RUBY_VERSION >= "1.9") && ENV['COVERAGE']
18
+ require 'simplecov'
19
+ SimpleCov.start do
20
+ add_filter '/gems/'
21
+ add_filter '/spec/'
22
+ add_filter 'default_user_handler.rb'
23
+
24
+ root(ROOT)
25
+ end
26
+ end
27
+
28
+
29
+ def with(what)
30
+ yield(what)
31
+ end
32
+
33
+
34
+ module Bacon
35
+ module MochaRequirementsCounter
36
+ def self.increment
37
+ Counter[:requirements] += 1
38
+ end
39
+ end
40
+
41
+ class Context
42
+ include Mocha::API
43
+
44
+ alias_method :it_before_mocha, :it
45
+
46
+ def it(description)
47
+ it_before_mocha(description) do
48
+ begin
49
+ mocha_setup
50
+ yield
51
+ mocha_verify(MochaRequirementsCounter)
52
+ rescue Mocha::ExpectationError => e
53
+ raise Error.new(:failed, "#{e.message}\n#{e.backtrace[0...10].join("\n")}")
54
+ ensure
55
+ mocha_teardown
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ unless ENV['COVERAGE']
63
+ def focus(test_label)
64
+ # silence_warnings do
65
+ Bacon.const_set(:RestrictName, %r{#{test_label}})
66
+ # end
67
+ end
68
+ end
69
+
70
+ Bacon.summary_on_exit()
Binary file
Binary file
Binary file
data/spec/factories.rb ADDED
@@ -0,0 +1,23 @@
1
+
2
+ FactoryGirl.define do
3
+ factory :data_common, :default_strategy => :build do
4
+ time { Time.now }
5
+ host "localhost"
6
+ plugin "memory"
7
+ plugin_instance nil
8
+
9
+ type "memory"
10
+ type_instance "active"
11
+ end
12
+
13
+ factory :data_point, :parent => :data_common, :class => "RRDNotifier::Packet" do
14
+ values { [rand(200)] }
15
+ interval 10
16
+ end
17
+
18
+ factory :notification, :parent => :data_common, :class => "RRDNotifier::Packet" do
19
+ severity 1
20
+ message "notification message"
21
+ end
22
+
23
+ end