QueryStatistics.java

package li.selman.persistencetest.analysis;

import com.google.errorprone.annotations.Var;
import java.time.Duration;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import li.selman.persistencetest.core.CapturedQuery;
import li.selman.persistencetest.core.StatementType;

/**
 * Aggregate counts and timing over a list of captured queries.
 *
 * @param totalCount total number of captured queries.
 * @param countsByType number of queries per {@link StatementType}; types with zero occurrences are absent.
 * @param totalDuration sum of every query's {@link CapturedQuery#duration()}; queries with no measured
 *     duration (execution never completed) contribute zero.
 * @param accessedTables union of every query's {@link CapturedQuery#tables()}, lower-cased and sorted.
 */
public record QueryStatistics(
        int totalCount, Map<StatementType, Integer> countsByType, Duration totalDuration, Set<String> accessedTables) {

    @SuppressWarnings("Var") // reassigning a compact constructor's implicit parameter for a defensive copy
    // is the standard record idiom; it can't be annotated @Var since the underlying field is final.
    public QueryStatistics {
        countsByType = Map.copyOf(countsByType);
        accessedTables = Set.copyOf(accessedTables);
    }

    /** Number of captured queries of the given type; zero if none. */
    public int countOf(StatementType type) {
        return countsByType.getOrDefault(type, 0);
    }

    /** {@link #totalDuration()} divided evenly across {@link #totalCount()}, or zero if there were none. */
    public Duration averageDuration() {
        return totalCount == 0 ? Duration.ZERO : totalDuration.dividedBy(totalCount);
    }

    /** Computes statistics over {@code queries}. */
    public static QueryStatistics of(List<CapturedQuery> queries) {
        Map<StatementType, Integer> counts = new EnumMap<>(StatementType.class);
        Set<String> tables = new TreeSet<>();
        @Var Duration total = Duration.ZERO;
        for (CapturedQuery query : queries) {
            counts.merge(query.statementType(), 1, Integer::sum);
            tables.addAll(query.tables());
            if (query.duration() != null) {
                total = total.plus(query.duration());
            }
        }
        return new QueryStatistics(queries.size(), counts, total, tables);
    }
}