Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/invidious.cr
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ end

if CONFIG.statistics_enabled
Invidious::Jobs.register Invidious::Jobs::StatisticsRefreshJob.new(PG_DB, SOFTWARE)
add_handler Metrics::RouteMetricsCollector.new
end

if (CONFIG.use_pubsub_feeds.is_a?(Bool) && CONFIG.use_pubsub_feeds.as(Bool)) || (CONFIG.use_pubsub_feeds.is_a?(Int32) && CONFIG.use_pubsub_feeds.as(Int32) > 0)
Expand Down
8 changes: 8 additions & 0 deletions src/invidious/helpers/helpers.cr
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,11 @@ def get_playback_statistic

return tracker.as(Hash(String, Int64 | Float64))
end

def to_prometheus_metrics(metrics_struct : Hash(String, Number)) : String
return String.build do |str|
metrics_struct.each.each do |key, value|
str << key << " " << value << "\n"
end
end
end
8 changes: 8 additions & 0 deletions src/invidious/jobs/statistics_refresh_job.cr
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ class Invidious::Jobs::StatisticsRefreshJob < Invidious::Jobs::BaseJob
"playback" => {} of String => Int64 | Float64,
}

STATISTICS_PROMETHEUS = {
"invidious_updated_at" => Time.utc.to_unix,
"invidious_last_channel_refreshed_at" => 0_i64,
}

private getter db : DB::Database

def initialize(@db, @software_config : Hash(String, String))
Expand Down Expand Up @@ -59,6 +64,9 @@ class Invidious::Jobs::StatisticsRefreshJob < Invidious::Jobs::BaseJob
users["activeHalfyear"] = Invidious::Database::Statistics.count_users_active_6m
users["activeMonth"] = Invidious::Database::Statistics.count_users_active_1m

STATISTICS_PROMETHEUS["invidious_updated_at"] = Time.utc.to_unix
STATISTICS_PROMETHEUS["invidious_last_channel_refreshed_at"] = Invidious::Database::Statistics.channel_last_update.try &.to_unix || 0_i64

STATISTICS["metadata"] = {
"updatedAt" => Time.utc.to_unix,
"lastChannelRefreshedAt" => Invidious::Database::Statistics.channel_last_update.try &.to_unix || 0_i64,
Expand Down
46 changes: 46 additions & 0 deletions src/invidious/metrics.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Module containing an optional Kemal handler, which can be used
# to collect metrics about the usage of various Invidious routes.
module Metrics
record MetricLabels, request_method : String, request_route : String, response_code : Int32

class RouteMetricsCollector < Kemal::Handler
# Counts how many times a given route was used
@@num_of_request_counters = Hash(MetricLabels, Int64).new
# Counts how much time was used to handle requests to each route
@@request_duration_seconds_sums = Hash(MetricLabels, Float32).new

def self.num_of_request_counters
return @@num_of_request_counters
end

def self.request_duration_seconds_sums
return @@request_duration_seconds_sums
end

def call(context : HTTP::Server::Context)
request_handling_started = Time.utc
begin
call_next(context)
ensure
request_handling_finished = Time.utc
request_path = context.route.path
request_method = context.request.method
seconds_spent_handling = (request_handling_finished - request_handling_started).to_f
response_status = context.response.status_code.to_i

LOGGER.trace("Collecting metrics: handling #{request_method} #{request_path} took #{seconds_spent_handling}s and finished with status #{response_status}")
metric_key = MetricLabels.new request_path, request_method, response_status

unless @@num_of_request_counters.has_key?(metric_key)
@@num_of_request_counters[metric_key] = 0
end
@@num_of_request_counters[metric_key] += 1

unless @@request_duration_seconds_sums.has_key?(metric_key)
@@request_duration_seconds_sums[metric_key] = 0.0
end
@@request_duration_seconds_sums[metric_key] += seconds_spent_handling
end
end
end
end
35 changes: 35 additions & 0 deletions src/invidious/routes/api/v1/misc.cr
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "../../../metrics.cr"

module Invidious::Routes::API::V1::Misc
# Stats API endpoint for Invidious
def self.stats(env)
Expand Down Expand Up @@ -26,6 +28,39 @@ module Invidious::Routes::API::V1::Misc
end
end

def self.metrics(env)
if !CONFIG.statistics_enabled
env.response.status_code = 204
return
end

env.response.content_type = "text/plain"

return String.build do |str|
Metrics::RouteMetricsCollector.num_of_request_counters.each do |metric_labels, value|
str << "http_requests_total{"
str << "method=\"" << metric_labels.request_method << "\" "
str << "route=\"" << metric_labels.request_route << "\" "
str << "response_code=\"" << metric_labels.response_code << "\""
str << "} "
str << value << "\n"
end

Metrics::RouteMetricsCollector.request_duration_seconds_sums.each do |metric_labels, value|
str << "http_request_duration_seconds_sum{"
str << "method=\"" << metric_labels.request_method << "\" "
str << "route=\"" << metric_labels.request_route << "\" "
str << "response_code=\"" << metric_labels.response_code << "\""
str << "} "
str << value << "\n"
end

Invidious::Jobs::StatisticsRefreshJob::STATISTICS_PROMETHEUS.each.each do |key, value|
str << key << " " << value << "\n"
end
end
end

# APIv1 currently uses the same logic for both
# user playlists and Invidious playlists. This means that we can't
# reasonably split them yet. This should be addressed in APIv2
Expand Down
1 change: 1 addition & 0 deletions src/invidious/routing.cr
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ module Invidious::Routing

# Misc
get "/api/v1/stats", {{namespace}}::Misc, :stats
get "/api/v1/metrics", {{namespace}}::Misc, :metrics
get "/api/v1/playlists/:plid", {{namespace}}::Misc, :get_playlist
get "/api/v1/auth/playlists/:plid", {{namespace}}::Misc, :get_playlist
get "/api/v1/mixes/:rdid", {{namespace}}::Misc, :mixes
Expand Down
Loading