"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SummarizeHeuristicRunner = exports.SummarizeHeuristicData = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
/**
 * Simple implementation of class for tracking summarize heuristic data.
 */
class SummarizeHeuristicData {
    get lastAttempt() {
        return this._lastAttempt;
    }
    get lastSuccessfulSummary() {
        return this._lastSuccessfulSummary;
    }
    get opsSinceLastSummary() {
        return this.numNonRuntimeOpsBefore + this.numRuntimeOpsBefore;
    }
    constructor(lastOpSequenceNumber, 
    /**
     * Baseline attempt data used for comparisons with subsequent attempts/calculations.
     */
    attemptBaseline) {
        this.lastOpSequenceNumber = lastOpSequenceNumber;
        this.hasMissingOpData = false;
        this.totalOpsSize = 0;
        /**
         * Cumulative size in bytes of all the ops at the beginning of the summarization attempt.
         * Is used to adjust totalOpsSize appropriately after successful summarization.
         */
        this.totalOpsSizeBefore = 0;
        this.numNonRuntimeOps = 0;
        /**
         * Number of non-runtime ops at beginning of attempting to summarize.
         * Is used to adjust numNonRuntimeOps appropriately after successful summarization.
         */
        this.numNonRuntimeOpsBefore = 0;
        this.numRuntimeOps = 0;
        /**
         * Number of runtime ops at beginning of attempting to summarize.
         * Is used to adjust numRuntimeOps appropriately after successful summarization.
         */
        this.numRuntimeOpsBefore = 0;
        this._lastAttempt = attemptBaseline;
        this._lastSuccessfulSummary = { ...attemptBaseline };
    }
    updateWithLastSummaryAckInfo(lastSummary) {
        this._lastAttempt = lastSummary;
        this._lastSuccessfulSummary = { ...lastSummary };
    }
    recordAttempt(refSequenceNumber) {
        this._lastAttempt = {
            refSequenceNumber: refSequenceNumber ?? this.lastOpSequenceNumber,
            summaryTime: Date.now(),
        };
        this.numNonRuntimeOpsBefore = this.numNonRuntimeOps;
        this.numRuntimeOpsBefore = this.numRuntimeOps;
        this.totalOpsSizeBefore = this.totalOpsSize;
    }
    markLastAttemptAsSuccessful() {
        this._lastSuccessfulSummary = { ...this.lastAttempt };
        this.numNonRuntimeOps -= this.numNonRuntimeOpsBefore;
        this.numNonRuntimeOpsBefore = 0;
        this.numRuntimeOps -= this.numRuntimeOpsBefore;
        this.numRuntimeOpsBefore = 0;
        this.totalOpsSize -= this.totalOpsSizeBefore;
        this.totalOpsSizeBefore = 0;
    }
}
exports.SummarizeHeuristicData = SummarizeHeuristicData;
/**
 * This class contains the heuristics for when to summarize.
 */
class SummarizeHeuristicRunner {
    constructor(heuristicData, configuration, trySummarize, logger, summarizeStrategies = getDefaultSummaryHeuristicStrategies()) {
        this.heuristicData = heuristicData;
        this.configuration = configuration;
        this.logger = logger;
        this.summarizeStrategies = summarizeStrategies;
        this.idleTimer = new internal_1.Timer(this.idleTime, () => this.runSummarize("idle"));
        this.runSummarize = (reason) => {
            this.idleTimer?.clear();
            // We shouldn't attempt a summary if there are no new processed ops
            const opsSinceLastAck = this.opsSinceLastAck;
            if (opsSinceLastAck > 0) {
                trySummarize(reason);
            }
        };
    }
    get idleTime() {
        const maxIdleTime = this.configuration.maxIdleTime;
        const minIdleTime = this.configuration.minIdleTime;
        const weightedNumOfOps = getWeightedNumberOfOps(this.heuristicData.numRuntimeOps, this.heuristicData.numNonRuntimeOps, this.configuration.runtimeOpWeight, this.configuration.nonRuntimeOpWeight);
        const pToMaxOps = (weightedNumOfOps * 1) / this.configuration.maxOps;
        if (pToMaxOps >= 1) {
            return minIdleTime;
        }
        // Return a ratioed idle time based on the percentage of ops
        return maxIdleTime - (maxIdleTime - minIdleTime) * pToMaxOps;
    }
    get opsSinceLastAck() {
        return (this.heuristicData.lastOpSequenceNumber -
            this.heuristicData.lastSuccessfulSummary.refSequenceNumber);
    }
    start() {
        this.idleTimer?.start(this.idleTime);
    }
    run() {
        for (const strategy of this.summarizeStrategies) {
            if (strategy.shouldRunSummary(this.configuration, this.heuristicData)) {
                return this.runSummarize(strategy.summarizeReason);
            }
        }
        this.idleTimer?.restart(this.idleTime);
    }
    shouldRunLastSummary() {
        const weightedOpsSinceLastAck = getWeightedNumberOfOps(this.heuristicData.numRuntimeOps, this.heuristicData.numNonRuntimeOps, this.configuration.runtimeOpWeight, this.configuration.nonRuntimeOpWeight);
        const minOpsForLastSummaryAttempt = this.configuration.minOpsForLastSummaryAttempt;
        this.logger.sendTelemetryEvent({
            eventName: "ShouldRunLastSummary",
            weightedOpsSinceLastAck,
            minOpsForLastSummaryAttempt,
        });
        return weightedOpsSinceLastAck >= minOpsForLastSummaryAttempt;
    }
    dispose() {
        this.idleTimer?.clear();
    }
}
exports.SummarizeHeuristicRunner = SummarizeHeuristicRunner;
/**
 * Strategy used to run a summary when it's been a while since our last successful summary
 */
class MaxTimeSummaryHeuristicStrategy {
    constructor() {
        this.summarizeReason = "maxTime";
    }
    shouldRunSummary(configuration, heuristicData) {
        const timeSinceLastSummary = Date.now() - heuristicData.lastSuccessfulSummary.summaryTime;
        return timeSinceLastSummary > configuration.maxTime;
    }
}
function getWeightedNumberOfOps(runtimeOpCount, nonRuntimeOpCount, runtimeOpWeight, nonRuntimeOpWeight) {
    return runtimeOpWeight * runtimeOpCount + nonRuntimeOpWeight * nonRuntimeOpCount;
}
/**
 * Strategy used to do a weighted analysis on the ops we've processed since the last successful summary
 */
class WeightedOpsSummaryHeuristicStrategy {
    constructor() {
        this.summarizeReason = "maxOps";
    }
    shouldRunSummary(configuration, heuristicData) {
        const weightedNumOfOps = getWeightedNumberOfOps(heuristicData.numRuntimeOps, heuristicData.numNonRuntimeOps, configuration.runtimeOpWeight, configuration.nonRuntimeOpWeight);
        return weightedNumOfOps > configuration.maxOps;
    }
}
function getDefaultSummaryHeuristicStrategies() {
    return [new MaxTimeSummaryHeuristicStrategy(), new WeightedOpsSummaryHeuristicStrategy()];
}
//# sourceMappingURL=summarizerHeuristics.js.map