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

com.fizzgate.stats.FlowStat Maven / Gradle / Ivy

There is a newer version: 3.3.0
Show newest version
/*
 *  Copyright (C) 2020 the original author or authors.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see .
 */

package com.fizzgate.stats;

import com.fizzgate.stats.circuitbreaker.CircuitBreakManager;
import com.fizzgate.stats.circuitbreaker.CircuitBreaker;
import com.fizzgate.util.Consts;
import com.fizzgate.util.ResourceIdUtils;
import com.fizzgate.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

/**
 * Flow Statistic
 * 
 * @author Francis Dong
 *
 */
public class FlowStat {

	private static final Logger log = LoggerFactory.getLogger(FlowStat.class);

	/**
	 * Time slot interval in millisecond
	 */
	public static long INTERVAL = 1000;

	public boolean cleanResource = true;

	public boolean createTimeSlotOnlyTraffic = true;

	/**
	 * A string Resource ID as key
	 */
	public ConcurrentMap resourceStats = new ConcurrentHashMap<>(256);

	/**
	 * Retention time of statistic data
	 */
	public static long RETENTION_TIME_IN_MINUTES = 5;

	private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
	private Lock w = rwl.writeLock();

	private ExecutorService pool = Executors.newFixedThreadPool(2);

	private CircuitBreakManager circuitBreakManager;

	public FlowStat() {
		runScheduleJob();
	}

	public FlowStat(boolean cleanResource, boolean createTimeSlotOnlyTraffic) {
		this.cleanResource = cleanResource;
		this.createTimeSlotOnlyTraffic = createTimeSlotOnlyTraffic;
		runScheduleJob();
	}

	public FlowStat(CircuitBreakManager circuitBreakManager) {
		this.circuitBreakManager = circuitBreakManager;
		runScheduleJob();
	}

	public void setCircuitBreakManager(CircuitBreakManager circuitBreakManager) {
		this.circuitBreakManager = circuitBreakManager;
	}

	private void runScheduleJob() {
		pool.submit(new HousekeepJob(this));
		pool.submit(new PeakConcurrentJob(this));
	}

	/**
	 * Update retention time
	 * 
	 * @param retentionTimeInMinutes
	 */
	public void updateRetentionTime(int retentionTimeInMinutes) {
		RETENTION_TIME_IN_MINUTES = retentionTimeInMinutes;
	}

	/**
	 * Returns the current time slot ID
	 * 
	 * @return
	 */
	public long currentTimeSlotId() {
		return (System.currentTimeMillis() / INTERVAL) * INTERVAL;
	}

	/**
	 * Returns the time slot ID of the specified time
	 * 
	 * @param timeMilli
	 * @return
	 */
	public long getTimeSlotId(long timeMilli) {
		return (System.currentTimeMillis() / INTERVAL) * INTERVAL;
	}
	
	/**
	 * Increase concurrent request counter for given resources chain
	 * 
	 * @param resourceConfigs Resource configurations
	 * @param curTimeSlotId   current time slot ID, it should be generated by
	 *                        Flowstat.currentTimeSlotId()
	 * @return IncrRequestResult
	 */
	public IncrRequestResult incrRequest(List resourceConfigs, long curTimeSlotId) {
		return incrRequest(resourceConfigs, curTimeSlotId, null);
	}
	
