Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
Mats^3 tool that can output an embeddable HTML describing a MatsFactory and all its endpoints, as well as "local statistics", gathered using a Mats Interceptor.
package io.mats3.localinspect;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import io.mats3.MatsFactory;
import io.mats3.MatsFactory.FactoryConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.mats3.MatsEndpoint;
import io.mats3.MatsEndpoint.ProcessContext;
import io.mats3.MatsInitiator;
import io.mats3.MatsStage;
import io.mats3.api.intercept.MatsInitiateInterceptor.MatsInitiateInterceptOutgoingMessages;
import io.mats3.api.intercept.MatsOutgoingMessage.MatsEditableOutgoingMessage;
import io.mats3.api.intercept.MatsOutgoingMessage.MatsSentOutgoingMessage;
import io.mats3.api.intercept.MatsOutgoingMessage.MessageType;
import io.mats3.api.intercept.MatsStageInterceptor.MatsStageInterceptOutgoingMessages;
import io.mats3.api.intercept.MatsStageInterceptor.StageCompletedContext.StageProcessResult;
/**
* Interceptor that collects "local stats" for Initiators and Stages of Endpoints, which can be used in conjunction with
* a MatsFactory report generator, {@link LocalHtmlInspectForMatsFactory}.
*
* To install, invoke the {@link #install(MatsFactory)} method, supplying the MatsFactory. The report generator
* will fetch the interceptor from the MatsFactory.
*
* Implementation note: Mats allows Initiators and Endpoints to be defined "runtime" - there is no specific setup
* time vs. run time. This implies that the set of Endpoints and Initiators in a MatsFactory can increase from one run
* of a report generation to the next (Endpoint may even be deleted, but this is really only meant for testing).
* Moreover, this stats collecting class only collects stats for Initiators and Endpoints that have had traffic, since
* it relies on the interceptors API. This means that while the MatsFactory might know about an Initiator or an
* Endpoint, this stats class might not yet have picked it up. This is why all methods return Optional. On the other
* hand, the set of Stages of an endpoint cannot change after it has been {@link MatsEndpoint#finishSetup()
* finishedSetup()} (and it cannot be {@link MatsEndpoint#start() start()}'ed before it has been
* finishedSetup()) - thus if e.g. only the initial stage of an Endpoint has so far seen traffic, this
* class has nevertheless created stats objects for all of the Endpoint's stages.
*
* @author Endre Stølsvik 2021-04-09 00:37 - http://stolsvik.com/, [email protected]
*/
public class LocalStatsMatsInterceptor
implements MatsInitiateInterceptOutgoingMessages, MatsStageInterceptOutgoingMessages {
private static final Logger log = LoggerFactory.getLogger(LocalStatsMatsInterceptor.class);
public static final int DEFAULT_NUM_SAMPLES = 1100;
/**
* This is an Out Of Memory avoidance in case of wrongly used initiatorIds. These are not supposed to be dynamic,
* but there is nothing hindering a user from creating a new initiatorId per initiation. Thus, if we go above a
* certain number of such entries, we stop adding.
*
* Value is 500.
*/
public static final int MAX_NUMBER_OF_DYNAMIC_ENTRIES = 500;
/**
* Creates an instance of this interceptor and installs it on the provided {@link MatsFactory}. Note that this
* interceptor is stateful wrt. the MatsFactory, thus a new instance is needed per MatsFactory - which is fulfilled
* using this method. It should only be invoked once per MatsFactory. You get the created interceptor in return,
* but that is not needed when employed with {@link LocalHtmlInspectForMatsFactory}, as that will fetch the
* instance from the MatsFactory using {@link FactoryConfig#getPlugins(Class)}.
*
* @param matsFactory
* the {@link MatsFactory MatsFactory} to add it to.
*/
public static LocalStatsMatsInterceptor install(MatsFactory matsFactory) {
LocalStatsMatsInterceptor interceptor = new LocalStatsMatsInterceptor(DEFAULT_NUM_SAMPLES);
matsFactory.getFactoryConfig().installPlugin(interceptor);
return interceptor;
}
private final int _numSamples;
private LocalStatsMatsInterceptor(int numSamples) {
_numSamples = numSamples;
}
private final ConcurrentHashMap _initiators = new ConcurrentHashMap<>();
private final ConcurrentHashMap, EndpointStatsImpl> _endpoints = new ConcurrentHashMap<>();
private final ConcurrentHashMap, StageStatsImpl> _stages = new ConcurrentHashMap<>();
// ======================================================================================================
// ===== Exposed API for LocalStatsMatsInterceptor
public Optional getInitiatorStats(MatsInitiator matsInitiator) {
return Optional.ofNullable(_initiators.get(matsInitiator));
}
public Optional getEndpointStats(MatsEndpoint, ?> matsEndpoint) {
return Optional.ofNullable(_endpoints.get(matsEndpoint));
}
public Optional getStageStats(MatsStage, ?, ?> matsStage) {
return Optional.ofNullable(_stages.get(matsStage));
}
public interface InitiatorStats {
StatsSnapshot getTotalExecutionTimeNanos();
NavigableMap getOutgoingMessageCounts();
}
public interface EndpointStats {
List getStagesStats();
StageStats getStageStats(MatsStage, ?, ?> stage);
StatsSnapshot getTotalEndpointProcessingTimeNanos();
/**
* @return whether the reply DTO is void, in which case it is regarded as a Terminator endpoint.
*/
boolean isTerminatorEndpoint();
/**
* Only relevant for Endpoints that {@link #isTerminatorEndpoint()}. Terminator endpoints have a special
* set of timings: Time taken from the start of initiation to the terminator receives it. Note: Most initiations
* specify a terminator in the same codebase as the initiation, but this is not a requirement. This timing is
* special in that it uses the differences in initiation timestamp (System.currentTimeMillis()) vs. reception at
* terminator (with millisecond precision) until it sees a reception that is on the same nodename as the
* initiation. At that point it switches over to using only timings that go between initiation and reception on
* the same node - this both removes the problem of time skews, and provide for more precise timings (since it
* uses System.nanoTime()), at the expense of only sampling a subset of the available observations.
*/
NavigableMap getInitiatorToTerminatorTimeNanos();
}
public interface StageStats {
int getIndex();
boolean isInitial();
/**
* Note: Only has millisecond resolution, AND is susceptible to time skews between nodes (uses
* System.currentTimeMillis() on the sending and receiving node).
*/
StatsSnapshot getSpentQueueTimeNanos();
/**
* Note: Not present for the {@link #isInitial()} stage, as there is no "between" for the initial stage.
*
* Note: Only recorded for messages that happens to have the two "between" stages executed on the same node, to
* both eliminate time skews between nodes, and to get higher precision (nanoTime()).
*/
Optional getBetweenStagesTimeNanos();
/**
* @return the stage's total execution time (from right after received, to right before going back to receive
* loop).
*/
StatsSnapshot getStageTotalExecutionTimeNanos();
NavigableMap getIncomingMessageCounts();
NavigableMap getProcessResultCounts();
NavigableMap getOutgoingMessageCounts();
}
interface StatsSnapshot {
/**
* @return the current set of samples. While starting, when we still haven't had max-samples yet, the array size
* will be lower than max-samples, down to 0 elements.
*/
long[] getSamples();
/**
* @param percentile
* the requested percentile, in the range [0, 1]. 0 means the lowest value (i.e. all samples are >=
* this value), while 1 means the highest sample (i.e. all samples are <= this value).
* @return the value at the desired percentile
*/
long getValueAtPercentile(double percentile);
/**
* @return the number of executions so far, which can be > max-samples.
*/
long getNumObservations();
// ===== Timings:
default long getMin() {
return getValueAtPercentile(0);
};
double getAverage();
default long getMax() {
return getValueAtPercentile(1);
};
double getStdDev();
default double getMedian() {
return getValueAtPercentile(0.5);
}
default double get75thPercentile() {
return getValueAtPercentile(0.75);
}
default double get95thPercentile() {
return getValueAtPercentile(0.95);
}
default double get98thPercentile() {
return getValueAtPercentile(0.98);
}
default double get99thPercentile() {
return getValueAtPercentile(0.99);
}
default double get999thPercentile() {
return getValueAtPercentile(0.999);
}
}
public interface MessageRepresentation {
MessageType getMessageType();
String getInitiatingAppName();
String getInitiatorId();
}
public interface IncomingMessageRepresentation extends MessageRepresentation,
Comparable {
String getFromAppName();
String getFromStageId();
}
public interface OutgoingMessageRepresentation extends MessageRepresentation,
Comparable {
String getTo();
Class> getMessageClass();
}
// ======================================================================================================
// ===== INITIATION interceptor implementation
public static final String EXTRA_STATE_REQUEST_NANOS = "mats.rts";
public static final String EXTRA_STATE_REQUEST_NODENAME = "mats.rnn";
public static final String EXTRA_STATE_ENDPOINT_ENTER_NANOS = "mats.eets";
public static final String EXTRA_STATE_ENDPOINT_ENTER_NODENAME = "mats.eenn";
public static final String EXTRA_STATE_OR_SIDELOAD_INITIATOR_NANOS = "mats.its";
public static final String EXTRA_STATE_OR_SIDELOAD_INITIATOR_NODENAME = "mats.inn";
@Override
public void initiateInterceptOutgoingMessages(InitiateInterceptOutgoingMessagesContext context) {
List outgoingMessages = context.getOutgoingMessages();
// :: INITIATOR TO TERMINATOR TIMING:
// Decorate outgoing messages with extra-state or sideloads, to get initiator to terminator timings
for (MatsEditableOutgoingMessage msg : outgoingMessages) {
// ?: Is this a REQUEST?
if (msg.getMessageType() == MessageType.REQUEST) {
// -> Yes, REQUEST.
// Set nanoTime and nodename in extra-state, for the final REPLY to terminator.
msg.setSameStackHeightExtraState(EXTRA_STATE_OR_SIDELOAD_INITIATOR_NANOS, context.getStartedNanoTime());
msg.setSameStackHeightExtraState(EXTRA_STATE_OR_SIDELOAD_INITIATOR_NODENAME,
context.getInitiator().getParentFactory().getFactoryConfig().getNodename());
}
else {
// -> No, so SEND or PUBLISH
// Set nanoTime and nodename in sideload, for the receiving endpoint
msg.addString(EXTRA_STATE_OR_SIDELOAD_INITIATOR_NANOS, Long.toString(context.getStartedNanoTime()));
msg.addString(EXTRA_STATE_OR_SIDELOAD_INITIATOR_NODENAME,
context.getInitiator().getParentFactory().getFactoryConfig().getNodename());
}
}
}
@Override
public void initiateCompleted(InitiateCompletedContext context) {
MatsInitiator initiator = context.getInitiator();
InitiatorStatsImpl initiatorStats = _initiators.computeIfAbsent(initiator, (v) -> new InitiatorStatsImpl(
_numSamples));
// :: TIMING
long totalExecutionNanos = context.getTotalExecutionNanos();
initiatorStats.recordTotalExecutionTimeNanos(totalExecutionNanos);
// :: OUTGOING MESSAGES
List outgoingMessages = context.getOutgoingMessages();
for (MatsSentOutgoingMessage msg : outgoingMessages) {
initiatorStats.recordOutgoingMessage(msg.getMessageType(), msg.getTo(),
msg.getData() == null ? null : msg.getData().getClass(),
msg.getInitiatingAppName(), msg.getInitiatorId());
}
}
// ======================================================================================================
// ===== STAGE interceptor implementation
@Override
public void stageReceived(StageReceivedContext i_context) {
MatsStage, ?, ?> stage = i_context.getStage();
// Must get-or-create this first, since this is what creates the StageStats.
EndpointStatsImpl endpointStats = getOrCreateEndpointStatsImpl(stage.getParentEndpoint());
// Get the StageStats
StageStatsImpl stageStats = _stages.get(stage);
// Get the ProcessContext
ProcessContext