metric_system 0.1.0 → 0.1.2
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.
- checksums.yaml +4 -4
- data/README.md +5 -1
- data/lib/metric_system/database.rb +164 -0
- data/lib/metric_system/io.rb +15 -0
- data/lib/metric_system/server.rb +126 -0
- data/lib/metric_system/sqlite3_extensions.rb +3 -157
- data/lib/metric_system/version.rb +2 -2
- data/lib/metric_system/web/dashboard.erb +163 -0
- data/lib/metric_system/web.rb +36 -0
- data/lib/metric_system.rb +24 -147
- data/lib/sqlite3/database_extension.rb +85 -0
- data/lib/sqlite3/query.rb +77 -0
- data/lib/sqlite3/record.rb +65 -0
- data/lib/to_js.rb +55 -0
- data/test/benchmark.rb +48 -0
- data/test/counters_test.rb +14 -9
- data/test/gauges_test.rb +9 -1
- data/test/mixed_test.rb +10 -2
- data/test/parallel.rb +68 -0
- metadata +13 -3
- data/lib/metric_system/benchmark.rb +0 -11
data/lib/metric_system.rb
CHANGED
@@ -1,67 +1,33 @@
|
|
1
1
|
require "expectation"
|
2
|
-
require_relative "metric_system/core_extensions"
|
3
|
-
require_relative "metric_system/sqlite3_extensions"
|
4
|
-
|
5
|
-
# -- The MetricSystem module --------------------------------------------------
|
6
2
|
|
3
|
+
require_relative "metric_system/core_extensions"
|
7
4
|
require "forwardable"
|
8
|
-
class MetricSystem
|
9
|
-
extend Forwardable
|
10
|
-
delegate [:exec, :select, :transaction, :rollback] => :@db
|
11
|
-
|
12
|
-
PERIODS = [
|
13
|
-
[ :year, 31536000, "strftime('%Y-01-01', starts_at, 'unixepoch')" ],
|
14
|
-
[ :month, 2592000, "strftime('%Y-%m-01', starts_at, 'unixepoch')" ],
|
15
|
-
[ :week, 604800, "strftime('%Y-%m-%d', starts_at, 'unixepoch', 'weekday 1', '-7 days')" ],
|
16
|
-
[ :day, 86400, "strftime('%Y-%m-%d', starts_at, 'unixepoch')" ],
|
17
|
-
[ :hour, 3600, "strftime('%Y-%m-%d %H:00:00', starts_at, 'unixepoch')" ],
|
18
|
-
[ :minute, 60, "strftime('%Y-%m-%d %H:%M:00', starts_at, 'unixepoch')" ],
|
19
|
-
[ :second, 1, "strftime('%Y-%m-%d %H:%M:%S', starts_at, 'unixepoch')" ],
|
20
|
-
]
|
21
5
|
|
22
|
-
|
23
|
-
|
6
|
+
module MetricSystem
|
7
|
+
extend self
|
24
8
|
|
25
|
-
|
26
|
-
exec <<-SQL
|
27
|
-
CREATE TABLE IF NOT EXISTS #{name}(
|
28
|
-
id INTEGER PRIMARY KEY,
|
9
|
+
attr :target, :database
|
29
10
|
|
30
|
-
|
31
|
-
|
32
|
-
starts_at TIMESTAMP NOT NULL DEFAULT (strftime('%s','now')) -- the timestamp
|
33
|
-
);
|
11
|
+
def target=(target)
|
12
|
+
@target = @database = nil
|
34
13
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
period, -- the name of the period (year, day, etc.)
|
44
|
-
sum, -- the sum of event values
|
45
|
-
count, -- the count of events
|
46
|
-
value -- the aggregated value
|
47
|
-
);
|
48
|
-
|
49
|
-
CREATE UNIQUE INDEX IF NOT EXISTS aggregated_#{name}_uidx1 ON aggregated_#{name}(name, starts_at, duration);
|
50
|
-
CREATE INDEX IF NOT EXISTS aggregated_#{name}_idx2 ON aggregated_#{name}(starts_at);
|
51
|
-
CREATE INDEX IF NOT EXISTS aggregated_#{name}_idx3 ON aggregated_#{name}(duration);
|
52
|
-
SQL
|
14
|
+
case target
|
15
|
+
when nil
|
16
|
+
when String
|
17
|
+
require_relative "metric_system/database"
|
18
|
+
@target = @database = MetricSystem::Database.new(target)
|
19
|
+
else
|
20
|
+
require_relative "metric_system/io"
|
21
|
+
@target = MetricSystem::IO.new(target)
|
53
22
|
end
|
54
|
-
|
55
|
-
exec <<-SQL
|
56
|
-
PRAGMA synchronous = NORMAL;
|
57
|
-
|
58
|
-
CREATE VIEW IF NOT EXISTS aggregates AS
|
59
|
-
SELECT * FROM aggregated_gauges
|
60
|
-
UNION
|
61
|
-
SELECT * FROM aggregated_counters
|
62
|
-
SQL
|
63
23
|
end
|
64
24
|
|
25
|
+
extend Forwardable
|
26
|
+
delegate [:aggregate, :select, :print, :run, :ask, :register] => :"@target"
|
27
|
+
delegate [:transaction] => :"@target"
|
28
|
+
delegate [:add_event] => :"@target"
|
29
|
+
delegate [:quit_server!] => :"@target"
|
30
|
+
|
65
31
|
def gauge(name, value, starts_at = nil)
|
66
32
|
add_event :gauges, name, value, starts_at
|
67
33
|
end
|
@@ -70,99 +36,10 @@ class MetricSystem
|
|
70
36
|
add_event :counters, name, value, starts_at
|
71
37
|
end
|
72
38
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
# 3 events: "a", "a.b", "a.b.c"
|
78
|
-
names = begin
|
79
|
-
parts = name.split(".")
|
80
|
-
parts.length.downto(1).map do |cnt|
|
81
|
-
parts[0,cnt].join(".")
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
if starts_at
|
86
|
-
starts_at = Time.parse(starts_at) if starts_at.is_a?(String)
|
87
|
-
|
88
|
-
names.each do |name|
|
89
|
-
@db.run "INSERT INTO #{table}(name, value, starts_at) VALUES(?, ?, ?)", name, value, starts_at.to_i
|
90
|
-
end
|
91
|
-
else
|
92
|
-
names.each do |name|
|
93
|
-
@db.run "INSERT INTO #{table}(name, value) VALUES(?, ?)", name, value
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
public
|
99
|
-
|
100
|
-
PERIODS_BY_KEY = PERIODS.by(&:first)
|
101
|
-
|
102
|
-
def aggregate(*keys)
|
103
|
-
if keys.empty?
|
104
|
-
keys = PERIODS.map(&:first)
|
105
|
-
end
|
106
|
-
|
107
|
-
transaction do
|
108
|
-
keys.each do |period|
|
109
|
-
aggregate_for_period :period => period, :source => :counters, :dest => :aggregated_counters, :aggregate => "sum"
|
110
|
-
end
|
111
|
-
|
112
|
-
@db.exec "DELETE FROM counters"
|
113
|
-
end
|
114
|
-
|
115
|
-
transaction do
|
116
|
-
keys.each do |period|
|
117
|
-
aggregate_for_period :period => period, :source => :gauges, :dest => :aggregated_gauges, :aggregate => "CAST(sum AS FLOAT) / count"
|
118
|
-
end
|
119
|
-
|
120
|
-
@db.exec "DELETE FROM gauges"
|
39
|
+
def measure(name, starts_at = nil, &block)
|
40
|
+
start = Time.now
|
41
|
+
yield.tap do
|
42
|
+
gauge name, Time.now - start, starts_at
|
121
43
|
end
|
122
44
|
end
|
123
|
-
|
124
|
-
def aggregate_for_period(options)
|
125
|
-
expect! options => {
|
126
|
-
:period => PERIODS.map(&:first)
|
127
|
-
}
|
128
|
-
period, source, dest, aggregate = options.values_at :period, :source, :dest, :aggregate
|
129
|
-
|
130
|
-
_, duration, starts_at = PERIODS_BY_KEY[period]
|
131
|
-
|
132
|
-
# sql expression to calculate value from sum and count of event values
|
133
|
-
aggregate = source == :gauges ? "CAST(sum AS FLOAT) / count" : "sum"
|
134
|
-
|
135
|
-
@db.exec <<-SQL
|
136
|
-
CREATE TEMPORARY TABLE batch AS
|
137
|
-
SELECT name, starts_at, SUM(sum) AS sum, SUM(count) AS count FROM
|
138
|
-
(
|
139
|
-
SELECT name AS name,
|
140
|
-
#{starts_at} AS starts_at,
|
141
|
-
SUM(value) AS sum,
|
142
|
-
COUNT(value) AS count
|
143
|
-
FROM #{source}
|
144
|
-
GROUP BY name, starts_at
|
145
|
-
|
146
|
-
UNION
|
147
|
-
|
148
|
-
SELECT #{dest}.name AS name,
|
149
|
-
#{dest}.starts_at AS starts_at,
|
150
|
-
sum AS sum,
|
151
|
-
count AS count
|
152
|
-
FROM #{dest}
|
153
|
-
INNER JOIN #{source} ON #{dest}.name=#{source}.name
|
154
|
-
WHERE duration=#{duration}
|
155
|
-
AND #{dest}.starts_at >= (SELECT MIN(starts_at) FROM #{source})
|
156
|
-
)
|
157
|
-
GROUP BY name, starts_at;
|
158
|
-
|
159
|
-
INSERT OR REPLACE INTO #{dest}(name, starts_at, period, duration, sum, count, value)
|
160
|
-
SELECT name, starts_at, '#{period}', #{duration}, sum, count, #{aggregate}
|
161
|
-
FROM batch;
|
162
|
-
SQL
|
163
|
-
|
164
|
-
@db.exec <<-SQL
|
165
|
-
DROP TABLE batch;
|
166
|
-
SQL
|
167
|
-
end
|
168
45
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class SQLite3::Database
|
2
|
+
# execute multiple SQL statements at once.
|
3
|
+
def exec(sql, *args)
|
4
|
+
args = prepare_arguments(args)
|
5
|
+
|
6
|
+
while sql =~ /\S/ do
|
7
|
+
statement = prepare(sql)
|
8
|
+
|
9
|
+
sql = statement.remainder
|
10
|
+
if statement.active?
|
11
|
+
statement.execute!(*args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
rescue
|
16
|
+
STDERR.puts "#{sql}: #{$!}"
|
17
|
+
raise
|
18
|
+
end
|
19
|
+
|
20
|
+
# -- cached queries ---------------------------------------------------------
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def query(sql)
|
25
|
+
expect! sql => [ String, Symbol ]
|
26
|
+
|
27
|
+
if sql.is_a?(Symbol)
|
28
|
+
expect! sql => registry.keys
|
29
|
+
sql = registry.fetch(sql)
|
30
|
+
end
|
31
|
+
|
32
|
+
@queries ||= {}
|
33
|
+
@queries[sql] ||= SQLite3::Query.new sql, prepare(sql)
|
34
|
+
end
|
35
|
+
|
36
|
+
def prepare_arguments(args)
|
37
|
+
args.map do |arg|
|
38
|
+
case arg
|
39
|
+
when Time then arg.to_i
|
40
|
+
when Date then arg.to_time.to_i
|
41
|
+
else arg
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
public
|
47
|
+
|
48
|
+
def run(sql, *args)
|
49
|
+
query(sql).run *prepare_arguments(args)
|
50
|
+
end
|
51
|
+
|
52
|
+
def ask(sql, *args)
|
53
|
+
query(sql).ask *prepare_arguments(args)
|
54
|
+
end
|
55
|
+
|
56
|
+
# run a select like query. Returns an array of records.
|
57
|
+
def select(sql, *args)
|
58
|
+
query(sql).select *prepare_arguments(args)
|
59
|
+
end
|
60
|
+
|
61
|
+
def print(sql, *args)
|
62
|
+
require "pp"
|
63
|
+
|
64
|
+
results = select sql, *args
|
65
|
+
log_sql = sql.gsub(/\n/, " ").gsub(/\s+/, " ")
|
66
|
+
puts "=" * log_sql.length
|
67
|
+
puts log_sql
|
68
|
+
puts "-" * log_sql.length
|
69
|
+
|
70
|
+
results.each do |result|
|
71
|
+
pp result.to_a
|
72
|
+
end
|
73
|
+
puts "=" * log_sql.length
|
74
|
+
end
|
75
|
+
|
76
|
+
# -- query registry
|
77
|
+
|
78
|
+
def registry
|
79
|
+
@registry ||= {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def register(name, query)
|
83
|
+
registry[name] = query
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class SQLite3::Query
|
2
|
+
def initialize(sql, statement)
|
3
|
+
expect! statement => SQLite3::Statement
|
4
|
+
|
5
|
+
@sql, @statement = sql, statement
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(*args)
|
9
|
+
# STDERR.puts "Q: #{@sql} #{args.map(&:inspect).join(", ")}"
|
10
|
+
@statement.execute *args
|
11
|
+
end
|
12
|
+
|
13
|
+
def select(*args)
|
14
|
+
@klass ||= SQLite3::Record.for_columns(@statement.columns)
|
15
|
+
|
16
|
+
ary = run(*args).map do |rec|
|
17
|
+
@klass.build *rec
|
18
|
+
end
|
19
|
+
|
20
|
+
ary.extend Description
|
21
|
+
ary.columns = @statement.columns
|
22
|
+
ary
|
23
|
+
end
|
24
|
+
|
25
|
+
module Description
|
26
|
+
attr :columns, true
|
27
|
+
|
28
|
+
# A Google Chart compatible data table; see
|
29
|
+
# https://developers.google.com/chart/interactive/docs/reference#dataparam
|
30
|
+
def data_table
|
31
|
+
cols = columns.map do |column|
|
32
|
+
type = case column
|
33
|
+
when /_at$/ then :datetime
|
34
|
+
when /_on$/ then :date
|
35
|
+
when /value/ then :number
|
36
|
+
else :string
|
37
|
+
end
|
38
|
+
|
39
|
+
{ id: column, type: type, label: column }
|
40
|
+
end
|
41
|
+
|
42
|
+
rows = map { |record| convert_record record, cols }
|
43
|
+
|
44
|
+
{ cols: cols, rows: rows }
|
45
|
+
end
|
46
|
+
|
47
|
+
def convert_record(record, cols)
|
48
|
+
values = cols.map do |col|
|
49
|
+
id, type = col.values_at(:id, :type)
|
50
|
+
v = record.send(id)
|
51
|
+
|
52
|
+
case type
|
53
|
+
when :date then f = v.strftime("%a %b, %Y")
|
54
|
+
when :datetime then f = v.inspect
|
55
|
+
when :number then f = v
|
56
|
+
else f = v
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
{ v: v, f: f }
|
61
|
+
end
|
62
|
+
|
63
|
+
{ c: values }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def ask(*args)
|
68
|
+
results = run(*args)
|
69
|
+
row = results.first
|
70
|
+
results.reset
|
71
|
+
|
72
|
+
if !row then nil
|
73
|
+
elsif row.length == 1 then row.first
|
74
|
+
else row
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# manage SQLite3::Records
|
2
|
+
#
|
3
|
+
# The SQLite3::Record module is able to generate classes that are optimized
|
4
|
+
# for a specific set of columns. It is build on top of Struct, which is way
|
5
|
+
# faster than Hashes, for example.
|
6
|
+
module SQLite3::Record
|
7
|
+
module ClassMethods
|
8
|
+
attr :columns, true
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def to_time(s)
|
13
|
+
case s
|
14
|
+
when String then Time.parse(s)
|
15
|
+
when Fixnum then Time.at(s)
|
16
|
+
else s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_date(s)
|
21
|
+
return unless time = to_time(s)
|
22
|
+
time.to_date
|
23
|
+
end
|
24
|
+
|
25
|
+
public
|
26
|
+
|
27
|
+
def build(*attrs)
|
28
|
+
attrs = columns.zip(attrs).map do |key, value|
|
29
|
+
case key
|
30
|
+
when /_at$/ then to_time(value)
|
31
|
+
when /_on$/ then to_date(value)
|
32
|
+
else value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
new *attrs
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_a
|
41
|
+
self.class.columns.map do |column| send(column) end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_hash
|
45
|
+
kvs = self.class.columns.inject([]) do |ary, column|
|
46
|
+
ary << column << send(column)
|
47
|
+
end
|
48
|
+
|
49
|
+
Hash[*kvs]
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.for_columns(columns)
|
53
|
+
columns = columns.map(&:to_sym)
|
54
|
+
|
55
|
+
@@classes ||= {}
|
56
|
+
@@classes[columns] ||= begin
|
57
|
+
struct = Struct.new(*columns)
|
58
|
+
struct.extend SQLite3::Record::ClassMethods
|
59
|
+
struct.include SQLite3::Record
|
60
|
+
|
61
|
+
struct.columns = columns
|
62
|
+
struct
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/to_js.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
class Object
|
4
|
+
def to_js
|
5
|
+
convert_to_js
|
6
|
+
end
|
7
|
+
|
8
|
+
alias :convert_to_js :to_json
|
9
|
+
end
|
10
|
+
|
11
|
+
class Array
|
12
|
+
def convert_to_js
|
13
|
+
"[" + map(&:convert_to_js).join(", ") + "]"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Hash
|
18
|
+
def convert_to_js
|
19
|
+
"{" + map { |k,v| "#{k.convert_to_js}: #{v.convert_to_js}" }.join(", ") + "}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Date
|
24
|
+
def convert_to_js
|
25
|
+
"new Date(#{year}, #{month-1}, #{day})"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Time
|
30
|
+
def convert_to_js
|
31
|
+
"new Date(#{year}, #{month-1}, #{day}, #{hour}, #{min}, #{sec}, #{usec / 1000})"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Numeric
|
36
|
+
def convert_to_js
|
37
|
+
"%f" % self
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class OpenStruct
|
42
|
+
def convert_to_js
|
43
|
+
@table.to_js
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if defined?(SQLite3::Record)
|
48
|
+
|
49
|
+
module SQLite3::Record::ClassMethods
|
50
|
+
def convert_to_js
|
51
|
+
to_hash.convert_to_js
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
data/test/benchmark.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
$: << "#{File.dirname(__FILE__)}/../lib"
|
2
|
+
require "metric_system"
|
3
|
+
|
4
|
+
def benchmark(msg, &block)
|
5
|
+
starts = Time.now
|
6
|
+
|
7
|
+
yield
|
8
|
+
|
9
|
+
ensure
|
10
|
+
runtime = Time.now - starts
|
11
|
+
if runtime > 0.1
|
12
|
+
STDERR.puts "%s: %.3f secs" % [ msg, runtime = Time.now - starts ]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
TICKS_PER_DAY = 86400
|
17
|
+
TICKS_PER_DAY = 10
|
18
|
+
DBPATH = "samples.sqlite"
|
19
|
+
|
20
|
+
File.unlink(DBPATH) rescue nil
|
21
|
+
MetricSystem.target = DBPATH
|
22
|
+
|
23
|
+
benchmark "Building metric_system" do
|
24
|
+
distance = 24 * 3600.0 / TICKS_PER_DAY
|
25
|
+
|
26
|
+
0.upto(365) do |day|
|
27
|
+
midnight = Time.parse("2013-01-01").to_i + day * 24 * 3600
|
28
|
+
|
29
|
+
benchmark "Day ##{day}: add metrics" do
|
30
|
+
MetricSystem.transaction do
|
31
|
+
1.upto(TICKS_PER_DAY) do |step|
|
32
|
+
time = midnight + distance * step
|
33
|
+
STDERR.print "."
|
34
|
+
MetricSystem.count "clicks", 1, time
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
benchmark "Day ##{day}: aggregate" do
|
40
|
+
MetricSystem.aggregate
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
MetricSystem.print "SELECT COUNT(*) FROM aggregates"
|
46
|
+
|
47
|
+
__END__
|
48
|
+
db.print "SELECT * FROM aggregates"
|
data/test/counters_test.rb
CHANGED
@@ -3,8 +3,16 @@ require "metric_system"
|
|
3
3
|
require "test/unit"
|
4
4
|
|
5
5
|
class MetricSystem::TestCounters < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
MetricSystem.target = ":memory:"
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
MetricSystem.target = nil
|
12
|
+
end
|
13
|
+
|
6
14
|
def db
|
7
|
-
|
15
|
+
MetricSystem
|
8
16
|
end
|
9
17
|
|
10
18
|
def test_two_events
|
@@ -25,6 +33,8 @@ class MetricSystem::TestCounters < Test::Unit::TestCase
|
|
25
33
|
end
|
26
34
|
|
27
35
|
def test_conversion
|
36
|
+
db = MetricSystem.target
|
37
|
+
expect! db => MetricSystem::Database
|
28
38
|
db.exec "CREATE TABLE tmp(value, starts_at, starts_on)"
|
29
39
|
now = Time.parse("2014-03-02 11:10:11 +0100")
|
30
40
|
day = Date.parse("2014-03-02")
|
@@ -42,7 +52,6 @@ class MetricSystem::TestCounters < Test::Unit::TestCase
|
|
42
52
|
|
43
53
|
r = db.select("SELECT name, value, period, starts_at FROM aggregates ORDER BY duration, name")
|
44
54
|
assert_equal(r.map(&:to_a), [
|
45
|
-
["foo", 1, "second", Time.parse("2014-03-02 11:10:11 +0100")],
|
46
55
|
["foo", 1, "minute", Time.parse("2014-03-02 11:10:00 +0100")],
|
47
56
|
["foo", 1, "hour", Time.parse("2014-03-02 11:00:00 +0100")],
|
48
57
|
["foo", 1, "day", Time.parse("2014-03-02 00:00:00 +0100")],
|
@@ -66,7 +75,7 @@ class MetricSystem::TestCounters < Test::Unit::TestCase
|
|
66
75
|
def test_combined_name
|
67
76
|
db.count "foo", 3, "2014-03-02 12:10:11"
|
68
77
|
db.count "foo.bar", 2, "2014-03-02 12:10:11"
|
69
|
-
db.aggregate :
|
78
|
+
db.aggregate :minute, :hour
|
70
79
|
|
71
80
|
r = db.select <<-SQL
|
72
81
|
SELECT name, value, period, starts_at
|
@@ -75,8 +84,6 @@ class MetricSystem::TestCounters < Test::Unit::TestCase
|
|
75
84
|
SQL
|
76
85
|
|
77
86
|
assert_equal(r.map(&:to_a), [
|
78
|
-
["foo" , 5, "second", Time.parse("2014-03-02 11:10:11 +0100")],
|
79
|
-
["foo.bar", 2, "second", Time.parse("2014-03-02 11:10:11 +0100")],
|
80
87
|
["foo" , 5, "minute", Time.parse("2014-03-02 11:10:00 +0100")],
|
81
88
|
["foo.bar", 2, "minute", Time.parse("2014-03-02 11:10:00 +0100")],
|
82
89
|
["foo" , 5, "hour", Time.parse("2014-03-02 11:00:00 +0100")],
|
@@ -87,8 +94,8 @@ class MetricSystem::TestCounters < Test::Unit::TestCase
|
|
87
94
|
def test_double_aggregate
|
88
95
|
db.count "foo", 3, "2014-03-02 12:10:11"
|
89
96
|
db.count "foo.bar", 2, "2014-03-02 12:10:11"
|
90
|
-
db.aggregate :
|
91
|
-
db.aggregate :
|
97
|
+
db.aggregate :minute, :hour
|
98
|
+
db.aggregate :minute, :hour
|
92
99
|
|
93
100
|
r = db.select <<-SQL
|
94
101
|
SELECT name, value, period, starts_at
|
@@ -97,8 +104,6 @@ class MetricSystem::TestCounters < Test::Unit::TestCase
|
|
97
104
|
SQL
|
98
105
|
|
99
106
|
assert_equal(r.map(&:to_a), [
|
100
|
-
["foo" , 5, "second", Time.parse("2014-03-02 11:10:11 +0100")],
|
101
|
-
["foo.bar", 2, "second", Time.parse("2014-03-02 11:10:11 +0100")],
|
102
107
|
["foo" , 5, "minute", Time.parse("2014-03-02 11:10:00 +0100")],
|
103
108
|
["foo.bar", 2, "minute", Time.parse("2014-03-02 11:10:00 +0100")],
|
104
109
|
["foo" , 5, "hour", Time.parse("2014-03-02 11:00:00 +0100")],
|
data/test/gauges_test.rb
CHANGED
@@ -3,8 +3,16 @@ require "metric_system"
|
|
3
3
|
require "test/unit"
|
4
4
|
|
5
5
|
class MetricSystem::TestGauging < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
MetricSystem.target = ":memory:"
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
MetricSystem.target = nil
|
12
|
+
end
|
13
|
+
|
6
14
|
def db
|
7
|
-
|
15
|
+
MetricSystem
|
8
16
|
end
|
9
17
|
|
10
18
|
def test_two_events
|
data/test/mixed_test.rb
CHANGED
@@ -3,11 +3,19 @@ require "metric_system"
|
|
3
3
|
require "test/unit"
|
4
4
|
|
5
5
|
class MetricSystem::TestMixed < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
MetricSystem.target = ":memory:"
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
MetricSystem.target = nil
|
12
|
+
end
|
13
|
+
|
6
14
|
def db
|
7
|
-
|
15
|
+
MetricSystem
|
8
16
|
end
|
9
17
|
|
10
|
-
def
|
18
|
+
def test_two_events
|
11
19
|
db.gauge "foo", 1, "2014-03-02 12:10:11"
|
12
20
|
db.gauge "foo", 2, "2014-03-02 14:10:11"
|
13
21
|
db.count "bar", 1, "2014-03-02 12:10:11"
|
data/test/parallel.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
$: << "#{File.dirname(__FILE__)}/../lib"
|
2
|
+
require "metric_system"
|
3
|
+
|
4
|
+
SOCKET = "performance.socket"
|
5
|
+
DBPATH = "samples.sqlite"
|
6
|
+
|
7
|
+
mode, _ = *ARGV
|
8
|
+
if mode == nil then
|
9
|
+
require "metric_system/server"
|
10
|
+
|
11
|
+
Thread.new do
|
12
|
+
sleep 1
|
13
|
+
system "ruby #{__FILE__} sender"
|
14
|
+
end
|
15
|
+
|
16
|
+
MetricSystem::Server.run DBPATH, SOCKET, :quit_server => true
|
17
|
+
|
18
|
+
puts "server stopped"
|
19
|
+
|
20
|
+
MetricSystem.print "SELECT COUNT(*) FROM aggregates"
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
# ---------------------------------------------------------------------
|
25
|
+
|
26
|
+
def benchmark(msg, &block)
|
27
|
+
starts = Time.now
|
28
|
+
|
29
|
+
yield
|
30
|
+
|
31
|
+
ensure
|
32
|
+
runtime = Time.now - starts
|
33
|
+
if runtime > 0.1
|
34
|
+
STDERR.puts "%s: %.3f secs" % [ msg, runtime = Time.now - starts ]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# TICKS_PER_DAY = 86400
|
39
|
+
TICKS_PER_DAY = 10
|
40
|
+
|
41
|
+
File.unlink(DBPATH) rescue nil
|
42
|
+
|
43
|
+
require "time"
|
44
|
+
require "socket"
|
45
|
+
|
46
|
+
benchmark "Sending #{365 * TICKS_PER_DAY} events to metric_system" do
|
47
|
+
distance = 24 * 3600.0 / TICKS_PER_DAY
|
48
|
+
MetricSystem.target = UNIXSocket.new(SOCKET)
|
49
|
+
0.upto(364) do |day|
|
50
|
+
midnight = Time.parse("2013-01-01").to_i + day * 24 * 3600
|
51
|
+
|
52
|
+
benchmark "Day ##{day}: add metrics" do
|
53
|
+
1.upto(TICKS_PER_DAY) do |step|
|
54
|
+
time = midnight + distance * step
|
55
|
+
MetricSystem.count "clicks", 1, time
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
STDERR.puts "Quitting server"
|
62
|
+
MetricSystem.quit_server!
|
63
|
+
|
64
|
+
__END__
|
65
|
+
|
66
|
+
db.print "SELECT COUNT(*) FROM aggregates"
|
67
|
+
|
68
|
+
db.print "SELECT * FROM aggregates"
|