Skip to content

Commit b3e9f76

Browse files
committed
attendee-gate: metrics.rb
1 parent 0295a71 commit b3e9f76

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

attendee-gate/index.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ def self.generate_data(event:, context:)
1515
Generator.handle(event:, context:, environ:)
1616
end
1717

18+
def self.generate_metrics(event:, context:)
19+
require_relative './metrics'
20+
Metrics.handle(event:, context:, environ:)
21+
end
22+
1823
def self.http(event:, context:)
1924
@app ||= begin
2025
require_relative './app'

attendee-gate/metrics.rb

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
require 'httpx'
2+
require 'uri'
3+
require 'aws-sdk-s3'
4+
5+
module AttendeeGate
6+
class Metrics
7+
TITO_BASE_URL = 'https://api.tito.io/v3'
8+
TITO_CHECKIN_URL = 'https://checkin.tito.io'
9+
10+
CheckinList = Data.define(:id, :slug, :event, :title) do
11+
alias inspect_ inspect
12+
def inspect
13+
inspect_.gsub(/"chk_[^"]+/, '"chk_...')
14+
end
15+
end
16+
Ticket = Struct.new(:id, :release_title, :checkin, keyword_init: true)
17+
DataDimension = Data.define(:event_slug, :list_id, :list_title, :release_title) do
18+
def to_prom
19+
%|event_slug="#{event_slug}",list_id="#{list_id}",list_title="#{list_title}",release_title="#{release_title}"|
20+
end
21+
end
22+
DataPoint = Struct.new(:dimension, :total_value, :checkin_value, keyword_init: true) do
23+
def to_prom
24+
[
25+
%|tito_checkin_list_total{#{dimension.to_prom}} #{total_value}|,
26+
%|tito_checkin_list_completed{#{dimension.to_prom}} #{checkin_value}|,
27+
].join("\n")
28+
end
29+
end
30+
31+
def self.handle(event:, context:, environ:)
32+
new(
33+
bucket: event.fetch('bucket'),
34+
key: event.fetch('key'),
35+
event_slug: event.fetch('event_slug'),
36+
environ:,
37+
).perform
38+
end
39+
40+
def initialize(bucket:, key:, event_slug:, environ:)
41+
@bucket = bucket
42+
@key = key
43+
@event_slug = event_slug
44+
@envrion = environ
45+
p(bucket: @bucket, key: @key, event_slug: @event_slug)
46+
47+
@http = HTTPX.with(
48+
headers: {
49+
'user-agent' => "attendee-gate (+https://github.com/ruby-no-kai/rubykaigi-net-apps/tree/main/attendee-gate)",
50+
'authorization' => "Token token=#{environ.tito_api_token}",
51+
},
52+
)
53+
end
54+
55+
def perform
56+
data = {}
57+
tito_checkin_lists.each do |list|
58+
tickets = {}
59+
fetch_tito_tickets(list) do |page|
60+
page.each do |ticket|
61+
tickets[ticket.fetch('id')] = Ticket.new(
62+
id: ticket.fetch('id'),
63+
release_title: ticket.fetch('release_title'),
64+
checkin: false,
65+
)
66+
end
67+
end
68+
fetch_tito_checkins(list) do |page|
69+
page.each do |checkin|
70+
t = tickets[checkin.fetch('ticket_id')]
71+
if t
72+
t.checkin = true
73+
else
74+
warn "checkin for unknown ticket: #{checkin.inspect}"
75+
end
76+
end
77+
end
78+
tickets.each_value do |t|
79+
dimension = DataDimension.new(
80+
event_slug: @event_slug,
81+
list_id: list.id,
82+
list_title: list.title,
83+
release_title: t.release_title,
84+
)
85+
data[dimension] ||= DataPoint.new(dimension: dimension, total_value: 0, checkin_value: 0)
86+
data[dimension].total_value += 1
87+
data[dimension].checkin_value += 1 if t.checkin
88+
end
89+
end
90+
91+
prom = data.each_value.map do |dp|
92+
dp.to_prom
93+
end.concat(["tito_checkin_updated_at #{Time.now.to_i}", '']).join("\n")
94+
95+
s3 = Aws::S3::Client.new
96+
s3.put_object(bucket: @bucket, key: @key, body: prom, content_type: 'text/plain; version=0.0.4')
97+
end
98+
99+
private def tito_checkin_lists
100+
@http.get("#{TITO_BASE_URL}/#{@event_slug}/checkin_lists").json.fetch('checkin_lists').filter_map do |checkin_list|
101+
next if checkin_list.fetch('state') == 'expired'
102+
CheckinList.new(
103+
id: checkin_list.fetch('id'),
104+
slug: checkin_list.fetch('slug'),
105+
event: @event_slug,
106+
title: checkin_list.fetch('title'),
107+
)
108+
end
109+
end
110+
111+
private def fetch_tito_checkins(list)
112+
pagenum = 1
113+
total_items = 0
114+
loop do
115+
p(kind: :checkins, list:,pagenum:,total_items:)
116+
resp = @http.get("#{TITO_CHECKIN_URL}/checkin_lists/#{list.slug}/checkins", params: { 'page' => pagenum }, headers: { 'Accept' => 'application/json' })
117+
resp.raise_for_status
118+
page = resp.json
119+
total_items += page.size
120+
break if page.empty?
121+
yield page
122+
pagenum += 1
123+
end
124+
end
125+
126+
private def fetch_tito_tickets(list)
127+
pagenum = 1
128+
total_items = 0
129+
loop do
130+
p(kind: :tickets, list:,pagenum:,total_items:)
131+
resp = @http.get("#{TITO_CHECKIN_URL}/checkin_lists/#{list.slug}/tickets", params: { 'page' => pagenum }, headers: { 'Accept' => 'application/json' })
132+
resp.raise_for_status
133+
page = resp.json
134+
total_items += page.size
135+
break if page.empty?
136+
yield page
137+
pagenum += 1
138+
end
139+
end
140+
end
141+
end
142+
143+
if $0 == __FILE__
144+
require_relative './index'
145+
AttendeeGate::Metrics.new(
146+
bucket: ENV.fetch('S3_BUCKET','rk-attendee-gate'),
147+
key: ENV.fetch('S3_KEY','dev/prometheus/2024'),
148+
event_slug: ENV.fetch('TITO_EVENT_SLUG', 'rubykaigi/2024'),
149+
environ: AttendeeGate::Handlers.environ,
150+
).perform
151+
end

0 commit comments

Comments
 (0)