"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMetricsMonitor = void 0;
const prom_client_1 = require("prom-client");
const memoizee_1 = __importDefault(require("memoizee"));
const events = __importStar(require("./metric-events"));
const events_1 = require("./types/events");
const date_fns_1 = require("date-fns");
const metrics_1 = require("./util/metrics");
class MetricsMonitor {
    constructor() { }
    async startMonitoring(config, stores, version, eventBus, instanceStatsService, schedulerService, db) {
        if (!config.server.serverMetrics) {
            return Promise.resolve();
        }
        const { eventStore, environmentStore } = stores;
        const cachedEnvironments = (0, memoizee_1.default)(async () => environmentStore.getAll(), {
            promise: true,
            maxAge: (0, date_fns_1.hoursToMilliseconds)(1),
        });
        (0, prom_client_1.collectDefaultMetrics)();
        const requestDuration = (0, metrics_1.createSummary)({
            name: 'http_request_duration_milliseconds',
            help: 'App response time',
            labelNames: ['path', 'method', 'status', 'appName'],
            percentiles: [0.1, 0.5, 0.9, 0.95, 0.99],
            maxAgeSeconds: 600,
            ageBuckets: 5,
        });
        const schedulerDuration = (0, metrics_1.createSummary)({
            name: 'scheduler_duration_seconds',
            help: 'Scheduler duration time',
            labelNames: ['jobId'],
            percentiles: [0.1, 0.5, 0.9, 0.95, 0.99],
            maxAgeSeconds: 600,
            ageBuckets: 5,
        });
        const dbDuration = (0, metrics_1.createSummary)({
            name: 'db_query_duration_seconds',
            help: 'DB query duration time',
            labelNames: ['store', 'action'],
            percentiles: [0.1, 0.5, 0.9, 0.95, 0.99],
            maxAgeSeconds: 600,
            ageBuckets: 5,
        });
        const functionDuration = (0, metrics_1.createSummary)({
            name: 'function_duration_seconds',
            help: 'Function duration time',
            labelNames: ['functionName', 'className'],
            percentiles: [0.1, 0.5, 0.9, 0.95, 0.99],
            maxAgeSeconds: 600,
            ageBuckets: 5,
        });
        const featureToggleUpdateTotal = (0, metrics_1.createCounter)({
            name: 'feature_toggle_update_total',
            help: 'Number of times a toggle has been updated. Environment label would be "n/a" when it is not available, e.g. when a feature toggle is created.',
            labelNames: ['toggle', 'project', 'environment', 'environmentType'],
        });
        const featureToggleUsageTotal = (0, metrics_1.createCounter)({
            name: 'feature_toggle_usage_total',
            help: 'Number of times a feature toggle has been used',
            labelNames: ['toggle', 'active', 'appName'],
        });
        const featureTogglesTotal = (0, metrics_1.createGauge)({
            name: 'feature_toggles_total',
            help: 'Number of feature toggles',
            labelNames: ['version'],
        });
        const featureTogglesArchivedTotal = (0, metrics_1.createGauge)({
            name: 'feature_toggles_archived_total',
            help: 'Number of archived feature toggles',
        });
        const usersTotal = (0, metrics_1.createGauge)({
            name: 'users_total',
            help: 'Number of users',
        });
        const serviceAccounts = (0, metrics_1.createGauge)({
            name: 'service_accounts_total',
            help: 'Number of service accounts',
        });
        const apiTokens = (0, metrics_1.createGauge)({
            name: 'api_tokens_total',
            help: 'Number of API tokens',
            labelNames: ['type'],
        });
        const enabledMetricsBucketsPreviousDay = (0, metrics_1.createGauge)({
            name: 'enabled_metrics_buckets_previous_day',
            help: 'Number of hourly enabled/disabled metric buckets in the previous day',
        });
        const variantMetricsBucketsPreviousDay = (0, metrics_1.createGauge)({
            name: 'variant_metrics_buckets_previous_day',
            help: 'Number of hourly variant metric buckets in the previous day',
        });
        const usersActive7days = (0, metrics_1.createGauge)({
            name: 'users_active_7',
            help: 'Number of users active in the last 7 days',
        });
        const usersActive30days = (0, metrics_1.createGauge)({
            name: 'users_active_30',
            help: 'Number of users active in the last 30 days',
        });
        const usersActive60days = (0, metrics_1.createGauge)({
            name: 'users_active_60',
            help: 'Number of users active in the last 60 days',
        });
        const usersActive90days = (0, metrics_1.createGauge)({
            name: 'users_active_90',
            help: 'Number of users active in the last 90 days',
        });
        const projectsTotal = (0, metrics_1.createGauge)({
            name: 'projects_total',
            help: 'Number of projects',
            labelNames: ['mode'],
        });
        const environmentsTotal = (0, metrics_1.createGauge)({
            name: 'environments_total',
            help: 'Number of environments',
        });
        const groupsTotal = (0, metrics_1.createGauge)({
            name: 'groups_total',
            help: 'Number of groups',
        });
        const rolesTotal = (0, metrics_1.createGauge)({
            name: 'roles_total',
            help: 'Number of roles',
        });
        const customRootRolesTotal = (0, metrics_1.createGauge)({
            name: 'custom_root_roles_total',
            help: 'Number of custom root roles',
        });
        const customRootRolesInUseTotal = (0, metrics_1.createGauge)({
            name: 'custom_root_roles_in_use_total',
            help: 'Number of custom root roles in use',
        });
        const segmentsTotal = (0, metrics_1.createGauge)({
            name: 'segments_total',
            help: 'Number of segments',
        });
        const contextTotal = (0, metrics_1.createGauge)({
            name: 'context_total',
            help: 'Number of context',
        });
        const strategiesTotal = (0, metrics_1.createGauge)({
            name: 'strategies_total',
            help: 'Number of strategies',
        });
        const clientAppsTotal = (0, metrics_1.createGauge)({
            name: 'client_apps_total',
            help: 'Number of registered client apps aggregated by range by last seen',
            labelNames: ['range'],
        });
        const samlEnabled = (0, metrics_1.createGauge)({
            name: 'saml_enabled',
            help: 'Whether SAML is enabled',
        });
        const oidcEnabled = (0, metrics_1.createGauge)({
            name: 'oidc_enabled',
            help: 'Whether OIDC is enabled',
        });
        const clientSdkVersionUsage = (0, metrics_1.createCounter)({
            name: 'client_sdk_versions',
            help: 'Which sdk versions are being used',
            labelNames: ['sdk_name', 'sdk_version'],
        });
        const productionChanges30 = (0, metrics_1.createGauge)({
            name: 'production_changes_30',
            help: 'Changes made to production environment last 30 days',
            labelNames: ['environment'],
        });
        const productionChanges60 = (0, metrics_1.createGauge)({
            name: 'production_changes_60',
            help: 'Changes made to production environment last 60 days',
            labelNames: ['environment'],
        });
        const productionChanges90 = (0, metrics_1.createGauge)({
            name: 'production_changes_90',
            help: 'Changes made to production environment last 90 days',
            labelNames: ['environment'],
        });
        const rateLimits = (0, metrics_1.createGauge)({
            name: 'rate_limits',
            help: 'Rate limits (per minute) for METHOD/ENDPOINT pairs',
            labelNames: ['endpoint', 'method'],
        });
        const featureCreatedByMigration = (0, metrics_1.createCounter)({
            name: 'feature_created_by_migration_count',
            help: 'Feature createdBy migration count',
        });
        const eventCreatedByMigration = (0, metrics_1.createCounter)({
            name: 'event_created_by_migration_count',
            help: 'Event createdBy migration count',
        });
        const proxyRepositoriesCreated = (0, metrics_1.createCounter)({
            name: 'proxy_repositories_created',
            help: 'Proxy repositories created',
        });
        const frontendApiRepositoriesCreated = (0, metrics_1.createCounter)({
            name: 'frontend_api_repositories_created',
            help: 'Frontend API repositories created',
        });
        const mapFeaturesForClientDuration = (0, metrics_1.createHistogram)({
            name: 'map_features_for_client_duration',
            help: 'Duration of mapFeaturesForClient function',
        });
        const projectEnvironmentsDisabled = (0, metrics_1.createCounter)({
            name: 'project_environments_disabled',
            help: 'How many "environment disabled" events we have received for each project',
            labelNames: ['project_id'],
        });
        async function collectStaticCounters() {
            try {
                const stats = await instanceStatsService.getStats();
                featureTogglesTotal.reset();
                featureTogglesTotal
                    .labels({ version })
                    .set(stats.featureToggles);
                featureTogglesArchivedTotal.reset();
                featureTogglesArchivedTotal.set(stats.archivedFeatureToggles);
                usersTotal.reset();
                usersTotal.set(stats.users);
                serviceAccounts.reset();
                serviceAccounts.set(stats.serviceAccounts);
                apiTokens.reset();
                for (const [type, value] of stats.apiTokens) {
                    apiTokens.labels({ type }).set(value);
                }
                enabledMetricsBucketsPreviousDay.reset();
                enabledMetricsBucketsPreviousDay.set(stats.previousDayMetricsBucketsCount.enabledCount);
                variantMetricsBucketsPreviousDay.reset();
                variantMetricsBucketsPreviousDay.set(stats.previousDayMetricsBucketsCount.variantCount);
                usersActive7days.reset();
                usersActive7days.set(stats.activeUsers.last7);
                usersActive30days.reset();
                usersActive30days.set(stats.activeUsers.last30);
                usersActive60days.reset();
                usersActive60days.set(stats.activeUsers.last60);
                usersActive90days.reset();
                usersActive90days.set(stats.activeUsers.last90);
                productionChanges30.reset();
                productionChanges30.set(stats.productionChanges.last30);
                productionChanges60.reset();
                productionChanges60.set(stats.productionChanges.last60);
                productionChanges90.reset();
                productionChanges90.set(stats.productionChanges.last90);
                projectsTotal.reset();
                stats.projects.forEach((projectStat) => {
                    projectsTotal
                        .labels({ mode: projectStat.mode })
                        .set(projectStat.count);
                });
                environmentsTotal.reset();
                environmentsTotal.set(stats.environments);
                groupsTotal.reset();
                groupsTotal.set(stats.groups);
                rolesTotal.reset();
                rolesTotal.set(stats.roles);
                customRootRolesTotal.reset();
                customRootRolesTotal.set(stats.customRootRoles);
                customRootRolesInUseTotal.reset();
                customRootRolesInUseTotal.set(stats.customRootRolesInUse);
                segmentsTotal.reset();
                segmentsTotal.set(stats.segments);
                contextTotal.reset();
                contextTotal.set(stats.contextFields);
                strategiesTotal.reset();
                strategiesTotal.set(stats.strategies);
                samlEnabled.reset();
                samlEnabled.set(stats.SAMLenabled ? 1 : 0);
                oidcEnabled.reset();
                oidcEnabled.set(stats.OIDCenabled ? 1 : 0);
                clientAppsTotal.reset();
                stats.clientApps.forEach(({ range, count }) => clientAppsTotal.labels({ range }).set(count));
                rateLimits.reset();
                rateLimits
                    .labels({ endpoint: '/api/client/metrics', method: 'POST' })
                    .set(config.metricsRateLimiting.clientMetricsMaxPerMinute);
                rateLimits
                    .labels({
                    endpoint: '/api/client/register',
                    method: 'POST',
                })
                    .set(config.metricsRateLimiting.clientRegisterMaxPerMinute);
                rateLimits
                    .labels({
                    endpoint: '/api/frontend/metrics',
                    method: 'POST',
                })
                    .set(config.metricsRateLimiting.frontendMetricsMaxPerMinute);
                rateLimits
                    .labels({
                    endpoint: '/api/frontend/register',
                    method: 'POST',
                })
                    .set(config.metricsRateLimiting.frontendRegisterMaxPerMinute);
                rateLimits
                    .labels({
                    endpoint: '/api/admin/user-admin',
                    method: 'POST',
                })
                    .set(config.rateLimiting.createUserMaxPerMinute);
                rateLimits
                    .labels({ endpoint: '/auth/simple', method: 'POST' })
                    .set(config.rateLimiting.simpleLoginMaxPerMinute);
                rateLimits
                    .labels({
                    endpoint: '/auth/reset/password-email',
                    method: 'POST',
                })
                    .set(config.rateLimiting.passwordResetMaxPerMinute);
                rateLimits
                    .labels({
                    endpoint: '/api/signal-endpoint/:name',
                    method: 'POST',
                })
                    .set(config.rateLimiting.callSignalEndpointMaxPerSecond * 60);
            }
            catch (e) { }
        }
        await schedulerService.schedule(collectStaticCounters.bind(this), (0, date_fns_1.hoursToMilliseconds)(2), 'collectStaticCounters', 0);
        eventBus.on(events.REQUEST_TIME, ({ path, method, time, statusCode, appName }) => {
            requestDuration
                .labels({ path, method, status: statusCode, appName })
                .observe(time);
        });
        eventBus.on(events.SCHEDULER_JOB_TIME, ({ jobId, time }) => {
            schedulerDuration.labels(jobId).observe(time);
        });
        eventBus.on(events.FUNCTION_TIME, ({ functionName, className, time }) => {
            functionDuration
                .labels({ functionName, className })
                .observe(time);
        });
        eventBus.on(events.EVENTS_CREATED_BY_PROCESSED, ({ updated }) => {
            eventCreatedByMigration.inc(updated);
        });
        eventBus.on(events.FEATURES_CREATED_BY_PROCESSED, ({ updated }) => {
            featureCreatedByMigration.inc(updated);
        });
        eventBus.on(events.DB_TIME, ({ store, action, time }) => {
            dbDuration.labels({ store, action }).observe(time);
        });
        eventBus.on(events.PROXY_REPOSITORY_CREATED, () => {
            proxyRepositoriesCreated.inc();
        });
        eventBus.on(events.FRONTEND_API_REPOSITORY_CREATED, () => {
            frontendApiRepositoriesCreated.inc();
        });
        eventBus.on(events.PROXY_FEATURES_FOR_TOKEN_TIME, ({ duration }) => {
            mapFeaturesForClientDuration.observe(duration);
        });
        eventStore.on(events_1.FEATURE_CREATED, ({ featureName, project }) => {
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment: 'n/a',
                environmentType: 'n/a',
            });
        });
        eventStore.on(events_1.FEATURE_VARIANTS_UPDATED, ({ featureName, project }) => {
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment: 'n/a',
                environmentType: 'n/a',
            });
        });
        eventStore.on(events_1.FEATURE_METADATA_UPDATED, ({ featureName, project }) => {
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment: 'n/a',
                environmentType: 'n/a',
            });
        });
        eventStore.on(events_1.FEATURE_UPDATED, ({ featureName, project }) => {
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment: 'default',
                environmentType: 'production',
            });
        });
        eventStore.on(events_1.FEATURE_STRATEGY_ADD, async ({ featureName, project, environment }) => {
            const environmentType = await this.resolveEnvironmentType(environment, cachedEnvironments);
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment,
                environmentType,
            });
        });
        eventStore.on(events_1.FEATURE_STRATEGY_REMOVE, async ({ featureName, project, environment }) => {
            const environmentType = await this.resolveEnvironmentType(environment, cachedEnvironments);
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment,
                environmentType,
            });
        });
        eventStore.on(events_1.FEATURE_STRATEGY_UPDATE, async ({ featureName, project, environment }) => {
            const environmentType = await this.resolveEnvironmentType(environment, cachedEnvironments);
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment,
                environmentType,
            });
        });
        eventStore.on(events_1.FEATURE_ENVIRONMENT_DISABLED, async ({ featureName, project, environment }) => {
            const environmentType = await this.resolveEnvironmentType(environment, cachedEnvironments);
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment,
                environmentType,
            });
        });
        eventStore.on(events_1.FEATURE_ENVIRONMENT_ENABLED, async ({ featureName, project, environment }) => {
            const environmentType = await this.resolveEnvironmentType(environment, cachedEnvironments);
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment,
                environmentType,
            });
        });
        eventStore.on(events_1.FEATURE_ARCHIVED, ({ featureName, project }) => {
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment: 'n/a',
                environmentType: 'n/a',
            });
        });
        eventStore.on(events_1.FEATURE_REVIVED, ({ featureName, project }) => {
            featureToggleUpdateTotal.increment({
                toggle: featureName,
                project,
                environment: 'n/a',
                environmentType: 'n/a',
            });
        });
        eventBus.on(events_1.CLIENT_METRICS, (m) => {
            for (const entry of Object.entries(m.bucket.toggles)) {
                featureToggleUsageTotal.increment({
                    toggle: entry[0],
                    active: 'true',
                    appName: m.appName,
                }, entry[1].yes);
                featureToggleUsageTotal.increment({
                    toggle: entry[0],
                    active: 'false',
                    appName: m.appName,
                }, entry[1].no);
            }
        });
        eventStore.on(events_1.CLIENT_REGISTER, (m) => {
            if (m.sdkVersion && m.sdkVersion.indexOf(':') > -1) {
                const [sdkName, sdkVersion] = m.sdkVersion.split(':');
                clientSdkVersionUsage.increment({
                    sdk_name: sdkName,
                    sdk_version: sdkVersion,
                });
            }
        });
        eventStore.on(events_1.PROJECT_ENVIRONMENT_REMOVED, ({ project }) => {
            projectEnvironmentsDisabled.increment({ project_id: project });
        });
        await this.configureDbMetrics(db, eventBus, schedulerService);
        return Promise.resolve();
    }
    async configureDbMetrics(db, eventBus, schedulerService) {
        if (db?.client) {
            const dbPoolMin = (0, metrics_1.createGauge)({
                name: 'db_pool_min',
                help: 'Minimum DB pool size',
            });
            dbPoolMin.set(db.client.pool.min);
            const dbPoolMax = (0, metrics_1.createGauge)({
                name: 'db_pool_max',
                help: 'Maximum DB pool size',
            });
            dbPoolMax.set(db.client.pool.max);
            const dbPoolFree = (0, metrics_1.createGauge)({
                name: 'db_pool_free',
                help: 'Current free connections in DB pool',
            });
            const dbPoolUsed = (0, metrics_1.createGauge)({
                name: 'db_pool_used',
                help: 'Current connections in use in DB pool',
            });
            const dbPoolPendingCreates = (0, metrics_1.createGauge)({
                name: 'db_pool_pending_creates',
                help: 'how many asynchronous create calls are running in DB pool',
            });
            const dbPoolPendingAcquires = (0, metrics_1.createGauge)({
                name: 'db_pool_pending_acquires',
                help: 'how many acquires are waiting for a resource to be released in DB pool',
            });
            eventBus.on(events_1.DB_POOL_UPDATE, (data) => {
                dbPoolFree.set(data.free);
                dbPoolUsed.set(data.used);
                dbPoolPendingCreates.set(data.pendingCreates);
                dbPoolPendingAcquires.set(data.pendingAcquires);
            });
            await schedulerService.schedule(async () => this.registerPoolMetrics.bind(this, db.client.pool, eventBus), (0, date_fns_1.minutesToMilliseconds)(1), 'registerPoolMetrics', 0);
        }
    }
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    registerPoolMetrics(pool, eventBus) {
        try {
            eventBus.emit(events_1.DB_POOL_UPDATE, {
                used: pool.numUsed(),
                free: pool.numFree(),
                pendingCreates: pool.numPendingCreates(),
                pendingAcquires: pool.numPendingAcquires(),
            });
            // eslint-disable-next-line no-empty
        }
        catch (e) { }
    }
    async resolveEnvironmentType(environment, cachedEnvironments) {
        const environments = await cachedEnvironments();
        const env = environments.find((e) => e.name === environment);
        if (env) {
            return env.type;
        }
        else {
            return 'unknown';
        }
    }
}
exports.default = MetricsMonitor;
function createMetricsMonitor() {
    return new MetricsMonitor();
}
exports.createMetricsMonitor = createMetricsMonitor;
module.exports = {
    createMetricsMonitor,
};
//# sourceMappingURL=metrics.js.map