Skip to content

Commit 1819178

Browse files
committed
include top user agents in daily infra reports
1 parent 5cd00a0 commit 1819178

4 files changed

Lines changed: 131 additions & 3 deletions

File tree

src/db/log/migrations/4.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CREATE INDEX IF NOT EXISTS request_user_agent_date ON request(user_agent, date);

src/db/log/request/blocking_queries.rs

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use super::schema::{self, Columns};
22
use crate::Result;
33
use rusqlite::{named_params, Connection};
4+
use std::time::Instant;
5+
use time::{Duration, OffsetDateTime};
6+
use tracing::info;
47

58
pub struct InsertArgs {
69
pub ip: String,
@@ -115,7 +118,7 @@ pub fn select_daily_infra_report(conn: &Connection) -> Result<DailyInfraReport>
115118
SELECT COUNT(*), COUNT(DISTINCT {ip})
116119
FROM {table}
117120
WHERE {date} >= strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-24 hours')
118-
AND ({user_agent} LIKE 'axios%' OR {user_agent} = 'btcmap.org')
121+
AND {user_agent} = 'btcmap.org'
119122
"#,
120123
table = schema::TABLE_NAME,
121124
date = Columns::Date.as_str(),
@@ -130,7 +133,7 @@ pub fn select_daily_infra_report(conn: &Connection) -> Result<DailyInfraReport>
130133
SELECT COUNT(*), COUNT(DISTINCT {ip})
131134
FROM {table}
132135
WHERE {date} >= strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-24 hours')
133-
AND ({user_agent} LIKE 'okhttp%' OR {user_agent} LIKE 'BTC Map Android%')
136+
AND {user_agent} LIKE 'BTC Map Android%'
134137
"#,
135138
table = schema::TABLE_NAME,
136139
date = Columns::Date.as_str(),
@@ -167,6 +170,106 @@ pub fn select_daily_infra_report(conn: &Connection) -> Result<DailyInfraReport>
167170
})
168171
}
169172

