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.
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
- def initialize(path)
23
- @db = SQLite3::Database.new(path)
6
+ module MetricSystem
7
+ extend self
24
8
 
25
- [ :counters, :gauges ].each do |name|
26
- exec <<-SQL
27
- CREATE TABLE IF NOT EXISTS #{name}(
28
- id INTEGER PRIMARY KEY,
9
+ attr :target, :database
29
10
 
30
- name NOT NULL, -- the event name
31
- value NOT NULL, -- the value
32
- starts_at TIMESTAMP NOT NULL DEFAULT (strftime('%s','now')) -- the timestamp
33
- );
11
+ def target=(target)
12
+ @target = @database = nil
34
13
 
35
- CREATE INDEX IF NOT EXISTS #{name}_idx1 ON #{name}(name, starts_at);
36
-
37
- CREATE TABLE IF NOT EXISTS aggregated_#{name}(
38
- id INTEGER PRIMARY KEY,
39
-
40
- name NOT NULL, -- the event name
41
- starts_at TIMESTAMP NOT NULL, -- the start-at timestamp
42
- duration NOT NULL, -- the duration (estimate, in secs.)
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
- private
74
-
75
- def add_event(table, name, value, starts_at)
76
- # get names of all related events. An event "a.b.c" is actually
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"
@@ -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
- @db ||= MetricSystem.new ":memory:"
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 :second, :minute, :hour
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 :second, :minute, :hour
91
- db.aggregate :second, :minute, :hour
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
- @db ||= MetricSystem.new ":memory:"
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
- @db ||= MetricSystem.new ":memory:"
15
+ MetricSystem
8
16
  end
9
17
 
10
- def xtest_two_events
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"