// called every fixed interval (lets say one second) public void logResourceUsage() { ResourceUsageRecord createdResourceUsageRecord = new ResourceUsageRecord(); createdResourceUsageRecord.setRecordedTaskInfos(computeContext.computeBudgetedTasks.list.Select(v => new RecordedTaskInfo(v.budget.priority, v.humanReadableUniqueName))); createdResourceUsageRecord.sumOfbudgetPriorities = computeContext.computeBudgetedTasks.list.Select(v => (float)v.budget.priority).Aggregate((sum, current) => sum + current); records.Add(createdResourceUsageRecord); }
public UsageRecord(ResourceUsageRecord record, Tenant tenant, Guid batchId) { var meter = Meter.MeterMappings[record.UsageType]; this.PartitionKey = batchId.ToString(); this.RowKey = $"{record.EngagementAccount}:{record.UsageType.ToString()}"; this.SubscriptionId = Guid.Parse(tenant.SubscriptionId); this.EventId = Guid.NewGuid(); this.EventDateTime = DateTime.UtcNow; this.Quantity = record.Quantity / meter.MeterUnit; this.MeterId = meter.MeterId; this.ResourceUri = tenant.ResourceId; this.Location = tenant.Location; this.TagsDictionary = tenant.Tags; this.AdditionalInfoDictionary = new Dictionary <string, string> { { nameof(tenant.SKU), tenant.SKU }, { nameof(tenant.State), tenant.State.ToString() } }; this.SerializeCollections(); }
/// <summary> /// Initializes a new instance of the <see cref="PSResourceUsageRecord" /> class. /// </summary> /// <param name="usageRecord">The base usage record for this instance.</param> public PSResourceUsageRecord(ResourceUsageRecord usageRecord) { this.CopyFrom(usageRecord); }
public async Task <bool> OnMessageReportUpdatedAsync(string messageId, string customMessageId, List <ReportDetail> reports, ConnectorIdentifier connector) { if (string.IsNullOrEmpty(messageId) && !string.IsNullOrEmpty(customMessageId)) { // Get id mapping, retry 3 times if cannot found (in case racing with case #2 who has not insert the mapping yet) int retry = 3; while (retry > 0) { messageId = (await MessageIdMappingTableEntity.GetAsync(idMappingTable, customMessageId))?.MessageId; if (!string.IsNullOrEmpty(messageId)) { break; } retry--; await Task.Delay(1000); } if (string.IsNullOrEmpty(messageId)) { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for customMessageId={customMessageId}"); return(false); } } // Get record from summary table var entity = await MessageSummaryTableEntity.GetAsync(this.summaryTable, messageId); if (entity == null) { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for messageId={messageId}"); return(false); } // Calculating the unit according to provider // This is for long message that will be charged as multiple messages var metadata = await this.credentialManager.GetMetadata(connector.ConnectorName); var units = BillingHelper.CalculateBillingUnits(entity.MessageBody, metadata); // Report might be ealier than dispatch result, so make sure units is updated if (entity.Units == null) { entity.Units = units; entity.LastUpdateTime = DateTime.UtcNow; await MessageSummaryTableEntity.InsertOrMergeAsync(this.summaryTable, entity); } // Update detail table if (reports != null && reports.Count > 0) { await this.UpdateMessageDetailsAsync(entity.EngagementAccount, entity, reports); } // Get account detail var account = await this.store.GetAccountAsync(entity.EngagementAccount); var succeed = reports.Where(r => r.State == MessageState.DELIVERED).Count(); var failed = reports.Where(r => r.State != MessageState.DELIVERED && r.State != MessageState.UNKNOWN).Count(); // Push metric and billing usage if (Enum.TryParse(entity.MessageCategory, out MessageCategory category)) { if (succeed > 0) { this.metricManager.LogDeliverSuccess(succeed * units, entity.EngagementAccount, account?.SubscriptionId ?? string.Empty, category); if (Constants.MessageCategoryToUsageTypeMappings.TryGetValue(category, out ResourceUsageType usageType)) { var usage = new ResourceUsageRecord(); usage.EngagementAccount = entity.EngagementAccount; usage.UsageType = usageType; usage.Quantity = succeed * units; await this.billingAgent.StoreBillingUsageAsync(new List <ResourceUsageRecord> { usage }, CancellationToken.None); SmsProviderEventSource.Current.Info(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Succeeded, $"Usage pushed to billing service account={entity.EngagementAccount} usageType={usageType} quantity={succeed}x{units}"); } } if (failed > 0) { this.metricManager.LogDeliverFailed(failed * units, entity.EngagementAccount, account?.SubscriptionId ?? string.Empty, category); } } else { SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Failed, $"Failed to parse MessageCategory={entity.MessageCategory} MessageId={entity.MessageId}"); } return(true); }
public async Task <bool> OnReportUpdatedAsync(string engagementAccount, Report report) { try { // Get record in in-progress table var inProgressEntity = await ReportInProgressTableEntity.GetAsync(reportInProgressTable, engagementAccount, report.MessageIdentifer.MessageId); if (inProgressEntity == null) { // Record should be already in history table var historyTable = await GetHistoryTableAsync(engagementAccount); var historyEntity = await MessageHistoryTableEntity.GetAsync(historyTable, engagementAccount, report.MessageIdentifer.MessageId); if (historyEntity == null) { EmailProviderEventSource.Current.Warning(EmailProviderEventSource.EmptyTrackingId, this, nameof(this.OnReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for messageId={report.MessageIdentifer.MessageId}"); return(false); } // TODO: if after 24h we get new number of target shall we charge? historyEntity.Targets = report.TotalTarget; historyEntity.Delivered = report.TotalDelivered; historyEntity.Opened = report.TotalOpened; historyEntity.Clicked = report.TotalClicked; historyEntity.LastUpdateTime = DateTime.UtcNow; await MessageHistoryTableEntity.InsertOrMergeAsync(historyTable, historyEntity); return(true); } var lastTarget = inProgressEntity.Targets; // Update the record in in-progress table inProgressEntity.Targets = report.TotalTarget; inProgressEntity.Delivered = report.TotalDelivered; inProgressEntity.Opened = report.TotalOpened; inProgressEntity.Clicked = report.TotalClicked; inProgressEntity.LastUpdateTime = DateTime.UtcNow; var updated = await ReportInProgressTableEntity.TryUpdateAsync(reportInProgressTable, inProgressEntity); // New target sent, do the charge (only if update succeed) if (updated && lastTarget < report.TotalTarget) { try { var delta = report.TotalTarget - lastTarget; // Get account detail var account = await this.store.GetAccountAsync(engagementAccount); metricManager.LogDeliverSuccess(delta, engagementAccount, account?.SubscriptionId ?? string.Empty); var usage = new ResourceUsageRecord(); usage.EngagementAccount = engagementAccount; usage.UsageType = ResourceUsageType.EmailMessage; usage.Quantity = delta; await this.billingAgent.StoreBillingUsageAsync(new List <ResourceUsageRecord> { usage }, CancellationToken.None); EmailProviderEventSource.Current.Info(EmailProviderEventSource.EmptyTrackingId, this, nameof(this.OnReportUpdatedAsync), OperationStates.Succeeded, $"Usage pushed to billing service account={engagementAccount} quantity={usage.Quantity}"); } catch (Exception ex) { // We should monitor for billing failure EmailProviderEventSource.Current.CriticalException(EmailProviderEventSource.EmptyTrackingId, this, nameof(this.OnReportUpdatedAsync), OperationStates.Failed, $"Failed at pushing to billing service account={engagementAccount}", ex); } } // If the record in in-progress for 24h, treat it as completed and move it to history table if (inProgressEntity.Timestamp.AddHours(Constants.ReportInProgressIntervalByHours) < DateTime.UtcNow) { // Record should be already in history table var historyTable = await GetHistoryTableAsync(engagementAccount); var historyEntity = new MessageHistoryTableEntity(inProgressEntity); await MessageHistoryTableEntity.InsertOrMergeAsync(historyTable, historyEntity); await ReportInProgressTableEntity.DeleteAsync(reportInProgressTable, inProgressEntity); } return(true); } catch (Exception ex) { EmailProviderEventSource.Current.ErrorException(EmailProviderEventSource.EmptyTrackingId, this, nameof(this.OnReportUpdatedAsync), OperationStates.Failed, string.Empty, ex); return(false); } }