All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.takipi.common.udf.volume.ThresholdFunction Maven / Gradle / Ivy

package com.takipi.common.udf.volume;

import java.net.HttpURLConnection;
import java.util.Collections;
import java.util.Comparator;

import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;

import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.takipi.common.api.ApiClient;
import com.takipi.common.api.data.volume.Transaction;
import com.takipi.common.api.request.alert.Anomaly;
import com.takipi.common.api.request.alert.Anomaly.AnomalyContributor;
import com.takipi.common.api.request.alert.AnomalyAlertRequest;
import com.takipi.common.api.request.event.EventModifyLabelsRequest;
import com.takipi.common.api.request.label.CreateLabelRequest;
import com.takipi.common.api.request.volume.EventsVolumeRequest;
import com.takipi.common.api.request.volume.TransactionsVolumeRequest;
import com.takipi.common.api.result.EmptyResult;
import com.takipi.common.api.result.GenericResult;
import com.takipi.common.api.result.event.EventResult;
import com.takipi.common.api.result.volume.EventsVolumeResult;
import com.takipi.common.api.result.volume.TransactionsVolumeResult;
import com.takipi.common.api.url.UrlClient.Response;
import com.takipi.common.api.util.ValidationUtil.VolumeType;
import com.takipi.common.udf.ContextArgs;
import com.takipi.common.udf.input.Input;

public class ThresholdFunction {
	static ThresholdInput getThresholdInput(String rawInput) {
		System.out.println("validateInput rawInput:" + rawInput);

		if (Strings.isNullOrEmpty(rawInput)) {
			throw new IllegalArgumentException("Input is empty");
		}

		ThresholdInput input;

		try {
			input = ThresholdInput.of(rawInput);
		} catch (Exception e) {
			throw new IllegalArgumentException(e.getMessage(), e);
		}

		if (input.relative_to == null) {
			throw new IllegalArgumentException("Missing 'relative_to'");
		}

		if (input.timespan <= 0) {
			throw new IllegalArgumentException("'timespan' must be positive");
		}

		if (input.threshold <= 0l) {
			throw new IllegalArgumentException("'threshold' must be positive");
		}

		if ((input.relative_to == Mode.Method_Calls) && (input.rate <= 0.0)) {
			throw new IllegalArgumentException("'rate' must be positive");
		}

		if ((input.relative_to == Mode.Method_Calls) && (input.rate > 100.0)) {
			throw new IllegalArgumentException("'rate' can't be more then 100 for method calls");
		}

		if ((input.relative_to == Mode.Thread_Calls) && (input.rate <= 0.0)) {
			throw new IllegalArgumentException("'rate' must be positive");
		}

		return input;
	}