	/**
	 * Increase concurrent request counter for given resources chain
	 * 
	 * @param resourceConfigs Resource configurations
	 * @param curTimeSlotId   current time slot ID, it should be generated by
	 *                        Flowstat.currentTimeSlotId()
	 * @param totalBlockFunc  [optional] callback function for statistic of total
	 *                        block requests of the resource and its parent resources
	 * @return IncrRequestResult
	 */
	public IncrRequestResult incrRequest(List resourceConfigs, long curTimeSlotId,
			BiFunction, List> totalBlockFunc) {
		if (resourceConfigs == null || resourceConfigs.size() == 0) {
			return null;
		}
		w.lock();
		try {
			// check if exceed limit
			for (ResourceConfig resourceConfig : resourceConfigs) {
				long maxCon = resourceConfig.getMaxCon();
				long maxQPS = resourceConfig.getMaxQPS();
				if (maxCon >= 0 || maxQPS >= 0) {
					ResourceStat resourceStat = getResourceStat(resourceConfig.getResourceId());
					// check concurrent request
					if (maxCon >= 0) {
						long n = resourceStat.getConcurrentRequests().get();
						if (n >= maxCon) {
							resourceStat.incrBlockRequestToTimeSlot(curTimeSlotId);
							if (totalBlockFunc != null) {
								List parentResCfgs = totalBlockFunc.apply(resourceConfig,
										resourceConfigs);
								if (parentResCfgs != null && parentResCfgs.size() > 0) {
									for (ResourceConfig pResCfg : parentResCfgs) {
										getResourceStat(pResCfg.getResourceId())
												.incrTotalBlockRequestToTimeSlot(curTimeSlotId);
									}
								}
							}
							return IncrRequestResult.block(resourceConfig.getResourceId(),
									BlockType.CONCURRENT_REQUEST);
						}
					}

					// check QPS
					if (maxQPS >= 0) {
						long total = resourceStat.getTimeSlot(curTimeSlotId).getCounter();
						if (total >= maxQPS) {
							resourceStat.incrBlockRequestToTimeSlot(curTimeSlotId);
							if (totalBlockFunc != null) {
								List parentResCfgs = totalBlockFunc.apply(resourceConfig,
										resourceConfigs);
								if (parentResCfgs != null && parentResCfgs.size() > 0) {
									for (ResourceConfig pResCfg : parentResCfgs) {
										getResourceStat(pResCfg.getResourceId())
												.incrTotalBlockRequestToTimeSlot(curTimeSlotId);
									}
								}
							}
							return IncrRequestResult.block(resourceConfig.getResourceId(), BlockType.QPS);
						}
					}
				}
			}

			// increase request and concurrent request
			for (ResourceConfig resourceConfig : resourceConfigs) {
				ResourceStat resourceStat = getResourceStat(resourceConfig.getResourceId());
				int cons = resourceStat.getConcurrentRequests().incrementAndGet();
				resourceStat.getTimeSlot(curTimeSlotId).updatePeakConcurrentReqeusts(cons);
				resourceStat.getTimeSlot(curTimeSlotId).incr();
			}
			return IncrRequestResult.success();
		} finally {
			w.unlock();
		}
	}

	public IncrRequestResult incrRequest(ServerWebExchange exchange, List resourceConfigs, long curTimeSlotId,
										 BiFunction, List> totalBlockFunc) {
		if (resourceConfigs == null || resourceConfigs.size() == 0) {
			return null;
		}
		w.lock();
		try {
			// check if exceed limit
			for (ResourceConfig resourceConfig : resourceConfigs) {
				long maxCon = resourceConfig.getMaxCon();
				long maxQPS = resourceConfig.getMaxQPS();
				if (maxCon >= 0 || maxQPS >= 0) {
					ResourceStat resourceStat = getResourceStat(resourceConfig.getResourceId());
					// check concurrent request
					if (maxCon >= 0) {
						long n = resourceStat.getConcurrentRequests().get();
						if (n >= maxCon) {
							resourceStat.incrBlockRequestToTimeSlot(curTimeSlotId);
							if (totalBlockFunc != null) {
								List parentResCfgs = totalBlockFunc.apply(resourceConfig,
										resourceConfigs);
								if (parentResCfgs != null && parentResCfgs.size() > 0) {
									for (ResourceConfig pResCfg : parentResCfgs) {
										getResourceStat(pResCfg.getResourceId())
												.incrTotalBlockRequestToTimeSlot(curTimeSlotId);
									}
								}
							}
							return IncrRequestResult.block(resourceConfig.getResourceId(),
									BlockType.CONCURRENT_REQUEST);
						}
					}

					// check QPS
					if (maxQPS >= 0) {
						long total = resourceStat.getTimeSlot(curTimeSlotId).getCounter();
						if (total >= maxQPS) {
							resourceStat.incrBlockRequestToTimeSlot(curTimeSlotId);
							if (totalBlockFunc != null) {
								List parentResCfgs = totalBlockFunc.apply(resourceConfig,
										resourceConfigs);
								if (parentResCfgs != null && parentResCfgs.size() > 0) {
									for (ResourceConfig pResCfg : parentResCfgs) {
										getResourceStat(pResCfg.getResourceId())
												.incrTotalBlockRequestToTimeSlot(curTimeSlotId);
									}
								}
							}
							return IncrRequestResult.block(resourceConfig.getResourceId(), BlockType.QPS);
						}
					}
				}
			}

			for (ResourceConfig resourceConfig : resourceConfigs) {
				getResourceStat(resourceConfig.getResourceId());
			}

			String service = WebUtils.getClientService(exchange);
			String path    = WebUtils.getClientReqPath(exchange);
			String resource = ResourceIdUtils.buildResourceId(null, null, null, service, path);
			boolean permit = circuitBreakManager.permit(exchange, curTimeSlotId, this, service, path);
			if (!permit) {
				return IncrRequestResult.block(resource, BlockType.CIRCUIT_BREAK);
			}

			// increase request and concurrent request
			for (ResourceConfig resourceConfig : resourceConfigs) {
				ResourceStat resourceStat = getResourceStat(resourceConfig.getResourceId());
				int cons = resourceStat.getConcurrentRequests().incrementAndGet();
				resourceStat.getTimeSlot(curTimeSlotId).updatePeakConcurrentReqeusts(cons);
				resourceStat.getTimeSlot(curTimeSlotId).incr();
			}
			return IncrRequestResult.success();
		} finally {
			w.unlock();
		}
	}