173+
pub struct TopUserAgent {
174+
pub user_agent: String,
175+
pub count: i64,
176+
pub unique_ips: i64,
177+
}
178+
179+
pub fn select_top_user_agents(conn: &Connection) -> Result<Vec<TopUserAgent>> {
180+
let overall_start = Instant::now();
181+
182+
let since_date = (OffsetDateTime::now_utc() - Duration::hours(24))
183+
.format(&time::format_description::well_known::Rfc3339)
184+
.unwrap()
185+
.to_string();
186+
187+
let sql_top = format!(
188+
r#"
189+
SELECT {user_agent}, COUNT(*) as count
190+
FROM {table}
191+
WHERE {date} >= ?1
192+
AND {user_agent} IS NOT NULL
193+
GROUP BY {user_agent}
194+
ORDER BY count DESC
195+
LIMIT 10
196+
"#,
197+
table = schema::TABLE_NAME,
198+
date = Columns::Date.as_str(),
199+
user_agent = Columns::UserAgent.as_str(),
200+
);
201+
202+
let top_agents: Vec<(String, i64)> = {
203+
let start = Instant::now();
204+
let mut stmt = conn.prepare(&sql_top)?;
205+
let rows = stmt.query_map([&since_date], |row| Ok((row.get(0)?, row.get(1)?)))?;
206+
let result = rows.collect::<Result<Vec<_>, _>>()?;
207+
info!(
208+
elapsed_ms = start.elapsed().as_millis() as u64,
209+
count = result.len(),
210+
"top_user_agents: first query (get top 10 user agents)"
211+
);
212+
result
213+
};
214+
215+
if top_agents.is_empty() {
216+
return Ok(Vec::new());
217+
}
218+
219+
let placeholders: Vec<String> = top_agents.iter().map(|_| "?".to_string()).collect();
220+
let sql_unique = format!(
221+
r#"
222+
SELECT {user_agent}, COUNT(DISTINCT {ip}) as unique_ips
223+
FROM {table}
224+
WHERE {date} >= ?1
225+
AND {user_agent} IN ({})
226+
GROUP BY {user_agent}
227+
"#,
228+
placeholders.join(", "),
229+
table = schema::TABLE_NAME,
230+
date = Columns::Date.as_str(),
231+
user_agent = Columns::UserAgent.as_str(),
232+
ip = Columns::Ip.as_str(),
233+
);
234+
235+
let unique_ips: std::collections::HashMap<String, i64> = {
236+
let start = Instant::now();
237+
let mut stmt = conn.prepare(&sql_unique)?;
238+
let mut params: Vec<&dyn rusqlite::ToSql> = vec![&since_date as &dyn rusqlite::ToSql];
239+
params.extend(top_agents.iter().map(|(ua, _)| ua as &dyn rusqlite::ToSql));
240+
let rows = stmt.query_map(params.as_slice(), |row| {
241+
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?))
242+
})?;
243+
let result = rows
244+
.collect::<Result<Vec<(String, i64)>, _>>()?
245+
.into_iter()
246+
.collect::<std::collections::HashMap<_, _>>();
247+
info!(
248+
elapsed_ms = start.elapsed().as_millis() as u64,
249+
count = result.len(),
250+
"top_user_agents: second query (get unique IPs for {} agents)",
251+
top_agents.len()
252+
);
253+
result
254+
};
255+
256+
let result = top_agents
257+
.into_iter()
258+
.map(|(user_agent, count)| TopUserAgent {
259+
user_agent: user_agent.clone(),
260+
count,
261+
unique_ips: *unique_ips.get(&user_agent).unwrap_or(&0),
262+
})
263+
.collect();
264+
265+
info!(
266+
elapsed_ms = overall_start.elapsed().as_millis() as u64,
267+
"top_user_agents: total"
268+
);
269+
270+
Ok(result)
271+
}
272+
170273
#[cfg(test)]
171274
mod test {
172275
use crate::db::log::request::blocking_queries::InsertArgs;

src/db/log/request/queries.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::super::LogPool;
22
use super::blocking_queries;
3-
use super::blocking_queries::{DailyInfraReport, InsertArgs};
3+
use super::blocking_queries::{DailyInfraReport, InsertArgs, TopUserAgent};
44
use crate::db::log::request::schema::Request;
55
use crate::Result;
66

@@ -25,3 +25,10 @@ pub async fn select_daily_infra_report(pool: &LogPool) -> Result<DailyInfraRepor
2525
.interact(move |conn| blocking_queries::select_daily_infra_report(conn))
2626
.await?
2727
}
28+
29+
pub async fn select_top_user_agents(pool: &LogPool) -> Result<Vec<TopUserAgent>> {
30+
pool.get()
31+
.await?
32+
.interact(|conn| blocking_queries::select_top_user_agents(conn))
33+
.await?
34+
}

src/rpc/log/get_daily_infra_report.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub struct Res {
1010
pub web: PlatformStats,
1111
pub android: PlatformStats,
1212
pub ios: PlatformStats,
13+
pub top_user_agents: Vec<TopUserAgent>,
1314
}
1415

1516
#[derive(Serialize)]
@@ -18,8 +19,16 @@ pub struct PlatformStats {
1819
pub unique_ips: i64,
1920
}
2021

22+
#[derive(Serialize)]
23+
pub struct TopUserAgent {
24+
pub user_agent: String,
25+
pub count: i64,
26+
pub unique_ips: i64,
27+
}
28+
2129
pub async fn run(pool: &LogPool) -> Result<Res> {
2230
let report = queries::select_daily_infra_report(pool).await?;
31+
let top_user_agents = queries::select_top_user_agents(pool).await?;
2332
Ok(Res {
2433
total_requests: report.total_requests,
2534
unique_ips: report.unique_ips,
@@ -35,5 +44,13 @@ pub async fn run(pool: &LogPool) -> Result<Res> {
3544
requests: report.ios_requests,
3645
unique_ips: report.ios_unique_ips,
3746
},
47+
top_user_agents: top_user_agents
48+
.into_iter()
49+
.map(|ua| TopUserAgent {
50+
user_agent: ua.user_agent,
51+
count: ua.count,
52+
unique_ips: ua.unique_ips,
53+
})
54+
.collect(),
3855
})
3956
}

0 commit comments

Comments
 (0)