"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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __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.metrics = exports.recordRedisCommandMetric = exports.retrieveCost = exports.dataProviderMetricsLabel = exports.buildHttpRequestMetricsLabel = exports.Metrics = exports.buildMetricsMiddleware = exports.setupMetrics = exports.CMD_SENT_STATUS = void 0;
exports.setupMetricsServer = setupMetricsServer;
const fastify_1 = __importDefault(require("fastify"));
const path_1 = require("path");
const client = __importStar(require("prom-client"));
const index_1 = require("../index");
const util_1 = require("../util");
const error_1 = require("../validation/error");
const constants_1 = require("./constants");
const logger = (0, util_1.makeLogger)('Metrics');
var CMD_SENT_STATUS;
(function (CMD_SENT_STATUS) {
    CMD_SENT_STATUS["TIMEOUT"] = "TIMEOUT";
    CMD_SENT_STATUS["FAIL"] = "FAIL";
    CMD_SENT_STATUS["SUCCESS"] = "SUCCESS";
})(CMD_SENT_STATUS || (exports.CMD_SENT_STATUS = CMD_SENT_STATUS = {}));
function setupMetricsServer(name, adapterSettings) {
    const mTLSOptions = (0, index_1.getTLSOptions)(adapterSettings);
    const metricsApp = (0, fastify_1.default)({
        ...mTLSOptions,
        logger: false,
    });
    const metricsPort = adapterSettings.METRICS_PORT;
    const endpoint = adapterSettings.METRICS_USE_BASE_URL
        ? (0, path_1.join)(adapterSettings.BASE_URL, 'metrics')
        : '/metrics';
    const eaHost = adapterSettings.EA_HOST;
    logger.info(`Metrics endpoint: http://${eaHost}:${metricsPort}${endpoint}`);
    (0, exports.setupMetrics)(name);
    metricsApp.get(endpoint, async (_, res) => {
        logger.trace('Metrics endpoint hit');
        res.type('txt');
        res.send(await client.register.metrics());
    });
    return metricsApp;
}
const setupMetrics = (name) => {
    client.collectDefaultMetrics();
    client.register.setDefaultLabels({
        app_name: name || 'N/A',
        app_version: process.env['npm_package_version'],
    });
};
exports.setupMetrics = setupMetrics;
/**
 * Builds metrics middleware that records end to end EA response times
 * and count of requests
 *
 * @returns the cache middleware function
 */
const buildMetricsMiddleware = (rawReq, res, done) => {
    const req = rawReq;
    // The request context can technically be empty if the input validation failed
    const feedId = req.requestContext?.meta?.metrics?.feedId || 'N/A';
    const labels = (0, exports.buildHttpRequestMetricsLabel)(feedId, req.requestContext?.meta?.error, req.requestContext?.meta?.metrics?.cacheHit, res.statusCode);
    // Record number of requests sent to EA
    exports.metrics.get('httpRequestsTotal').labels(labels).inc();
    // Record response time of request through entire EA
    exports.metrics.get('httpRequestDurationSeconds').observe(res.elapsedTime / 1000);
    (0, util_1.censorLogs)(() => logger.debug(`Response time for ${feedId}: ${res.elapsedTime}ms`));
    const requestSize = Buffer.byteLength(JSON.stringify(req.body));
    exports.metrics.get('requestPayloadSize').observe(requestSize);
    done();
};
exports.buildMetricsMiddleware = buildMetricsMiddleware;
class Metrics {
    constructor(metricsDefinition) {
        this.metricsDefinition = metricsDefinition;
    }
    // Register metrics and set the metrics map for use during runtime
    // Ideally called on adapter startup to avoid metrics conflicts
    initialize() {
        if (!this.metrics) {
            this.metrics = this.metricsDefinition();
        }
    }
    getMetricsDefinition() {
        return this.metrics;
    }
    get(name) {
        const metric = this.metrics?.[name];
        if (!metric) {
            throw new Error(`Metric "${name}" was not initialized before use`);
        }
        return metric;
    }
    clear() {
        client.register.clear();
        this.metrics = undefined;
    }
}
exports.Metrics = Metrics;
const httpRequestsTotalLabels = [
    'method',
    'status_code',
    'retry',
    'type',
    'feed_id',
    'provider_status_code',
];
const cacheMetricsLabels = ['feed_id', 'participant_id', 'cache_type'];
const buildHttpRequestMetricsLabel = (feedId, error, cacheHit, responseStatusCode) => {
    const labels = {};
    labels.method = 'POST';
    labels.feed_id = feedId;
    if (error instanceof error_1.AdapterError) {
        // If error present and an instace of AdapterError, build label from error info
        labels.type = error?.metricsLabel || constants_1.HttpRequestType.ADAPTER_ERROR;
        labels.status_code = error?.statusCode;
        labels.provider_status_code = error?.providerStatusCode;
    }
    else if (error instanceof Error) {
        // If error present and not instance of generic Error, unexpected failure occurred
        labels.type = constants_1.HttpRequestType.ADAPTER_ERROR;
        labels.status_code = 500;
    }
    else {
        // If no error present, request went as expected
        labels.status_code = responseStatusCode || 200;
        if (cacheHit) {
            labels.type = constants_1.HttpRequestType.CACHE_HIT;
        }
        else {
            labels.type = constants_1.HttpRequestType.DATA_PROVIDER_HIT;
            labels.provider_status_code = 200;
        }
    }
    return labels;
};
exports.buildHttpRequestMetricsLabel = buildHttpRequestMetricsLabel;
// Data Provider Requests Metrics
const dataProviderMetricsLabel = (providerStatusCode, method = 'get') => ({
    provider_status_code: providerStatusCode,
    method: method.toUpperCase(),
});
exports.dataProviderMetricsLabel = dataProviderMetricsLabel;
// Retrieve cost field from response if exists
// If not return default cost of 1
const retrieveCost = (data) => {
    const cost = data?.['cost'];
    if (typeof cost === 'number' || typeof cost === 'string') {
        return Number(cost);
    }
    else {
        return 1;
    }
};
exports.retrieveCost = retrieveCost;
const recordRedisCommandMetric = (status, functionName) => exports.metrics
    .get('redisCommandsSentCount')
    .labels({ status: CMD_SENT_STATUS[status], function_name: functionName })
    .inc();