	/**
	 * Add request RT and Decrease concurrent request for given resources chain
	 * 
	 * @param resourceConfigs
	 * @param timeSlotId
	 * @param rt
	 * @param isSuccess
	 */
	public void addRequestRT(List resourceConfigs, long timeSlotId, long rt, boolean isSuccess, HttpStatus statusCode) {
		if (resourceConfigs == null || resourceConfigs.size() == 0) {
			return;
		}
		for (int i = resourceConfigs.size() - 1; i >= 0; i--) {
			ResourceStat resourceStat = getResourceStat(resourceConfigs.get(i).getResourceId());
			resourceStat.decrConcurrentRequest(timeSlotId);
			resourceStat.addRequestRT(timeSlotId, rt, isSuccess);

			if (statusCode.is2xxSuccessful()) {
				resourceStat.incr2xxStatusCount(timeSlotId);

			} else if (statusCode.is4xxClientError()) {
				resourceStat.incr4xxStatusCount(timeSlotId);

			} else if (statusCode.is5xxServerError()) {
				resourceStat.incr5xxStatusCount(timeSlotId);

			} else if (statusCode == HttpStatus.GATEWAY_TIMEOUT) {
				resourceStat.incr504StatusCount(timeSlotId);
			}
		}
	}

	/*public void addRequestRT(ServerWebExchange exchange, List resourceConfigs, long timeSlotId, long rt, boolean isSuccess) {
		if (resourceConfigs == null || resourceConfigs.size() == 0) {
			return;
		}
		for (int i = resourceConfigs.size() - 1; i >= 0; i--) {
			ResourceStat resourceStat = getResourceStat(resourceConfigs.get(i).getResourceId());
			resourceStat.decrConcurrentRequest(timeSlotId);
			resourceStat.addRequestRT(timeSlotId, rt, isSuccess);
		}

		String service = WebUtils.getClientService(exchange);
		String path    = WebUtils.getClientReqPath(exchange);
		circuitBreakManager.correctCircuitBreakerStateAsError(exchange, timeSlotId, this, service, path);
	}*/

	/**
	 * Increase concurrent request counter of the specified resource
	 * 
	 * @param resourceId    Resource ID
	 * @param curTimeSlotId current time slot ID, it should be generated by
	 *                      Flowstat.currentTimeSlotId()
	 * @param maxCon        Maximum concurrent request of the specified resource,
	 *                      null/zero/negative for no limit
	 * @param maxRPS        Maximum RPS of the specified resource,
	 *                      null/zero/negative for no limit
	 * @return true if the request is not blocked; false if exceed the maximum
	 *         concurrent request/RPS of the specified resource
	 */
	public boolean incrRequest(String resourceId, long curTimeSlotId, Long maxCon, Long maxRPS) {
		ResourceStat resourceStat = getResourceStat(resourceId);
		boolean success = resourceStat.incrConcurrentRequest(curTimeSlotId, maxCon);
		if (success) {
			success = resourceStat.incrRequestToTimeSlot(curTimeSlotId, maxRPS);
		}
		return success;
	}

	/**
	 * Decrease concurrent request of the specified resource of the specified time
	 * slot
	 * 
	 * @param resourceId Resource ID
	 * @param timeSlotId TimeSlot ID
	 * @return
	 */
	public void decrConcurrentRequest(String resourceId, long timeSlotId) {
		if (resourceId == null) {
			return;
		}
		ResourceStat resourceStat = getResourceStat(resourceId);

		long conns = resourceStat.getConcurrentRequests().get();
		resourceStat.decrConcurrentRequest(timeSlotId);
	}

