def run_update_analytics_counts(self, options: Dict[str, Any]) -> None: # installation_epoch relies on there being at least one realm; we # shouldn't run the analytics code if that condition isn't satisfied if not Realm.objects.exists(): logger.info("No realms, stopping update_analytics_counts") return fill_to_time = parse_datetime(options['time']) if options['utc']: fill_to_time = fill_to_time.replace(tzinfo=timezone_utc) if fill_to_time.tzinfo is None: raise ValueError("--time must be timezone aware. Maybe you meant to use the --utc option?") fill_to_time = floor_to_hour(fill_to_time.astimezone(timezone_utc)) if options['stat'] is not None: stats = [COUNT_STATS[options['stat']]] else: stats = list(COUNT_STATS.values()) logger.info("Starting updating analytics counts through %s" % (fill_to_time,)) if options['verbose']: start = time.time() last = start for stat in stats: process_count_stat(stat, fill_to_time) if options['verbose']: print("Updated %s in %.3fs" % (stat.property, time.time() - last)) last = time.time() if options['verbose']: print("Finished updating analytics counts through %s in %.3fs" % (fill_to_time, time.time() - start)) logger.info("Finished updating analytics counts through %s" % (fill_to_time,))
def run_update_analytics_counts(self, options): # type: (Dict[str, Any]) -> None fill_to_time = parse_datetime(options['time']) if options['utc']: fill_to_time = fill_to_time.replace(tzinfo=timezone_utc) if fill_to_time.tzinfo is None: raise ValueError("--time must be timezone aware. Maybe you meant to use the --utc option?") fill_to_time = floor_to_hour(fill_to_time.astimezone(timezone_utc)) if options['stat'] is not None: stats = [COUNT_STATS[options['stat']]] else: stats = list(COUNT_STATS.values()) logger.info("Starting updating analytics counts through %s" % (fill_to_time,)) if options['verbose']: start = time.time() last = start for stat in stats: process_count_stat(stat, fill_to_time) if options['verbose']: print("Updated %s in %.3fs" % (stat.property, time.time() - last)) last = time.time() if options['verbose']: print("Finished updating analytics counts through %s in %.3fs" % (fill_to_time, time.time() - start)) logger.info("Finished updating analytics counts through %s" % (fill_to_time,))
def process_count_stat(stat: CountStat, fill_to_time: datetime, realm: Optional[Realm]=None) -> None: # TODO: The realm argument is not yet supported, in that we don't # have a solution for how to update FillState if it is passed. It # exists solely as partial plumbing for when we do fully implement # doing single-realm analytics runs for use cases like data import. # # Also, note that for the realm argument to be properly supported, # the CountStat object passed in needs to have come from # E.g. get_count_stats(realm), i.e. have the realm_id already # entered into the SQL query defined by the CountState object. if stat.frequency == CountStat.HOUR: time_increment = timedelta(hours=1) elif stat.frequency == CountStat.DAY: time_increment = timedelta(days=1) else: raise AssertionError("Unknown frequency: %s" % (stat.frequency,)) verify_UTC(fill_to_time) if floor_to_hour(fill_to_time) != fill_to_time: raise ValueError("fill_to_time must be on an hour boundary: %s" % (fill_to_time,)) fill_state = FillState.objects.filter(property=stat.property).first() if fill_state is None: currently_filled = installation_epoch() fill_state = FillState.objects.create(property=stat.property, end_time=currently_filled, state=FillState.DONE) logger.info("INITIALIZED %s %s", stat.property, currently_filled) elif fill_state.state == FillState.STARTED: logger.info("UNDO START %s %s", stat.property, fill_state.end_time) do_delete_counts_at_hour(stat, fill_state.end_time) currently_filled = fill_state.end_time - time_increment do_update_fill_state(fill_state, currently_filled, FillState.DONE) logger.info("UNDO DONE %s", stat.property) elif fill_state.state == FillState.DONE: currently_filled = fill_state.end_time else: raise AssertionError("Unknown value for FillState.state: %s." % (fill_state.state,)) if isinstance(stat, DependentCountStat): for dependency in stat.dependencies: dependency_fill_time = last_successful_fill(dependency) if dependency_fill_time is None: logger.warning("DependentCountStat %s run before dependency %s.", stat.property, dependency) return fill_to_time = min(fill_to_time, dependency_fill_time) currently_filled = currently_filled + time_increment while currently_filled <= fill_to_time: logger.info("START %s %s", stat.property, currently_filled) start = time.time() do_update_fill_state(fill_state, currently_filled, FillState.STARTED) do_fill_count_stat_at_hour(stat, currently_filled, realm) do_update_fill_state(fill_state, currently_filled, FillState.DONE) end = time.time() currently_filled = currently_filled + time_increment logger.info("DONE %s (%dms)", stat.property, (end-start)*1000)
def process_count_stat(stat, fill_to_time): # type: (CountStat, datetime) -> None if stat.frequency == CountStat.HOUR: time_increment = timedelta(hours=1) elif stat.frequency == CountStat.DAY: time_increment = timedelta(days=1) else: raise AssertionError("Unknown frequency: %s" % (stat.frequency, )) if floor_to_hour(fill_to_time) != fill_to_time: raise ValueError("fill_to_time must be on an hour boundary: %s" % (fill_to_time, )) if fill_to_time.tzinfo is None: raise ValueError("fill_to_time must be timezone aware: %s" % (fill_to_time, )) fill_state = FillState.objects.filter(property=stat.property).first() if fill_state is None: currently_filled = installation_epoch() fill_state = FillState.objects.create(property=stat.property, end_time=currently_filled, state=FillState.DONE) logger.info("INITIALIZED %s %s" % (stat.property, currently_filled)) elif fill_state.state == FillState.STARTED: logger.info("UNDO START %s %s" % (stat.property, fill_state.end_time)) do_delete_counts_at_hour(stat, fill_state.end_time) currently_filled = fill_state.end_time - time_increment do_update_fill_state(fill_state, currently_filled, FillState.DONE) logger.info("UNDO DONE %s" % (stat.property, )) elif fill_state.state == FillState.DONE: currently_filled = fill_state.end_time else: raise AssertionError("Unknown value for FillState.state: %s." % (fill_state.state, )) if isinstance(stat, DependentCountStat): for dependency in stat.dependencies: dependency_fill_time = last_successful_fill(dependency) if dependency_fill_time is None: logger.warning( "DependentCountStat %s run before dependency %s." % (stat.property, dependency)) return fill_to_time = min(fill_to_time, dependency_fill_time) currently_filled = currently_filled + time_increment while currently_filled <= fill_to_time: logger.info("START %s %s" % (stat.property, currently_filled)) start = time.time() do_update_fill_state(fill_state, currently_filled, FillState.STARTED) do_fill_count_stat_at_hour(stat, currently_filled) do_update_fill_state(fill_state, currently_filled, FillState.DONE) end = time.time() currently_filled = currently_filled + time_increment logger.info("DONE %s (%dms)" % (stat.property, (end - start) * 1000))
def process_count_stat(stat, fill_to_time): # type: (CountStat, datetime) -> None if stat.frequency == CountStat.HOUR: time_increment = timedelta(hours=1) elif stat.frequency == CountStat.DAY: time_increment = timedelta(days=1) else: raise AssertionError("Unknown frequency: %s" % (stat.frequency,)) if floor_to_hour(fill_to_time) != fill_to_time: raise ValueError("fill_to_time must be on an hour boundary: %s" % (fill_to_time,)) if fill_to_time.tzinfo is None: raise ValueError("fill_to_time must be timezone aware: %s" % (fill_to_time,)) fill_state = FillState.objects.filter(property=stat.property).first() if fill_state is None: currently_filled = installation_epoch() fill_state = FillState.objects.create(property=stat.property, end_time=currently_filled, state=FillState.DONE) logger.info("INITIALIZED %s %s" % (stat.property, currently_filled)) elif fill_state.state == FillState.STARTED: logger.info("UNDO START %s %s" % (stat.property, fill_state.end_time)) do_delete_counts_at_hour(stat, fill_state.end_time) currently_filled = fill_state.end_time - time_increment do_update_fill_state(fill_state, currently_filled, FillState.DONE) logger.info("UNDO DONE %s" % (stat.property,)) elif fill_state.state == FillState.DONE: currently_filled = fill_state.end_time else: raise AssertionError("Unknown value for FillState.state: %s." % (fill_state.state,)) if isinstance(stat, DependentCountStat): for dependency in stat.dependencies: dependency_fill_time = last_successful_fill(dependency) if dependency_fill_time is None: logger.warning("DependentCountStat %s run before dependency %s." % (stat.property, dependency)) return fill_to_time = min(fill_to_time, dependency_fill_time) currently_filled = currently_filled + time_increment while currently_filled <= fill_to_time: logger.info("START %s %s" % (stat.property, currently_filled)) start = time.time() do_update_fill_state(fill_state, currently_filled, FillState.STARTED) do_fill_count_stat_at_hour(stat, currently_filled) do_update_fill_state(fill_state, currently_filled, FillState.DONE) end = time.time() currently_filled = currently_filled + time_increment logger.info("DONE %s (%dms)" % (stat.property, (end-start)*1000))
def run_update_analytics_counts(self, options: Dict[str, Any]) -> None: # installation_epoch relies on there being at least one realm; we # shouldn't run the analytics code if that condition isn't satisfied if not Realm.objects.exists(): logger.info("No realms, stopping update_analytics_counts") return fill_to_time = parse_datetime(options["time"]) assert fill_to_time is not None if options["utc"]: fill_to_time = fill_to_time.replace(tzinfo=timezone.utc) if fill_to_time.tzinfo is None: raise ValueError( "--time must be timezone aware. Maybe you meant to use the --utc option?" ) fill_to_time = floor_to_hour(fill_to_time.astimezone(timezone.utc)) if options["stat"] is not None: stats = [COUNT_STATS[options["stat"]]] else: stats = list(COUNT_STATS.values()) logger.info("Starting updating analytics counts through %s", fill_to_time) if options["verbose"]: start = time.time() last = start for stat in stats: process_count_stat(stat, fill_to_time) if options["verbose"]: print(f"Updated {stat.property} in {time.time() - last:.3f}s") last = time.time() if options["verbose"]: print( f"Finished updating analytics counts through {fill_to_time} in {time.time() - start:.3f}s" ) logger.info("Finished updating analytics counts through %s", fill_to_time) if settings.PUSH_NOTIFICATION_BOUNCER_URL and settings.SUBMIT_USAGE_STATISTICS: send_analytics_to_remote_server()
def time_range(start, end, frequency, min_length): # type: (datetime, datetime, str, Optional[int]) -> List[datetime] if frequency == CountStat.HOUR: end = floor_to_hour(end) step = timedelta(hours=1) elif frequency == CountStat.DAY: end = floor_to_day(end) step = timedelta(days=1) else: raise ValueError("Unknown frequency: %s" % (frequency, )) times = [] if min_length is not None: start = min(start, end - (min_length - 1) * step) current = end while current >= start: times.append(current) current -= step return list(reversed(times))
def time_range(start, end, frequency, min_length): # type: (datetime, datetime, str, Optional[int]) -> List[datetime] if frequency == CountStat.HOUR: end = floor_to_hour(end) step = timedelta(hours=1) elif frequency == CountStat.DAY: end = floor_to_day(end) step = timedelta(days=1) else: raise ValueError("Unknown frequency: %s" % (frequency,)) times = [] if min_length is not None: start = min(start, end - (min_length-1)*step) current = end while current >= start: times.append(current) current -= step return list(reversed(times))
def time_range(start: datetime, end: datetime, frequency: str, min_length: Optional[int]) -> List[datetime]: verify_UTC(start) verify_UTC(end) if frequency == CountStat.HOUR: end = floor_to_hour(end) step = timedelta(hours=1) elif frequency == CountStat.DAY: end = floor_to_day(end) step = timedelta(days=1) else: raise AssertionError(f"Unknown frequency: {frequency}") times = [] if min_length is not None: start = min(start, end - (min_length - 1) * step) current = end while current >= start: times.append(current) current -= step return list(reversed(times))