11use super :: schema:: { self , Columns } ;
22use crate :: Result ;
33use rusqlite:: { named_params, Connection } ;
4+ use std:: time:: Instant ;
5+ use time:: { Duration , OffsetDateTime } ;
6+ use tracing:: info;
47
58pub 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) ]
171274mod test {
172275 use crate :: db:: log:: request:: blocking_queries:: InsertArgs ;
0 commit comments