apminsight
Version:
monitor nodejs applications
500 lines (466 loc) • 16.7 kB
JavaScript
var metricstore = require("./../metrics/metricstore");
var constants = require("./../constants");
var logger = require("./../util/logger");
var reqhandler = require("./reqhandler");
var os = require("os");
var dns = require("dns");
var fs = require("fs");
var path = require("path");
var process = require("process");
var rescodehandler = require("./responsecode");
var utils = require("./../util/utils");
var cloudchecker = require("./cloudchecker");
var dataformatter = require("./dataformatter");
var keyTxnTh = require("./../config/keyTxnThreshold");
var connInfo,
connPayload,
isConnSuccess = false;
function init() {
checkAndSendConnect();
scheduleDataService();
}
function scheduleDataService() {
setInterval(function () {
try {
logger.info("data processing starts");
if (
apmInsightAgentInstance.getInsInfo().getStatus() ===
constants.shutdown
) {
logger.critical("agent is shutdown state");
return;
}
if (!isConnSuccess) {
sendConnect();
return;
}
processCollectedData();
logger.info("data processing ends");
} catch (e) {
logger.error("Exception occured in data processing ::", e);
} finally {
metricstore.clearMetric();
}
}, 60 * 1000).unref();
setInterval(function () {
metricstore.clearTraceHistory();
}, 15 * 60 * 1000).unref();
}
function checkAndSendConnect() {
if (!isConnSuccess) {
sendConnect();
}
}
function sendConnect() {
var config = apmInsightAgentInstance.getConfig();
fetchAndIncludeDependencies();
if (!config.isLkeyConfiguredProperly()) {
logger.critical(
"Agent not configured properly. Please check the apminsightnode.json file for the configured licenseKey. Note: Copy-paste the entire alphanumeric device key as the license key."
);
return;
}
if (!config.isAppNameConfiguredProperly()) {
logger.critical(
"Agent not configured properly. Please check the apminsightnode.json file for the configured appName. The appName must not be an empty string. Trying to find package.json file to read the application name."
);
return;
}
if (!config.isPortConfiguredProperly()) {
logger.critical(
"Agent not configured properly. Please check the apminsightnode.json file for the configured port number. The port must be a positive number. Trying to auto-detect the application's port number."
);
return;
}
var insInfo = apmInsightAgentInstance.getInsInfo();
if (
!insInfo.getStatus() ||
rescodehandler.isAllowedToSendRequest(
insInfo.getStatus(),
insInfo.getRetryCounter()
)
) {
if (!connPayload) {
getConnectPayload(function (payload) {
connPayload = payload;
sendConnectReq();
});
} else {
sendConnectReq();
}
}
}
function sendConnectReq() {
reqhandler.sendToCollector(
constants.connectUrl,
connPayload,
handleConnectResponse
);
}
function handleConnectResponse(err, uri, responseData) {
if (!isValidResponse(err, uri, responseData)) {
return;
}
var data = responseData.data;
var resCode = data[constants.responseCode];
var instanceInfo = data[constants.instanceInfo];
if (resCode) {
logger.critical(
"received response code :",
rescodehandler.getRescodeMessage(resCode)
);
}
if (!instanceInfo || utils.isEmpty(instanceInfo[constants.instanceid])) {
apmInsightAgentInstance.getInsInfo().updateStatus(resCode);
return;
}
var instanceId = instanceInfo[constants.instanceid];
apmInsightAgentInstance
.getInsInfo()
.updateInstanceInfo(instanceId, resCode);
apmInsightAgentInstance
.getThreshold()
.update(
data[constants.customConfigInfo],
data[constants.agentSpecifInfo]
);
if (data[constants.txnSpecificInfo]) {
keyTxnTh.updateKeyTxnInfo(data[constants.txnSpecificInfo]);
}
//agentInfoPath won't be available for one agent
if(apmInsightAgentInstance.getConfig() && apmInsightAgentInstance.getConfig().getAgentInfoPath()){
doPermissionChanges(
apmInsightAgentInstance.getConfig()
);
}
isConnSuccess = true;
}
function handleDataResponse(err, uri, responseData) {
if (!isValidResponse(err, uri, responseData)) {
return;
}
var data = responseData.data;
var resCode = data[constants.responseCode];
var insInfo = apmInsightAgentInstance.getInsInfo();
if (resCode && resCode !== insInfo.getStatus()) {
logger.critical(
"received response code :",
rescodehandler.getRescodeMessage(resCode)
);
}
insInfo.updateStatus(resCode);
if (data[constants.customConfigInfo] || data[constants.agentSpecifInfo]) {
apmInsightAgentInstance
.getThreshold()
.update(
data[constants.customConfigInfo],
data[constants.agentSpecifInfo]
);
}
if (data[constants.txnSpecificInfo]) {
keyTxnTh.updateKeyTxnInfo(data[constants.txnSpecificInfo]);
}
}
function isValidResponse(err, uri, responseData) {
if (err) {
logger.error("Error occured for in " + uri + " request", err);
return false;
}
if (!responseData || !responseData.data) {
logger.critical("received empty response data for " + uri + " request");
return false;
}
logger.info("Response data for " + uri, responseData);
return true;
}
function handleConfigUpdateResponse(err, uri, responseData) {
if (!isValidResponse(err, uri, responseData)) {
return;
}
apmInsightAgentInstance.getConfig()._newRumAppKeyAdded = false;
}
function delayedSendToCollector(url, payload, callback, delay) {
setTimeout(function() {
reqhandler.sendToCollector(url, payload, callback);
}, delay); // delay is dynamically set by the function parameter
}
function processCollectedData() {
var insInfo = apmInsightAgentInstance.getInsInfo();
var payload;
switch (insInfo.getStatus()) {
case constants.manageAgent:
payload = JSON.stringify(
getDataWithTime(insInfo, dataformatter.getTxnMetric())
);
reqhandler.sendToCollector(
constants.dataUrl,
payload,
handleDataResponse
);
var nvmPayload = JSON.stringify(dataformatter.getNodeVMMetrics());
delayedSendToCollector(
constants.nodeVmUrl,
nvmPayload,
handleDataResponse,
constants.nodeVmDelay
);
var traceData = dataformatter.getFormattedTraceData();
if (traceData && traceData.length > 0) {
var traceDataStr = JSON.stringify(
getDataWithTime(insInfo, traceData)
);
delayedSendToCollector(
constants.traceUrl,
traceDataStr,
handleDataResponse,
constants.traceDataDelay
);
}
if (apmInsightAgentInstance.getConfig().isNewRumAppKeyAdded()) {
var rumIntegAppKeyArray = apmInsightAgentInstance
.getConfig()
.getRumIntegAppKey();
var appKeyObj = {
"rum.app.keys": rumIntegAppKeyArray
};
delayedSendToCollector(
constants.configUpdateUrl,
JSON.stringify(appKeyObj),
handleConfigUpdateResponse,
constants.configUpdateDelay
);
}
return;
case constants.unmanageAgent:
payload = JSON.stringify(getDataWithTime(insInfo, []));
reqhandler.sendToCollector(
constants.dataUrl,
payload,
handleDataResponse
);
return;
default:
if (
rescodehandler.isAllowedToSendRequest(
insInfo.getStatus(),
insInfo.getRetryCounter()
)
) {
payload = JSON.stringify(getDataWithTime(insInfo, []));
reqhandler.sendToCollector(
constants.dataUrl,
payload,
handleDataResponse
);
} else {
logger.critical("request will be send in specific interval");
}
return;
}
}
// TODO : Last reported time
function getDataWithTime(insInfo, data) {
var millis = new Date().getTime();
var lastReported = insInfo.getLastReported()
? insInfo.getLastReported()
: millis;
return [millis, lastReported, data];
}
function getConnectPayload(cb) {
cloudchecker.doCloudCheck(function (instanceName, cloudProvider) {
var config = apmInsightAgentInstance.getConfig();
var payload = {
agent_info: {
"application.type": constants.applicationType,
"agent.version": config.getAgentVersion(),
"agent.version.info": config.getAgentFullVersion(),
"application.name": config.getApplicationName(),
port: config.getApplicationPort(),
"host.type": utils.isEmpty(cloudProvider)
? os.platform()
: cloudProvider,
hostname: utils.isEmpty(instanceName)
? os.hostname()
: instanceName,
"hostlicense.apply": config.isHostBasedLicense()
},
environment: {
UserName: process.env.USER,
OSVersion: os.release(),
MachineName: os.hostname(),
AgentInstallPath: config.getAgentInstalledPath(),
"NODEJS version": process.version,
OSArch: os.arch(),
OS: os.platform(),
IP: getIP()
}
};
setAgentSpecificInfo(payload, config);
checkCgroup(payload);
if (!utils.isEmpty(instanceName)) {
payload.agent_info["cloud.instance.id"] = instanceName;
}
addFQDN(function (data) {
payload.agent_info.fqdn = data;
cb(JSON.stringify(payload));
});
});
}
function addFQDN(cb) {
dns.lookup(os.hostname(), { hints: dns.ADDRCONFIG }, function (err, ip) {
if (err) {
logger.error("Error while getting IP from hostname : " + err);
cb(os.hostname());
return;
}
dns.lookupService(ip, 0, function (err, fqdn) {
if (err) {
logger.error("Error while getting FQDN from IP : " + err);
cb(os.hostname());
return;
}
cb(fqdn);
});
});
}
function getIP() {
var nets = os.networkInterfaces();
var IPs = [];
try {
for (var name of Object.keys(nets)) {
for (var net of nets[name]) {
var familyV4Value = typeof net.family === "string" ? "IPv4" : 4;
if (net.family === familyV4Value && !net.internal) {
IPs.push(net.address);
}
}
}
} catch (err) {
logger.error(
"Error while finding the IP address of the machine :: ",
err
);
}
return IPs;
}
function setAgentSpecificInfo(payload, config) {
if (utils.isEmpty(payload) || utils.isEmpty(config)) {
logger.error("Invalid arguments passed to setAgentSpecificInfo");
return;
}
const appGroupName = config.getAppGroupName();
const tags = config.getTags();
if (!utils.isEmpty(appGroupName) || !utils.isEmpty(tags)) {
payload.agent_specific_info = payload.agent_specific_info || {};
if (!utils.isEmpty(appGroupName)) {
payload.agent_specific_info[constants.appGroupName] = appGroupName;
}
if (!utils.isEmpty(tags)) {
payload.agent_specific_info[constants.tags] = tags;
}
}
}
function fetchAndIncludeDependencies() {
try {
var filePath = path.join(process.cwd(), "package.json");
var packageJson = JSON.parse(fs.readFileSync(filePath));
if (packageJson && packageJson.dependencies) {
apmInsightAgentInstance.setDependencyList(packageJson.dependencies);
logger.info(
"Dependencies : " + JSON.stringify(packageJson.dependencies)
);
}
} catch (e) {
logger.info("package.json file not in process started path.");
}
}
function checkCgroup(payload) {
try {
var cgroupInfo = fs.readFileSync("/proc/self/cgroup", "utf8");
var mountInfoFile = fs.readFileSync("/proc/self/mountinfo", "utf8");
var regExp = /^cpu(.+|$)/; //Line which starts with cpu
var regExpForDocker =
/^\/(docker\/|lxc\/|.+?\/docker-|ecs\/.+?\/|kubepods\/.+?\/)([a-f0-9]+)($|\.scope|\/kubepods.*)/gm; //Docker container check which included ECS, EKS, Kubepods and mostly all container setup.
var dockerId = "";
if (!utils.isEmpty(cgroupInfo)) {
let lines = cgroupInfo.split("\n");
for (
var cgroupIndex = 0;
cgroupIndex < lines.length;
cgroupIndex++
) {
var cgroupParts = lines[cgroupIndex].split(":");
if (cgroupParts.length == 3 && regExp.exec(cgroupParts[1])) {
var groups = regExpForDocker.exec(cgroupParts[2]);
if (groups && groups[2]) {
dockerId = groups[2];
}
}
}
}
if (utils.isEmpty(dockerId) && !utils.isEmpty(mountInfoFile)) {
let lines = mountInfoFile.split("\n");
for (var mountIndex = 0; mountIndex < lines.length; mountIndex++) {
var mountParts = lines[mountIndex].split("docker/containers/");
if (mountParts.length == 2) {
dockerId = mountParts[1].split("/", 2)[0];
}
}
}
if (!utils.isEmpty(dockerId)) {
payload.agent_info["hostname"] = dockerId;
payload.agent_info["host.type"] = "DOCKER";
logger.info("Application hosted in Docker.");
return;
}
} catch (e) {
logger.info("Application not hosted in docker.");
}
}
function doPermissionChanges(configDetails) {
if (utils.isEmpty(configDetails.getBaseDir())) {
logger.info(
"Agent basedir is undefined. Might be an error occured while creating apminsightdata folder."
);
return;
}
const insInfo = apmInsightAgentInstance.getInsInfo();
const apmInfoFilePath = insInfo.getInfoFilePath();
if (utils.isEmpty(apmInfoFilePath)) {
logger.info("Unable to find apminsight.json file");
} else {
fs.chmod(apmInfoFilePath, 0o600, function (error) {
if (error) {
logger.error(
"Error while changing the permission of apminsight.json file :: ",
error
);
return;
}
logger.info(
"Made permission changes in apminsight.json file successfully"
);
});
}
var agentInfo = configDetails.getAgentInfoPath();
if (utils.isEmpty(agentInfo)) {
logger.info("Unable to find apminsightnode.json file");
} else {
fs.chmod(agentInfo, 0o600, function (error) {
if (error) {
logger.error(
"Error while changing the permission of apminsightnode.json file :: ",
error
);
return;
}
logger.info(
"Made permission changes in apminsightnode.json file successfully"
);
});
}
}
module.exports = {
init: init,
checkAndSendConnect: checkAndSendConnect
};