	/**
	 * Add request RT to the specified time slot counter
	 * 
	 * @param resourceId Resource ID
	 * @param timeSlotId TimeSlot ID
	 * @param rt         Response time of request
	 * @param isSuccess  Whether the request is success or not
	 * @return
	 */
	public void addRequestRT(String resourceId, long timeSlotId, long rt, boolean isSuccess) {
		if (resourceId == null) {
			return;
		}
		ResourceStat resourceStat = getResourceStat(resourceId);
		resourceStat.addRequestRT(timeSlotId, rt, isSuccess);
	}

	public ResourceStat getResourceStat(String resourceId) {
		ResourceStat resourceStat = null;
		if (resourceStats.containsKey(resourceId)) {
			resourceStat = resourceStats.get(resourceId);
		} else {
			resourceStat = new ResourceStat(resourceId);
			ResourceStat rs = resourceStats.putIfAbsent(resourceId, resourceStat);
			if (rs != null) {
				resourceStat = rs;
			}
		}
		return resourceStat;
	}

	/**
	 * Returns the current concurrent requests of the specified resource
*
* * @param resourceId Resource ID */ public long getConcurrentRequests(String resourceId) { ResourceStat resourceStat = getResourceStat(resourceId); return resourceStat.getConcurrentRequests().get(); } /** * Returns current TimeWindowStat of the specified resource * * @param resourceId * @return */ public TimeWindowStat getCurrentTimeWindowStat(String resourceId) { long startTimeMilli = currentTimeSlotId(); return getTimeWindowStat(resourceId, startTimeMilli, startTimeMilli + 1000); } /** * Returns current TimeWindowStat of the specified resource * * @param resourceId * @param curTimeSlotId * @return */ @SuppressWarnings("unused") private TimeWindowStat getCurrentTimeWindowStat(String resourceId, long curTimeSlotId) { return getTimeWindowStat(resourceId, curTimeSlotId, curTimeSlotId + 1000); } /** * Returns the TimeWindowStat of previous second of the specified time * * @param resourceId * @param timeMilli * @return */ public TimeWindowStat getPreviousSecondStat(String resourceId, long timeMilli) { long endTimeMilli = (timeMilli / INTERVAL) * INTERVAL; return getTimeWindowStat(resourceId, endTimeMilli - 1000, endTimeMilli); } /** * Returns the timeWindowStat of the specific resource in the specified time * window [startTimeMilli, endTimeMilli) * * @param startTimeMilli included * @param endTimeMilli excluded * @return */ public TimeWindowStat getTimeWindowStat(String resourceId, long startTimeMilli, long endTimeMilli) { long startSlotId = (startTimeMilli / INTERVAL) * INTERVAL; long endSlotId = (endTimeMilli / INTERVAL) * INTERVAL; if (startSlotId == endSlotId) { endSlotId = endSlotId + INTERVAL; } if (resourceStats.containsKey(resourceId)) { ResourceStat resourceStat = resourceStats.get(resourceId); return resourceStat.getTimeWindowStat(startSlotId, endSlotId); } return null; } /** * Returns the ResourceTimeWindowStat list in the specified time window * [startTimeMilli, endTimeMilli), The time slot unit is one second * * @param resourceId optional, returns ResourceSlot list of all resources * while resourceId is null * @param startTimeMilli * @param endTimeMilli * @return */ @SuppressWarnings("unused") public List getResourceTimeWindowStats(String resourceId, long startTimeMilli, long endTimeMilli) { return this.getResourceTimeWindowStats(resourceId, startTimeMilli, endTimeMilli, 1); } /** * Returns the ResourceTimeWindow list in the specified time window * [startTimeMilli, endTimeMilli) * * @param resourceId optional, returns ResourceTimeWindowStat list of all * resources while resourceId is null * @param startTimeMilli * @param endTimeMilli * @param slotIntervalInSec interval of custom time slot in millisecond, such as * 60 for 1 minutes * @return */ @SuppressWarnings("unused") public List getResourceTimeWindowStats(String resourceId, long startTimeMilli, long endTimeMilli, long slotIntervalInSec) { List list = new ArrayList<>(); long startSlotId = (startTimeMilli / INTERVAL) * INTERVAL; long endSlotId = (endTimeMilli / INTERVAL) * INTERVAL; if (startSlotId == endSlotId) { endSlotId = endSlotId + INTERVAL; } if (slotIntervalInSec < 1 || (endSlotId - startSlotId) / 1000 < slotIntervalInSec) { return list; } long slotInterval = slotIntervalInSec * 1000; if (resourceId == null) { Set> entrys = resourceStats.entrySet(); for (Entry entry : entrys) { String rid = entry.getKey(); ResourceTimeWindowStat resourceWin = new ResourceTimeWindowStat(rid); long end = startSlotId + slotInterval; for (long start = startSlotId; end <= endSlotId;) { TimeWindowStat tws = getTimeWindowStat(rid, start, end); if (tws != null) { resourceWin.getWindows().add(tws); } start += slotInterval; end += slotInterval; } if (resourceWin.getWindows().size() > 0) { list.add(resourceWin); } } } else { ResourceTimeWindowStat resourceWin = new ResourceTimeWindowStat(resourceId); long end = startSlotId + slotInterval; for (long start = startSlotId; end <= endSlotId;) { TimeWindowStat tws = getTimeWindowStat(resourceId, start, end); if (tws != null) { resourceWin.getWindows().add(tws); } start += slotInterval; end += slotInterval; } if (resourceWin.getWindows().size() > 0) { list.add(resourceWin); } } return list; } class HousekeepJob implements Runnable { private FlowStat stat; public HousekeepJob(FlowStat stat) { this.stat = stat; } @Override public void run() { long n = FlowStat.RETENTION_TIME_IN_MINUTES * 60 * 1000 / FlowStat.INTERVAL * FlowStat.INTERVAL; long lastSlotId = stat.currentTimeSlotId() - n; while (true) { long slotId = stat.currentTimeSlotId() - n; /*if (log.isDebugEnabled()) { log.debug("{} - {} resource stats size {}", lastSlotId, slotId, stat.resourceStats.size()); } Set> es = stat.resourceStats.entrySet(); for (Entry e : es) { String resourceId = e.getKey(); ConcurrentMap timeSlots = e.getValue().getTimeSlots(); if (log.isDebugEnabled()) { log.debug("{} - {} {} has {} timeslot", lastSlotId, slotId, resourceId, timeSlots.size()); } }*/ for (long i = lastSlotId; i < slotId;) { Set> entrys = stat.resourceStats.entrySet(); for (Entry entry : entrys) { String resourceId = entry.getKey(); ConcurrentMap timeSlots = entry.getValue().getTimeSlots(); /*if (log.isDebugEnabled()) { log.debug("{} - {} {} remove {} timeslot", lastSlotId, slotId, resourceId, i); }*/ timeSlots.remove(i); } i = i + FlowStat.INTERVAL; } lastSlotId = slotId; // log.debug("housekeeping done"); if (cleanResource) { long currentTimeSlot = stat.currentTimeSlotId(); long startTimeSlot = currentTimeSlot - n; for (Entry entry : stat.resourceStats.entrySet()) { String resource = entry.getKey(); if (ResourceIdUtils.NODE_RESOURCE.equals(resource)) { continue; } ResourceStat resourceStat = entry.getValue(); boolean noTraffic = true; long timeSlot = startTimeSlot; for (; timeSlot < currentTimeSlot; timeSlot += FlowStat.INTERVAL) { int reqCnt = resourceStat.getTimeSlot(timeSlot).getCounter(); if (reqCnt > 0) { noTraffic = false; break; } } if (noTraffic) { stat.resourceStats.remove(resource); log.info("HousekeepJob remove {}", resource); } } } try { Thread.sleep(10 * 1000); } catch (Exception e) { log.error("HouseKeepJob error", e); } } } } class PeakConcurrentJob implements Runnable { private FlowStat stat; public PeakConcurrentJob(FlowStat stat) { this.stat = stat; } @Override public void run() { Long lastTimeSlotId = null; while (true) { long curTimeSlotId = stat.currentTimeSlotId(); if (lastTimeSlotId == null || lastTimeSlotId.longValue() != curTimeSlotId) { // log.debug("PeakConcurrentJob start"); Set> entrys = stat.resourceStats.entrySet(); for (Entry entry : entrys) { String resource = entry.getKey(); // log.debug("PeakConcurrentJob: resourceId={} slotId=={}", resourceId, // curTimeSlotId); ResourceStat resourceStat = entry.getValue(); if (createTimeSlotOnlyTraffic && resourceStat.getConcurrentRequests().get() > 0) { resourceStat.getTimeSlot(curTimeSlotId); } else { resourceStat.getTimeSlot(curTimeSlotId); } // String resource = resourceStat.getResourceId(); CircuitBreaker cb = circuitBreakManager.getCircuitBreaker(resource); if (cb != null) { cb.correctState(curTimeSlotId, stat); } } lastTimeSlotId = curTimeSlotId; // log.debug("PeakConcurrentJob done"); } try { Thread.sleep(1); } catch (Exception e) { log.error("PeakConcurrentJob error", e); } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy