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

io.antmedia.statistic.StatsCollector Maven / Gradle / Ivy

Go to download

Ant Media Server supports RTMP, RTSP, MP4, HLS, WebRTC, Adaptive Streaming, etc.

There is a newer version: 2.11.3
Show newest version
package io.antmedia.statistic;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.LongSerializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.bytedeco.javacpp.Pointer;
import org.red5.server.Launcher;
import org.red5.server.api.IServer;
import org.red5.server.api.listeners.IScopeListener;
import org.red5.server.api.scope.IScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import io.antmedia.AntMediaApplicationAdapter;
import io.antmedia.SystemUtils;
import io.antmedia.console.AdminApplication;
import io.antmedia.console.datastore.AbstractConsoleDataStore;
import io.antmedia.console.rest.CommonRestService;
import io.antmedia.datastore.db.types.Licence;
import io.antmedia.datastore.db.types.User;
import io.antmedia.licence.ILicenceService;
import io.antmedia.muxer.IAntMediaStreamHandler;
import io.antmedia.rest.RestServiceBase;
import io.antmedia.rest.WebRTCClientStats;
import io.antmedia.rest.model.UserType;
import io.antmedia.settings.ServerSettings;
import io.antmedia.statistic.GPUUtils.MemoryStatus;
import io.antmedia.webrtc.api.IWebRTCAdaptor;
import io.antmedia.websocket.WebSocketCommunityHandler;
import io.vertx.core.Vertx;
import io.vertx.ext.dropwizard.MetricsService;



public class StatsCollector implements IStatsCollector, ApplicationContextAware, DisposableBean {	

	public static final String FREE_NATIVE_MEMORY = "freeNativeMemory";

	public static final String TOTAL_NATIVE_MEMORY = "totalNativeMemory";

	public static final String IN_USE_NATIVE_MEMORY = "inUseNativeMemory";

	public static final String AVAILABLE_MEMORY = "availableMemory";

	public static final String IN_USE_SWAP_SPACE = "inUseSwapSpace";

	public static final String FREE_SWAP_SPACE = "freeSwapSpace";

	public static final String TOTAL_SWAP_SPACE = "totalSwapSpace";

	public static final String VIRTUAL_MEMORY = "virtualMemory";

	public static final String PROCESSOR_COUNT = "processorCount";

	public static final String JAVA_VERSION = "javaVersion";

	public static final String OS_ARCH = "osArch";

	public static final String OS_NAME = "osName";

	public static final String IN_USE_SPACE = "inUseSpace";

	public static final String FREE_SPACE = "freeSpace";

	public static final String TOTAL_SPACE = "totalSpace";

	public static final String USABLE_SPACE = "usableSpace";

	public static final String IN_USE_MEMORY = "inUseMemory";

	public static final String FREE_MEMORY = "freeMemory";

	public static final String TOTAL_MEMORY = "totalMemory";

	public static final String MAX_MEMORY = "maxMemory";

	public static final String PROCESS_CPU_LOAD = "processCPULoad";

	public static final String SYSTEM_CPU_LOAD = "systemCPULoad";

	public static final String PROCESS_CPU_TIME = "processCPUTime";

	public static final String CPU_USAGE = "cpuUsage";

	public static final String INSTANCE_ID = "instanceId";
	
	public static final String MARKETPLACE_NAME = "marketplace";
	
	public static final String USER_EMAIL = "userEmail";
	
	public static final String LICENSE_VALID = "licenseValid";

	public static final String INSTANCE_TYPE = "instanceType";

	public static final String INSTANCE_VERSION = "instanceVersion";

	public static final String JVM_MEMORY_USAGE = "jvmMemoryUsage";

	public static final String NATIVE_MEMORY_USAGE = "nativeMemoryUsage";

	public static final String SYSTEM_INFO = "systemInfo";

	public static final String SYSTEM_MEMORY_INFO = "systemMemoryInfo";

	public static final String FILE_SYSTEM_INFO = "fileSystemInfo";

	public static final String GPU_UTILIZATION = "gpuUtilization";

	public static final String GPU_DEVICE_INDEX = "index";

	public static final String GPU_MEMORY_UTILIZATION = "memoryUtilization";

	public static final String GPU_MEMORY_TOTAL = "memoryTotal";

	public static final String GPU_MEMORY_FREE = "memoryFree";

	public static final String GPU_MEMORY_USED = "memoryUsed";

	public static final String GPU_DEVICE_NAME = "deviceName";

	public static final String GPU_USAGE_INFO = "gpuUsageInfo";

	public static final String TOTAL_LIVE_STREAMS = "totalLiveStreamSize";

	public static final String LOCAL_WEBRTC_LIVE_STREAMS = "localWebRTCLiveStreams";

	public static final String LOCAL_LIVE_STREAMS = "localLiveStreams";