exports.recordRedisCommandMetric = recordRedisCommandMetric;
exports.metrics = new Metrics(() => ({
    httpRequestsTotal: new client.Counter({
        name: 'http_requests_total',
        help: 'The number of http requests this external adapter has serviced for its entire uptime',
        labelNames: httpRequestsTotalLabels,
    }),
    httpRequestDurationSeconds: new client.Histogram({
        name: 'http_request_duration_seconds',
        help: 'A histogram bucket of the distribution of http request durations',
        buckets: constants_1.requestDurationBuckets,
    }),
    httpRequestsPerBgExecute: new client.Gauge({
        name: 'http_requests_per_bg_execute',
        help: 'The number of HTTP requests made in a single background execute cycle',
        labelNames: ['adapter_endpoint'],
    }),
    dataProviderRequests: new client.Counter({
        name: 'data_provider_requests',
        help: 'The number of http requests that are made to a data provider',
        labelNames: ['method', 'provider_status_code'],
    }),
    dataProviderRequestDurationSeconds: new client.Histogram({
        name: 'data_provider_request_duration_seconds',
        help: 'A histogram bucket of the distribution of data provider request durations',
        buckets: constants_1.requestDurationBuckets,
    }),
    requesterQueueSize: new client.Gauge({
        name: 'requester_queue_size',
        help: 'The number of provider http requests currently queued to be executed',
    }),
    requesterQueueOverflow: new client.Counter({
        name: 'requester_queue_overflow',
        help: 'Total times the requester queue replaced the oldest item to avoid an overflow',
    }),
    requestPayloadSize: new client.Histogram({
        name: 'request_payload_size',
        help: 'A histogram bucket of the distribution of incoming request payload size',
        buckets: [100, 500, 1000, 2000, 5000],
    }),
    bgExecuteSubscriptionSetCount: new client.Gauge({
        name: 'bg_execute_subscription_set_count',
        help: 'The number of active subscriptions in background execute',
        labelNames: ['adapter_endpoint', 'transport_type', 'transport'],
    }),
    bgExecuteTotal: new client.Counter({
        name: 'bg_execute_total',
        help: 'The number of background executes performed per endpoint',
        labelNames: ['adapter_endpoint', 'transport'],
    }),
    bgExecuteErrors: new client.Counter({
        name: 'bg_execute_errors',
        help: 'The number of background execute errors per endpoint x transport',
        labelNames: ['adapter_endpoint', 'transport'],
    }),
    bgExecuteDurationSeconds: new client.Gauge({
        name: 'bg_execute_duration_seconds',
        help: 'A histogram bucket of the distribution of background execute durations',
        labelNames: ['adapter_endpoint', 'transport'],
    }),
    cacheDataGetCount: new client.Counter({
        name: 'cache_data_get_count',
        help: 'A counter that increments every time a value is fetched from the cache',
        labelNames: cacheMetricsLabels,
    }),
    cacheDataGetValues: new client.Gauge({
        name: 'cache_data_get_values',
        help: 'A gauge keeping track of values being fetched from cache',
        labelNames: cacheMetricsLabels,
    }),
    cacheDataMaxAge: new client.Gauge({
        name: 'cache_data_max_age',
        help: 'A gauge tracking the max age of stored values in the cache',
        labelNames: cacheMetricsLabels,
    }),
    cacheDataSetCount: new client.Counter({
        name: 'cache_data_set_count',
        help: 'A counter that increments every time a value is set to the cache',
        labelNames: [...cacheMetricsLabels, 'status_code'],
    }),
    cacheDataStalenessSeconds: new client.Gauge({
        name: 'cache_data_staleness_seconds',
        help: 'Observes the cache staleness of the data returned (i.e., time since the data was written to the cache)',
        labelNames: cacheMetricsLabels,
    }),
    cacheOverflowCount: new client.Counter({
        name: 'cache_overflow_count',
        help: 'A counter that increments every time an item overflows in local cache',
    }),
    totalDataStalenessSeconds: new client.Gauge({
        name: 'total_data_staleness_seconds',
        help: 'Observes the total staleness of the data returned (i.e., time since the provider indicated the data was sent)',
        labelNames: cacheMetricsLabels,
    }),
    providerTimeDelta: new client.Gauge({
        name: 'provider_time_delta',
        help: 'Measures the difference between the time indicated by a DP for a value vs the time it was written to cache',
        labelNames: ['feed_id'],
    }),
    redisConnectionsOpen: new client.Counter({
        name: 'redis_connections_open',
        help: 'The number of redis connections that are open',
    }),
    redisRetriesCount: new client.Counter({
        name: 'redis_retries_count',
        help: 'The number of retries that have been made to establish a redis connection',
    }),
    redisCommandsSentCount: new client.Counter({
        name: 'redis_commands_sent_count',
        help: 'The number of redis commands sent',
        labelNames: ['status', 'function_name'],
    }),
    streamHandlerErrors: new client.Counter({
        name: 'stream_handler_errors',
        help: 'The number of stream handler errors per endpoint x transport',
        labelNames: ['adapter_endpoint', 'transport'],
    }),
    cacheWarmerCount: new client.Gauge({
        name: 'cache_warmer_get_count',
        help: 'The number of cache warmers running',
        labelNames: ['isBatched'],
    }),
    wsConnectionAttempt: new client.Counter({
        name: 'ws_connection_attempt',
        help: 'The number of open connection attempts',
    }),
    wsConnectionActive: new client.Gauge({
        name: 'ws_connection_active',
        help: 'The number of active connections',
    }),
    wsConnectionErrors: new client.Counter({
        name: 'ws_connection_errors',
        help: 'The number of connection errors',
        labelNames: ['message'],
    }),
    wsConnectionClosures: new client.Counter({
        name: 'ws_connection_closures',
        help: 'The number of connection closures',
        labelNames: ['url', 'code'],
    }),
    wsSubscriptionActive: new client.Gauge({
        name: 'ws_subscription_active',
        help: 'The number of currently active subscriptions',
        labelNames: ['feed_id', 'subscription_key'],
    }),
    wsSubscriptionTotal: new client.Counter({
        name: 'ws_subscription_total',
        help: 'The number of subscriptions opened in total',
        labelNames: ['feed_id', 'subscription_key'],
    }),
    wsMessageTotal: new client.Counter({
        name: 'ws_message_total',
        help: 'The number of messages sent in total',
        labelNames: ['feed_id', 'subscription_key', 'direction'],
    }),
    transportPollingFailureCount: new client.Counter({
        name: 'transport_polling_failure_count',
        help: 'The number of times the polling mechanism ran out of attempts and failed to return a response',
        labelNames: ['adapter_endpoint'],
    }),
    transportPollingDurationSeconds: new client.Gauge({
        name: 'transport_polling_duration_seconds',
        help: 'A histogram bucket of the distribution of transport polling idle time durations',
        labelNames: ['adapter_endpoint', 'succeeded'],
    }),
    rateLimitCreditsSpentTotal: new client.Counter({
        name: 'rate_limit_credits_spent_total',
        help: 'The number of data provider credits the adapter is consuming',
        labelNames: ['participant_id', 'feed_id'],
    }),
    porBalanceAddressLength: new client.Gauge({
        name: 'por_balance_address_length',
        help: 'The number of addresses in PoR request input parameters',
        labelNames: ['feed_id'],
    }),
}));
//# sourceMappingURL=index.js.map