private static void AggregateSum(string basePropertyName, ActionPropertyBag targetAction, ConcurrentDictionary <string, long> childMap)
        {
            string fullPropertyname = basePropertyName + ActionPropertyNames.SumConstStrSuffix;

            if (ShouldAggregateProperty <long>(fullPropertyname, childMap, out long childValue))
            {
                targetAction.Sum(fullPropertyname, childValue);
            }
        }
        private static void AggregateMin(string basePropertyName, ActionPropertyBag targetAction, ConcurrentDictionary <string, int> childMap)
        {
            string fullPropertyname = basePropertyName + ActionPropertyNames.MinConstStrSuffix;

            if (ShouldAggregateProperty <int>(fullPropertyname, childMap, out int childValue))
            {
                targetAction.Min(fullPropertyname, childValue);
            }
        }
 private void SetScopesProperty(ActionPropertyBag propertyBag, IEnumerable <string> scopes)
 {
     // TODO(mats): how is this supposed to work?  Should we get multiple properties somehow, one per scope?  or do we send up all scopes in a space-delimited string (will make querying hard on the backend)
     foreach (string scope in scopes)
     {
         if (_telemetryAllowedScopes.Contains(scope))
         {
             propertyBag.Add(ActionPropertyNames.ScopeConstStrKey, scope);
         }
         else if (!string.IsNullOrEmpty(scope))
         {
             propertyBag.Add(ActionPropertyNames.ScopeConstStrKey, "ScopeRedacted");
         }
     }
 }
        private void EndGenericAction(string actionId, string outcome, ErrorSource errorSource, string error, string errorDescription, string accountCid)
        {
            if (string.IsNullOrEmpty(actionId))
            {
                // This is an invalid action; we do not want to upload it.
                _errorStore.ReportError("Tried to end an action with an empty actionId", ErrorType.Action, ErrorSeverity.Warning);
                return;
            }

            ActionPropertyBag propertyBag = GetActionPropertyBagFromId(actionId);

            if (propertyBag == null)
            {
                _errorStore.ReportError("Trying to end an action that doesn't exist or was already uploaded", ErrorType.Action, ErrorSeverity.Warning);
                return;
            }

            if (propertyBag.ReadyForUpload)
            {
                return;
            }

            int startingCount = 1;
            var endTime       = DateTime.UtcNow;

            propertyBag.Add(ActionPropertyNames.OutcomeConstStrKey, outcome);
            propertyBag.Add(ActionPropertyNames.FailureSourceConstStrKey, MatsConverter.AsString(errorSource));
            propertyBag.Add(ActionPropertyNames.FailureConstStrKey, error);
            propertyBag.Add(ActionPropertyNames.FailureDescriptionConstStrKey, errorDescription);
            propertyBag.Add(ActionPropertyNames.EndTimeConstStrKey, DateTimeUtils.GetMillisecondsSinceEpoch(endTime));
            // propertyBag->Add(ActionPropertyNames::getAccountIdConstStrKey(), accountCid);  Commenting this out for GDPR reasons; once pipeline supports this we can upload again.
            propertyBag.Add(ActionPropertyNames.CountConstStrKey, startingCount);
            PopulateDuration(propertyBag);

            //Check if should aggregate here
            var contents = propertyBag.GetContents();

            if (_eventFilter.ShouldAggregateAction(contents))
            {
                EndAggregatedAction(actionId, propertyBag);
            }
            else
            {
                propertyBag.ReadyForUpload = true;
            }
        }
        /// <summary>
        /// Returns true if the actions are the same, either are ready for upload, and either is not aggregable.
        /// This is used to determine whether or not we should aggregate this particular property bag of event data.
        /// </summary>
        /// <param name="action1"></param>
        /// <param name="action2"></param>
        /// <returns></returns>
        public static bool IsEquivalentClass(ActionPropertyBag action1, ActionPropertyBag action2)
        {
            if (action1 == action2 || action1.ReadyForUpload || action2.ReadyForUpload || !action1.IsAggregable || !action2.IsAggregable)
            {
                return(false);
            }

            var contents1 = action1.GetContents();
            var contents2 = action2.GetContents();
            var v         = GetComparisonStringProperties();

            foreach (string s in v)
            {
                if (!IsPropertyEquivalent(s, contents1.StringProperties, contents2.StringProperties))
                {
                    return(false);
                }
            }

            return(true);
        }
        public static void AggregateActions(ActionPropertyBag targetAction, ActionPropertyBag childAction)
        {
            targetAction.IncrementCount();
            targetAction.Sum(ActionPropertyNames.CountConstStrKey, 1);

            var childContents = childAction.GetContents();

            foreach (string s in GetIntAggregationProperties())
            {
                AggregateMax(s, targetAction, childContents.IntProperties);
                AggregateMin(s, targetAction, childContents.IntProperties);
                AggregateSum(s, targetAction, childContents.IntProperties);
            }

            foreach (string s in GetInt64AggregationProperties())
            {
                AggregateMax(s, targetAction, childContents.Int64Properties);
                AggregateMin(s, targetAction, childContents.Int64Properties);
                AggregateSum(s, targetAction, childContents.Int64Properties);
            }
        }
        private void EndAggregatedAction(string actionId, ActionPropertyBag propertyBag)
        {
            lock (_lockActionIdToPropertyBag)
            {
                propertyBag.IsAggregable = true;

                bool shouldRemove = false;
                foreach (var targetPropertyBag in _actionIdToPropertyBag.Values)
                {
                    if (ActionComparer.IsEquivalentClass(targetPropertyBag, propertyBag))
                    {
                        ActionAggregator.AggregateActions(targetPropertyBag, propertyBag);
                        shouldRemove = true;
                        break;
                    }
                }

                if (shouldRemove)
                {
                    _actionIdToPropertyBag.Remove(actionId);
                }
            }
        }
        private ActionArtifacts CreateGenericAction(MatsScenario scenario, string correlationId, ActionType actionType)
        {
            string     actionId = MatsId.Create();
            MatsAction action   = new MatsAction(actionId, scenario, correlationId);

            string corrIdTrim = correlationId.TrimCurlyBraces();

            var propertyBag    = new ActionPropertyBag(_errorStore);
            var startTimePoint = DateTime.UtcNow;

            propertyBag.Add(ActionPropertyNames.UploadIdConstStrKey, MatsId.Create());
            propertyBag.Add(ActionPropertyNames.ActionTypeConstStrKey, MatsConverter.AsString(actionType));
            propertyBag.Add(ScenarioPropertyNames.IdConstStrKey, scenario?.ScenarioId);
            propertyBag.Add(ActionPropertyNames.CorrelationIdConstStrKey, corrIdTrim);
            propertyBag.Add(ActionPropertyNames.StartTimeConstStrKey, DateTimeUtils.GetMillisecondsSinceEpoch(startTimePoint));

            lock (_lockActionIdToPropertyBag)
            {
                _actionIdToPropertyBag[actionId] = propertyBag;
            }

            return(new ActionArtifacts(action, propertyBag));
        }
        private void PopulateDuration(ActionPropertyBag propertyBag)
        {
            var  contents = propertyBag.GetContents();
            long startTime;
            long endTime;

            if (!contents.Int64Properties.TryGetValue(ActionPropertyNames.StartTimeConstStrKey, out startTime))
            {
                _errorStore.ReportError("Could not retrieve start time for duration calculation.", ErrorType.Action, ErrorSeverity.LibraryError);
                return;
            }

            if (!contents.Int64Properties.TryGetValue(ActionPropertyNames.EndTimeConstStrKey, out endTime))
            {
                _errorStore.ReportError("Could not retrieve end time for duration calculation.", ErrorType.Action, ErrorSeverity.LibraryError);
                return;
            }

            long duration = endTime - startTime;

            propertyBag.Add(ActionPropertyNames.DurationConstStrKey + ActionPropertyNames.SumConstStrSuffix, duration);
            propertyBag.Add(ActionPropertyNames.DurationConstStrKey + ActionPropertyNames.MaxConstStrSuffix, duration);
            propertyBag.Add(ActionPropertyNames.DurationConstStrKey + ActionPropertyNames.MinConstStrSuffix, duration);
        }