	public static final String LOCAL_WEBRTC_VIEWERS = "localWebRTCViewers";

	public static final String LOCAL_HLS_VIEWERS = "localHLSViewers";

	private static final String TIME = "time";

	protected static final Logger logger = LoggerFactory.getLogger(StatsCollector.class);

	private static final String MEASURED_BITRATE = "measured_bitrate";

	private static final String SEND_BITRATE = "send_bitrate";

	private static final String AUDIO_FRAME_SEND_PERIOD = "audio_frame_send_period";

	private static final String VIDEO_FRAME_SEND_PERIOD = "video_frame_send_period";

	private static final String STREAM_ID = "streamId";

	private static final String WEBRTC_CLIENT_ID = "webrtcClientId";

	private static final String WEBRTC_VIEWER_INFO = "webrtcViewerInfo";

	private Queue scopes = new ConcurrentLinkedQueue<>();

	public static final String GA_TRACKING_ID = "UA-93263926-3";

	private Vertx vertx;
	private Queue cpuMeasurements = new ConcurrentLinkedQueue<>();

	Gson gson = new Gson();

	private int windowSize = 5;
	private int measurementPeriod = 1000;
	private int staticSendPeriod = 15000;

	private int cpuLoad;
	private int cpuLimit = 70;

	/**
	 * Min Free Ram Size that free memory should be always more than min
	 */
	private int minFreeRamSize = 50;

	private String kafkaBrokers = null;

	public static final String INSTANCE_STATS_TOPIC_NAME = "ams-instance-stats";

	public static final String WEBRTC_STATS_TOPIC_NAME = "ams-webrtc-stats";

	public static final String UP_TIME = "up-time";

	public static final String START_TIME = "start-time";

	public static final String SERVER_TIMING = "server-timing";

	private static final String ENCODERS_BLOCKED = "encoders-blocked";

	private static final String ENCODERS_NOT_OPENED = "encoders-not-opened";

	private static final String PUBLISH_TIMEOUT_ERRORS = "publish-timeout-errors";

	private static final String THREAD_DUMP = "thread-dump";

	public static final String DEAD_LOCKED_THREAD = "dead-locked-thread";

	public static final String THREAD_COUNT = "thread-count";

	public static final String THREAD_PEEK_COUNT = "thread-peek-count";

	private static final String THREAD_NAME = "thread-name";

	private static final String THREAD_ID = "thread-id";

	private static final String THREAD_BLOCKED_TIME = "blocked-time";

	private static final String THREAD_BLOCKED_COUNT = "blocked-count";

	private static final String THREAD_WAITED_TIME = "waited-time";

	private static final String THREAD_WAITED_COUNT = "waited-count";

	private static final String THREAD_LOCK_NAME = "lock-name";

	private static final String THREAD_LOCK_OWNER_ID = "lock-owner-id";

	private static final String THREAD_LOCK_OWNER_NAME = "lock-owner-name";

	private static final String THREAD_IN_NATIVE = "in-native";

	private static final String THREAD_SUSPENDED = "suspended";

	private static final String THREAD_STATE = "state";

	private static final String THREAD_CPU_TIME = "cpu-time";

	private static final String THREAD_USER_TIME = "user-time";

	public static final String IN_USE_JVM_NATIVE_MEMORY = "inUseMemory";

	public static final String MAX_JVM_NATIVE_MEMORY = "maxMemory";

	public static final String JVM_NATIVE_MEMORY_USAGE = "jvmNativeMemoryUsage";

	private static final String HOST_ADDRESS = "host-address";

	private static final String VERTX_WORKER_QUEUE_SIZE = "vertx.pools.worker.vert.x-worker-thread.queue-size";

	private static final String VERTX_WORKER_THREAD_QUEUE_SIZE = "vertx-worker-thread-queue-size";

	private static final String WEBRTC_VERTX_WORKER_THREAD_QUEUE_SIZE = "webrtc-vertx-worker-thread-queue-size";


	private Producer kafkaProducer = null;

	private long cpuMeasurementTimerId = -1;

	private long kafkaTimerId = -1;

	private boolean heartBeatEnabled = true;

	private long hearbeatPeriodicTask;

	private int heartbeatPeriodMs = 300000;

	private String hostAddress;

	private Vertx webRTCVertx;

	private int time2Log = 0;

	private String marketplace;

	private static MetricsService vertXMetrics;

	private static MetricsService webRTCVertxMetrics;

	private ILicenceService licenseService;

	private String userEmail;