	static void execute(String rawContextArgs, ThresholdInput input) {
		System.out.println("execute:" + rawContextArgs);

		ContextArgs args = (new Gson()).fromJson(rawContextArgs, ContextArgs.class);

		if (!args.viewValidate()) {
			throw new IllegalArgumentException("Bad context args - " + rawContextArgs);
		}

		ApiClient apiClient = args.apiClient();

		VolumeType volumeType = ((input.relative_to == Mode.Method_Calls) ? VolumeType.all : VolumeType.hits);

		DateTime to = DateTime.now();
		DateTime from = to.minusMinutes(input.timespan);

		DateTimeFormatter fmt = ISODateTimeFormat.dateTime().withZoneUTC();

		EventsVolumeRequest eventsVolumeRequest = EventsVolumeRequest.newBuilder().setServiceId(args.serviceId)
				.setViewId(args.viewId).setFrom(from.toString(fmt)).setTo(to.toString(fmt)).setVolumeType(volumeType)
				.build();

		Response eventsVolumeResponse = apiClient.get(eventsVolumeRequest);

		if (eventsVolumeResponse.isBadResponse()) {
			throw new IllegalStateException("Can't create events volume.");
		}

		EventsVolumeResult eventsVolumeResult = eventsVolumeResponse.data;

		if (eventsVolumeResult == null) {
			throw new IllegalStateException("Missing events volume result.");
		}

		if (eventsVolumeResult.events == null) {
			return;
		}

		long hitCount = 0l;

		for (EventResult event : eventsVolumeResult.events) {
			if (event.stats != null) {
				hitCount += event.stats.hits;
			}
		}

		if (hitCount <= input.threshold) {
			return;
		}

		boolean thresholdExceeded = false;

		switch (input.relative_to) {
		case Absolute: {
			thresholdExceeded = true;
		}
			break;

		case Method_Calls: {
			long invocationsCount = 0l;

			Collections.sort(eventsVolumeResult.events, new Comparator() {
				@Override
				public int compare(EventResult o1, EventResult o2) {
					int i1 = Integer.parseInt(o1.id);
					int i2 = Integer.parseInt(o2.id);
					return i1 - i2;
				};
			});

			for (EventResult event : eventsVolumeResult.events) {
				if (event.stats != null) {
					System.out.println(event.id + ": " + event.summary + " - hits: " + event.stats.hits + " - inv: "
							+ event.stats.invocations);

					invocationsCount += Math.max(event.stats.invocations, event.stats.hits);
				}
			}

			invocationsCount = Math.max(invocationsCount, hitCount);

			double failRate = (hitCount / (double) invocationsCount) * 100.0;

			thresholdExceeded = (failRate >= input.rate);
		}
			break;

		case Thread_Calls: {
			TransactionsVolumeRequest transactionsVolumeRequest = TransactionsVolumeRequest.newBuilder()
					.setServiceId(args.serviceId).setViewId(args.viewId)
					.setFrom(DateTime.now().minusMinutes(input.timespan).toString()).setTo(DateTime.now().toString())
					.build();

			Response transactionsVolumeResponse = apiClient.get(transactionsVolumeRequest);

			if (transactionsVolumeResponse.isBadResponse()) {
				throw new IllegalStateException("Can't create transactions volume.");
			}

			TransactionsVolumeResult transactionsVolumeResult = transactionsVolumeResponse.data;

			if (transactionsVolumeResult == null) {
				throw new IllegalStateException("Missing events volume result.");
			}

			long transactionInvocationsCount = 0l;

			for (Transaction transaction : transactionsVolumeResult.transactions) {
				if (transaction.stats != null) {
					transactionInvocationsCount += transaction.stats.invocations;
				}
			}

			if (transactionInvocationsCount > 0l) {
				double failRate = (hitCount / (double) transactionInvocationsCount) * 100.0;

				thresholdExceeded = (failRate >= input.rate);
			}
		}
			break;
		}

		System.out.println("Threshold response: " + thresholdExceeded);

		if (!thresholdExceeded) {
			return;
		}

		// Send anomaly message to integrations

		Anomaly anomaly = Anomaly.create();

		anomaly.addAnomalyPeriod(args.viewId, from.getMillis(), to.getMillis());

		for (EventResult event : eventsVolumeResult.events) {
			if ((event.stats != null) && (event.stats.hits > 0)) {
				anomaly.addContributor(Integer.parseInt(event.id), event.stats.hits);
			}
		}

		AnomalyAlertRequest anomalyAlertRequest = AnomalyAlertRequest.newBuilder().setServiceId(args.serviceId)
				.setViewId(args.viewId).setFrom(from.toString()).setTo(to.toString()).setDesc(input.toString())
				.setAnomaly(anomaly).build();

		Response anomalyAlertResponse = apiClient.post(anomalyAlertRequest);

		if (anomalyAlertResponse.isBadResponse()) {
			throw new IllegalStateException("Failed alerting on anomaly for view - " + args.viewId);
		}

		GenericResult alertResult = anomalyAlertResponse.data;

		if (alertResult == null) {
			throw new IllegalStateException("Failed getting anomaly alert result on view - " + args.viewId);
		}

		if (!alertResult.result) {
			throw new IllegalStateException(
					"Anomaly alert on view - " + args.viewId + " failed with - " + alertResult.message);
		}

		// Mark all contributors as Alert label
		if (!StringUtils.isEmpty(input.label)) {
			CreateLabelRequest createLabel = CreateLabelRequest.newBuilder().setServiceId(args.serviceId)
					.setName(input.label).build();

			Response createResult = apiClient.post(createLabel);

			if ((createResult.isBadResponse()) && (createResult.responseCode != HttpURLConnection.HTTP_CONFLICT)) {
				throw new IllegalStateException("Can't create label " + input);
			}

			for (AnomalyContributor contributor : anomaly.getAnomalyContributors()) {
				EventModifyLabelsRequest addLabel = EventModifyLabelsRequest.newBuilder().setServiceId(args.serviceId)
						.setEventId(String.valueOf(contributor.id)).addLabel(input.label).build();

				Response addResult = apiClient.post(addLabel);

				if (addResult.isBadResponse()) {
					throw new IllegalStateException("Can't apply label " + input.label + " to event " + args.eventId);
				}
			}
		}
	}

	static class ThresholdInput extends Input {
		public Mode relative_to;
		public long threshold;
		public double rate;
		public int timespan; // minutes
		public String label;

		private ThresholdInput(String raw) {
			super(raw);
		}

		@Override
		public String toString() {
			StringBuilder builder = new StringBuilder();

			builder.append("Threshold(");

			switch (relative_to) {
			case Absolute:
				builder.append(threshold);
				break;

			case Method_Calls:
			case Thread_Calls: {
				builder.append(threshold);
				builder.append(", ");

				builder.append(String.format("%.2f", rate));
				builder.append('%');

				builder.append(" of ");
				builder.append(relative_to);
			}
				break;
			}

			builder.append(")");

			return builder.toString();
		}

		static ThresholdInput of(String raw) {
			return new ThresholdInput(raw);
		}
	}

	public enum Mode {
		Absolute, Method_Calls, Thread_Calls
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy