package codeine.collectors; import codeine.PeerStatusChangedUpdater; import codeine.SnoozeKeeper; import codeine.api.NodeInfo; import codeine.configuration.FeatureFlags; import codeine.configuration.PathHelper; import codeine.jsons.collectors.CollectorExecutionInfo; import codeine.jsons.collectors.CollectorExecutionInfoWithResult; import codeine.jsons.collectors.CollectorInfo; import codeine.jsons.info.CodeineRuntimeInfo; import codeine.jsons.peer_status.PeerStatus; import codeine.jsons.project.ProjectJson; import codeine.mail.NotificationDeliverToDatabase; import codeine.model.Constants; import codeine.model.ExitStatus; import codeine.model.Result; import codeine.utils.MiscUtils; import codeine.utils.StringUtils; import codeine.utils.TextFileUtils; import codeine.utils.logging.LogUtils; import codeine.utils.network.HttpUtils; import codeine.utils.os_process.ShellScript; import com.google.common.base.Stopwatch; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Maps; import com.google.gson.Gson; import com.google.inject.assistedinject.Assisted; import org.apache.log4j.Logger; import javax.inject.Inject; import java.io.File; import java.util.Map; import java.util.concurrent.TimeUnit; public class OneCollectorRunner implements IOneCollectorRunner { private static final int MIN_INTERVAL_MILLI = 20000; private static final Logger log = Logger.getLogger(OneCollectorRunner.class); @Inject private PathHelper pathHelper; @Inject private FeatureFlags featureFlags; @Inject private PeerStatus peerStatus; @Inject private Gson gson; @Inject private PeerStatusChangedUpdater peerStatusChangedUpdater; @Inject private SnoozeKeeper snoozeKeeper; @Inject private NotificationDeliverToDatabase notificationDeliverToDatabase; @Inject private CodeineRuntimeInfo codeineRuntimeInfo; private CollectorInfo collectorInfo; private ProjectJson project; private NodeInfo node; private Long lastRuntime; private Result result; private Result previousResult; private Stopwatch stopwatch; private LoadingCache<Long, Object> notificationsCount = CacheBuilder.newBuilder() .maximumSize(100) .expireAfterWrite(24, TimeUnit.HOURS) .build( new CacheLoader<Long, Object>() { @Override public Object load(Long key) { return new Object(); } }); @Inject public OneCollectorRunner(@Assisted CollectorInfo collector, @Assisted ProjectJson project, @Assisted NodeInfo node) { this.collectorInfo = collector; this.project = project; this.node = node; } @Override public void execute() { runOnceCheckMinInterval(); } private void runOnceCheckMinInterval() { if (featureFlags.isCollectorsDisabled()) { log.info("collectors are disabled"); return; } if (lastRuntime == null || System.currentTimeMillis() - lastRuntime > minInterval()) { try { runOnce(); } catch (Exception e) { log.warn("got exception when executing collector ", e); } lastRuntime = System.currentTimeMillis(); } else { log.info("skipping collector " + collectorInfo); } } private void runOnce() { ShellScript shellScript = createShellScript(); long startTime = System.currentTimeMillis(); stopwatch = Stopwatch.createStarted(); executeScriptAndDeleteIt(shellScript); stopwatch.stop(); if (null == result.output()) { result.output("No Output\n"); } CollectorExecutionInfo info = new CollectorExecutionInfo(collectorInfo.name(), collectorInfo.type(), result.exit(), outputFromFile(), stopwatch.elapsed(TimeUnit.MILLISECONDS), startTime); CollectorExecutionInfoWithResult resultWrapped = new CollectorExecutionInfoWithResult(info, result); processResult(resultWrapped, stopwatch); } public String outputFromFile() { if (null == result || null == result.outputFromFile()) { return ""; } return result.outputFromFile(); } private void processResult(CollectorExecutionInfoWithResult resultWrapped, Stopwatch stopwatch) { resultWrapped.result().limitOutputLength(); writeResult(resultWrapped); CollectorExecutionInfo lastValue = updateStatusInDataset(resultWrapped.info()); log.info("collector '" + collectorInfo.name() + "' took:" + stopwatch + " result:" + resultWrapped.info().valueAndExitStatus() + (null != lastValue ? " previous:" + lastValue.valueAndExitStatus() : "")); updateDatastoreIfNeeded(); sendNotificationIfNeeded(); } private ShellScript createShellScript() { ShellScript shellScript = new ShellScript(getKey(), collectorInfo.script_content(), project.operating_system(), null, pathHelper.getProjectDir(project.name()), prepareEnv(), collectorInfo.cred()); shellScript.create(); return shellScript; } private String getKey() { return codeineRuntimeInfo.port() + "_" + pathHelper.getMonitorsDir(project.name()) + File.separator + node.name() + "_" + collectorInfo.name(); } private void executeScriptAndDeleteIt(ShellScript shellScript) { try { result = shellScript.execute(); } catch (Exception ex) { result = new Result(ExitStatus.EXCEPTION, ex.getMessage()); log.debug("error in collector", ex); } finally { shellScript.delete(); } } private Map<String, String> prepareEnv() { Map<String, String> env = Maps.newHashMap(); env.put(Constants.EXECUTION_ENV_NODE_NAME, node.name()); env.put(Constants.EXECUTION_ENV_NODE_ALIAS, node.alias()); env.put(Constants.EXECUTION_ENV_PROJECT_NAME, project.name()); env.put(Constants.EXECUTION_ENV_NODE_TAGS, StringUtils.collectionToString(peerStatus.getTags(project.name(), node.name()), ";")); env.putAll(project.environmentVariables()); return env; } private void sendNotificationIfNeeded() { if (new NotificationChecker().shouldSendNotification(snoozeKeeper, collectorInfo, project.name(), node.name(), notificationsCount, result, previousResult)) { notificationDeliverToDatabase.sendCollectorResult( collectorInfo.name(), node, project, result.output(), result.exit(), stopwatch.toString(), true, (int)notificationsCount.size()); } previousResult = result; } private boolean shouldUpdate() { if (result == null) { return false; } if (previousResult == null) { return true; } boolean shouldUpdate; shouldUpdate = !MiscUtils.equals(result.outputFromFile(), previousResult.outputFromFile()) || !MiscUtils.equals(result.exit(), previousResult.exit()); if (shouldUpdate) { LogUtils.info(log, "collector should update", result.outputFromFile(), previousResult.outputFromFile(), result.exit(), previousResult.exit()); } return shouldUpdate; } private void updateDatastoreIfNeeded() { if (shouldUpdate()) { peerStatusChangedUpdater.pushUpdate("collector " + collectorInfo.name()); } } private CollectorExecutionInfo updateStatusInDataset(CollectorExecutionInfo info) { return peerStatus.updateStatus(project, info, node.name(), node.alias()); } private void writeResult(CollectorExecutionInfoWithResult result) { String file = pathHelper.getCollectorOutputDirWithNode(project.name(), node.name()) + "/" + HttpUtils.specialEncode(collectorInfo.name()) + ".txt"; log.debug("Output for " + collectorInfo.name() + " will be written to: " + file); TextFileUtils.setContents(file, gson.toJson(result)); } private int minInterval() { if (collectorInfo.min_interval() == null || collectorInfo.min_interval() <= 0) { return MIN_INTERVAL_MILLI; } return collectorInfo.min_interval() * 60000; } public void updateConf(CollectorInfo collectorInfo) { this.collectorInfo = collectorInfo; } }