	public void start() {
		cpuMeasurementTimerId  = getVertx().setPeriodic(measurementPeriod, l -> 
		{
			addCpuMeasurement(SystemUtils.getSystemCpuLoad());

			//log every 5 minute
			if (300000/measurementPeriod == time2Log) {
				if(logger != null) 
				{
					logger.info("System cpu load:{} process cpu load:{} available memory: {} KB used memory(RSS): {} KB", cpuLoad, SystemUtils.getProcessCpuLoad(), SystemUtils.convertByteSize(SystemUtils.osAvailableMemory(), "KB"), SystemUtils.convertByteSize(Pointer.physicalBytes(), "KB"));

					int vertxWorkerQueueSize = getVertWorkerQueueSizeStatic();

					int webRTCVertxWorkerQueueSize = getWebRTCVertxWorkerQueueSizeStatic();

					logger.info("Vertx worker queue size:{} WebRTCVertx worker queue size:{}", vertxWorkerQueueSize, webRTCVertxWorkerQueueSize);

				}

				time2Log = 0;
			}
			time2Log++;
		});
		startKafkaProducer();

		if (heartBeatEnabled) {

			logger.warn("Starting heartbeats for the version:{} and type:{}", Launcher.getVersion(), Launcher.getVersionType());

			getVertx().setPeriodic(heartbeatPeriodMs, l -> 
			{
				startAnalytic();
			});
		}
		else {
			logger.info("Heartbeats are disabled for this instance");
		}
	}

	private void startKafkaProducer() {
		if (kafkaBrokers != null && !kafkaBrokers.isEmpty()) {
			kafkaProducer = createKafkaProducer();		

			kafkaTimerId  = getVertx().setPeriodic(staticSendPeriod, l -> {
				sendInstanceStats(scopes);
				sendWebRTCClientStats();
			});
		}	
	}
	
	private static int getVertWorkerQueueSizeStatic() {
		io.vertx.core.json.JsonObject queueSizeMetrics = vertXMetrics.getMetricsSnapshot(VERTX_WORKER_QUEUE_SIZE);
		io.vertx.core.json.JsonObject jsonObject = null;
		if (queueSizeMetrics != null) {
			jsonObject = queueSizeMetrics.getJsonObject(VERTX_WORKER_QUEUE_SIZE);
		}
		return jsonObject != null ? jsonObject.getInteger("count") : -1;
	}
	
	public int getVertWorkerQueueSize() {
		return getVertWorkerQueueSizeStatic();
	}

	private static int getWebRTCVertxWorkerQueueSizeStatic() {
		io.vertx.core.json.JsonObject queueSizeMetrics = webRTCVertxMetrics.getMetricsSnapshot(VERTX_WORKER_QUEUE_SIZE);
		io.vertx.core.json.JsonObject jsonObject = null;
		if (queueSizeMetrics != null) {
			jsonObject = queueSizeMetrics.getJsonObject(VERTX_WORKER_QUEUE_SIZE);
		}
		return jsonObject != null ? jsonObject.getInteger("count") : -1;
	}
	
	public int getWebRTCVertxWorkerQueueSize() {
		return getWebRTCVertxWorkerQueueSizeStatic();
	}

	private void sendWebRTCClientStats() {
		getVertx().executeBlocking(
				b -> {
					collectAndSendWebRTCClientsStats();
					b.complete();
				}, 
				null);
	}
	

	public void collectAndSendWebRTCClientsStats() {

		for (Iterator iterator = scopes.iterator(); iterator.hasNext();) { 
			IScope scope = iterator.next();

			if( scope.getContext().getApplicationContext().containsBean(IWebRTCAdaptor.BEAN_NAME))
			{
				IWebRTCAdaptor webrtcAdaptor = (IWebRTCAdaptor)scope.getContext().getApplicationContext().getBean(IWebRTCAdaptor.BEAN_NAME);
				Set streams = webrtcAdaptor.getStreams();
				List webRTCClientStats;
				for (String streamId : streams) {
					webRTCClientStats = webrtcAdaptor.getWebRTCClientStats(streamId);
					sendWebRTCClientStats2Kafka(webRTCClientStats, streamId);			
				}							
			}
		}


	}

	public void sendWebRTCClientStats2Kafka(List webRTCClientStatList, String streamId) {
		JsonObject jsonObject;
		String dateTime = DateTimeFormatter.ISO_INSTANT.format(Instant.now());
		for (WebRTCClientStats webRTCClientStat : webRTCClientStatList) 
		{
			jsonObject = new JsonObject();
			jsonObject.addProperty(STREAM_ID, streamId);
			jsonObject.addProperty(WEBRTC_CLIENT_ID, webRTCClientStat.getClientId());
			jsonObject.addProperty(AUDIO_FRAME_SEND_PERIOD, (int)webRTCClientStat.getAudioFrameSendPeriod());
			jsonObject.addProperty(VIDEO_FRAME_SEND_PERIOD, (int)webRTCClientStat.getVideoFrameSendPeriod());
			jsonObject.addProperty(MEASURED_BITRATE, webRTCClientStat.getMeasuredBitrate());
			jsonObject.addProperty(SEND_BITRATE, webRTCClientStat.getSendBitrate());
			jsonObject.addProperty(TIME, dateTime);
			jsonObject.addProperty(HOST_ADDRESS, hostAddress);
			jsonObject.addProperty(WEBRTC_VIEWER_INFO, webRTCClientStat.getClientInfo());

			//logstash cannot parse json array so that we send each info separately
			send2Kafka(jsonObject, WEBRTC_STATS_TOPIC_NAME);
		}
	}

