"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var os = require("os");
var EnvelopeFactory = require("../Library/EnvelopeFactory");
var Logging = require("../Library/Logging");
var Sender = require("../Library/Sender");
var Constants = require("../Declarations/Constants");
var Contracts = require("../Declarations/Contracts");
var Vm = require("../Library/AzureVirtualMachine");
var Config = require("../Library/Config");
var Context = require("../Library/Context");
var Network = require("./NetworkStatsbeat");
var Util = require("../Library/Util");
var STATSBEAT_LANGUAGE = "node";
var Statsbeat = /** @class */ (function () {
    function Statsbeat(config, context) {
        this._attach = Constants.StatsbeatAttach.sdk; // Default is SDK
        this._feature = Constants.StatsbeatFeature.NONE;
        this._instrumentation = Constants.StatsbeatInstrumentation.NONE;
        this._isInitialized = false;
        this._statbeatMetrics = [];
        this._networkStatsbeatCollection = [];
        this._config = config;
        this._context = context || new Context();
        this._longHandleTimeout = null;
        var statsbeatConnectionString = this._getConnectionString(config);
        this._statsbeatConfig = new Config(statsbeatConnectionString);
        this._statsbeatConfig.samplingPercentage = 100; // Do not sample
        this._sender = new Sender(this._statsbeatConfig, null, null, null, null, true, this._shutdownStatsbeat.bind(this));
    }
    Statsbeat.prototype.enable = function (isEnabled) {
        var _this = this;
        this._isEnabled = isEnabled;
        if (this._isEnabled && !this._isInitialized) {
            this._getCustomProperties();
            this._isInitialized = true;
        }
        if (isEnabled) {
            if (!this._handle) {
                this._handle = setInterval(function () {
                    _this.trackShortIntervalStatsbeats();
                }, Statsbeat.STATS_COLLECTION_SHORT_INTERVAL);
                this._handle.unref(); // Allow the app to terminate even while this loop is going on
            }
            if (!this._longHandle) {
                // Add 15 second delay before first long interval statsbeat
                this._longHandleTimeout = setTimeout(function () {
                    if (_this.isEnabled()) {
                        _this.trackLongIntervalStatsbeats();
                    }
                }, Statsbeat.STATS_COLLECTION_INITIAL_DELAY);
                this._longHandleTimeout.unref(); // Allow the app to terminate even while this loop is going on
                this._longHandle = setInterval(function () {
                    _this.trackLongIntervalStatsbeats();
                }, Statsbeat.STATS_COLLECTION_LONG_INTERVAL);
                this._longHandle.unref(); // Allow the app to terminate even while this loop is going on
            }
        }
        else {
            if (this._handle) {
                clearInterval(this._handle);
                this._handle = null;
            }
            if (this._longHandle) {
                clearInterval(this._longHandle);
                this._longHandle = null;
            }
            if (this._longHandleTimeout) {
                clearTimeout(this._longHandleTimeout);
                this._longHandleTimeout = null;
            }
        }
    };
    Statsbeat.prototype.isInitialized = function () {
        return this._isInitialized;
    };
    Statsbeat.prototype.isEnabled = function () {
        return this._isEnabled;
    };
    Statsbeat.prototype.setCodelessAttach = function () {
        this._attach = Constants.StatsbeatAttach.codeless;
    };
    Statsbeat.prototype.addFeature = function (feature) {
        this._feature |= feature;
    };
    Statsbeat.prototype.removeFeature = function (feature) {
        this._feature &= ~feature;
    };
    Statsbeat.prototype.addInstrumentation = function (instrumentation) {
        this._instrumentation |= instrumentation;
    };
    Statsbeat.prototype.removeInstrumentation = function (instrumentation) {
        this._instrumentation &= ~instrumentation;
    };
    Statsbeat.prototype.countRequest = function (endpoint, host, duration, success, statusCode) {
        if (!this.isEnabled()) {
            return;
        }
        var counter = this._getNetworkStatsbeatCounter(endpoint, host);
        counter.totalRequestCount++;
        counter.intervalRequestExecutionTime += duration;
        if (success === false) {
            if (!statusCode) {
                return;
            }
            var currentStatusCounter = counter.totalFailedRequestCount.find(function (statusCounter) { return statusCode === statusCounter.statusCode; });
            if (currentStatusCounter) {
                currentStatusCounter.count++;
            }
            else {
                counter.totalFailedRequestCount.push({ statusCode: statusCode, count: 1 });
            }
        }
        else {
            counter.totalSuccesfulRequestCount++;
        }
    };
    Statsbeat.prototype.countException = function (endpoint, host, exceptionType) {
        if (!this.isEnabled()) {
            return;
        }
        var counter = this._getNetworkStatsbeatCounter(endpoint, host);
        var currentErrorCounter = counter.exceptionCount.find(function (exceptionCounter) { return exceptionType.name === exceptionCounter.exceptionType; });
        if (currentErrorCounter) {
            currentErrorCounter.count++;
        }
        else {
            counter.exceptionCount.push({ exceptionType: exceptionType.name, count: 1 });
        }
    };
    Statsbeat.prototype.countThrottle = function (endpoint, host, statusCode) {
        if (!this.isEnabled()) {
            return;
        }
        var counter = this._getNetworkStatsbeatCounter(endpoint, host);
        var currentStatusCounter = counter.throttleCount.find(function (statusCounter) { return statusCode === statusCounter.statusCode; });
        if (currentStatusCounter) {
            currentStatusCounter.count++;
        }
        else {
            counter.throttleCount.push({ statusCode: statusCode, count: 1 });
        }
    };
    Statsbeat.prototype.countRetry = function (endpoint, host, statusCode) {
        if (!this.isEnabled()) {
            return;
        }
        var counter = this._getNetworkStatsbeatCounter(endpoint, host);
        var currentStatusCounter = counter.retryCount.find(function (statusCounter) { return statusCode === statusCounter.statusCode; });
        if (currentStatusCounter) {
            currentStatusCounter.count++;
        }
        else {
            counter.retryCount.push({ statusCode: statusCode, count: 1 });
        }
    };
    Statsbeat.prototype.trackShortIntervalStatsbeats = function () {
        return __awaiter(this, void 0, void 0, function () {
            var networkProperties, error_1;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 3, , 4]);
                        return [4 /*yield*/, this._getResourceProvider()];
                    case 1:
                        _a.sent();
                        networkProperties = {
                            "os": this._os,
                            "rp": this._resourceProvider,
                            "cikey": this._cikey,
                            "runtimeVersion": this._runtimeVersion,
                            "language": this._language,
                            "version": this._sdkVersion,
                            "attach": this._attach
                        };
                        this._trackRequestDuration(networkProperties);
                        this._trackRequestsCount(networkProperties);
                        return [4 /*yield*/, this._sendStatsbeats()];
                    case 2:
                        _a.sent();
                        return [3 /*break*/, 4];
                    case 3:
                        error_1 = _a.sent();
                        Logging.info(Statsbeat.TAG, "Failed to send Statsbeat metrics: " + Util.dumpObj(error_1));
                        return [3 /*break*/, 4];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    Statsbeat.prototype.trackLongIntervalStatsbeats = function () {
        return __awaiter(this, void 0, void 0, function () {
            var commonProperties, attachProperties, instrumentationProperties, featureProperties, error_2;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        _a.trys.push([0, 3, , 4]);
                        return [4 /*yield*/, this._getResourceProvider()];
                    case 1:
                        _a.sent();
                        commonProperties = {
                            "os": this._os,
                            "rp": this._resourceProvider,
                            "cikey": this._cikey,
                            "runtimeVersion": this._runtimeVersion,
                            "language": this._language,
                            "version": this._sdkVersion,
                            "attach": this._attach
                        };
                        attachProperties = Object.assign({
                            "rpId": this._resourceIdentifier
                        }, commonProperties);
                        this._statbeatMetrics.push({ name: Constants.StatsbeatCounter.ATTACH, value: 1, properties: attachProperties });
                        if (this._instrumentation != Constants.StatsbeatInstrumentation.NONE) { // Only send if there are some instrumentations enabled
                            instrumentationProperties = Object.assign({ "feature": this._instrumentation, "type": Constants.StatsbeatFeatureType.Instrumentation }, commonProperties);
                            this._statbeatMetrics.push({ name: Constants.StatsbeatCounter.FEATURE, value: 1, properties: instrumentationProperties });
                        }
                        if (this._feature != Constants.StatsbeatFeature.NONE) { // Only send if there are some features enabled
                            featureProperties = Object.assign({ "feature": this._feature, "type": Constants.StatsbeatFeatureType.Feature }, commonProperties);
                            this._statbeatMetrics.push({ name: Constants.StatsbeatCounter.FEATURE, value: 1, properties: featureProperties });
                        }
                        return [4 /*yield*/, this._sendStatsbeats()];
                    case 2:
                        _a.sent();
                        return [3 /*break*/, 4];
                    case 3:
                        error_2 = _a.sent();
                        Logging.info(Statsbeat.TAG, "Failed to send Statsbeat metrics: " + Util.dumpObj(error_2));
                        return [3 /*break*/, 4];
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    Statsbeat.prototype._getNetworkStatsbeatCounter = function (endpoint, host) {
        var shortHost = this._getShortHost(host);
        // Check if counter is available
        for (var i = 0; i < this._networkStatsbeatCollection.length; i++) {
            // Same object
            if (endpoint === this._networkStatsbeatCollection[i].endpoint &&
                shortHost === this._networkStatsbeatCollection[i].host) {
                return this._networkStatsbeatCollection[i];
            }
        }
        // Create a new one if not found
        var newCounter = new Network.NetworkStatsbeat(endpoint, shortHost);
        this._networkStatsbeatCollection.push(newCounter);
        return newCounter;
    };
    Statsbeat.prototype._trackRequestDuration = function (commonProperties) {
        for (var i = 0; i < this._networkStatsbeatCollection.length; i++) {
            var currentCounter = this._networkStatsbeatCollection[i];
            currentCounter.time = +new Date;
            var intervalRequests = (currentCounter.totalRequestCount - currentCounter.lastRequestCount) || 0;
            var totalRequestExecutionTime = currentCounter.intervalRequestExecutionTime - currentCounter.lastIntervalRequestExecutionTime;
            var averageRequestExecutionTime = totalRequestExecutionTime > 0 ? (totalRequestExecutionTime / intervalRequests) || 0 : 0;
            currentCounter.lastIntervalRequestExecutionTime = currentCounter.intervalRequestExecutionTime; // reset
            if (intervalRequests > 0) {
                // Add extra properties
                var properties = Object.assign({
                    "endpoint": this._networkStatsbeatCollection[i].endpoint,
                    "host": this._networkStatsbeatCollection[i].host
                }, commonProperties);
                this._statbeatMetrics.push({
                    name: Constants.StatsbeatCounter.REQUEST_DURATION,
                    value: averageRequestExecutionTime,
                    properties: properties
                });
            }
            // Set last counters
            currentCounter.lastRequestCount = currentCounter.totalRequestCount;
            currentCounter.lastTime = currentCounter.time;
        }
    };
    Statsbeat.prototype._getShortHost = function (originalHost) {
        var shortHost = originalHost;
        try {
            var hostRegex = new RegExp(/^https?:\/\/(?:www\.)?([^\/.-]+)/);
            var res = hostRegex.exec(originalHost);
            if (res != null && res.length > 1) {
                shortHost = res[1];
            }
            shortHost = shortHost.replace(".in.applicationinsights.azure.com", "");
        }
        catch (error) {
            // Ignore error
        }
        return shortHost;
    };
    Statsbeat.prototype._trackRequestsCount = function (commonProperties) {
        var _this = this;
        var _loop_1 = function (i) {
            currentCounter = this_1._networkStatsbeatCollection[i];
            var properties = Object.assign({ "endpoint": currentCounter.endpoint, "host": currentCounter.host }, commonProperties);
            if (currentCounter.totalSuccesfulRequestCount > 0) {
                this_1._statbeatMetrics.push({
                    name: Constants.StatsbeatCounter.REQUEST_SUCCESS,
                    value: currentCounter.totalSuccesfulRequestCount,
                    properties: properties
                });
                currentCounter.totalSuccesfulRequestCount = 0; //Reset
            }
            if (currentCounter.totalFailedRequestCount.length > 0) {
                currentCounter.totalFailedRequestCount.forEach(function (currentCounter) {
                    properties = Object.assign(__assign(__assign({}, properties), { "statusCode": currentCounter.statusCode }));
                    _this._statbeatMetrics.push({
                        name: Constants.StatsbeatCounter.REQUEST_FAILURE,
                        value: currentCounter.count,
                        properties: properties
                    });
                });
                currentCounter.totalFailedRequestCount = []; //Reset
            }
            if (currentCounter.retryCount.length > 0) {
                currentCounter.retryCount.forEach(function (currentCounter) {
                    properties = Object.assign(__assign(__assign({}, properties), { "statusCode": currentCounter.statusCode }));
                    _this._statbeatMetrics.push({
                        name: Constants.StatsbeatCounter.RETRY_COUNT,
                        value: currentCounter.count,
                        properties: properties
                    });
                });
                currentCounter.retryCount = []; //Reset
            }
            if (currentCounter.throttleCount.length > 0) {
                currentCounter.throttleCount.forEach(function (currentCounter) {
                    properties = Object.assign(__assign(__assign({}, properties), { "statusCode": currentCounter.statusCode }));
                    _this._statbeatMetrics.push({
                        name: Constants.StatsbeatCounter.THROTTLE_COUNT,
                        value: currentCounter.count,
                        properties: properties
                    });
                });
                currentCounter.throttleCount = []; //Reset
            }
            if (currentCounter.exceptionCount.length > 0) {
                currentCounter.exceptionCount.forEach(function (currentCounter) {
                    properties = Object.assign(__assign(__assign({}, properties), { "exceptionType": currentCounter.exceptionType }));
                    _this._statbeatMetrics.push({
                        name: Constants.StatsbeatCounter.EXCEPTION_COUNT,
                        value: currentCounter.count,
                        properties: properties
                    });
                });
                currentCounter.exceptionCount = []; //Reset
            }
        };
        var this_1 = this, currentCounter;
        for (var i = 0; i < this._networkStatsbeatCollection.length; i++) {
            _loop_1(i);
        }
    };
    Statsbeat.prototype._sendStatsbeats = function () {
        return __awaiter(this, void 0, void 0, function () {
            var envelopes, i, statsbeat, envelope;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!(this._statbeatMetrics.length > 0)) return [3 /*break*/, 2];
                        envelopes = [];
                        for (i = 0; i < this._statbeatMetrics.length; i++) {
                            statsbeat = {
                                name: this._statbeatMetrics[i].name,
                                value: this._statbeatMetrics[i].value,
                                properties: this._statbeatMetrics[i].properties
                            };
                            envelope = EnvelopeFactory.createEnvelope(statsbeat, Contracts.TelemetryType.Metric, null, this._context, this._statsbeatConfig);
                            envelope.name = Constants.StatsbeatTelemetryName;
                            envelopes.push(envelope);
                        }
                        this._statbeatMetrics = [];
                        return [4 /*yield*/, this._sender.send(envelopes)];
                    case 1:
                        _a.sent();
                        _a.label = 2;
                    case 2: return [2 /*return*/];
                }
            });
        });
    };
    Statsbeat.prototype._getCustomProperties = function () {
        this._language = STATSBEAT_LANGUAGE;
        this._cikey = this._config.instrumentationKey;
        this._sdkVersion = Context.sdkVersion; // "node" or "node-nativeperf"
        this._os = os.type();
        this._runtimeVersion = process.version;
    };
    Statsbeat.prototype._getResourceProvider = function () {
        var _this = this;
        return new Promise(function (resolve, reject) {
            // Check resource provider
            var waiting = false;
            _this._resourceProvider = Constants.StatsbeatResourceProvider.unknown;
            _this._resourceIdentifier = Constants.StatsbeatResourceProvider.unknown;
            if (process.env.AKS_ARM_NAMESPACE_ID) { //AKS
                _this._resourceProvider = Constants.StatsbeatResourceProvider.aks;
                _this._resourceIdentifier = process.env.AKS_ARM_NAMESPACE_ID;
            }
            else if (process.env.WEBSITE_SITE_NAME) { // Web apps
                _this._resourceProvider = Constants.StatsbeatResourceProvider.appsvc;
                _this._resourceIdentifier = process.env.WEBSITE_SITE_NAME;
                if (process.env.WEBSITE_HOME_STAMPNAME) {
                    _this._resourceIdentifier += "/" + process.env.WEBSITE_HOME_STAMPNAME;
                }
            }
            else if (process.env.FUNCTIONS_WORKER_RUNTIME) { // Function apps
                _this._resourceProvider = Constants.StatsbeatResourceProvider.functions;
                if (process.env.WEBSITE_HOSTNAME) {
                    _this._resourceIdentifier = process.env.WEBSITE_HOSTNAME;
                }
            }
            else if (_this._config) {
                if (_this._isVM === undefined || _this._isVM == true) {
                    waiting = true;
                    Vm.AzureVirtualMachine.getAzureComputeMetadata(_this._config, function (vmInfo) {
                        _this._isVM = vmInfo.isVM;
                        if (_this._isVM) {
                            _this._resourceProvider = Constants.StatsbeatResourceProvider.vm;
                            _this._resourceIdentifier = vmInfo.id + "/" + vmInfo.subscriptionId;
                            // Override OS as VM info have higher precedence
                            if (vmInfo.osType) {
                                _this._os = vmInfo.osType;
                            }
                        }
                        resolve();
                    });
                }
                else {
                    _this._resourceProvider = Constants.StatsbeatResourceProvider.unknown;
                }
            }
            if (!waiting) {
                resolve();
            }
        });
    };
    Statsbeat.prototype._shutdownStatsbeat = function () {
        this.enable(false); // Disable Statsbeat as is it failed 3 times consecutively during initialization, is possible SDK is running in private or restricted network
    };
    Statsbeat.prototype._getConnectionString = function (config) {
        var currentEndpoint = config.endpointUrl;
        var euEndpoints = [
            "westeurope",
            "northeurope",
            "francecentral",
            "francesouth",
            "germanywestcentral",
            "norwayeast",
            "norwaywest",
            "swedencentral",
            "switzerlandnorth",
            "switzerlandwest",
            "uksouth",
            "ukwest"
        ];
        for (var i = 0; i < euEndpoints.length; i++) {
            if (currentEndpoint.indexOf(euEndpoints[i]) > -1) {
                return Statsbeat.EU_CONNECTION_STRING;
            }
        }
        return Statsbeat.NON_EU_CONNECTION_STRING;
    };
    Statsbeat.NON_EU_CONNECTION_STRING = "InstrumentationKey=c4a29126-a7cb-47e5-b348-11414998b11e;IngestionEndpoint=https://westus-0.in.applicationinsights.azure.com";
    Statsbeat.EU_CONNECTION_STRING = "InstrumentationKey=7dc56bab-3c0c-4e9f-9ebb-d1acadee8d0f;IngestionEndpoint=https://westeurope-5.in.applicationinsights.azure.com";
    Statsbeat.STATS_COLLECTION_SHORT_INTERVAL = 900000; // 15 minutes
    Statsbeat.STATS_COLLECTION_LONG_INTERVAL = 86400000; // 1 day
    Statsbeat.STATS_COLLECTION_INITIAL_DELAY = 15000; // 15 seconds
    Statsbeat.TAG = "Statsbeat";
    return Statsbeat;
}());
module.exports = Statsbeat;
//# sourceMappingURL=Statsbeat.js.map