Module: Flightdeck::Metrics::TimeBucket
- Defined in:
- app/models/flightdeck/metrics/time_bucket.rb
Overview
The only adapter-specific SQL in Flightdeck.
Charts bucket timestamps into fixed-width windows. Doing that portably means converting a timestamp to a UTC epoch integer and dividing — never date_trunc, which does not exist everywhere and drags the server's timezone into the answer. Everything downstream is an integer number of seconds since the epoch, in UTC; the display timezone is applied at render time and nowhere else.
Defined Under Namespace
Classes: UnsupportedAdapter
Constant Summary collapse
- IDENTIFIER =
Identifiers are built by Flightdeck, never by a request, but they are still validated before being interpolated so that can never change by accident.
/\A[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)?\z/
Class Method Summary collapse
- .adapter_name ⇒ Object
-
.bucket(column, seconds, family: self.family) ⇒ Object
The epoch second at which
column's bucket starts. -
.elapsed(from_column, to_column, family: self.family) ⇒ Object
Seconds between two timestamp columns, for averaging in SQL.
- .elapsed_sql(from_column, to_column) ⇒ Object
-
.epoch(column, family: self.family) ⇒ Object
Seconds since the UTC epoch, as a number the database can do arithmetic on.
-
.family(name = adapter_name) ⇒ Object
Normalises the adapter into the three SQL dialects we support.
- .sql(column, seconds) ⇒ Object
Class Method Details
.adapter_name ⇒ Object
22 23 24 |
# File 'app/models/flightdeck/metrics/time_bucket.rb', line 22 def adapter_name SolidQueue::Record.connection.adapter_name.to_s end |
.bucket(column, seconds, family: self.family) ⇒ Object
The epoch second at which column's bucket starts.
SQLite gets integer division rather than FLOOR() on purpose: floor() is only available when SQLite is compiled with math functions, and integer division is exact for the post-1970 timestamps we deal with.
57 58 59 60 61 62 63 64 65 66 67 |
# File 'app/models/flightdeck/metrics/time_bucket.rb', line 57 def bucket(column, seconds, family: self.family) seconds = interval!(seconds) col = identifier!(column) case family when :postgresql then "FLOOR(EXTRACT(EPOCH FROM #{col}) / #{seconds}) * #{seconds}" when :mysql then "FLOOR(UNIX_TIMESTAMP(#{col}) / #{seconds}) * #{seconds}" when :sqlite then "(CAST(strftime('%s', #{col}) AS INTEGER) / #{seconds}) * #{seconds}" else raise UnsupportedAdapter, "unknown family #{family.inspect}" end end |
.elapsed(from_column, to_column, family: self.family) ⇒ Object
Seconds between two timestamp columns, for averaging in SQL.
72 73 74 |
# File 'app/models/flightdeck/metrics/time_bucket.rb', line 72 def elapsed(from_column, to_column, family: self.family) "(#{epoch(to_column, family: family)} - #{epoch(from_column, family: family)})" end |
.elapsed_sql(from_column, to_column) ⇒ Object
76 |
# File 'app/models/flightdeck/metrics/time_bucket.rb', line 76 def elapsed_sql(from_column, to_column) = Arel.sql(elapsed(from_column, to_column)) |
.epoch(column, family: self.family) ⇒ Object
Seconds since the UTC epoch, as a number the database can do arithmetic on.
41 42 43 44 45 46 47 48 49 50 |
# File 'app/models/flightdeck/metrics/time_bucket.rb', line 41 def epoch(column, family: self.family) col = identifier!(column) case family when :postgresql then "EXTRACT(EPOCH FROM #{col})" when :mysql then "UNIX_TIMESTAMP(#{col})" when :sqlite then "CAST(strftime('%s', #{col}) AS INTEGER)" else raise UnsupportedAdapter, "unknown family #{family.inspect}" end end |
.family(name = adapter_name) ⇒ Object
Normalises the adapter into the three SQL dialects we support.
27 28 29 30 31 32 33 34 35 36 37 |
# File 'app/models/flightdeck/metrics/time_bucket.rb', line 27 def family(name = adapter_name) case name.to_s.downcase when /postgres/ then :postgresql when /mysql|trilogy/ then :mysql when /sqlite/ then :sqlite else raise UnsupportedAdapter, "Flightdeck cannot bucket time on the #{name} adapter. " \ "Supported adapters: PostgreSQL, MySQL/Trilogy, SQLite." end end |
.sql(column, seconds) ⇒ Object
69 |
# File 'app/models/flightdeck/metrics/time_bucket.rb', line 69 def sql(column, seconds) = Arel.sql(bucket(column, seconds)) |