@@ -4,6 +4,9 @@ import { WorkspaceWithTariffPlan } from '../types';
44import HawkCatcher from '@hawk.so/nodejs' ;
55import { CriticalError , NonCriticalError } from '../../../lib/workerErrors' ;
66import { MS_IN_SEC } from '../../../lib/utils/consts' ;
7+ import TimeMs from '../../../lib/utils/time' ;
8+
9+ const SEC_IN_DAY = TimeMs . DAY / TimeMs . SECOND ;
710
811const WORKSPACE_PROJECTION = {
912 _id : 1 ,
@@ -138,6 +141,45 @@ export class DbHelper {
138141 /**
139142 * Returns total event counts for last billing period
140143 *
144+ * @param project - project to check
145+ * @param since - timestamp of the time from which we count the events
146+ */
147+ public async getEventsCountByProject (
148+ project : ProjectDBScheme ,
149+ since : number
150+ ) : Promise < number > {
151+ try {
152+ const query = {
153+ timestamp : {
154+ $gt : since ,
155+ } ,
156+ } ;
157+
158+ return await this . getRawEventsCountByProject ( project , query ) ;
159+ } catch ( e ) {
160+ HawkCatcher . send ( e ) ;
161+ throw new CriticalError ( e ) ;
162+ }
163+ }
164+
165+ /**
166+ * Calculates total events count for all provided projects since the specific date
167+ *
168+ * @param projects - projects to calculate for
169+ * @param since - timestamp of the time from which we count the events
170+ */
171+ public async getEventsCountByProjects ( projects : ProjectDBScheme [ ] , since : number ) : Promise < number > {
172+ const sum = ( array : number [ ] ) : number => array . reduce ( ( acc , val ) => acc + val , 0 ) ;
173+
174+ return Promise . all ( projects . map (
175+ project => this . getEventsCountByProject ( project , since )
176+ ) )
177+ . then ( sum ) ;
178+ }
179+
180+ /**
181+ * Returns total event counts for last billing period using dailyEvents counters.
182+ *
141183 * Full days are summed from dailyEvents per-day counters (grouper
142184 * increments `count` for originals and repetitions alike); only the
143185 * partial day containing `since` is counted from the raw collections,
@@ -146,39 +188,42 @@ export class DbHelper {
146188 * @param project - project to check
147189 * @param since - timestamp of the time from which we count the events
148190 */
149- public async getEventsCountByProject (
191+ public async getEventsCountByProjectUsingDailyEvents (
150192 project : ProjectDBScheme ,
151193 since : number
152194 ) : Promise < number > {
153195 try {
154196 const projectId = project . _id . toString ( ) ;
155- const repetitionsCollection = this . eventsDbConnection . collection ( 'repetitions:' + projectId ) ;
156- const eventsCollection = this . eventsDbConnection . collection ( 'events:' + projectId ) ;
157197 const dailyEventsCollection = this . eventsDbConnection . collection ( 'dailyEvents:' + projectId ) ;
158-
159- const sinceNextMidnight = this . getNextUtcMidnight ( since ) ;
198+ const firstFullDayTimestamp = this . getFirstFullDailyEventsTimestamp ( since ) ;
160199
161200 const boundaryDayQuery = {
162201 timestamp : {
163202 $gt : since ,
164- $lt : sinceNextMidnight ,
203+ $lt : firstFullDayTimestamp ,
165204 } ,
166205 } ;
167206
168- const [ repetitionsCount , originalEventCount , dailyCounters ] = await Promise . all ( [
169- repetitionsCollection . countDocuments ( boundaryDayQuery ) ,
170- eventsCollection . countDocuments ( boundaryDayQuery ) ,
207+ const [ boundaryDayCount , dailyCounters ] = await Promise . all ( [
208+ since < firstFullDayTimestamp
209+ ? this . getRawEventsCountByProject ( project , boundaryDayQuery )
210+ : 0 ,
171211 dailyEventsCollection
172212 . aggregate < { count : number } > ( [
173- { $match : { groupingTimestamp : { $gte : sinceNextMidnight } } } ,
174- { $group : { _id : null , count : { $sum : '$count' } } } ,
213+ { $match : { groupingTimestamp : { $gte : firstFullDayTimestamp } } } ,
214+ {
215+ $group : {
216+ _id : null ,
217+ count : { $sum : '$count' } ,
218+ } ,
219+ } ,
175220 ] )
176221 . toArray ( ) ,
177222 ] ) ;
178223
179224 const fullDaysCount = dailyCounters . length > 0 ? dailyCounters [ 0 ] . count : 0 ;
180225
181- return repetitionsCount + originalEventCount + fullDaysCount ;
226+ return boundaryDayCount + fullDaysCount ;
182227 } catch ( e ) {
183228 HawkCatcher . send ( e ) ;
184229 throw new CriticalError ( e ) ;
@@ -187,15 +232,16 @@ export class DbHelper {
187232
188233 /**
189234 * Calculates total events count for all provided projects since the specific date
235+ * using dailyEvents counters for full days.
190236 *
191237 * @param projects - projects to calculate for
192238 * @param since - timestamp of the time from which we count the events
193239 */
194- public async getEventsCountByProjects ( projects : ProjectDBScheme [ ] , since : number ) : Promise < number > {
240+ public async getEventsCountByProjectsUsingDailyEvents ( projects : ProjectDBScheme [ ] , since : number ) : Promise < number > {
195241 const sum = ( array : number [ ] ) : number => array . reduce ( ( acc , val ) => acc + val , 0 ) ;
196242
197243 return Promise . all ( projects . map (
198- project => this . getEventsCountByProject ( project , since )
244+ project => this . getEventsCountByProjectUsingDailyEvents ( project , since )
199245 ) )
200246 . then ( sum ) ;
201247 }
@@ -233,6 +279,37 @@ export class DbHelper {
233279 return date . getTime ( ) / MS_IN_SEC ;
234280 }
235281
282+ /**
283+ * Returns first dailyEvents bucket that can be safely used without counting
284+ * events before the requested timestamp.
285+ *
286+ * @param timestamp - unix timestamp in seconds
287+ */
288+ private getFirstFullDailyEventsTimestamp ( timestamp : number ) : number {
289+ const midnight = timestamp - ( timestamp % SEC_IN_DAY ) ;
290+
291+ return timestamp === midnight ? timestamp : this . getNextUtcMidnight ( timestamp ) ;
292+ }
293+
294+ /**
295+ * Counts raw original events and repetitions for the passed query.
296+ *
297+ * @param project - project to check
298+ * @param query - MongoDB timestamp query
299+ */
300+ private async getRawEventsCountByProject ( project : ProjectDBScheme , query : Record < string , unknown > ) : Promise < number > {
301+ const projectId = project . _id . toString ( ) ;
302+ const repetitionsCollection = this . eventsDbConnection . collection ( 'repetitions:' + projectId ) ;
303+ const eventsCollection = this . eventsDbConnection . collection ( 'events:' + projectId ) ;
304+
305+ const [ repetitionsCount , originalEventCount ] = await Promise . all ( [
306+ repetitionsCollection . countDocuments ( query ) ,
307+ eventsCollection . countDocuments ( query ) ,
308+ ] ) ;
309+
310+ return repetitionsCount + originalEventCount ;
311+ }
312+
236313 /**
237314 * Returns plan from cache, refetches once on miss
238315 *
0 commit comments