All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sirius.biz.statistics.Statistics Maven / Gradle / Ivy

There is a newer version: 9.6
Show newest version
/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.biz.statistics;

import com.google.common.collect.Lists;
import sirius.db.mixing.OMA;
import sirius.db.mixing.constraints.FieldOperator;
import sirius.kernel.Lifecycle;
import sirius.kernel.async.BackgroundLoop;
import sirius.kernel.commons.Context;
import sirius.kernel.di.std.Framework;
import sirius.kernel.di.std.Part;
import sirius.kernel.di.std.Register;
import sirius.kernel.health.Counter;
import sirius.kernel.health.Exceptions;
import sirius.kernel.health.Log;
import sirius.kernel.health.metrics.MetricProvider;
import sirius.kernel.health.metrics.MetricsCollector;

import javax.annotation.Nonnull;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Provides a statistical warehouse which stores statistics per user defined events and objects.
 * 

* An event is defined by creating a {@link StatisticalEvent}. The warehouse will then accept calls to * addStatistic and incrementStatistic for that event and user defined object ids. As statistics * can be stored for various {@link AggregationLevel}s, these are computed automatically as long as the event * is triggered for the lowest (finest) level. *

* Additionally various queries are provided to simplify the extraction and visualization of the recorded statistics. */ @Framework("biz.statistics") @Register(classes = {Statistics.class, BackgroundLoop.class, Lifecycle.class, MetricProvider.class}) public class Statistics extends BackgroundLoop implements Lifecycle, MetricProvider { @Part private OMA oma; public static final Log LOG = Log.get("statistics"); private Counter statisticUpdates = new Counter(); private static class RecordedEvent { StatisticalEvent event; String objectId; int value; boolean delete; } private List todoQueue = Lists.newArrayList(); /** * Increments the statistic for the given event of the given object by the given value. * * @param event the event to increment the statistic for * @param objectId the object to increment the statistic for * @param value the value by which the statistic should be incremented */ public void addStatistic(StatisticalEvent event, String objectId, int value) { RecordedEvent recordedEvent = new RecordedEvent(); recordedEvent.event = event; recordedEvent.objectId = objectId; recordedEvent.value = value; synchronized (this) { todoQueue.add(recordedEvent); } } /** * Boilerplate method to invoke addStatistic with 1 as value. * * @param event the event to increment the statistic for * @param objectId the object to increment the statistic for */ public void incrementStatistic(StatisticalEvent event, String objectId) { addStatistic(event, objectId, 1); } /** * Deletes all recorded statistics for the given object. * * @param objectId the id identifying the object for which the statistics should be deleted */ public void deleteStatistic(String objectId) { RecordedEvent recordedEvent = new RecordedEvent(); recordedEvent.objectId = objectId; recordedEvent.delete = true; synchronized (this) { todoQueue.add(recordedEvent); } } protected void commitStatistics() { final List copy; synchronized (this) { copy = Lists.newArrayList(todoQueue); todoQueue.clear(); } for (RecordedEvent event : copy) { try { statisticUpdates.inc(); if (event.delete) { handleDelete(event); } else { handleIncrement(event); } } catch (SQLException e) { Exceptions.handle(LOG, e); } } } private void handleIncrement(RecordedEvent event) throws SQLException { LocalDate now = LocalDate.now(); for (AggregationLevel level : AggregationLevel.values()) { if (event.event.getFinestAggregationLevel().ordinal() <= level.ordinal()) { processIncrement(event.event.getEventName(), level, event.objectId, AggregationLevel.convertDate(now, level), event.value); } } } private void handleDelete(RecordedEvent event) throws SQLException { oma.getDatabase() .createQuery("DELETE FROM statisticvalue WHERE objectId = ${id}") .set("id", event.objectId) .executeUpdate(); } private void processIncrement(String event, AggregationLevel level, String objectId, LocalDate tod, int value) throws SQLException { String query = "UPDATE statisticvalue SET statisticValue = statisticValue + ${value} " + "WHERE objectId = ${objectId} " + "AND event = ${event} " + "AND level = ${level} " + "AND tod = ${tod} "; int rowsUpdated = oma.getDatabase() .createQuery(query) .set("objectId", objectId) .set("event", event) .set("level", level.name()) .set("value", value) .set("tod", java.sql.Date.valueOf(tod)) .executeUpdate(); if (rowsUpdated == 0) { oma.getDatabase() .insertRow("statisticvalue", Context.create() .set("objectId", objectId) .set("event", event) .set("level", level.name()) .set("statisticValue", value) .set("tod", java.sql.Date.valueOf(tod))); } } /** * Returns a list of all recorded statistics for the given period, event and object. * * @param event the event for which the statistic should be queried * @param level the aggregation level of the recorded values * @param objectId the object for which the statistic was recorded * @param dateFrom the start of the search period * @param dateTo the end of the search period * @return a list of all statistical values recorded for the given parameters */ public List getStatistics(StatisticalEvent event, AggregationLevel level, String objectId, LocalDate dateFrom, LocalDate dateTo) { return oma.select(StatisticValue.class) .eq(StatisticValue.EVENT, event.getEventName()) .eq(StatisticValue.LEVEL, level) .eq(StatisticValue.OBJECT_ID, objectId) .where(FieldOperator.on(StatisticValue.TOD).greaterOrEqual(dateFrom)) .where(FieldOperator.on(StatisticValue.TOD).lessOrEqual(dateTo)) .queryList() .stream() .map(StatisticValue::getStatisticValue) .collect(Collectors.toList()); } /** * Determines the recorded statistic value for the given parameters. * * @param event the event of the statistic value to find * @param level the aggregation level of the statistic value to find * @param objectId the object of the statistic value to find * @param date the date of the statistic value to find * @return the statistic value computed or recorded for the given parameters or 0 if no value was found */ public long getStatisticValue(StatisticalEvent event, AggregationLevel level, String objectId, LocalDate date) { StatisticValue e = findStatisticValue(event, level, objectId, date); if (e == null) { return 0; } return e.getStatisticValue(); } protected StatisticValue findStatisticValue(StatisticalEvent event, AggregationLevel level, String objectId, LocalDate date) { return oma.select(StatisticValue.class) .eq(StatisticValue.EVENT, event.getEventName()) .eq(StatisticValue.LEVEL, level) .eq(StatisticValue.OBJECT_ID, objectId) .eq(StatisticValue.TOD, AggregationLevel.convertDate(date, level)) .queryFirst(); } /** * Returns a list of statistic values for the last 30 days. * * @param event the event of the statistic to fetch * @param objectId the object of the statistic to fetch * @return a list of the values of the last 30 days starting from the current day */ public List getLastThirtyDays(StatisticalEvent event, String objectId) { LocalDate date = LocalDate.now(); List result = Lists.newArrayListWithCapacity(30); boolean dataFound = false; for (int day = 0; day < 30; day++) { StatisticValue e = findStatisticValue(event, AggregationLevel.DAYS, objectId, date); if (e != null) { dataFound = true; result.add(e.getStatisticValue()); } else { result.add(0L); } date = date.minusDays(1); } if (dataFound) { return result; } else { return Collections.emptyList(); } } /** * Computes a statistical report for the current month. * * @param event the event of the statistic to fetch * @param objectId the object of the statistic to fetch * @return the statistical values recorded for the current month */ public MonthStatistic getThisMonth(StatisticalEvent event, String objectId) { return new MonthStatistic(getStatisticValue(event, AggregationLevel.MONTHS, objectId, LocalDate.now()), getStatisticValue(event, AggregationLevel.MONTHS, objectId, LocalDate.now().minusMonths(1)), getStatisticValue(event, AggregationLevel.MONTHS, objectId, LocalDate.now().minusYears(1)), LocalDate.now()); } /** * Computes a statistical report for the previous month. * * @param event the event of the statistic to fetch * @param objectId the object of the statistic to fetch * @return the statistical values recorded for the previous month */ public MonthStatistic getLastMonth(StatisticalEvent event, String objectId) { return new MonthStatistic(getStatisticValue(event, AggregationLevel.MONTHS, objectId, LocalDate.now().minusMonths(1)), getStatisticValue(event, AggregationLevel.MONTHS, objectId, LocalDate.now().minusMonths(2)), getStatisticValue(event, AggregationLevel.MONTHS, objectId, LocalDate.now().minusMonths(1).minusYears(1)), LocalDate.now().minusMonths(1)); } /** * Returns a list of statistic values for the last 12 months. * * @param event the event of the statistic to fetch * @param objectId the object of the statistic to fetch * @return a list of the values of the last 12 month starting from the current month */ public List getLast12Month(StatisticalEvent event, String objectId) { List result = Lists.newArrayListWithCapacity(12); boolean dataFound = false; LocalDate date = LocalDate.now(); for (int month = 0; month < 12; month++) { StatisticValue e = findStatisticValue(event, AggregationLevel.MONTHS, objectId, date); if (e != null) { dataFound = true; result.add(e.getStatisticValue()); } else { result.add(0L); } date = date.minusMonths(1); } if (dataFound) { return result; } else { return Collections.emptyList(); } } /** * Computes a statistical report for the current year * * @param event the event of the statistic to fetch * @param objectId the object of the statistic to fetch * @return the statistical values recorded for the current year */ public YearStatistic getYearStatistic(StatisticalEvent event, String objectId) { return new YearStatistic(getStatisticValue(event, AggregationLevel.YEARS, objectId, LocalDate.now()), getStatisticValue(event, AggregationLevel.YEARS, objectId, LocalDate.now().minusYears(1))); } @Override public void started() { // Nothing to do here } @Override public void stopped() { // Ensure, that statistics are completely committed when shutting down the system commitStatistics(); } @Override public void awaitTermination() { // Nothing to do here } @Nonnull @Override public String getName() { return "Statistics"; } @Override protected void doWork() throws Exception { commitStatistics(); } @Override public void gather(MetricsCollector collector) { collector.differentialMetric("statistics-updates", "statistics-updates", "Statistics Updated", statisticUpdates.getCount(), "/min"); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy