"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_assert_1 = require("node:assert");
const internal_1 = require("@fluidframework/telemetry-utils/internal");
const sinon_1 = __importDefault(require("sinon"));
const index_js_1 = require("../../summary/index.js");
describe("Runtime", () => {
    describe("Summarization", () => {
        describe("Summarize Heuristic Runner", () => {
            let clock;
            before(() => {
                clock = sinon_1.default.useFakeTimers();
            });
            after(() => {
                clock.restore();
            });
            const defaultSummaryConfig = {
                state: "enabled",
                maxTime: 5000 * 12, // 1 min (active)
                maxOps: 1000, // 1k ops (active)
                minOpsForLastSummaryAttempt: 50,
                maxAckWaitTime: 120000, // 2 min
                maxOpsSinceLastSummary: 7000,
                initialSummarizerDelayMs: 0,
                minIdleTime: 5000, // 5 sec (idle)
                maxIdleTime: 5000, // 5 sec (idle)
                nonRuntimeOpWeight: 0.1,
                runtimeOpWeight: 1,
            };
            let summaryConfig;
            let data;
            let runner;
            let mockLogger;
            let attempts;
            const trySummarize = (reason) => {
                attempts.push(reason);
            };
            const getLastAttempt = () => attempts.length > 0 ? attempts[attempts.length - 1] : undefined;
            function assertAttemptCount(count, message) {
                const fullMessage = `${attempts.length} !== ${count}; ${message ?? "unexpected attempt count"}`;
                (0, node_assert_1.strict)(attempts.length === count, fullMessage);
            }
            function initialize({ refSequenceNumber = 0, lastOpSequenceNumber = refSequenceNumber, summaryTime = Date.now(), maxTime = defaultSummaryConfig.maxTime, maxOps = defaultSummaryConfig.maxOps, maxAckWaitTime = defaultSummaryConfig.maxAckWaitTime, maxOpsSinceLastSummary = defaultSummaryConfig.maxOpsSinceLastSummary, initialSummarizerDelayMs = defaultSummaryConfig.initialSummarizerDelayMs, minOpsForLastSummaryAttempt = defaultSummaryConfig.minOpsForLastSummaryAttempt, run = true, minIdleTime = defaultSummaryConfig.minIdleTime, maxIdleTime = defaultSummaryConfig.maxIdleTime, nonRuntimeOpWeight = defaultSummaryConfig.nonRuntimeOpWeight, runtimeOpWeight = defaultSummaryConfig.runtimeOpWeight, } = {}) {
                mockLogger = new internal_1.MockLogger();
                data = new index_js_1.SummarizeHeuristicData(lastOpSequenceNumber, {
                    refSequenceNumber,
                    summaryTime,
                });
                summaryConfig = {
                    state: "enabled",
                    maxTime,
                    maxOps,
                    maxAckWaitTime,
                    maxOpsSinceLastSummary,
                    initialSummarizerDelayMs,
                    minOpsForLastSummaryAttempt,
                    minIdleTime,
                    maxIdleTime,
                    nonRuntimeOpWeight,
                    runtimeOpWeight,
                };
                runner = new index_js_1.SummarizeHeuristicRunner(data, summaryConfig, trySummarize, mockLogger.toTelemetryLogger());
                if (run) {
                    runner.run();
                }
            }
            beforeEach(() => {
                attempts = [];
            });
            afterEach(() => {
                clock.reset();
            });
            it("Should summarize after maxOps with no prior summary", () => {
                const maxOps = 100;
                initialize({ maxOps });
                data.lastOpSequenceNumber = maxOps;
                data.numRuntimeOps = maxOps;
                runner.run();
                assertAttemptCount(0, "should not run yet");
                data.lastOpSequenceNumber++;
                data.numRuntimeOps++;
                runner.run();
                assertAttemptCount(1, "should run now");
                (0, node_assert_1.strict)(getLastAttempt() === "maxOps");
            });
            it("Should summarize after maxOps", () => {
                const lastSummary = 1000;
                const maxOps = 100;
                initialize({ refSequenceNumber: lastSummary, maxOps });
                data.lastOpSequenceNumber = lastSummary + maxOps;
                data.numRuntimeOps = maxOps;
                runner.run();
                assertAttemptCount(0, "should not run yet");
                data.lastOpSequenceNumber++;
                data.numRuntimeOps++;
                runner.run();
                assertAttemptCount(1, "should run now");
                (0, node_assert_1.strict)(getLastAttempt() === "maxOps");
            });
            it("Should summarize after maxTime", () => {
                const lastSummary = 1000;
                const idleTime = 101;
                const maxTime = 1000;
                const idlesPerActive = Math.floor((maxTime + 1) / (idleTime - 1));
                const remainingTime = (maxTime + 1) % (idleTime - 1);
                initialize({
                    refSequenceNumber: lastSummary,
                    minIdleTime: idleTime,
                    maxIdleTime: idleTime,
                    maxTime,
                });
                data.lastOpSequenceNumber = lastSummary + 1;
                for (let i = 0; i < idlesPerActive; i++) {
                    // Prevent idle timer from triggering with periodic "ops" (heuristic runs)
                    clock.tick(idleTime - 1);
                    runner.run();
                }
                clock.tick(remainingTime - 1);
                runner.run();
                assertAttemptCount(0, "should not run yet");
                clock.tick(1);
                runner.run();
                assertAttemptCount(1, "should run now");
                (0, node_assert_1.strict)(getLastAttempt() === "maxTime");
            });
            it("Should summarize after idleTime", () => {
                const lastSummary = 1000;
                const idleTime = 101;
                const maxTime = 1000;
                initialize({
                    refSequenceNumber: lastSummary,
                    minIdleTime: idleTime,
                    maxIdleTime: idleTime,
                    maxTime,
                });
                data.lastOpSequenceNumber = lastSummary + 1;
                clock.tick(idleTime - 1);
                assertAttemptCount(0, "should not run yet");
                clock.tick(1);
                assertAttemptCount(1, "should run now");
                (0, node_assert_1.strict)(getLastAttempt() === "idle");
            });
            it("Should summarize after idleTime after a few interruptions", () => {
                const lastSummary = 1000;
                const idleTime = 101;
                const maxTime = 1000;
                initialize({
                    refSequenceNumber: lastSummary,
                    minIdleTime: idleTime,
                    maxIdleTime: idleTime,
                    maxTime,
                });
                data.lastOpSequenceNumber = lastSummary + 1;
                clock.tick(idleTime - 1);
                assertAttemptCount(0, "should not run yet");
                runner.run(); // interrupts idle timer
                clock.tick(idleTime - 1);
                assertAttemptCount(0, "still should not run yet");
                runner.run(); // interrupts idle timer
                clock.tick(idleTime - 1);
                assertAttemptCount(0, "still should not run yet again");
                clock.tick(idleTime);
                assertAttemptCount(1, "should run now");
                (0, node_assert_1.strict)(getLastAttempt() === "idle");
            });
            it("Should summarize on close if enough outstanding ops", () => {
                const minOpsForLastSummaryAttempt = 10;
                initialize({ minOpsForLastSummaryAttempt, runtimeOpWeight: 1 });
                data.numRuntimeOps = minOpsForLastSummaryAttempt;
                node_assert_1.strict.strictEqual(runner.shouldRunLastSummary(), true, "should run on close");
            });
            it("Should not summarize on close if insufficient outstanding ops", () => {
                const minOpsForLastSummaryAttempt = 10;
                initialize({ minOpsForLastSummaryAttempt, runtimeOpWeight: 1 });
                data.numRuntimeOps = minOpsForLastSummaryAttempt - 1;
                node_assert_1.strict.strictEqual(runner.shouldRunLastSummary(), false, "should not run on close");
            });
            it("Should summarize on close weights ops properly", () => {
                const minOpsForLastSummaryAttempt = 2;
                initialize({
                    minOpsForLastSummaryAttempt,
                    runtimeOpWeight: 0.1,
                    nonRuntimeOpWeight: 1.1,
                });
                data.numRuntimeOps += 8;
                node_assert_1.strict.strictEqual(runner.shouldRunLastSummary(), false, "should not run yet");
                data.numNonRuntimeOps += 1;
                node_assert_1.strict.strictEqual(runner.shouldRunLastSummary(), false, "should not run yet");
                data.numRuntimeOps += 1;
                node_assert_1.strict.strictEqual(runner.shouldRunLastSummary(), true, "should run");
            });
            it("Should not run idle timer after dispose", () => {
                const lastSummary = 1000;
                const idleTime = 101;
                const maxTime = 1000;
                initialize({
                    refSequenceNumber: lastSummary,
                    minIdleTime: idleTime,
                    maxIdleTime: idleTime,
                    maxTime,
                });
                data.lastOpSequenceNumber = lastSummary + 1;
                clock.tick(idleTime - 1);
                runner.run();
                assertAttemptCount(0, "should not run yet");
                runner.dispose();
                clock.tick(1);
                runner.run();
                assertAttemptCount(0, "should still run since disposed");
            });
            it("Idle time value should change based on op counts", () => {
                const minIdleTime = 0;
                const maxIdleTime = 1;
                const maxTime = 1000;
                const maxOps = 1000;
                const runtimeOpWeight = 1;
                const nonRuntimeOpWeight = 1;
                initialize({
                    minIdleTime,
                    maxIdleTime,
                    maxTime,
                    maxOps,
                    runtimeOpWeight,
                    nonRuntimeOpWeight,
                });
                data.lastOpSequenceNumber = maxOps;
                node_assert_1.strict.strictEqual(runner.idleTime, maxIdleTime, "should start at the maxIdleTime");
                data.numRuntimeOps += 50;
                node_assert_1.strict.strictEqual(runner.idleTime, maxIdleTime - 0.05);
                data.numRuntimeOps += 123;
                node_assert_1.strict.strictEqual(runner.idleTime, maxIdleTime - 0.173);
                data.numRuntimeOps += 500;
                node_assert_1.strict.strictEqual(runner.idleTime, maxIdleTime - 0.673);
                data.numRuntimeOps += 326;
                node_assert_1.strict.strictEqual(runner.idleTime, maxIdleTime - 0.999);
                data.numRuntimeOps += 1;
                node_assert_1.strict.strictEqual(runner.idleTime, minIdleTime);
                data.numRuntimeOps += 100;
                node_assert_1.strict.strictEqual(runner.idleTime, minIdleTime, "should never go below the minIdleTime");
            });
            it("Idle time should change based on op weights", () => {
                const minIdleTime = 0;
                const maxIdleTime = 1000;
                const maxTime = 1000;
                const maxOps = 1000;
                const runtimeOpWeight = 0.1;
                const nonRuntimeOpWeight = 1.1;
                initialize({
                    minIdleTime,
                    maxIdleTime,
                    maxTime,
                    maxOps,
                    runtimeOpWeight,
                    nonRuntimeOpWeight,
                });
                data.lastOpSequenceNumber = maxOps;
                node_assert_1.strict.strictEqual(runner.idleTime, maxIdleTime, "should start at the maxIdleTime");
                data.numRuntimeOps += 50;
                node_assert_1.strict.strictEqual(runner.idleTime, maxIdleTime - 5);
                data.numRuntimeOps += 123;
                node_assert_1.strict.strictEqual(runner.idleTime, maxIdleTime - 17.3);
                data.numNonRuntimeOps += 500;
                node_assert_1.strict.strictEqual(runner.idleTime, maxIdleTime - 567.3);
                data.numNonRuntimeOps += 1;
                node_assert_1.strict.strictEqual(runner.idleTime, maxIdleTime - 568.4);
            });
            it("Weights ops properly", () => {
                const maxOps = 2;
                const runtimeOpWeight = 0.1;
                const nonRuntimeOpWeight = 1.1;
                initialize({ maxOps, runtimeOpWeight, nonRuntimeOpWeight });
                data.lastOpSequenceNumber = maxOps;
                data.numRuntimeOps += 9;
                runner.run();
                assertAttemptCount(0, "should not run yet");
                data.numNonRuntimeOps += 1;
                runner.run();
                assertAttemptCount(0, "should not run yet");
                data.numRuntimeOps += 1;
                runner.run();
                assertAttemptCount(1, "should run");
                (0, node_assert_1.strict)(getLastAttempt() === "maxOps");
            });
        });
    });
});
//# sourceMappingURL=summarizerHeuristics.spec.js.map