statsd 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +137 -0
- data/config.js +39 -0
- data/config.yml +34 -0
- data/em-server.rb +51 -0
- data/exampleConfig.js +8 -0
- data/lib/statsd/echos.rb +21 -0
- data/lib/statsd/graphite.rb +91 -0
- data/lib/statsd/mongo.rb +146 -0
- data/lib/statsd/server.rb +41 -0
- data/lib/statsd.rb +75 -0
- data/netcat-example.sh +5 -0
- data/php-example.php +96 -0
- data/python_example.py +89 -0
- data/stats.js +128 -0
- data/statsd.gemspec +24 -0
- data/webapp/Gemfile +5 -0
- data/webapp/Gemfile.lock +21 -0
- data/webapp/README.md +2 -0
- data/webapp/app.rb +12 -0
- data/webapp/bin/rackup +16 -0
- data/webapp/bin/statsd-web +15 -0
- data/webapp/config.yml +3 -0
- data/webapp/public/jquery-1.4.4.js +7179 -0
- data/webapp/public/jquery.flot.js +2119 -0
- data/webapp/public/jquery.flot.selection.js +299 -0
- data/webapp/vendor/cache/SystemTimer-1.2.2.gem +0 -0
- data/webapp/vendor/cache/rack-1.2.1.gem +0 -0
- data/webapp/vendor/cache/redis-2.1.1.gem +0 -0
- data/webapp/vendor/cache/sinatra-1.1.3.gem +0 -0
- data/webapp/vendor/cache/tilt-1.2.2.gem +0 -0
- data/webapp/vendor/cache/vegas-0.1.8.gem +0 -0
- data/webapp/views/chart.erb +94 -0
- metadata +111 -0
data/php-example.php
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
<?php
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Sends statistics to the stats daemon over UDP
|
5
|
+
*
|
6
|
+
**/
|
7
|
+
|
8
|
+
class StatsD {
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Log timing information
|
12
|
+
*
|
13
|
+
* @param string $stats The metric to in log timing info for.
|
14
|
+
* @param float $time The ellapsed time (ms) to log
|
15
|
+
* @param float|1 $sampleRate the rate (0-1) for sampling.
|
16
|
+
**/
|
17
|
+
public static function timing($stat, $time, $sampleRate=1) {
|
18
|
+
StatsD::send(array($stat => "$time|ms"), $sampleRate);
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Increments one or more stats counters
|
23
|
+
*
|
24
|
+
* @param string|array $stats The metric(s) to increment.
|
25
|
+
* @param float|1 $sampleRate the rate (0-1) for sampling.
|
26
|
+
* @return boolean
|
27
|
+
**/
|
28
|
+
public static function increment($stats, $sampleRate=1) {
|
29
|
+
StatsD::updateStats($stats, 1, $sampleRate);
|
30
|
+
}
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Decrements one or more stats counters.
|
34
|
+
*
|
35
|
+
* @param string|array $stats The metric(s) to decrement.
|
36
|
+
* @param float|1 $sampleRate the rate (0-1) for sampling.
|
37
|
+
* @return boolean
|
38
|
+
**/
|
39
|
+
public static function decrement($stats, $sampleRate=1) {
|
40
|
+
StatsD::updateStats($stats, -1, $sampleRate);
|
41
|
+
}
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Updates one or more stats counters by arbitrary amounts.
|
45
|
+
*
|
46
|
+
* @param string|array $stats The metric(s) to update. Should be either a string or array of metrics.
|
47
|
+
* @param int|1 $delta The amount to increment/decrement each metric by.
|
48
|
+
* @param float|1 $sampleRate the rate (0-1) for sampling.
|
49
|
+
* @return boolean
|
50
|
+
**/
|
51
|
+
public static function updateStats($stats, $delta=1, $sampleRate=1) {
|
52
|
+
if (!is_array($stats)) { $stats = array($stats); }
|
53
|
+
$data = array();
|
54
|
+
foreach($stats as $stat) {
|
55
|
+
$data[$stat] = "$delta|c";
|
56
|
+
}
|
57
|
+
|
58
|
+
StatsD::send($data, $sampleRate);
|
59
|
+
}
|
60
|
+
|
61
|
+
/*
|
62
|
+
* Squirt the metrics over UDP
|
63
|
+
**/
|
64
|
+
public static function send($data, $sampleRate=1) {
|
65
|
+
$config = Config::getInstance();
|
66
|
+
if (! $config->isEnabled("statsd")) { return; }
|
67
|
+
|
68
|
+
// sampling
|
69
|
+
$sampledData = array();
|
70
|
+
|
71
|
+
if ($sampleRate < 1) {
|
72
|
+
foreach ($data as $stat => $value) {
|
73
|
+
if ((mt_rand() / mt_getrandmax()) <= $sampleRate) {
|
74
|
+
$sampledData[$stat] = "$value|@$sampleRate";
|
75
|
+
}
|
76
|
+
}
|
77
|
+
} else {
|
78
|
+
$sampledData = $data;
|
79
|
+
}
|
80
|
+
|
81
|
+
if (empty($sampledData)) { return; }
|
82
|
+
|
83
|
+
// Wrap this in a try/catch - failures in any of this should be silently ignored
|
84
|
+
try {
|
85
|
+
$host = $config->getConfig("statsd.host");
|
86
|
+
$port = $config->getConfig("statsd.port");
|
87
|
+
$fp = fsockopen("udp://$host", $port, $errno, $errstr);
|
88
|
+
if (! $fp) { return; }
|
89
|
+
foreach ($sampledData as $stat => $value) {
|
90
|
+
fwrite($fp, "$stat:$value");
|
91
|
+
}
|
92
|
+
fclose($fp);
|
93
|
+
} catch (Exception $e) {
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}
|
data/python_example.py
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# python_example.py
|
2
|
+
|
3
|
+
# Steve Ivy <steveivy@gmail.com>
|
4
|
+
# http://monkinetic.com
|
5
|
+
|
6
|
+
# this file expects local_settings.py to be in the same dir, with statsd host and port information:
|
7
|
+
#
|
8
|
+
# statsd_host = 'localhost'
|
9
|
+
# statsd_port = 8125
|
10
|
+
|
11
|
+
# Sends statistics to the stats daemon over UDP
|
12
|
+
class Statsd(object):
|
13
|
+
|
14
|
+
@staticmethod
|
15
|
+
def timing(stats, time, sample_rate=1):
|
16
|
+
"""
|
17
|
+
Log timing information
|
18
|
+
>>> from python_example import Statsd
|
19
|
+
>>> Statsd.timing('some.time','500|ms')
|
20
|
+
"""
|
21
|
+
Statsd.update_stats(stats, time, sample_rate)
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def increment(stats, sample_rate=1):
|
25
|
+
"""
|
26
|
+
Increments one or more stats counters
|
27
|
+
>>> Statsd.increment('some.int')
|
28
|
+
>>> Statsd.increment('some.int',0.5)
|
29
|
+
"""
|
30
|
+
Statsd.update_stats(stats, 1, sample_rate)
|
31
|
+
|
32
|
+
@staticmethod
|
33
|
+
def decrement(stats, sample_rate=1):
|
34
|
+
"""
|
35
|
+
Decrements one or more stats counters
|
36
|
+
>>> Statsd.decrement('some.int')
|
37
|
+
"""
|
38
|
+
Statsd.update_stats(stats, -1, sample_rate)
|
39
|
+
|
40
|
+
@staticmethod
|
41
|
+
def update_stats(stats, delta=1, sampleRate=1):
|
42
|
+
"""
|
43
|
+
Updates one or more stats counters by arbitrary amounts
|
44
|
+
>>> Statsd.update_stats('some.int',10)
|
45
|
+
"""
|
46
|
+
if (type(stats) is not list):
|
47
|
+
stats = [stats]
|
48
|
+
data = {}
|
49
|
+
for stat in stats:
|
50
|
+
data[stat] = "%s|c" % delta
|
51
|
+
|
52
|
+
Statsd.send(data, sampleRate)
|
53
|
+
|
54
|
+
@staticmethod
|
55
|
+
def send(data, sample_rate=1):
|
56
|
+
"""
|
57
|
+
Squirt the metrics over UDP
|
58
|
+
"""
|
59
|
+
try:
|
60
|
+
import local_settings as settings
|
61
|
+
host = settings.statsd_host
|
62
|
+
port = settings.statsd_port
|
63
|
+
addr=(host, port)
|
64
|
+
except Error:
|
65
|
+
exit(1)
|
66
|
+
|
67
|
+
sampled_data = {}
|
68
|
+
|
69
|
+
if(sample_rate < 1):
|
70
|
+
import random
|
71
|
+
if random.random() <= sample_rate:
|
72
|
+
for stat in data.keys():
|
73
|
+
value = data[stat]
|
74
|
+
sampled_data[stat] = "%s|@%s" %(value, sample_rate)
|
75
|
+
else:
|
76
|
+
sampled_data=data
|
77
|
+
|
78
|
+
from socket import *
|
79
|
+
udp_sock = socket(AF_INET, SOCK_DGRAM)
|
80
|
+
try:
|
81
|
+
for stat in sampled_data.keys():
|
82
|
+
value = data[stat]
|
83
|
+
send_data = "%s:%s" % (stat, value)
|
84
|
+
udp_sock.sendto(send_data, addr)
|
85
|
+
except:
|
86
|
+
import sys
|
87
|
+
from pprint import pprint
|
88
|
+
print "Unexpected error:", pprint(sys.exc_info())
|
89
|
+
pass # we don't care
|
data/stats.js
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
var dgram = require('dgram')
|
2
|
+
, sys = require('sys')
|
3
|
+
, net = require('net')
|
4
|
+
, config = require('./config')
|
5
|
+
|
6
|
+
var counters = {};
|
7
|
+
var timers = {};
|
8
|
+
var debugInt, flushInt, server;
|
9
|
+
|
10
|
+
config.configFile(process.argv[2], function (config, oldConfig) {
|
11
|
+
if (! config.debug && debugInt) {
|
12
|
+
clearInterval(debugInt);
|
13
|
+
debugInt = false;
|
14
|
+
}
|
15
|
+
|
16
|
+
if (config.debug) {
|
17
|
+
if (debugInt !== undefined) { clearInterval(debugInt); }
|
18
|
+
debugInt = setInterval(function () {
|
19
|
+
sys.log("Counters:\n" + sys.inspect(counters) + "\nTimers:\n" + sys.inspect(timers));
|
20
|
+
}, config.debugInterval || 10000);
|
21
|
+
}
|
22
|
+
|
23
|
+
if (server === undefined) {
|
24
|
+
server = dgram.createSocket('udp4', function (msg, rinfo) {
|
25
|
+
if (config.dumpMessages) { sys.log(msg.toString()); }
|
26
|
+
var bits = msg.toString().split(':');
|
27
|
+
var key = bits.shift()
|
28
|
+
.replace(/\s+/g, '_')
|
29
|
+
.replace(/\//g, '-')
|
30
|
+
.replace(/[^a-zA-Z_\-0-9\.]/g, '');
|
31
|
+
|
32
|
+
if (bits.length == 0) {
|
33
|
+
bits.push("1");
|
34
|
+
}
|
35
|
+
|
36
|
+
for (var i = 0; i < bits.length; i++) {
|
37
|
+
var sampleRate = 1;
|
38
|
+
var fields = bits[i].split("|");
|
39
|
+
if (fields[1].trim() == "ms") {
|
40
|
+
if (! timers[key]) {
|
41
|
+
timers[key] = [];
|
42
|
+
}
|
43
|
+
timers[key].push(Number(fields[0] || 0));
|
44
|
+
} else {
|
45
|
+
if (fields[2] && fields[2].match(/^@([\d\.]+)/)) {
|
46
|
+
sampleRate = Number(fields[2].match(/^@([\d\.]+)/)[1]);
|
47
|
+
}
|
48
|
+
if (! counters[key]) {
|
49
|
+
counters[key] = 0;
|
50
|
+
}
|
51
|
+
counters[key] += Number(fields[0] || 1) * (1 / sampleRate);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
});
|
55
|
+
|
56
|
+
server.bind(config.port || 8125);
|
57
|
+
|
58
|
+
var flushInterval = Number(config.flushInterval || 10000);
|
59
|
+
|
60
|
+
flushInt = setInterval(function () {
|
61
|
+
var statString = '';
|
62
|
+
var ts = Math.round(new Date().getTime() / 1000);
|
63
|
+
var numStats = 0;
|
64
|
+
var key;
|
65
|
+
|
66
|
+
for (key in counters) {
|
67
|
+
var value = counters[key] / (flushInterval / 1000);
|
68
|
+
var message = 'stats.' + key + ' ' + value + ' ' + ts + "\n";
|
69
|
+
statString += message;
|
70
|
+
counters[key] = 0;
|
71
|
+
|
72
|
+
numStats += 1;
|
73
|
+
}
|
74
|
+
|
75
|
+
for (key in timers) {
|
76
|
+
if (timers[key].length > 0) {
|
77
|
+
var pctThreshold = config.percentThreshold || 90;
|
78
|
+
var values = timers[key].sort(function (a,b) { return a-b; });
|
79
|
+
var count = values.length;
|
80
|
+
var min = values[0];
|
81
|
+
var max = values[count - 1];
|
82
|
+
|
83
|
+
var mean = min;
|
84
|
+
var maxAtThreshold = max;
|
85
|
+
|
86
|
+
if (count > 1) {
|
87
|
+
var thresholdIndex = Math.round(((100 - pctThreshold) / 100) * count);
|
88
|
+
var numInThreshold = count - thresholdIndex;
|
89
|
+
values = values.slice(0, numInThreshold);
|
90
|
+
maxAtThreshold = values[numInThreshold - 1];
|
91
|
+
|
92
|
+
// average the remaining timings
|
93
|
+
var sum = 0;
|
94
|
+
for (var i = 0; i < numInThreshold; i++) {
|
95
|
+
sum += values[i];
|
96
|
+
}
|
97
|
+
|
98
|
+
mean = sum / numInThreshold;
|
99
|
+
}
|
100
|
+
|
101
|
+
timers[key] = [];
|
102
|
+
|
103
|
+
var message = "";
|
104
|
+
message += 'stats.timers.' + key + '.mean ' + mean + ' ' + ts + "\n";
|
105
|
+
message += 'stats.timers.' + key + '.upper ' + max + ' ' + ts + "\n";
|
106
|
+
message += 'stats.timers.' + key + '.upper_' + pctThreshold + ' ' + maxAtThreshold + ' ' + ts + "\n";
|
107
|
+
message += 'stats.timers.' + key + '.lower ' + min + ' ' + ts + "\n";
|
108
|
+
message += 'stats.timers.' + key + '.count ' + count + ' ' + ts + "\n";
|
109
|
+
statString += message;
|
110
|
+
|
111
|
+
numStats += 1;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
statString += 'statsd.numStats ' + numStats + ' ' + ts + "\n";
|
116
|
+
|
117
|
+
var graphite = net.createConnection(config.graphitePort, config.graphiteHost);
|
118
|
+
|
119
|
+
graphite.on('connect', function() {
|
120
|
+
this.write(statString);
|
121
|
+
this.end();
|
122
|
+
});
|
123
|
+
|
124
|
+
}, flushInterval);
|
125
|
+
}
|
126
|
+
|
127
|
+
});
|
128
|
+
|
data/statsd.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib/', __FILE__)
|
3
|
+
$:.unshift lib unless $:.include?(lib)
|
4
|
+
require File.expand_path("../lib/statsd/server", __FILE__) # for version info
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "statsd"
|
7
|
+
s.version = Statsd::Server::Version
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Andrew Coldham', 'Ben VandenBos']
|
10
|
+
s.email = ['quasor@me.com']
|
11
|
+
s.homepage = "http://github.com/quasor/statsd"
|
12
|
+
s.summary = "Ruby version of statsd."
|
13
|
+
s.description = "Ruby version of statsd."
|
14
|
+
|
15
|
+
s.required_rubygems_version = ">= 1.3.6"
|
16
|
+
|
17
|
+
s.add_dependency "eventmachine", "~> 0.12.10"
|
18
|
+
s.add_dependency "mongo", "~> 1.2.0"
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
22
|
+
s.require_path = 'lib'
|
23
|
+
end
|
24
|
+
|
data/webapp/Gemfile
ADDED
data/webapp/Gemfile.lock
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
SystemTimer (1.2.2)
|
5
|
+
rack (1.2.1)
|
6
|
+
redis (2.1.1)
|
7
|
+
sinatra (1.1.3)
|
8
|
+
rack (~> 1.1)
|
9
|
+
tilt (>= 1.2.2, < 2.0)
|
10
|
+
tilt (1.2.2)
|
11
|
+
vegas (0.1.8)
|
12
|
+
rack (>= 1.0.0)
|
13
|
+
|
14
|
+
PLATFORMS
|
15
|
+
ruby
|
16
|
+
|
17
|
+
DEPENDENCIES
|
18
|
+
SystemTimer
|
19
|
+
redis
|
20
|
+
sinatra
|
21
|
+
vegas
|
data/webapp/README.md
ADDED
data/webapp/app.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'mongo'
|
4
|
+
require 'yaml'
|
5
|
+
ROOT = File.expand_path(File.dirname(__FILE__))
|
6
|
+
APP_CONFIG = YAML::load(ERB.new(IO.read(File.join(ROOT,'config.yml'))).result)
|
7
|
+
get '/' do
|
8
|
+
db = Mongo::Connection.new(APP_CONFIG['dbhost']).db(APP_CONFIG['db'])
|
9
|
+
coll = db.collection("stats_10s")
|
10
|
+
@stats = coll.find({}).limit(100)
|
11
|
+
erb :chart
|
12
|
+
end
|
data/webapp/bin/rackup
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env rbx
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rackup' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('rack', 'rackup')
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
5
|
+
Pathname.new(__FILE__).realpath)
|
6
|
+
puts ENV['BUNDLE_GEMFILE']
|
7
|
+
require 'rubygems'
|
8
|
+
require 'bundler/setup'
|
9
|
+
|
10
|
+
#load Gem.bin_path('statsd', 'statsd')
|
11
|
+
|
12
|
+
require File.expand_path(File.dirname(__FILE__) + '/../statsd-web.rb')
|
13
|
+
require 'vegas'
|
14
|
+
|
15
|
+
Vegas::Runner.new(StatsdWeb, 'statsd-web')
|
data/webapp/config.yml
ADDED