	public Producer createKafkaProducer() {
		Properties props = new Properties();
		props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaBrokers);
		props.put(ProducerConfig.CLIENT_ID_CONFIG, Launcher.getInstanceId());
		props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, LongSerializer.class.getName());
		props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
		props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 10000);
		return new KafkaProducer<>(props);
	}

	public static JsonObject getFileSystemInfoJSObject() {
		JsonObject jsonObject = new JsonObject();
		jsonObject.addProperty(USABLE_SPACE, SystemUtils.osHDUsableSpace(null));
		jsonObject.addProperty(TOTAL_SPACE, SystemUtils.osHDTotalSpace(null));
		jsonObject.addProperty(FREE_SPACE, SystemUtils.osHDFreeSpace(null));
		jsonObject.addProperty(IN_USE_SPACE, SystemUtils.osHDInUseSpace(null));
		return jsonObject;
	}


	public static JsonObject getGPUInfoJSObject(int deviceIndex, GPUUtils gpuUtils) {
		JsonObject jsonObject = new JsonObject();
		jsonObject.addProperty(GPU_DEVICE_INDEX, deviceIndex);
		jsonObject.addProperty(GPU_UTILIZATION, gpuUtils.getGPUUtilization(deviceIndex));
		jsonObject.addProperty(GPU_MEMORY_UTILIZATION, gpuUtils.getMemoryUtilization(deviceIndex));
		MemoryStatus memoryStatus = gpuUtils.getMemoryStatus(deviceIndex);
		jsonObject.addProperty(GPU_MEMORY_TOTAL, memoryStatus.getMemoryTotal());
		jsonObject.addProperty(GPU_MEMORY_FREE, memoryStatus.getMemoryFree());
		jsonObject.addProperty(GPU_MEMORY_USED, memoryStatus.getMemoryUsed());
		jsonObject.addProperty(GPU_DEVICE_NAME, GPUUtils.getInstance().getDeviceName(deviceIndex));

		return jsonObject;
	}


	public static JsonArray getGPUInfoJSObject() {
		int deviceCount = GPUUtils.getInstance().getDeviceCount();
		JsonArray jsonArray = new JsonArray();
		if (deviceCount > 0) {
			for (int i=0; i < deviceCount; i++) {
				jsonArray.add(getGPUInfoJSObject(i, GPUUtils.getInstance()));
			}
		}
		return jsonArray;
	}

	public static JsonObject getCPUInfoJSObject() {
		JsonObject jsonObject = new JsonObject();
		jsonObject.addProperty(PROCESS_CPU_TIME, SystemUtils.getProcessCpuTime());
		jsonObject.addProperty(SYSTEM_CPU_LOAD, SystemUtils.getSystemCpuLoad());
		jsonObject.addProperty(PROCESS_CPU_LOAD, SystemUtils.getProcessCpuLoad());
		return jsonObject;
	}

	public static ThreadInfo[] getThreadDump() {
		ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
		return threadMXBean.dumpAllThreads(true, true);
	}

	public static JsonArray getThreadDumpJSON() {
		ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

		ThreadInfo[] threadDump = threadMXBean.dumpAllThreads(true, true);
		JsonArray jsonArray = new JsonArray();

		for (int i = 0; i < threadDump.length; i++) {
			JsonObject jsonObject = new JsonObject();

			jsonObject.addProperty(THREAD_NAME, threadDump[i].getThreadName());
			jsonObject.addProperty(THREAD_ID, threadDump[i].getThreadId());
			jsonObject.addProperty(THREAD_BLOCKED_TIME, threadDump[i].getBlockedTime());
			jsonObject.addProperty(THREAD_BLOCKED_COUNT, threadDump[i].getBlockedCount());
			jsonObject.addProperty(THREAD_WAITED_TIME, threadDump[i].getWaitedTime());
			jsonObject.addProperty(THREAD_WAITED_COUNT, threadDump[i].getWaitedCount());
			jsonObject.addProperty(THREAD_LOCK_NAME, threadDump[i].getLockName());
			jsonObject.addProperty(THREAD_LOCK_OWNER_ID, threadDump[i].getLockOwnerId());
			jsonObject.addProperty(THREAD_LOCK_OWNER_NAME, threadDump[i].getLockOwnerName());
			jsonObject.addProperty(THREAD_IN_NATIVE, threadDump[i].isInNative());
			jsonObject.addProperty(THREAD_SUSPENDED, threadDump[i].isSuspended());
			jsonObject.addProperty(THREAD_STATE, threadDump[i].getThreadState().toString());
			jsonObject.addProperty(THREAD_CPU_TIME, threadMXBean.getThreadCpuTime(threadDump[i].getThreadId()));
			jsonObject.addProperty(THREAD_USER_TIME, threadMXBean.getThreadUserTime(threadDump[i].getThreadId()));

			jsonArray.add(jsonObject);
		}

		return jsonArray;

	}

	private static JsonArray getDeadLockedThreads(long[] deadLockedThreads) {
		JsonArray jsonArray = new JsonArray();
		if (deadLockedThreads != null) {
			for (int i = 0; i < deadLockedThreads.length; i++) {
				jsonArray.add(deadLockedThreads[i]);
			}
		}
		return jsonArray;
	}

	public static JsonObject getThreadInfoJSONObject() {
		JsonObject jsonObject = new JsonObject();
		ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
		jsonObject.add(DEAD_LOCKED_THREAD, getDeadLockedThreads(threadMXBean.findDeadlockedThreads()));
		jsonObject.addProperty(THREAD_COUNT, threadMXBean.getThreadCount());
		jsonObject.addProperty(THREAD_PEEK_COUNT, threadMXBean.getPeakThreadCount());

		return jsonObject;
	}

	public static JsonObject getJVMMemoryInfoJSObject() {
		JsonObject jsonObject = new JsonObject();

		jsonObject.addProperty(MAX_MEMORY, SystemUtils.jvmMaxMemory());
		jsonObject.addProperty(TOTAL_MEMORY, SystemUtils.jvmTotalMemory());
		jsonObject.addProperty(FREE_MEMORY, SystemUtils.jvmFreeMemory());
		jsonObject.addProperty(IN_USE_MEMORY, SystemUtils.jvmInUseMemory());
		return jsonObject;
	}

	public static JsonObject getSystemInfoJSObject() {
		JsonObject jsonObject = new JsonObject();
		jsonObject.addProperty(OS_NAME, SystemUtils.osName);
		jsonObject.addProperty(OS_ARCH, SystemUtils.osArch);
		jsonObject.addProperty(JAVA_VERSION, SystemUtils.jvmVersion);
		jsonObject.addProperty(PROCESSOR_COUNT, SystemUtils.osProcessorX);
		return jsonObject;
	}

	public static JsonObject getSysteMemoryInfoJSObject() {
		JsonObject jsonObject = new JsonObject();

		jsonObject.addProperty(VIRTUAL_MEMORY, SystemUtils.osCommittedVirtualMemory());
		jsonObject.addProperty(TOTAL_MEMORY, SystemUtils.osTotalPhysicalMemory());
		jsonObject.addProperty(FREE_MEMORY, SystemUtils.osFreePhysicalMemory());
		jsonObject.addProperty(IN_USE_MEMORY, SystemUtils.osInUsePhysicalMemory());
		
		//to handle the problem in raspberry pi4 + ubuntu 20.04
		try {
			jsonObject.addProperty(TOTAL_SWAP_SPACE, SystemUtils.osTotalSwapSpace());
			jsonObject.addProperty(FREE_SWAP_SPACE, SystemUtils.osFreeSwapSpace());
			jsonObject.addProperty(IN_USE_SWAP_SPACE, SystemUtils.osInUseSwapSpace());
		}catch (Exception e) {
			logger.error("swap memory statistic can not be read");
		}
		
		jsonObject.addProperty(AVAILABLE_MEMORY, SystemUtils.osAvailableMemory());

		return jsonObject;
	}

	public static JsonObject getJVMNativeMemoryInfoJSObject() {
		JsonObject jsonObject = new JsonObject();

		long maxPhysicalBytes = Pointer.maxPhysicalBytes();
		long inUsephysicalBytes = Pointer.physicalBytes();

		jsonObject.addProperty(IN_USE_JVM_NATIVE_MEMORY, inUsephysicalBytes);
		jsonObject.addProperty(MAX_JVM_NATIVE_MEMORY, maxPhysicalBytes);
		return jsonObject;
	}


	/**
	 * Returns server uptime and startime in milliseconds
	 * @return
	 */
	public static JsonObject getServerTime() 
	{
		JsonObject jsonObject = new JsonObject();
		jsonObject.addProperty(StatsCollector.UP_TIME, ManagementFactory.getRuntimeMXBean().getUptime());
		jsonObject.addProperty(StatsCollector.START_TIME, ManagementFactory.getRuntimeMXBean().getStartTime());
		return jsonObject;
	}
	
	public static AdminApplication getAdminAppAdaptor(ApplicationContext appContext) {
		AdminApplication adaptor = null;
		
		if (appContext.containsBean(AntMediaApplicationAdapter.BEAN_NAME)) 
		{
			Object appHandler =appContext.getBean(AntMediaApplicationAdapter.BEAN_NAME);
			if (appHandler instanceof  AdminApplication) 
			{
				adaptor = (AdminApplication) appHandler;
			}
		}
		
		return adaptor;
	}

	public static AntMediaApplicationAdapter getAppAdaptor(ApplicationContext appContext) 
	{
		AntMediaApplicationAdapter adaptor = null;
		
		if (appContext.containsBean(AntMediaApplicationAdapter.BEAN_NAME)) 
		{
			Object appHandler =appContext.getBean(AntMediaApplicationAdapter.BEAN_NAME);
			if (appHandler instanceof  AntMediaApplicationAdapter) 
			{
				adaptor = (AntMediaApplicationAdapter) appHandler;
			}
		}
		
		return adaptor;
	}

	public static JsonObject getSystemResourcesInfo(Queue scopes) 
	{
		JsonObject jsonObject = new JsonObject();
		jsonObject.addProperty(INSTANCE_ID, Launcher.getInstanceId());
		jsonObject.add(CPU_USAGE, getCPUInfoJSObject());
		jsonObject.add(JVM_MEMORY_USAGE, getJVMMemoryInfoJSObject());
		jsonObject.add(SYSTEM_INFO, getSystemInfoJSObject());
		jsonObject.add(SYSTEM_MEMORY_INFO, getSysteMemoryInfoJSObject());
		jsonObject.add(FILE_SYSTEM_INFO, getFileSystemInfoJSObject());
		jsonObject.add(JVM_NATIVE_MEMORY_USAGE, getJVMNativeMemoryInfoJSObject());

		//add gpu info 
		jsonObject.add(StatsCollector.GPU_USAGE_INFO, StatsCollector.getGPUInfoJSObject());

		int localHlsViewers = 0;
		int localWebRTCViewers = 0;
		int localWebRTCStreams = 0;
		int localStreams = 0;
		int encodersBlocked = 0;
		int encodersNotOpened = 0;
		int publishTimeoutError = 0;
		if (scopes != null) {
			for (Iterator iterator = scopes.iterator(); iterator.hasNext();) { 
				IScope scope = iterator.next();
				localHlsViewers += getHLSViewers(scope);

				if( scope.getContext().getApplicationContext().containsBean(IWebRTCAdaptor.BEAN_NAME)) {
					IWebRTCAdaptor webrtcAdaptor = (IWebRTCAdaptor)scope.getContext().getApplicationContext().getBean(IWebRTCAdaptor.BEAN_NAME);
					localWebRTCStreams += webrtcAdaptor.getNumberOfLiveStreams();
					localWebRTCViewers += webrtcAdaptor.getNumberOfTotalViewers();
				}

				AntMediaApplicationAdapter adaptor = null;

				if ((adaptor = getAppAdaptor(scope.getContext().getApplicationContext())) != null)
				{
					encodersBlocked += adaptor.getNumberOfEncodersBlocked();
					encodersNotOpened += adaptor.getNumberOfEncoderNotOpenedErrors();
					publishTimeoutError += adaptor.getNumberOfPublishTimeoutError();
					localStreams += adaptor.getMuxAdaptors().size();
					
				}
			}
		}

		//add local webrtc viewer size
		jsonObject.addProperty(StatsCollector.LOCAL_WEBRTC_LIVE_STREAMS, localWebRTCStreams);
		jsonObject.addProperty(StatsCollector.LOCAL_LIVE_STREAMS, localStreams);
		jsonObject.addProperty(StatsCollector.LOCAL_WEBRTC_VIEWERS, localWebRTCViewers);
		jsonObject.addProperty(StatsCollector.LOCAL_HLS_VIEWERS, localHlsViewers);	
		jsonObject.addProperty(StatsCollector.ENCODERS_BLOCKED, encodersBlocked);
		jsonObject.addProperty(StatsCollector.ENCODERS_NOT_OPENED, encodersNotOpened);
		jsonObject.addProperty(StatsCollector.PUBLISH_TIMEOUT_ERRORS, publishTimeoutError);
		jsonObject.addProperty(StatsCollector.VERTX_WORKER_THREAD_QUEUE_SIZE, getVertWorkerQueueSizeStatic());
		jsonObject.addProperty(StatsCollector.WEBRTC_VERTX_WORKER_THREAD_QUEUE_SIZE, getWebRTCVertxWorkerQueueSizeStatic());

		//add timing info
		jsonObject.add(StatsCollector.SERVER_TIMING, getServerTime());

		return jsonObject;
	}

	private static int getHLSViewers(IScope scope) {
		if (scope.getContext().getApplicationContext().containsBean(HlsViewerStats.BEAN_NAME)) {
			HlsViewerStats hlsViewerStats = (HlsViewerStats) scope.getContext().getApplicationContext().getBean(HlsViewerStats.BEAN_NAME);
			if (hlsViewerStats != null) {
				return hlsViewerStats.getTotalViewerCount();
			}
		}
		return 0;
	}

	public void sendInstanceStats(Queue scopes) {

		JsonObject jsonObject = getSystemResourcesInfo(scopes);

		jsonObject.addProperty(TIME, DateTimeFormatter.ISO_INSTANT.format(Instant.now()));
		jsonObject.addProperty(HOST_ADDRESS, hostAddress);

		send2Kafka(jsonObject, INSTANCE_STATS_TOPIC_NAME); 

	}

	public void send2Kafka(JsonElement jsonElement, String topicName) {
		ProducerRecord record = new ProducerRecord<>(topicName,
				gson.toJson(jsonElement));
		try {
			kafkaProducer.send(record).get();
		} 
		catch (ExecutionException e) {
			logger.error(ExceptionUtils.getStackTrace(e));
		} catch (InterruptedException e) {
			logger.error(ExceptionUtils.getStackTrace(e));
			Thread.currentThread().interrupt();
		}
	}

	public void addCpuMeasurement(int measurment) {
		cpuMeasurements.add(measurment);
		if(cpuMeasurements.size() > windowSize) {
			cpuMeasurements.poll();
		}

		int total = 0;
		for (int msrmnt : cpuMeasurements) {
			total += msrmnt;
		}		
		cpuLoad = total/cpuMeasurements.size();
	}


	@Override
	public boolean enoughResource(){

		boolean enoughResource = false;

		if(getCpuLoad() < getCpuLimit()) 
		{		
			int freeRam = getFreeRam();
			if (freeRam > getMinFreeRamSize() || freeRam == -1)  
			{
				//if it does not calculate the free ram, return true
				enoughResource = true;		
			}
			else {
				logger.error("Not enough resource. Due to not free RAM. Free RAM should be more than  {} but it is: {}", minFreeRamSize, getFreeRam());
			}

		}
		else {
			logger.error("Not enough resource. Due to high cpu load: {} cpu limit: {}", cpuLoad, cpuLimit);
		}

		return enoughResource; 
	}

	@Override
	public int getFreeRam() {
		long availableMemory = SystemUtils.osAvailableMemory();
		if (availableMemory != 0) {
			return (int)SystemUtils.convertByteSize(availableMemory, "MB");
		}
		return -1;
	}

	@Override
	public int getMinFreeRamSize() {
		return minFreeRamSize;
	}

	public void setMinFreeRamSize(int ramLimit) {
		this.minFreeRamSize = ramLimit;
	}

	public void setCpuLoad(int cpuLoad) {
		this.cpuLoad = cpuLoad;
	}

	@Override
	public int getCpuLoad() {
		return cpuLoad;
	}


	public int getWindowSize() {
		return windowSize;
	}


	public void setWindowSize(int windowSize) {
		this.windowSize = windowSize;
	}


	public Vertx getVertx() {
		return vertx;
	}


	public void setVertx(Vertx vertx) {
		this.vertx = vertx;
		vertXMetrics= MetricsService.create(vertx);
	}

	public void setWebRTCVertx(Vertx webRTCVertx) {
		this.webRTCVertx = webRTCVertx;
		webRTCVertxMetrics =  MetricsService.create(webRTCVertx);
	}


	public void setCpuLimit(int cpuLimit) {
		if (cpuLimit > 100) {
			this.cpuLimit = 100;
		}
		else if (cpuLimit < 10) {
			this.cpuLimit = 10;
		}
		else {
			this.cpuLimit = cpuLimit;
		}
	}

	@Override
	public int getCpuLimit() {
		return cpuLimit;
	}

	@Override
	public void setApplicationContext(org.springframework.context.ApplicationContext applicationContext)
			throws BeansException {
		IServer server = (IServer) applicationContext.getBean(IServer.ID);
		server.addListener(new IScopeListener() {

			@Override
			public void notifyScopeRemoved(IScope scope) {
				scopes.remove(scope);
			}

			@Override
			public void notifyScopeCreated(IScope scope) {
				scopes.add(scope);
			}
		});

		ServerSettings serverSettings = (ServerSettings) applicationContext.getBean(ServerSettings.BEAN_NAME);
		heartBeatEnabled = serverSettings.isHeartbeatEnabled();
		hostAddress = serverSettings.getHostAddress();
		measurementPeriod = serverSettings.getCpuMeasurementPeriodMs();
		windowSize = serverSettings.getCpuMeasurementWindowSize();
		marketplace = serverSettings.getMarketplace();
		
		licenseService = (ILicenceService) applicationContext.getBean(ILicenceService.BeanName.LICENCE_SERVICE.toString());

		setVertx((Vertx) applicationContext.getBean(IAntMediaStreamHandler.VERTX_BEAN_NAME));


		setWebRTCVertx((Vertx) applicationContext.getBean(WebSocketCommunityHandler.WEBRTC_VERTX_BEAN_NAME));

	}

	public int getStaticSendPeriod() {
		return staticSendPeriod;
	}

	public void setStaticSendPeriod(int staticSendPeriod) {
		this.staticSendPeriod = staticSendPeriod;
	}

	public void setKafkaProducer(Producer kafkaProducer) {
		this.kafkaProducer = kafkaProducer;
	}

	public String getKafkaBrokers() {
		return kafkaBrokers;
	}

	public void setKafkaBrokers(String kafkaBrokers) {
		this.kafkaBrokers = kafkaBrokers;
	}

	public void setScopes(Queue scopes) {
		this.scopes = scopes;
	}

	public void cancelHeartBeat() {
		vertx.cancelTimer(hearbeatPeriodicTask);
	}

	public boolean isHeartBeatEnabled() {
		return heartBeatEnabled;
	}

	public void setHeartBeatEnabled(boolean heartBeatEnabled) {
		this.heartBeatEnabled = heartBeatEnabled;
	}

	public int getHeartbeatPeriodMs() {
		return heartbeatPeriodMs;
	}

	public void setHeartbeatPeriodMs(int heartbeatPeriodMs) {
		this.heartbeatPeriodMs = heartbeatPeriodMs;
	}

	@Override
	public void destroy() throws Exception {
		if(logger != null) {
			logger.info("Shutting down stats collector ");
		}

		if (heartBeatEnabled) 
		{  
			//send session end if heartBeatEnabled 
			if(logger != null) {
				logger.info("Ending analytic session");
			}
		}
		vertx.close();
		webRTCVertx.close();
		if(logger != null) {
			logger.info("Closing vertx ");
		}

	}

	public int getMeasurementPeriod() {
		return measurementPeriod;
	}

	public void startAnalytic() {

		String instanceId = Launcher.getInstanceId();
		String version = Launcher.getVersion();
		String type = Launcher.getVersionType();

		JsonObject instance = new JsonObject();
		instance.addProperty(INSTANCE_ID, instanceId);
		instance.addProperty(INSTANCE_TYPE, type);
		instance.addProperty(INSTANCE_VERSION, version);
		instance.addProperty(MARKETPLACE_NAME, marketplace);
		
		
		instance.addProperty(USER_EMAIL, getUserEmail());
		
		if (RestServiceBase.isEnterprise()) 
		{
			Licence lastLicenseStatus = licenseService.getLastLicenseStatus();
			String status = "invalid";
			if (lastLicenseStatus != null) 
			{
				status = lastLicenseStatus.getStatus();
			}
			instance.addProperty(LICENSE_VALID, status);
		}
		
		try (CloseableHttpClient client = getHttpClient())
		{
			HttpRequestBase post = (HttpRequestBase)RequestBuilder.post().setUri("https://us-central1-ant-media-server-analytics.cloudfunctions.net/sendHeartbeat").setHeader(HttpHeaders.CONTENT_TYPE, "application/json")
					.setEntity(new StringEntity(instance.toString())).build();

			RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(2 * 1000).setSocketTimeout(5*1000).build();

			post.setConfig(requestConfig);

			client.execute(post);

		}catch (IOException e) {
			logger.error("Couldn't connect Ant Media Server Analytics");
		} 
	}
	
	public void setUserEmail(String userEmail) {
		this.userEmail = userEmail;
	}

	public String getUserEmail() 
	{
		if (userEmail == null) 
		{
			for (Iterator iterator = scopes.iterator(); iterator.hasNext();) 
			{ 
				IScope scope = iterator.next();

				
				AdminApplication adaptor = null;

				if ((adaptor = getAdminAppAdaptor(scope.getContext().getApplicationContext())) != null)
				{
					AbstractConsoleDataStore dataStore = adaptor.getDataStoreFactory().getDataStore();
					
					List userList = dataStore.getUserList();
					
					userEmail = findAdminUser(userList);
					break;
				}
			}
		}
		return userEmail;
	}

	private String findAdminUser(List userList) {
		
		String email = null;
		for (Iterator iterator2 = userList.iterator(); iterator2.hasNext();) 
		{
			User user = (User) iterator2.next();
			
			if (user.getUserType() == UserType.ADMIN && CommonRestService.SCOPE_SYSTEM.equals(user.getScope())) 
			{
				email = user.getEmail();
				break;
			}
		}
		return email;
	}

	public static CloseableHttpClient getHttpClient() {
		return  HttpClients.custom().setRedirectStrategy(new LaxRedirectStrategy())
				.build();
	}


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy