statsd 0.0.4
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/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