/// <summary> /// This is the main constructor. /// </summary> /// <param name="resourceTracker">The resource tracker.</param> /// <param name="client">The client to hold.</param> /// <param name="mappingChannelId">The mapping channel.param> /// <param name="maxAllowedPollWait">The maximum permitted poll length.</param> public ClientPriorityHolder(IResourceTracker resourceTracker , ClientHolder client , string mappingChannelId , IListenerClientPollAlgorithm priorityAlgorithm ) { if (client == null) { throw new ArgumentNullException("client"); } if (priorityAlgorithm == null) { throw new ArgumentNullException("algorithm"); } mPriorityAlgorithm = priorityAlgorithm; Client = client; mMappingChannel = mappingChannelId; //Create the metrics container to hold the calculations for poll priority and reservation amount. mMetrics = new ClientPriorityHolderMetrics(mPriorityAlgorithm , resourceTracker?.RegisterRequestRateLimiter(client.Name, client.ResourceProfiles) , client.Priority , client.Weighting ); mPriorityAlgorithm.InitialiseMetrics(mMetrics); }
/// <summary> /// This method recalculates the metrics after the poll has returned from the fabric. /// </summary> /// <param name="success">The flag indicating whether the last poll was successful.</param> /// <param name="hasErrored"></param> /// <param name="context">The metrics.</param> public override void PollMetricsRecalculate(bool success, bool hasErrored, ClientPriorityHolderMetrics context) { int newwait = (context.FabricPollWaitTime ?? (int)FabricPollWaitMin.TotalMilliseconds); if (!success && (context.LastReserved ?? 0) > 0 && (context.PriorityQueueLength ?? 0) > 0) { newwait += 100; if (newwait > (int)FabricPollWaitMax.TotalMilliseconds) { newwait = (int)FabricPollWaitMax.TotalMilliseconds; } } else if (success && newwait > (int)FabricPollWaitMin.TotalMilliseconds) { newwait -= 100; if (newwait < (int)FabricPollWaitMin.TotalMilliseconds) { newwait = (int)FabricPollWaitMin.TotalMilliseconds; } } context.FabricPollWaitTime = newwait; if (!hasErrored) { var rate = context.PollSuccessRate; context.SkipCount = rate <= 0 ? (int)50 : (int)Math.Round((decimal)1 / rate); } }
/// <summary> /// This method calculates the number of slots to take from the amount available. /// </summary> /// <param name="available">The available slots.</param> /// <param name="context">The metrics.</param> /// <returns>Returns the number of slots to reserve from the amount available.</returns> public override int CalculateSlots(int available, ClientPriorityHolderMetrics context) { double ratelimitAdjustment = context.RateLimiter?.RateLimitAdjustmentPercentage ?? 1D; //We make sure that a small fraction rate limit adjust resolves to zero as we use ceiling to make even small fractional numbers go to one. return((int)Math.Ceiling((double)available * CapacityPercentage * Math.Round(ratelimitAdjustment, 2, MidpointRounding.AwayFromZero))); }
/// <summary> /// This method is used to reduce the poll interval when the client reaches a certain success threshold /// for polling frequency, which is set of an increasing scale at 75%. /// </summary> private TimeSpan RecalculateMaximumPollWait(ClientPriorityHolderMetrics context) { var rate = context.PollSuccessRate; //If we have a poll success rate under the threshold then return the maximum value. if (!context.PollTimeReduceRatio.HasValue || rate < context.PollTimeReduceRatio.Value) { return(MaxAllowedWaitBetweenPolls); } decimal adjustRatio = ((1M - rate) / (1M - context.PollTimeReduceRatio.Value)); //This tends to 0 when the rate hits 100% double minTime = MinExpectedWaitBetweenPolls.TotalMilliseconds; double maxTime = MaxAllowedWaitBetweenPolls.TotalMilliseconds; double difference = maxTime - minTime; if (difference <= 0) { return(TimeSpan.FromMilliseconds(minTime)); } double newWait = (double)((decimal)difference * adjustRatio); return(TimeSpan.FromMilliseconds(minTime + newWait)); }
/// <summary> /// This is the priority based on the elapsed poll tick time and the overall priority. /// It is used to ensure that clients with the overall same base priority are accessed /// so the one polled last is then polled first the next time. /// </summary> /// <param name="queueLength">This contains the current queue length for the underlying fabric.</param> /// <param name="context">This is the metrics context.</param> /// <param name="timeStamp">This is an optional parameter that defaults to the current tick count. You can set this value for unit testing.</param> /// <returns>Returns the new priority.</returns> public override long PriorityRecalculate(long?queueLength, ClientPriorityHolderMetrics context, int?timeStamp = null) { if (!timeStamp.HasValue) { timeStamp = Environment.TickCount; } long newPriority = 0xFFFFFFFFFFFF; try { if (context.PriorityTickCount.HasValue) { newPriority += ConversionHelper.CalculateDelta(timeStamp.Value, context.PriorityTickCount.Value); } context.PriorityTickCount = timeStamp.Value; //Add the queue length to add the listener with the greatest number of messages. context.PriorityQueueLength = queueLength; newPriority += context.PriorityQueueLength ?? 0; newPriority = (long)((decimal)newPriority * context.PriorityWeighting); } catch (Exception ex) { } context.PriorityCalculated = newPriority; return(newPriority); }
public override bool PastDueCalculate(ClientPriorityHolderMetrics context, int?timeStamp = null) { if (timeStamp == null) { timeStamp = Environment.TickCount; } var timePassed = ConversionHelper.DeltaAsTimeSpan(context.LastPollTickCount, timeStamp); return(timePassed.HasValue && timePassed.Value > context.MaxAllowedPollWait); }
/// <summary> /// This method is used to recalcualte the capacity percentage. /// </summary> /// <param name="context"></param> /// <returns></returns> public override void CapacityPercentageRecalculate(ClientPriorityHolderMetrics context) { if (context.PollAttemptedBatch > 0) { //If the actual and reserved tend to 100% we want the capacity to grow to 95% double capacityPercentage = context.CapacityPercentage * ((double)context.PollAchievedBatch / (double)context.PollAttemptedBatch) * 1.05D; if (capacityPercentage >= 0.95D) { capacityPercentage = 0.95D; } else if (capacityPercentage <= 0.01D) { capacityPercentage = 0.01D; } context.CapacityPercentage = capacityPercentage; } }
/// <summary> /// This method returns true if the client should be skipped for this poll. /// </summary> /// <returns>Returns true if the poll should be skipped.</returns> public override bool ShouldSkip(ClientPriorityHolderMetrics context) { //Get the timespan since the last poll var lastPollTimeSpan = ConversionHelper.DeltaAsTimeSpan(context.LastPollTickCount); //Check whether we have waited the minimum poll time, if not skip if (lastPollTimeSpan.HasValue && lastPollTimeSpan.Value < context.MinExpectedPollWait) { return(true); } //Check whether we have waited over maximum poll time, then poll if (lastPollTimeSpan.HasValue && lastPollTimeSpan.Value > RecalculateMaximumPollWait(context)) { return(false); } return(context.SkipCountDecrement()); }
public override void CapacityPercentageRecalculate(ClientPriorityHolderMetrics context) { context.CapacityPercentage = 1D; }
public override void InitialiseMetrics(ClientPriorityHolderMetrics context) { context.FabricPollWaitTime = (int)FabricPollWaitMin.TotalMilliseconds; }
public abstract void CapacityPercentageRecalculate(ClientPriorityHolderMetrics context);
public abstract void CapacityReset(ClientPriorityHolderMetrics context);
public abstract long PriorityRecalculate(long?queueLength, ClientPriorityHolderMetrics context, int?timeStamp = null);
public override bool PastDueCalculate(ClientPriorityHolderMetrics context, int?timeStamp = default(int?)) { return(false); }
public abstract bool ShouldSkip(ClientPriorityHolderMetrics context);
public abstract void PollMetricsRecalculate(bool success, bool hasErrored, ClientPriorityHolderMetrics context);
public override bool ShouldSkip(ClientPriorityHolderMetrics context) { return(false); }
public override int CalculateSlots(int available, ClientPriorityHolderMetrics context) { //We make sure that a small fraction rate limit adjust resolves to zero as we use ceiling to make even small fractional numbers go to one. return(available); }
public abstract bool PastDueCalculate(ClientPriorityHolderMetrics context, int?timeStamp = null);
public abstract void InitialiseMetrics(ClientPriorityHolderMetrics context);
/// <summary> /// This method is used to reset the capacity calculation. /// </summary> public override void CapacityReset(ClientPriorityHolderMetrics context) { context.PollAttemptedBatch = 0; context.PollAchievedBatch = 0; context.CapacityPercentage = 0.75D; }
/// <summary> /// This is the priority based on the elapsed poll tick time and the overall priority. /// It is used to ensure that clients with the overall same base priority are accessed /// so the one polled last is then polled first the next time. /// </summary> public override long PriorityRecalculate(long?queueLength, ClientPriorityHolderMetrics context, int?timeStamp = null) { context.PriorityCalculated = 1; return(1); }
public override void PollMetricsRecalculate(bool success, bool hasErrored, ClientPriorityHolderMetrics context) { context.SkipCount = 0; }
public abstract int CalculateSlots(int available, ClientPriorityHolderMetrics context);