"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.otel = exports.MultiSpanExporter = exports.MultiMetricExporter = void 0;
const v8 = require('v8');
var fs = require('fs');
const os = require("os");
const Config_1 = require("../Config");
const openflow_api_1 = require("@openiap/openflow-api");
const sdk_metrics_base_1 = require("@opentelemetry/sdk-metrics-base");
const api_1 = require("@opentelemetry/api");
const core_1 = require("@opentelemetry/core");
const opentelemetry = require("@opentelemetry/sdk-node");
const Logger_1 = require("../Logger");
const context_utils_1 = require("@opentelemetry/api/build/src/trace/context-utils");
const exporter_trace_otlp_grpc_1 = require("@opentelemetry/exporter-trace-otlp-grpc");
const exporter_metrics_otlp_grpc_1 = require("@opentelemetry/exporter-metrics-otlp-grpc");
// import { OTLPTraceExporter as OTLPHTTPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
// import { OTLPMetricExporter as OTLPHTTPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
const { Resource } = require('@opentelemetry/resources');
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
const api_2 = require("@opentelemetry/api");
const Message_1 = require("../Messages/Message");
const Crypt_1 = require("../Crypt");
class MultiMetricExporter {
    constructor(exporters) {
        this.isshutdown = false;
        this.exporters = exporters;
    }
    addExporter(exporter) {
        this.exporters.push(exporter);
    }
    export(metrics, callback) {
        if (!this.exporters)
            return;
        if (this.exporters.length == 0)
            return;
        for (let i = 0; this.exporters.length > i; i++) {
            try {
                this.exporters[i].export(metrics, callback);
            }
            catch (error) {
                console.error(error);
            }
        }
    }
    async shutdown() {
        this.isshutdown = true;
        await Promise.all(this.exporters.map((e) => e.shutdown()));
    }
    async forceFlush() {
        this.exporters.forEach((exporter) => {
        });
    }
    selectAggregationTemporality(instrumentType) {
        if (!this.exporters)
            return;
        if (this.exporters.length == 0)
            return;
        for (let i = 0; this.exporters.length > i; i++) {
            try {
                return this.exporters[i].selectAggregationTemporality(instrumentType);
            }
            catch (error) {
                console.error(error);
            }
        }
    }
}
exports.MultiMetricExporter = MultiMetricExporter;
class MultiSpanExporter {
    constructor(exporters) {
        this.exporters = exporters;
    }
    addExporter(exporter) {
        this.exporters.push(exporter);
    }
    export(spans, callback) {
        if (!this.exporters)
            return;
        if (this.exporters.length == 0)
            return;
        for (let i = 0; this.exporters.length > i; i++) {
            try {
                this.exporters[i].export(spans, callback);
            }
            catch (error) {
                console.error(error);
            }
        }
    }
    async shutdown() {
        await Promise.all(this.exporters.map((e) => e.shutdown()));
    }
}
exports.MultiSpanExporter = MultiSpanExporter;
class otel {
    constructor() {
        this.default_boundaries = [0.0001, 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 75, 100];
        this.sdk = null;
        this.defaultlabels = {};
    }
    static async configure() {
        var _a, _b, _c;
        let hasLicense = true;
        try {
            Logger_1.Logger.License.validate();
        }
        catch (error) {
            hasLicense = false;
            // Logger.instanse.debug("otel", "configure", error.message ? error.message : error);
            Logger_1.Logger.instanse.info(error.message ? error.message : error, null);
        }
        if (otel.instance != null)
            return otel.instance;
        otel.instance = new otel();
        if (Config_1.Config.otel_debug_log) {
            api_1.diag.setLogger(new api_1.DiagConsoleLogger(), api_1.DiagLogLevel.DEBUG);
            // diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
        }
        else if (Config_1.Config.otel_warn_log) {
            api_1.diag.setLogger(new api_1.DiagConsoleLogger(), api_1.DiagLogLevel.WARN);
        }
        else if (Config_1.Config.otel_err_log) {
            api_1.diag.setLogger(new api_1.DiagConsoleLogger(), api_1.DiagLogLevel.ERROR);
        }
        const metricExporters = [];
        const spanExporters = [];
        if (hasLicense) {
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(Config_1.Config.otel_metric_url)) {
                metricExporters.push(new exporter_metrics_otlp_grpc_1.OTLPMetricExporter({
                    url: Config_1.Config.otel_metric_url
                }));
                Logger_1.Logger.instanse.info("Added local metric exporter for " + Config_1.Config.otel_metric_url, null);
            }
            if (!openflow_api_1.NoderedUtil.IsNullEmpty(Config_1.Config.otel_trace_url)) {
                spanExporters.push(new exporter_trace_otlp_grpc_1.OTLPTraceExporter({
                    url: Config_1.Config.otel_trace_url
                }));
                Logger_1.Logger.instanse.info("Added local span exporter for " + Config_1.Config.otel_trace_url, null);
            }
        }
        else if (!openflow_api_1.NoderedUtil.IsNullEmpty(Config_1.Config.otel_metric_url) || !openflow_api_1.NoderedUtil.IsNullEmpty(Config_1.Config.otel_trace_url)) {
            Logger_1.Logger.instanse.warn("otel settings ignored due to missing license", null);
        }
        // https://github.com/open-telemetry/opentelemetry-js/issues/2271
        if (Config_1.Config.enable_analytics) {
            Logger_1.Logger.instanse.verbose("analytics enabled, add exporters", null);
            // 4317 Default endpoint for OpenTelemetry gRPC receiver.
            // 4318 Default endpoint for OpenTelemetry HTTP receiver
            // url: "http://otel.stats.openiap.io:80",
            // credentials: grpc.credentials.createSsl()
            // url: "http://104.199.62.215:4317/v1/metrics"
            // url: "http://104.199.62.215:4318/v1/metrics"
            // url: "http://otel.stats.openiap.io:80/v1/metrics",
            // url: "https://exporter.stats.openiap.io:443/v1/metrics"
            let baseurl = "https://otel.stats.openiap.io:443";
            let usehttp = false;
            if (usehttp) {
                // metricExporters.push(new OTLPHTTPMetricExporter({
                //     url: baseurl + "/v1/metrics"
                // }));
                // spanExporters.push(new OTLPHTTPTraceExporter({
                //     url: baseurl + "/v1/trace"
                // }));
            }
            else {
                // const grpc = require("@grpc/grpc-js");
                // credentials: grpc.credentials.createSsl()
                // credentials: grpc.credentials.createInsecure()
                metricExporters.push(new exporter_metrics_otlp_grpc_1.OTLPMetricExporter({
                    url: baseurl + "/v1/metrics"
                }));
                // spanExporters.push(new OTLPTraceExporter({
                //     url: baseurl + "/v1/trace"
                // }));
            }
        }
        else {
            Logger_1.Logger.instanse.warn("analytics disabled, please consider enabling this, to help us improve the product!", null);
        }
        Logger_1.Logger.instanse.verbose("setdefaultlabels", null);
        otel.instance.setdefaultlabels();
        if (spanExporters.length > 0) {
            this.traceExporter = new MultiSpanExporter(spanExporters);
        }
        if (metricExporters.length > 0) {
            this.metricExporter = new MultiMetricExporter(metricExporters);
        }
        otel.instance.meterprovider = new sdk_metrics_base_1.MeterProvider({
            resource: new Resource({
                ...otel.instance.defaultlabels,
                [semantic_conventions_1.SemanticResourceAttributes.SERVICE_NAME]: 'openflow'
            }),
            views: [
                new sdk_metrics_base_1.View({
                    aggregation: new sdk_metrics_base_1.ExplicitBucketHistogramAggregation(otel.instance.default_boundaries),
                    instrumentType: sdk_metrics_base_1.InstrumentType.HISTOGRAM
                })
            ]
        });
        otel.instance.meter = otel.instance.meterprovider.getMeter("default");
        const metricReader = new sdk_metrics_base_1.PeriodicExportingMetricReader({
            exporter: this.metricExporter,
            exportIntervalMillis: 5000,
            exportTimeoutMillis: 5000,
        });
        otel.instance.meterprovider.addMetricReader(metricReader);
        otel.instance.sdk = new opentelemetry.NodeSDK({
            resource: new Resource({
                ...otel.instance.defaultlabels,
                [semantic_conventions_1.SemanticResourceAttributes.SERVICE_NAME]: 'openflow'
            }),
            instrumentations: [],
            traceExporter: this.traceExporter,
            // metricReader: metricReader as any
            // metricReader: new PeriodicExportingMetricReader({
            //     exporter: this.metricExporter,
            //     exportIntervalMillis: 5000,
            //     exportTimeoutMillis: 5000,
            // }) as any
        });
        if (otel.instance.meter) {
            otel.nodejs_heap_size_used_bytes = otel.instance.meter.createObservableUpDownCounter('nodejs_heap_size_used_bytes', {
                description: 'Process heap size used from Node.js in bytes'
            });
            otel.nodejs_heap_size_total_bytes = otel.instance.meter.createObservableUpDownCounter('nodejs_heap_size_total_bytes', {
                description: 'Process heap size from Node.js in bytes'
            });
            if (otel.perfTimeout)
                clearInterval(otel.perfTimeout);
            (_a = otel.nodejs_heap_size_used_bytes) === null || _a === void 0 ? void 0 : _a.addCallback((res) => {
                var stats = v8.getHeapStatistics();
                res.observe(stats.used_heap_size, { ...otel.instance.defaultlabels, domain: Config_1.Config.domain });
                // const memUsage = process.memoryUsage();
                // console.log(memUsage.heapUsed, stats)
                // res.observe(memUsage.heapUsed, { ...otel.instance.defaultlabels, domain: Config.domain });
            });
            (_b = otel.nodejs_heap_size_total_bytes) === null || _b === void 0 ? void 0 : _b.addCallback((res) => {
                var stats = v8.getHeapStatistics();
                res.observe(stats.total_heap_size, { ...otel.instance.defaultlabels, domain: Config_1.Config.domain });
                // const memUsage = process.memoryUsage();
                // console.log(memUsage.heapTotal, stats)
                // res.observe(memUsage.heapTotal, { ...otel.instance.defaultlabels, domain: Config.domain });
            });
            otel.nodejs_total_available_size = otel.instance.meter.createObservableUpDownCounter('nodejs_total_available_size');
            (_c = otel.nodejs_total_available_size) === null || _c === void 0 ? void 0 : _c.addCallback((res) => {
                var stats = v8.getHeapStatistics();
                res.observe(stats.total_available_size, otel.instance.defaultlabels);
            });
        }
        await otel.instance.sdk.start();
        console.log('Tracing initialized');
        return otel.instance;
    }
    createheapdump(parent) {
        return new Promise((resolve, reject) => {
            Logger_1.Logger.instanse.info("createheapdump", parent);
            const hostname = (Config_1.Config.getEnv("HOSTNAME", undefined) || os.hostname()) || "unknown";
            const filename = `${hostname}.${Date.now()}.heapsnapshot`;
            const inspector = require('node:inspector');
            const fs = require('node:fs');
            const session = new inspector.Session();
            const fd = fs.openSync(filename, 'w');
            session.connect();
            session.on('HeapProfiler.addHeapSnapshotChunk', (m) => {
                fs.writeSync(fd, m.params.chunk);
            });
            session.post('HeapProfiler.takeHeapSnapshot', null, async (err, r) => {
                Logger_1.Logger.instanse.info("createheapdump completed", parent);
                try {
                    session.disconnect();
                    fs.closeSync(fd);
                }
                catch (error) {
                }
                if (err) {
                    reject(err);
                    return;
                }
                var msg = new Message_1.Message();
                // var file = readFileSync(filename); // Buffer, will block event loop
                var file = fs.createReadStream(filename); // Stream, will not block event loop
                Logger_1.Logger.instanse.info("Uploading " + filename, parent);
                var id = await msg._addFile(file, filename, "application/json", null, false, Crypt_1.Crypt.rootToken());
                Logger_1.Logger.instanse.info("uploaded " + filename + " as " + id, parent);
                fs.unlinkSync(filename);
                resolve(id);
            });
            // const res = []
            // const getChunk = (m) => {
            //     res.push(m.params.chunk)
            // }
            // otel.session.on('HeapProfiler.addHeapSnapshotChunk', getChunk)
            // otel.session.post('HeapProfiler.takeHeapSnapshot', null, (err, r) => {
            //     otel.session.removeListener('HeapProfiler.addHeapSnapshotChunk', getChunk)
            //     if (err) return reject(err)
            //     const hostname = (Config.getEnv("HOSTNAME", undefined) || os.hostname()) || "unknown";
            //     const filename = `${hostname}.${Date.now()}.heapsnapshot`;
            //     var msg = new Message();
            //     utils.writeData(JSON.parse(res.join('')), fileName, this.config, this.s3Client).then((data) => {
            //         resolve(data)
            //     }).catch(err => reject(err))
            // })
        });
    }
    registerurl(metric_url, trace_url) {
        if (otel.metricExporter) {
            otel.metricExporter.addExporter(new exporter_metrics_otlp_grpc_1.OTLPMetricExporter({
                url: metric_url
            }));
        }
        if (otel.traceExporter) {
            otel.traceExporter.addExporter(new exporter_trace_otlp_grpc_1.OTLPTraceExporter({
                url: trace_url
            }));
        }
    }
    async shutdown() {
        Logger_1.Logger.instanse.verbose("shutdown started", null);
        if (otel.perfTimeout)
            clearInterval(otel.perfTimeout);
        try {
            // if (otel.instance.traceprovider) await otel.instance.traceprovider.shutdown();
        }
        catch (error) {
            Logger_1.Logger.instanse.error(error, null);
        }
        try {
            if (otel.instance.meterprovider)
                await otel.instance.meterprovider.shutdown();
        }
        catch (error) {
            Logger_1.Logger.instanse.error(error, null);
        }
        try {
            if (otel.traceExporter)
                await otel.traceExporter.shutdown();
        }
        catch (error) {
            Logger_1.Logger.instanse.error(error, null);
        }
        try {
            if (otel.metricExporter)
                await otel.metricExporter.shutdown();
        }
        catch (error) {
            Logger_1.Logger.instanse.error(error, null);
        }
        try {
            await otel.instance.sdk.shutdown();
        }
        catch (error) {
            Logger_1.Logger.instanse.error(error, null);
        }
        Logger_1.Logger.instanse.verbose("shutdown complete", null);
    }
    GetTraceSpanId(span) {
        if (span == null)
            return [null, null];
        // @ts-ignore
        if (span._spanContext == null)
            return [null, null];
        // @ts-ignore
        const { traceId, spanId } = span._spanContext;
        return [traceId, spanId];
    }
    startSpanExpress(name, req) {
        if (req == null)
            return this.startSpan(name, null, null);
        var traceId = "";
        var spanId = "";
        let span = null;
        // @ts-ignore
        if (req.span != null) {
            // @ts-ignore
            span = req.span;
            [traceId, spanId] = this.GetTraceSpanId(span);
        }
        else if (req.headers["x-trace-id"] && req.headers["x-trace-id"]) {
            traceId = req.header["x-trace-id"];
            spanId = req.header["x-span-id"];
        }
        if (span == null) {
            span = this.startSpan(req.method + " " + req.url, traceId, spanId);
        }
        else {
            span = this.startSubSpan(name, span);
        }
        span === null || span === void 0 ? void 0 : span.setAttribute("remoteip", otel.remoteip(req));
        span === null || span === void 0 ? void 0 : span.setAttribute("url", req.url);
        span === null || span === void 0 ? void 0 : span.setAttribute("method", req.method);
        // @ts-ignore
        req.span = span;
        span.addEvent(name);
        return span;
    }
    startSpan(name, traceId, spanId) {
        // if (NoderedUtil.IsNullUndefinded(otel.instance)) return otel.fakespan as any;
        if (!openflow_api_1.NoderedUtil.IsNullEmpty(traceId) && !openflow_api_1.NoderedUtil.IsNullEmpty(spanId)) {
            const spanContext = {
                traceId: traceId,
                spanId: spanId,
                traceFlags: api_2.TraceFlags.SAMPLED,
            };
            const context = api_2.trace.setSpanContext(api_2.ROOT_CONTEXT, spanContext);
            const tracer = opentelemetry.api.trace.getTracer('default');
            return tracer.startSpan(name, {}, context);
        }
        else {
            const tracer = opentelemetry.api.trace.getTracer('default');
            return tracer.startSpan(name, {}, opentelemetry.api.context.active());
        }
        // const tracer = opentelemetry.api.trace.getTracer('default');
        // let span = tracer.startSpan(name, {}, opentelemetry.api.context.active());
        // span?.setAttributes(this.defaultlabels);
        // return span;
    }
    startSubSpan(name, parent) {
        // parent?.addEvent(name);
        // return parent;
        if (parent == null || parent.isRecording() == undefined)
            return otel.fakespan;
        if (openflow_api_1.NoderedUtil.IsNullUndefinded(parent))
            return null;
        // if (NoderedUtil.IsNullUndefinded(parent)) return this.startSpan(name);
        const ctx = (0, context_utils_1.setSpan)(opentelemetry.api.context.active(), parent);
        const span = opentelemetry.api.trace.getTracer('default').startSpan(name, undefined, ctx);
        span === null || span === void 0 ? void 0 : span.setAttributes(this.defaultlabels);
        return span;
    }
    endSpan(span) {
        try {
            if (!openflow_api_1.NoderedUtil.IsNullUndefinded(span))
                span === null || span === void 0 ? void 0 : span.end();
            // span = null;
        }
        catch (error) {
            Logger_1.Logger.instanse.error(error, null);
        }
    }
    startTimer() {
        return (0, core_1.hrTime)();
    }
    endTimer(startTime, recorder, labels) {
        if (openflow_api_1.NoderedUtil.IsNullUndefinded(otel.instance))
            return undefined;
        if (openflow_api_1.NoderedUtil.IsNullUndefinded(recorder))
            return undefined;
        const performanceTimeOriginms = (0, core_1.hrTimeToMilliseconds)(startTime);
        const Milliseconds = ((0, core_1.hrTimeToMilliseconds)((0, core_1.hrTime)()) - performanceTimeOriginms);
        const Seconds = Milliseconds / 1000;
        recorder.record(Seconds, { ...otel.instance.defaultlabels, ...labels });
        return Milliseconds;
    }
    setdefaultlabels() {
        const hostname = (Config_1.Config.getEnv("HOSTNAME", undefined) || os.hostname()) || "unknown";
        this.defaultlabels["ofid"] = Logger_1.Logger.License.ofid(false);
        Logger_1.Logger.instanse.info("ofid=" + Logger_1.Logger.License.ofid(false), null);
        if (!openflow_api_1.NoderedUtil.IsNullEmpty(hostname)) {
            this.defaultlabels["hostname"] = hostname;
        }
    }
    static remoteip(req) {
        let remoteip = req.socket.remoteAddress;
        if (req.headers["X-Forwarded-For"] != null)
            remoteip = req.headers["X-Forwarded-For"];
        if (req.headers["X-real-IP"] != null)
            remoteip = req.headers["X-real-IP"];
        if (req.headers["x-forwarded-for"] != null)
            remoteip = req.headers["x-forwarded-for"];
        if (req.headers["x-real-ip"] != null)
            remoteip = req.headers["x-real-ip"];
        return remoteip;
    }
}
exports.otel = otel;
otel.fakespan = {
    context: () => undefined,
    setAttribute: () => undefined,
    setAttributes: () => undefined,
    addEvent: () => undefined,
    setStatus: () => undefined,
    updateName: () => undefined,
    end: () => undefined,
    isRecording: () => undefined,
    recordException: () => undefined,
};
function formatBytes(bytes, decimals = 2) {
    if (bytes === 0)
        return '0 Bytes';
    const k = 1024;
    const dm = decimals < 0 ? 0 : decimals;
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
//# sourceMappingURL=otel.js.map