Add basic telemetry

This commit is contained in:
Eugene Burmakin 2024-12-05 17:12:35 +01:00
parent 2b77a58dbf
commit f10f78999d
8 changed files with 160 additions and 1 deletions

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
class TelemetrySendingJob < ApplicationJob
queue_as :default
def perform
data = Telemetry::Gather.new.call
Telemetry::Send.new(data).call
end
end

View file

@ -0,0 +1,32 @@
# frozen_string_literal: true
class Telemetry::Gather
def initialize(measurement: 'dawarich_usage_metrics')
@measurement = measurement
end
def call
{
measurement:,
timestamp: Time.current.to_i,
tags: { instance_id: },
fields: { dau:, app_version: }
}
end
private
attr_reader :measurement
def instance_id
@instance_id ||= Digest::SHA2.hexdigest(User.first.api_key)
end
def app_version
"\"#{APP_VERSION}\""
end
def dau
User.where(last_sign_in_at: Time.zone.today.beginning_of_day..Time.zone.today.end_of_day).count
end
end

View file

@ -0,0 +1,44 @@
# frozen_string_literal: true
class Telemetry::Send
BUCKET = 'dawarich_metrics'
ORG = 'monitoring'
def initialize(payload)
@payload = payload
end
def call
line_protocol = build_line_protocol
response = send_request(line_protocol)
handle_response(response)
end
private
attr_reader :payload
def build_line_protocol
tag_string = payload[:tags].map { |k, v| "#{k}=#{v}" }.join(',')
field_string = payload[:fields].map { |k, v| "#{k}=#{v}" }.join(',')
"#{payload[:measurement]},#{tag_string} #{field_string} #{payload[:timestamp].to_i}"
end
def send_request(line_protocol)
HTTParty.post(
"#{TELEMETRY_URL}?org=#{ORG}&bucket=#{BUCKET}&precision=s",
body: line_protocol,
headers: {
'Authorization' => "Token #{Base64.decode64(TELEMETRY_STRING)}",
'Content-Type' => 'text/plain'
}
)
end
def handle_response(response)
Rails.logger.error("InfluxDB write failed: #{response.body}") unless response.success?
response
end
end

View file

@ -6,3 +6,5 @@ PHOTON_API_HOST = ENV.fetch('PHOTON_API_HOST', nil)
PHOTON_API_USE_HTTPS = ENV.fetch('PHOTON_API_USE_HTTPS', 'true') == 'true'
DISTANCE_UNIT = ENV.fetch('DISTANCE_UNIT', 'km').to_sym
APP_VERSION = File.read('.app_version').strip
TELEMETRY_STRING = Base64.encode64('IjVFvb8j3P9-ArqhSGav9j8YcJaQiuNIzkfOPKQDk2lvKXqb8t1NSRv50oBkaKtlrB_ZRzO9NdurpMtncV_HYQ==')
TELEMETRY_URL = 'https://influxdb2.frey.today/api/v2/write'

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddDeviseTrackableColumnsToUsers < ActiveRecord::Migration[7.2]
def change
change_table :users, bulk: true do |t|
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
end
end
end

9
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2024_11_28_095325) do
ActiveRecord::Schema[7.2].define(version: 2024_12_05_160055) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -155,6 +155,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_11_28_095325) do
t.bigint "user_id"
t.jsonb "geodata", default: {}, null: false
t.bigint "visit_id"
t.datetime "reverse_geocoded_at"
t.index ["altitude"], name: "index_points_on_altitude"
t.index ["battery"], name: "index_points_on_battery"
t.index ["battery_status"], name: "index_points_on_battery_status"
@ -164,6 +165,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_11_28_095325) do
t.index ["geodata"], name: "index_points_on_geodata", using: :gin
t.index ["import_id"], name: "index_points_on_import_id"
t.index ["latitude", "longitude"], name: "index_points_on_latitude_and_longitude"
t.index ["reverse_geocoded_at"], name: "index_points_on_reverse_geocoded_at"
t.index ["timestamp"], name: "index_points_on_timestamp"
t.index ["trigger"], name: "index_points_on_trigger"
t.index ["user_id"], name: "index_points_on_user_id"
@ -208,6 +210,11 @@ ActiveRecord::Schema[7.2].define(version: 2024_11_28_095325) do
t.string "theme", default: "dark", null: false
t.jsonb "settings", default: {"fog_of_war_meters"=>"100", "meters_between_routes"=>"1000", "minutes_between_routes"=>"60"}
t.boolean "admin", default: false
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end

View file

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe TelemetrySendingJob, type: :job do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Telemetry::Gather do
let!(:user) { create(:user, last_sign_in_at: Time.zone.today) }
describe '#call' do
subject(:gather) { described_class.new.call }
it 'returns a hash with measurement, timestamp, tags, and fields' do
expect(gather).to include(:measurement, :timestamp, :tags, :fields)
end
it 'includes the correct measurement' do
expect(gather[:measurement]).to eq('dawarich_usage_metrics')
end
it 'includes the current timestamp' do
expect(gather[:timestamp]).to be_within(1).of(Time.current.to_i)
end
it 'includes the correct instance_id in tags' do
expect(gather[:tags][:instance_id]).to eq(Digest::SHA2.hexdigest(user.api_key))
end
it 'includes the correct app_version in fields' do
expect(gather[:fields][:app_version]).to eq("\"#{APP_VERSION}\"")
end
it 'includes the correct dau in fields' do
expect(gather[:fields][:dau]).to eq(1)
end
context 'with a custom measurement' do
let(:measurement) { 'custom_measurement' }
subject(:gather) { described_class.new(measurement:).call }
it 'includes the correct measurement' do
expect(gather[:measurement]).to eq('custom_measurement')
end
end
end
end