internal async Task <EventHubsTriggerMetrics> CreateTriggerMetrics(List <PartitionProperties> partitionRuntimeInfo, bool alwaysLog = false)
        {
            long totalUnprocessedEventCount = 0;
            bool logPartitionInfo           = alwaysLog ? true : DateTime.UtcNow >= _nextPartitionLogTime;
            bool logPartitionWarning        = alwaysLog ? true : DateTime.UtcNow >= _nextPartitionWarningTime;

            // For each partition, get the last enqueued sequence number.
            // If the last enqueued sequence number does not equal the SequenceNumber from the lease info in storage,
            // accumulate new event counts across partitions to derive total new event counts.
            List <string> partitionErrors = new List <string>();

            for (int i = 0; i < partitionRuntimeInfo.Count; i++)
            {
                Tuple <BlobParitionCheckpoint, string> partitionLeaseFile = await GetPartitionLeaseFileAsync(i).ConfigureAwait(false);

                BlobParitionCheckpoint partitionLeaseInfo = partitionLeaseFile.Item1;
                string errorMsg = partitionLeaseFile.Item2;

                if (partitionRuntimeInfo[i] == null || partitionLeaseInfo == null)
                {
                    partitionErrors.Add(errorMsg);
                }
                else
                {
                    // Check for the unprocessed messages when there are messages on the event hub parition
                    // In that case, LastEnqueuedSequenceNumber will be >= 0
                    if ((partitionRuntimeInfo[i].LastEnqueuedSequenceNumber != -1 && partitionRuntimeInfo[i].LastEnqueuedSequenceNumber != partitionLeaseInfo.SequenceNumber) ||
                        (partitionLeaseInfo.Offset == null && partitionRuntimeInfo[i].LastEnqueuedSequenceNumber >= 0))
                    {
                        long partitionUnprocessedEventCount = GetUnprocessedEventCount(partitionRuntimeInfo[i], partitionLeaseInfo);
                        totalUnprocessedEventCount += partitionUnprocessedEventCount;
                    }
                }
            }

            // Only log if not all partitions are failing or it's time to log
            if (partitionErrors.Count > 0 && (partitionErrors.Count != partitionRuntimeInfo.Count || logPartitionWarning))
            {
                _logger.LogWarning($"Function '{_functionId}': Unable to deserialize partition or lease info with the " +
                                   $"following errors: {string.Join(" ", partitionErrors)}");
                _nextPartitionWarningTime = DateTime.UtcNow.AddMinutes(PartitionLogIntervalInMinutes);
            }

            if (totalUnprocessedEventCount > 0 && logPartitionInfo)
            {
                _logger.LogInformation($"Function '{_functionId}', Total new events: {totalUnprocessedEventCount}");
                _nextPartitionLogTime = DateTime.UtcNow.AddMinutes(PartitionLogIntervalInMinutes);
            }

            return(new EventHubsTriggerMetrics
            {
                Timestamp = DateTime.UtcNow,
                PartitionCount = partitionRuntimeInfo.Count,
                EventCount = totalUnprocessedEventCount
            });
        }
        private async Task <Tuple <BlobParitionCheckpoint, string> > GetPartitionLeaseFileAsync(int partitionId)
        {
            BlobParitionCheckpoint blobParitionCheckpoint = null;
            string prefix   = $"{EventHubOptions.GetBlobPrefix(_eventHubName, _client.Value.FullyQualifiedNamespace)}{_consumerGroup}/checkpoint/{partitionId}";
            string errorMsg = null;

            try
            {
                BlobClient     blockBlob  = BlobContainer.GetBlobClient(prefix);
                BlobProperties properties = await blockBlob.GetPropertiesAsync().ConfigureAwait(false);

                if (properties.Metadata.TryGetValue(SequenceNumberMetadataName, out string sequenceNumberString))
                {
                    blobParitionCheckpoint ??= new BlobParitionCheckpoint();
                    blobParitionCheckpoint.SequenceNumber = long.Parse(sequenceNumberString, CultureInfo.InvariantCulture);
                }

                if (properties.Metadata.TryGetValue(OffsetMetadataName, out string offsetString))
                {
                    blobParitionCheckpoint ??= new BlobParitionCheckpoint();
                    blobParitionCheckpoint.Offset = long.Parse(offsetString, CultureInfo.InvariantCulture);
                }

                if (blobParitionCheckpoint == null)
                {
                    errorMsg = $"Checkpoint file did not contain required metadata on Partition: '{partitionId}', " +
                               $"EventHub: '{_eventHubName}', '{_consumerGroup}'.";
                }
            }
            catch (RequestFailedException e) when(e.Status == (int)HttpStatusCode.NotFound)
            {
                errorMsg = $"Checkpoint file data could not be found for blob on Partition: '{partitionId}', " +
                           $"EventHub: '{_eventHubName}', '{_consumerGroup}'. Error: {e.Message}";
            }
            catch (Exception e)
            {
                errorMsg = $"Encountered exception while checking for last checkpointed sequence number for blob " +
                           $"on Partition: '{partitionId}', EventHub: '{_eventHubName}', Consumer Group: '{_consumerGroup}'. Error: {e.Message}";
            }

            return(new Tuple <BlobParitionCheckpoint, string>(blobParitionCheckpoint, errorMsg));
        }
        // Get the number of unprocessed events by deriving the delta between the server side info and the partition lease info,
        private static long GetUnprocessedEventCount(PartitionProperties partitionInfo, BlobParitionCheckpoint partitionLeaseInfo)
        {
            long partitionLeaseInfoSequenceNumber = partitionLeaseInfo.SequenceNumber ?? 0;

            // This handles two scenarios:
            //   1. If the partition has received its first message, Offset will be null and LastEnqueuedSequenceNumber will be 0
            //   2. If there are no instances set to process messages, Offset will be null and LastEnqueuedSequenceNumber will be >= 0
            if (partitionLeaseInfo.Offset == null && partitionInfo.LastEnqueuedSequenceNumber >= 0)
            {
                return(partitionInfo.LastEnqueuedSequenceNumber + 1);
            }

            if (partitionInfo.LastEnqueuedSequenceNumber > partitionLeaseInfoSequenceNumber)
            {
                return(partitionInfo.LastEnqueuedSequenceNumber - partitionLeaseInfoSequenceNumber);
            }

            // Partition is a circular buffer, so it is possible that
            // LastEnqueuedSequenceNumber < SequenceNumber
            long count = 0;

            unchecked
            {
                count = (long.MaxValue - partitionInfo.LastEnqueuedSequenceNumber) + partitionLeaseInfoSequenceNumber;
            }

            // It's possible for checkpointing to be ahead of the partition's LastEnqueuedSequenceNumber,
            // especially if checkpointing is happening often and load is very low.
            // If count is negative, we need to know that this read is invalid, so return 0.
            // e.g., (9223372036854775807 - 10) + 11 = -9223372036854775808
            return((count < 0) ? 0 : count);
        }