├── README
├── json-to-graphite.js
├── collectd-graphite-proxy.js
└── types.db
/README:
--------------------------------------------------------------------------------
1 |
2 | Graphite is great.
3 |
4 | Collectd has piles of plugins.
5 |
6 | Let's glue them together.
7 |
8 | In collectd.conf, put this:
9 |
10 |
11 |
12 |
13 |
14 |
15 | Then run collectd-graphite-proxy.js in node, with the hostname of the graphite
16 | system to target as a command-line parameter. It will take data from collectd
17 | and ship it to graphite.
18 |
19 | Versions of nodejs reported to work:
20 | - 0.2.0
21 | - 0.4.0
22 |
23 | This is a work in progress. It also may not stay in node/javascript since I
24 | am not really sold on the platform. I have only run this on node 0.2.0 and
25 | haven't bothered upgrading due to the ever volatile, moving target that nodejs
26 | is. If you find it does not work in newer versions, I'm happy to take patches
27 | to resolve such a problem.
28 |
29 | Future?
30 | - Support the binary protocol collectd uses for the network plugin.
31 | - Use node-ffi to call collectd plugins (as collectd would) and ship out
32 | graphite (or any other protocol!)
33 | - Implement as collectd plugin? The collectd plugin docs basically "do not use
34 | this api" and also requires GPL2, so maybe this route isn't the right one.
35 |
--------------------------------------------------------------------------------
/json-to-graphite.js:
--------------------------------------------------------------------------------
1 | var net = require("net");
2 |
3 | function to_dotted_notation(obj, parent_key, result) {
4 | if (typeof(parent_key) == 'undefined') parent_key = "";
5 | if (typeof(result) == 'undefined') result = {};
6 |
7 | if (typeof(obj) == 'object') {
8 | for (var i in obj) {
9 | var key = parent_key ? (parent_key + "." + i) : i;
10 | to_dotted_notation(obj[i], key, result);
11 | }
12 | } else if (typeof(obj) == 'number') {
13 | result[parent_key] = obj;
14 | }
15 | return result;
16 | }
17 |
18 | var stdin = process.openStdin();
19 | var input = "";
20 | stdin.on("data", function(chunk) {
21 | input += chunk;
22 | });
23 | stdin.on("end", function() {
24 | data = JSON.parse(input);
25 | results = to_dotted_notation(data);
26 |
27 | /* TODO(sissel): validate args */
28 | var address = process.argv[2].split(":");
29 | address.push(2003); /* default port */
30 | var host = address[0];
31 | var port = address[1];
32 | /* Only fetch matching keys */
33 | var args = process.argv.slice(3); /* argv[0] == 'node', argv[1] is script name */
34 |
35 | /* Create a regexp of (arg)|(arg)|(arg)... */
36 | var pattern = args.map(function(arg) { return "(" + arg + ")" }).join("|");
37 | var re = new RegExp(pattern);
38 |
39 | var now = Math.floor((new Date()).getTime() / 1000);
40 | var messages = []
41 | for (var key in results) {
42 | if (re.test(key)) {
43 | messages.push([key, results[key], now].join(" "));
44 | }
45 | }
46 |
47 | var graphite = net.createConnection(port, host);
48 | graphite.on('connect', function() {
49 | for (var i in messages) {
50 | graphite.write(messages[i].toLowerCase() + "\n");
51 | }
52 | graphite.end();
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/collectd-graphite-proxy.js:
--------------------------------------------------------------------------------
1 | /* TODO(sissel): make connections retry/etc
2 | * TODO(sissel): make graphite target configurable via command line
3 | *
4 | * This code is a work in progress.
5 | *
6 | * To use this, put the following in your collectd config:
7 | *
8 | * LoadPlugin write_http
9 | *
10 | *
11 | *
12 | *
13 | *
14 | * This will make collectd write 'PUTVAL' statements over HTTP to the above URL.
15 | * This code below will then convert the PUTVAL statements to graphite metrics
16 | * and ship them to 'monitor:2003'
17 | */
18 | var http = require("http");
19 | var net = require("net");
20 | var assert = require("assert");
21 | var fs = require('fs');
22 |
23 | var types = fs.readFileSync('/usr/share/collectd/types.db', encoding='utf8').split("\n");
24 |
25 | var typesObj = new Object;
26 |
27 | var type_comments_re = /^#/;
28 | var type_cut_re = /^([^\s]+)\s+(.*)/;
29 |
30 | for (var i in types) {
31 | if (!type_comments_re.exec(types[i])) {
32 | typeSet = type_cut_re.exec(types[i])
33 | if (!typeSet) { continue; }
34 | for (var t=0;t < typeSet.length;t++) {
35 | var name = typeSet[1];
36 | typesObj[name] = new Array();
37 | var eachType = typeSet[2].split(", ")
38 | for (var u=0; u < eachType.length; u++){
39 | var theName = eachType[u].split(":")[0];
40 | typesObj[name].push(theName);
41 | }
42 | }
43 | }
44 | }
45 |
46 |
47 | try {
48 | var graphite_connection = net.createConnection(2003, host=process.argv[2]);
49 | } catch (error) {
50 | throw error;
51 | }
52 | graphite_connection.on("close", function() {
53 | throw new Error("Connection closed");
54 | });
55 | graphite_connection.on("error", function() {
56 | throw new Error("Connection error");
57 | });
58 |
59 | var request_handler = function(request, response) {
60 | var putval_re = /^PUTVAL ([^ ]+)(?: ([^ ]+=[^ ]+)?) ([0-9.]+)(:.*)/;
61 | request.addListener("data", function(chunk) {
62 | metrics = chunk.toString().split("\n");
63 | for (var i in metrics) {
64 | var m = putval_re.exec(metrics[i]);
65 | if (!m) {
66 | continue;
67 | }
68 | var values = m[4].split(":");
69 |
70 | for (var v in values) {
71 |
72 | var name = m[1];
73 | var options = m[2];
74 | var time = m[3];
75 |
76 | if ( v == 0 ) {
77 | continue;
78 | }
79 |
80 | // Replace some chars for graphite, split into parts
81 | var name_parts = name.replace(/\./g, "_").replace(/\//g, ".").split(".");
82 |
83 | // Start to construct the new name
84 | var rebuild = ["agents"]
85 |
86 | var host = name_parts[0].split(/_/)[0]
87 | rebuild = rebuild.concat(host)
88 |
89 | // Pluigin names can contain an "instance" which is set apart by a dash
90 | var plugin = name_parts[1].split("-")
91 | rebuild = rebuild.concat(plugin[0])
92 | if (plugin.length > 1) {
93 | var plugin_instance = plugin.slice(1).join("-")
94 | rebuild = rebuild.concat(plugin_instance)
95 | }
96 | plugin = plugin[0]
97 |
98 | // Type names can also contain an "instance"
99 | var type = name_parts[2].split("-")
100 | if (type[0] != plugin) {
101 | // If type and plugin are equal, delete one to clean up a bit
102 | rebuild = rebuild.concat(type[0])
103 | }
104 | if (type.length > 1) {
105 | var type_instance = type.slice(1).join("-")
106 | rebuild = rebuild.concat(type_instance)
107 | }
108 | type = type[0]
109 |
110 | // Put the name back together
111 | name = rebuild.join(".")
112 |
113 | if ( values.length > 2 ) {
114 | var metric = name_parts[2];
115 |
116 | // If the metric contains a '-' (after removing the instance name)
117 | // then we want to remove it before looking up in the types.db
118 | index = metric.search(/-/)
119 | if (index > -1) {
120 | metric = /^([\w]+)-(.*)$/.exec(metric);
121 | } else {
122 | // Kinda a hack
123 | metric = [ "", metric]
124 | }
125 | name = name + "." + typesObj[metric[1]][v - 1];
126 | }
127 | message = [name, values[v], time].join(" ");
128 | graphite_connection.write(message + "\n");
129 |
130 | }
131 |
132 | }
133 | });
134 |
135 | request.addListener("end", function() {
136 | response.writeHead(200, {"Content-Type": "text/plain"});
137 | response.write("OK");
138 | response.end();
139 | });
140 | }
141 |
142 | var server = http.createServer()
143 | server.addListener("request", request_handler)
144 | server.listen(3012);
145 |
--------------------------------------------------------------------------------
/types.db:
--------------------------------------------------------------------------------
1 | absolute value:ABSOLUTE:0:U
2 | apache_bytes value:DERIVE:0:U
3 | apache_connections value:GAUGE:0:65535
4 | apache_idle_workers value:GAUGE:0:65535
5 | apache_requests value:DERIVE:0:U
6 | apache_scoreboard value:GAUGE:0:65535
7 | ath_nodes value:GAUGE:0:65535
8 | ath_stat value:DERIVE:0:U
9 | bitrate value:GAUGE:0:4294967295
10 | bytes value:GAUGE:0:U
11 | cache_operation value:DERIVE:0:U
12 | cache_ratio value:GAUGE:0:100
13 | cache_result value:DERIVE:0:U
14 | cache_size value:GAUGE:0:4294967295
15 | charge value:GAUGE:0:U
16 | compression_ratio value:GAUGE:0:2
17 | compression uncompressed:DERIVE:0:U, compressed:DERIVE:0:U
18 | connections value:DERIVE:0:U
19 | conntrack value:GAUGE:0:4294967295
20 | contextswitch value:DERIVE:0:U
21 | counter value:COUNTER:U:U
22 | cpufreq value:GAUGE:0:U
23 | cpu value:DERIVE:0:U
24 | current_connections value:GAUGE:0:U
25 | current_sessions value:GAUGE:0:U
26 | current value:GAUGE:U:U
27 | delay value:GAUGE:-1000000:1000000
28 | derive value:DERIVE:0:U
29 | df_complex value:GAUGE:0:U
30 | df_inodes value:GAUGE:0:U
31 | df used:GAUGE:0:1125899906842623, free:GAUGE:0:1125899906842623
32 | disk_latency read:GAUGE:0:U, write:GAUGE:0:U
33 | disk_merged read:DERIVE:0:U, write:DERIVE:0:U
34 | disk_octets read:DERIVE:0:U, write:DERIVE:0:U
35 | disk_ops_complex value:DERIVE:0:U
36 | disk_ops read:DERIVE:0:U, write:DERIVE:0:U
37 | disk_time read:DERIVE:0:U, write:DERIVE:0:U
38 | dns_answer value:DERIVE:0:U
39 | dns_notify value:DERIVE:0:U
40 | dns_octets queries:DERIVE:0:U, responses:DERIVE:0:U
41 | dns_opcode value:DERIVE:0:U
42 | dns_qtype_cached value:GAUGE:0:4294967295
43 | dns_qtype value:DERIVE:0:U
44 | dns_query value:DERIVE:0:U
45 | dns_question value:DERIVE:0:U
46 | dns_rcode value:DERIVE:0:U
47 | dns_reject value:DERIVE:0:U
48 | dns_request value:DERIVE:0:U
49 | dns_resolver value:DERIVE:0:U
50 | dns_response value:DERIVE:0:U
51 | dns_transfer value:DERIVE:0:U
52 | dns_update value:DERIVE:0:U
53 | dns_zops value:DERIVE:0:U
54 | email_check value:GAUGE:0:U
55 | email_count value:GAUGE:0:U
56 | email_size value:GAUGE:0:U
57 | entropy value:GAUGE:0:4294967295
58 | fanspeed value:GAUGE:0:U
59 | file_size value:GAUGE:0:U
60 | files value:GAUGE:0:U
61 | fork_rate value:DERIVE:0:U
62 | frequency value:GAUGE:0:U
63 | frequency_offset value:GAUGE:-1000000:1000000
64 | fscache_stat value:DERIVE:0:U
65 | gauge value:GAUGE:U:U
66 | http_request_methods value:DERIVE:0:U
67 | http_requests value:DERIVE:0:U
68 | http_response_codes value:DERIVE:0:U
69 | humidity value:GAUGE:0:100
70 | if_collisions value:DERIVE:0:U
71 | if_dropped rx:DERIVE:0:U, tx:DERIVE:0:U
72 | if_errors rx:DERIVE:0:U, tx:DERIVE:0:U
73 | if_multicast value:DERIVE:0:U
74 | if_octets rx:DERIVE:0:U, tx:DERIVE:0:U
75 | if_packets rx:DERIVE:0:U, tx:DERIVE:0:U
76 | if_rx_errors value:DERIVE:0:U
77 | if_tx_errors value:DERIVE:0:U
78 | invocations value:DERIVE:0:U
79 | io_octets rx:DERIVE:0:U, tx:DERIVE:0:U
80 | io_packets rx:DERIVE:0:U, tx:DERIVE:0:U
81 | ipt_bytes value:DERIVE:0:U
82 | ipt_packets value:DERIVE:0:U
83 | irq value:DERIVE:0:U
84 | latency value:GAUGE:0:65535
85 | links value:GAUGE:0:U
86 | load shortterm:GAUGE:0:100, midterm:GAUGE:0:100, longterm:GAUGE:0:100
87 | memcached_command value:DERIVE:0:U
88 | memcached_connections value:GAUGE:0:U
89 | memcached_items value:GAUGE:0:U
90 | memcached_octets rx:DERIVE:0:U, tx:DERIVE:0:U
91 | memcached_ops value:DERIVE:0:U
92 | memory value:GAUGE:0:281474976710656
93 | multimeter value:GAUGE:U:U
94 | mysql_commands value:DERIVE:0:U
95 | mysql_handler value:DERIVE:0:U
96 | mysql_locks value:DERIVE:0:U
97 | mysql_log_position value:DERIVE:0:U
98 | mysql_octets rx:DERIVE:0:U, tx:DERIVE:0:U
99 | nfs_procedure value:DERIVE:0:U
100 | nginx_connections value:GAUGE:0:U
101 | nginx_requests value:DERIVE:0:U
102 | node_octets rx:DERIVE:0:U, tx:DERIVE:0:U
103 | node_rssi value:GAUGE:0:255
104 | node_stat value:DERIVE:0:U
105 | node_tx_rate value:GAUGE:0:127
106 | operations value:DERIVE:0:U
107 | percent value:GAUGE:0:100.1
108 | pg_blks value:DERIVE:0:U
109 | pg_db_size value:GAUGE:0:U
110 | pg_n_tup_c value:DERIVE:0:U
111 | pg_n_tup_g value:GAUGE:0:U
112 | pg_numbackends value:GAUGE:0:U
113 | pg_scan value:DERIVE:0:U
114 | pg_xact value:DERIVE:0:U
115 | ping_droprate value:GAUGE:0:100
116 | ping value:GAUGE:0:65535
117 | ping_stddev value:GAUGE:0:65535
118 | players value:GAUGE:0:1000000
119 | power value:GAUGE:0:U
120 | protocol_counter value:DERIVE:0:U
121 | ps_code value:GAUGE:0:9223372036854775807
122 | ps_count processes:GAUGE:0:1000000, threads:GAUGE:0:1000000
123 | ps_cputime user:DERIVE:0:U, syst:DERIVE:0:U
124 | ps_data value:GAUGE:0:9223372036854775807
125 | ps_disk_octets read:DERIVE:0:U, write:DERIVE:0:U
126 | ps_disk_ops read:DERIVE:0:U, write:DERIVE:0:U
127 | ps_pagefaults minflt:DERIVE:0:U, majflt:DERIVE:0:U
128 | ps_rss value:GAUGE:0:9223372036854775807
129 | ps_stacksize value:GAUGE:0:9223372036854775807
130 | ps_state value:GAUGE:0:65535
131 | ps_vm value:GAUGE:0:9223372036854775807
132 | queue_length value:GAUGE:0:U
133 | records value:GAUGE:0:U
134 | requests value:GAUGE:0:U
135 | response_time value:GAUGE:0:U
136 | route_etx value:GAUGE:0:U
137 | route_metric value:GAUGE:0:U
138 | routes value:GAUGE:0:U
139 | serial_octets rx:DERIVE:0:U, tx:DERIVE:0:U
140 | signal_noise value:GAUGE:U:0
141 | signal_power value:GAUGE:U:0
142 | signal_quality value:GAUGE:0:U
143 | snr value:GAUGE:0:U
144 | spam_check value:GAUGE:0:U
145 | spam_score value:GAUGE:U:U
146 | swap_io value:DERIVE:0:U
147 | swap value:GAUGE:0:1099511627776
148 | tcp_connections value:GAUGE:0:4294967295
149 | temperature value:GAUGE:-273.15:U
150 | threads value:GAUGE:0:U
151 | time_dispersion value:GAUGE:-1000000:1000000
152 | timeleft value:GAUGE:0:3600
153 | time_offset value:GAUGE:-1000000:1000000
154 | total_bytes value:DERIVE:0:U
155 | total_connections value:DERIVE:0:U
156 | total_operations value:DERIVE:0:U
157 | total_requests value:DERIVE:0:U
158 | total_sessions value:DERIVE:0:U
159 | total_threads value:DERIVE:0:U
160 | total_time_in_ms value:DERIVE:0:U
161 | total_values value:DERIVE:0:U
162 | uptime value:GAUGE:0:4294967295
163 | users value:GAUGE:0:65535
164 | vcpu value:GAUGE:0:U
165 | virt_cpu_total value:DERIVE:0:U
166 | virt_vcpu value:DERIVE:0:U
167 | vmpage_action value:DERIVE:0:U
168 | vmpage_faults minflt:DERIVE:0:U, majflt:DERIVE:0:U
169 | vmpage_io in:DERIVE:0:U, out:DERIVE:0:U
170 | vmpage_number value:GAUGE:0:4294967295
171 | volatile_changes value:GAUGE:0:U
172 | voltage_threshold value:GAUGE:U:U, threshold:GAUGE:U:U
173 | voltage value:GAUGE:U:U
174 | vs_memory value:GAUGE:0:9223372036854775807
175 | vs_processes value:GAUGE:0:65535
176 | vs_threads value:GAUGE:0:65535
177 | #
178 | # Legacy types
179 | # (required for the v5 upgrade target)
180 | #
181 | arc_counts demand_data:COUNTER:0:U, demand_metadata:COUNTER:0:U, prefetch_data:COUNTER:0:U, prefetch_metadata:COUNTER:0:U
182 | arc_l2_bytes read:COUNTER:0:U, write:COUNTER:0:U
183 | arc_l2_size value:GAUGE:0:U
184 | arc_ratio value:GAUGE:0:U
185 | arc_size current:GAUGE:0:U, target:GAUGE:0:U, minlimit:GAUGE:0:U, maxlimit:GAUGE:0:U
186 | mysql_qcache hits:COUNTER:0:U, inserts:COUNTER:0:U, not_cached:COUNTER:0:U, lowmem_prunes:COUNTER:0:U, queries_in_cache:GAUGE:0:U
187 | mysql_threads running:GAUGE:0:U, connected:GAUGE:0:U, cached:GAUGE:0:U, created:COUNTER:0:U
188 |
--------------------------------------------------------------------------------