diff --git a/AppDynamics.Dexter.sln b/AppDynamics.Dexter.sln index 711e2d1..070c170 100644 --- a/AppDynamics.Dexter.sln +++ b/AppDynamics.Dexter.sln @@ -8,6 +8,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppDynamics.Dexter.Core", "AppDynamics.Dexter.Core.csproj", "{F2687CC0-8C55-4B74-AE08-7A1D6DDE18E1}" EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU diff --git a/DataObjects/JobConfiguration/JobStatus.cs b/DataObjects/JobConfiguration/JobStatus.cs index 04bc04b..d23a177 100644 --- a/DataObjects/JobConfiguration/JobStatus.cs +++ b/DataObjects/JobConfiguration/JobStatus.cs @@ -54,7 +54,7 @@ public enum JobStatus IndexWEBConfiguration = 73, IndexMOBILEConfiguration = 74, IndexBIQConfiguration = 75, - + IndexApplicationConfigurationDifferences = 76, IndexAPMMetrics = 80, diff --git a/Helpers/FileIOHelper.cs b/Helpers/FileIOHelper.cs index 4a2040c..ee072ac 100644 --- a/Helpers/FileIOHelper.cs +++ b/Helpers/FileIOHelper.cs @@ -609,7 +609,8 @@ public static bool AppendTwoCSVFiles(FileStream csvToAppendToSW, string csvToApp private static void copyStream(Stream input, Stream output) { - byte[] buffer = new byte[1024 * 128]; + // 1048576 = 1024*1024 = 2^20 = 1MB + byte[] buffer = new byte[1048576]; int bytesRead; while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0) { diff --git a/ProcessingSteps/FilePathMap.cs b/ProcessingSteps/FilePathMap.cs index 287003f..b68bb09 100644 --- a/ProcessingSteps/FilePathMap.cs +++ b/ProcessingSteps/FilePathMap.cs @@ -401,20 +401,6 @@ public class FilePathMap private const string CONVERT_SNAPSHOTS_SEGMENTS_FOLDED_CALL_STACKS_TIMERANGE_FILE_NAME = "snapshots.foldedcallstacks.{0:yyyyMMddHHmm}-{1:yyyyMMddHHmm}.csv"; private const string CONVERT_SNAPSHOTS_SEGMENTS_FOLDED_CALL_STACKS_WITH_TIME_TIMERANGE_FILE_NAME = "snapshots.foldedcallstackswithtime.{0:yyyyMMddHHmm}-{1:yyyyMMddHHmm}.csv"; - // Snapshot files - private const string CONVERT_SNAPSHOT_FILE_NAME = "snapshot.csv"; - private const string CONVERT_SNAPSHOT_SEGMENTS_FILE_NAME = "snapshot.segments.csv"; - private const string CONVERT_SNAPSHOT_SEGMENTS_EXIT_CALLS_FILE_NAME = "snapshot.exits.csv"; - private const string CONVERT_SNAPSHOT_SEGMENTS_SERVICE_ENDPOINTS_CALLS_FILE_NAME = "snapshot.serviceendpoints.csv"; - private const string CONVERT_SNAPSHOT_SEGMENTS_DETECTED_ERRORS_FILE_NAME = "snapshot.errors.csv"; - private const string CONVERT_SNAPSHOT_SEGMENTS_BUSINESS_DATA_FILE_NAME = "snapshot.businessdata.csv"; - private const string CONVERT_SNAPSHOT_SEGMENTS_METHOD_CALL_LINES_FILE_NAME = "snapshot.methodcalllines.csv"; - private const string CONVERT_SNAPSHOT_SEGMENTS_METHOD_CALL_LINES_OCCURRENCES_FILE_NAME = "snapshot.methodcalllinesoccurrences.csv"; - - // Folded call stacks for snapshot - private const string CONVERT_SNAPSHOT_SEGMENTS_FOLDED_CALL_STACKS_FILE_NAME = "snapshot.foldedcallstacks.csv"; - private const string CONVERT_SNAPSHOT_SEGMENTS_FOLDED_CALL_STACKS_WITH_TIME_FILE_NAME = "snapshot.foldedcallstacks.withtime.csv"; - // Flow map to flow grid conversion file names private const string CONVERT_ACTIVITY_GRIDS_FILE_NAME = "activitygrids.full.csv"; private const string CONVERT_ALL_ACTIVITY_GRIDS_FILE_NAME = "{0}.activitygrids.full.csv"; @@ -5632,7 +5618,7 @@ public string SnapshotsSegmentsIndexFilePath(JobTarget jobTarget) getFileSystemSafeString(getControllerNameForFileSystem(jobTarget.Controller)), getShortenedEntityNameForFileSystem(jobTarget.Application, jobTarget.ApplicationID), APM_SNAPSHOTS_FOLDER_NAME, - CONVERT_SNAPSHOT_SEGMENTS_FILE_NAME); + CONVERT_SNAPSHOTS_SEGMENTS_FILE_NAME); } public string SnapshotsExitCallsIndexFilePath(JobTarget jobTarget) diff --git a/ProcessingSteps/Index/IndexAPMFlowmaps.cs b/ProcessingSteps/Index/IndexAPMFlowmaps.cs index 15a0e65..b350845 100644 --- a/ProcessingSteps/Index/IndexAPMFlowmaps.cs +++ b/ProcessingSteps/Index/IndexAPMFlowmaps.cs @@ -68,6 +68,18 @@ public override bool Execute(ProgramOptions programOptions, JobConfiguration job int numEntitiesTotal = 0; + // Preload list of Tiers from APM metadata + List tiersAllIndexedItemsList = FileIOHelper.ReadListFromCSVFile(FilePathMap.APMTiersReportFilePath(), new APMTierReportMap()); + Dictionary tiersAllIndexedItemsDictionary = null; + if (tiersAllIndexedItemsList != null) + { + tiersAllIndexedItemsDictionary = tiersAllIndexedItemsList.ToDictionary(e => string.Format("{0}/{1}", e.ApplicationID, e.EntityID), e => e.Clone()); + } + else + { + tiersAllIndexedItemsDictionary = new Dictionary(); + } + Parallel.Invoke( () => { @@ -80,7 +92,7 @@ public override bool Execute(ProgramOptions programOptions, JobConfiguration job FileIOHelper.DeleteFile(FilePathMap.ApplicationFlowmapIndexFilePath(jobTarget)); - List activityFlowsList = convertFlowmapApplication(applicationList[0], jobTarget, jobConfiguration.Input.TimeRange); + List activityFlowsList = convertFlowmapApplication(applicationList[0], tiersAllIndexedItemsDictionary, jobTarget, jobConfiguration.Input.TimeRange); if (activityFlowsList != null) { @@ -101,7 +113,7 @@ public override bool Execute(ProgramOptions programOptions, JobConfiguration job thisMinuteJobTimeRange.From = jobConfiguration.Input.TimeRange.From.AddMinutes(minute); thisMinuteJobTimeRange.To = jobConfiguration.Input.TimeRange.From.AddMinutes(minute + 1); - activityFlowsList = convertFlowmapApplication(applicationList[0], jobTarget, thisMinuteJobTimeRange); + activityFlowsList = convertFlowmapApplication(applicationList[0], tiersAllIndexedItemsDictionary, jobTarget, thisMinuteJobTimeRange); if (activityFlowsList != null) { @@ -132,7 +144,7 @@ public override bool Execute(ProgramOptions programOptions, JobConfiguration job foreach (APMTier tier in tiersList) { - List activityFlowsList = convertFlowmapTier(tier, jobTarget, jobConfiguration.Input.TimeRange); + List activityFlowsList = convertFlowmapTier(tier, tiersAllIndexedItemsDictionary, jobTarget, jobConfiguration.Input.TimeRange); if (activityFlowsList != null) { @@ -160,7 +172,7 @@ public override bool Execute(ProgramOptions programOptions, JobConfiguration job foreach (APMNode node in nodesList) { - List activityFlowsList = convertFlowmapNode(node, jobTarget, jobConfiguration.Input.TimeRange); + List activityFlowsList = convertFlowmapNode(node, tiersAllIndexedItemsDictionary, jobTarget, jobConfiguration.Input.TimeRange); if (activityFlowsList != null) { @@ -216,7 +228,7 @@ public override bool Execute(ProgramOptions programOptions, JobConfiguration job foreach (APMBusinessTransaction businessTransaction in businessTransactionsList) { - List activityFlowsList = convertFlowmapsBusinessTransaction(businessTransaction, jobTarget, jobConfiguration.Input.TimeRange); + List activityFlowsList = convertFlowmapsBusinessTransaction(businessTransaction, tiersAllIndexedItemsDictionary, jobTarget, jobConfiguration.Input.TimeRange); FileIOHelper.WriteListToCSVFile(activityFlowsList, new BusinessTransactionActivityFlowReportMap(), FilePathMap.BusinessTransactionsFlowmapIndexFilePath(jobTarget), true); @@ -314,7 +326,7 @@ public override bool ShouldExecute(JobConfiguration jobConfiguration) return (jobConfiguration.Input.Flowmaps == true); } - private List convertFlowmapApplication(APMApplication application, JobTarget jobTarget, JobTimeRange jobTimeRange) + private List convertFlowmapApplication(APMApplication application, Dictionary tiersDictionary, JobTarget jobTarget, JobTimeRange jobTimeRange) { JObject flowmapData = FileIOHelper.LoadJObjectFromFile(FilePathMap.ApplicationFlowmapDataFilePath(jobTarget, jobTimeRange)); if (flowmapData == null) @@ -480,6 +492,10 @@ private List convertFlowmapApplication(APMApplication application, activityFlowTemplate.FromLink = String.Format(DEEPLINK_APM_APPLICATION, activityFlowTemplate.Controller, activityFlowTemplate.FromEntityID, DEEPLINK_THIS_TIMERANGE); break; + case ENTITY_TYPE_FLOWMAP_FEDERATED_APPLICATION: + activityFlowTemplate.FromType = "Federated Application"; + break; + default: activityFlowTemplate.FromName = getStringValueFromJToken(entityConnection, "sourceNode"); activityFlowTemplate.FromType = getStringValueFromJToken(entityConnection["sourceNodeDefinition"], "entityType"); @@ -505,10 +521,16 @@ private List convertFlowmapApplication(APMApplication application, break; case ENTITY_TYPE_FLOWMAP_APPLICATION: + // Let's build a pretty call chain that can be matched on the downstream application flowmap + activityFlowTemplate.ToName = String.Format("{{{0}}}->({1})-{{{2}}}", activityFlowTemplate.ApplicationName, activityFlowTemplate.FromName, activityFlowTemplate.ToName); activityFlowTemplate.ToType = APMApplication.ENTITY_TYPE; activityFlowTemplate.ToLink = String.Format(DEEPLINK_APM_APPLICATION, activityFlowTemplate.Controller, activityFlowTemplate.ToEntityID, DEEPLINK_THIS_TIMERANGE); break; + case ENTITY_TYPE_FLOWMAP_FEDERATED_APPLICATION: + activityFlowTemplate.ToType = "Federated Application"; + break; + default: activityFlowTemplate.ToName = getStringValueFromJToken(entityConnection, "targetNode"); activityFlowTemplate.ToType = getStringValueFromJToken(entityConnection["targetNodeDefinition"], "entityType"); @@ -518,66 +540,80 @@ private List convertFlowmapApplication(APMApplication application, // Process each of the stats nodes, duplicating things as we need them foreach (JToken entityConnectionStat in entityConnection["stats"]) { - ActivityFlow activityFlowRow = activityFlowTemplate.Clone(); + ActivityFlow activityFlow = activityFlowTemplate.Clone(); - activityFlowRow.CallType = getStringValueFromJToken(entityConnectionStat["exitPointCall"], "exitPointType"); - if (activityFlowRow.CallType.Length == 0) + // Inbound call is from Application, let's come up with the call chain that would match the outbound call from an application + // Here we have to look up Tier name from APM Entities index because flowmap only includes its ID + if (activityFlowTemplate.FromType == APMApplication.ENTITY_TYPE) + { + if (isTokenPropertyNull(entityConnectionStat, "upstreamCrossAppCallingEntity") == false) + { + APMTier tierLookup = null; + if (tiersDictionary.TryGetValue(String.Format("{0}/{1}", activityFlowTemplate.FromEntityID, getLongValueFromJToken(entityConnectionStat["upstreamCrossAppCallingEntity"], "entityId")), out tierLookup) == true) + { + activityFlow.FromName = String.Format("{{{0}}}->({1})-{{{2}}}", activityFlow.FromName, tierLookup.TierName, activityFlow.ApplicationName); + } + } + } + + activityFlow.CallType = getStringValueFromJToken(entityConnectionStat["exitPointCall"], "exitPointType"); + if (activityFlow.CallType.Length == 0) { - activityFlowRow.CallType = getStringValueFromJToken(entity, "backendType"); - if (activityFlowRow.CallType.Length == 0) + activityFlow.CallType = getStringValueFromJToken(entity, "backendType"); + if (activityFlow.CallType.Length == 0) { if (getBoolValueFromJToken(entityConnectionStat["exitPointCall"], "customExitPoint") == true) { - activityFlowRow.CallType = "Custom"; + activityFlow.CallType = "Custom"; } } } if ((getBoolValueFromJToken(entityConnectionStat, "async")) == true) { - activityFlowRow.CallType = String.Format("{0} async", activityFlowRow.CallType); + activityFlow.CallType = String.Format("{0} async", activityFlow.CallType); } if (isTokenPropertyNull(entityConnectionStat, "averageResponseTime") == false) { - activityFlowRow.ART = getLongValueFromJToken(entityConnectionStat["averageResponseTime"], "metricValue"); - activityFlowRow.MetricsIDs.Add(getLongValueFromJToken(entityConnectionStat["averageResponseTime"], "metricId")); + activityFlow.ART = getLongValueFromJToken(entityConnectionStat["averageResponseTime"], "metricValue"); + activityFlow.MetricsIDs.Add(getLongValueFromJToken(entityConnectionStat["averageResponseTime"], "metricId")); } if (isTokenPropertyNull(entityConnectionStat, "callsPerMinute") == false) { - activityFlowRow.CPM = getLongValueFromJToken(entityConnectionStat["callsPerMinute"], "metricValue"); - activityFlowRow.MetricsIDs.Add(getLongValueFromJToken(entityConnectionStat["callsPerMinute"], "metricId")); + activityFlow.CPM = getLongValueFromJToken(entityConnectionStat["callsPerMinute"], "metricValue"); + activityFlow.MetricsIDs.Add(getLongValueFromJToken(entityConnectionStat["callsPerMinute"], "metricId")); } if (isTokenPropertyNull(entityConnectionStat, "errorsPerMinute") == false) { - activityFlowRow.EPM = getLongValueFromJToken(entityConnectionStat["errorsPerMinute"], "metricValue"); - activityFlowRow.MetricsIDs.Add(getLongValueFromJToken(entityConnectionStat["errorsPerMinute"], "metricId")); + activityFlow.EPM = getLongValueFromJToken(entityConnectionStat["errorsPerMinute"], "metricValue"); + activityFlow.MetricsIDs.Add(getLongValueFromJToken(entityConnectionStat["errorsPerMinute"], "metricId")); } - if (isTokenPropertyNull(entityConnectionStat, "numberOfCalls") == false) { activityFlowRow.Calls = getLongValueFromJToken(entityConnectionStat["numberOfCalls"], "metricValue"); } - if (isTokenPropertyNull(entityConnectionStat, "numberOfErrors") == false) { activityFlowRow.Errors = getLongValueFromJToken(entityConnectionStat["numberOfErrors"], "metricValue"); } + if (isTokenPropertyNull(entityConnectionStat, "numberOfCalls") == false) { activityFlow.Calls = getLongValueFromJToken(entityConnectionStat["numberOfCalls"], "metricValue"); } + if (isTokenPropertyNull(entityConnectionStat, "numberOfErrors") == false) { activityFlow.Errors = getLongValueFromJToken(entityConnectionStat["numberOfErrors"], "metricValue"); } - if (activityFlowRow.ART < 0) { activityFlowRow.ART = 0; } - if (activityFlowRow.CPM < 0) { activityFlowRow.ART = 0; } - if (activityFlowRow.EPM < 0) { activityFlowRow.EPM = 0; } - if (activityFlowRow.Calls < 0) { activityFlowRow.Calls = 0; } - if (activityFlowRow.Errors < 0) { activityFlowRow.Errors = 0; } + if (activityFlow.ART < 0) { activityFlow.ART = 0; } + if (activityFlow.CPM < 0) { activityFlow.ART = 0; } + if (activityFlow.EPM < 0) { activityFlow.EPM = 0; } + if (activityFlow.Calls < 0) { activityFlow.Calls = 0; } + if (activityFlow.Errors < 0) { activityFlow.Errors = 0; } - activityFlowRow.ErrorsPercentage = Math.Round((double)(double)activityFlowRow.Errors / (double)activityFlowRow.Calls * 100, 2); - if (Double.IsNaN(activityFlowRow.ErrorsPercentage) == true) activityFlowRow.ErrorsPercentage = 0; + activityFlow.ErrorsPercentage = Math.Round((double)(double)activityFlow.Errors / (double)activityFlow.Calls * 100, 2); + if (Double.IsNaN(activityFlow.ErrorsPercentage) == true) activityFlow.ErrorsPercentage = 0; - activityFlowRow.MetricsIDs.RemoveAll(m => m == -1); + activityFlow.MetricsIDs.RemoveAll(m => m == -1); - if (activityFlowRow.MetricsIDs != null && activityFlowRow.MetricsIDs.Count > 0) + if (activityFlow.MetricsIDs != null && activityFlow.MetricsIDs.Count > 0) { StringBuilder sb = new StringBuilder(256); - foreach (int metricID in activityFlowRow.MetricsIDs) + foreach (int metricID in activityFlow.MetricsIDs) { sb.Append(String.Format(deepLinkMetricTemplateInMetricBrowser, entityIdForMetricBrowser, metricID)); sb.Append(","); } sb.Remove(sb.Length - 1, 1); - activityFlowRow.MetricLink = String.Format(DEEPLINK_METRIC, activityFlowRow.Controller, activityFlowRow.ApplicationID, sb.ToString(), DEEPLINK_THIS_TIMERANGE); + activityFlow.MetricLink = String.Format(DEEPLINK_METRIC, activityFlow.Controller, activityFlow.ApplicationID, sb.ToString(), DEEPLINK_THIS_TIMERANGE); } - activityFlowsList.Add(activityFlowRow); + activityFlowsList.Add(activityFlow); } } } @@ -588,7 +624,7 @@ private List convertFlowmapApplication(APMApplication application, return activityFlowsList; } - private List convertFlowmapTier(APMTier tier, JobTarget jobTarget, JobTimeRange jobTimeRange) + private List convertFlowmapTier(APMTier tier, Dictionary tiersDictionary, JobTarget jobTarget, JobTimeRange jobTimeRange) { JObject flowmapData = FileIOHelper.LoadJObjectFromFile(FilePathMap.TierFlowmapDataFilePath(jobTarget, jobTimeRange, tier)); if (flowmapData == null) @@ -662,6 +698,10 @@ private List convertFlowmapTier(APMTier tier, JobTarget jobTarget, activityFlowTemplate.FromLink = String.Format(DEEPLINK_APM_APPLICATION, activityFlowTemplate.Controller, activityFlowTemplate.FromEntityID, DEEPLINK_THIS_TIMERANGE); break; + case ENTITY_TYPE_FLOWMAP_FEDERATED_APPLICATION: + activityFlowTemplate.FromType = "Federated Application"; + break; + default: activityFlowTemplate.FromName = getStringValueFromJToken(entityConnection, "sourceNode"); activityFlowTemplate.FromType = getStringValueFromJToken(entityConnection["sourceNodeDefinition"], "entityType"); @@ -687,10 +727,16 @@ private List convertFlowmapTier(APMTier tier, JobTarget jobTarget, break; case ENTITY_TYPE_FLOWMAP_APPLICATION: + // Let's build a pretty call chain that can be matched on the downstream application flowmap + activityFlowTemplate.ToName = String.Format("{{{0}}}->({1})-{{{2}}}", activityFlowTemplate.ApplicationName, activityFlowTemplate.FromName, activityFlowTemplate.ToName); activityFlowTemplate.ToType = APMApplication.ENTITY_TYPE; activityFlowTemplate.ToLink = String.Format(DEEPLINK_APM_APPLICATION, activityFlowTemplate.Controller, activityFlowTemplate.ToEntityID, DEEPLINK_THIS_TIMERANGE); break; + case ENTITY_TYPE_FLOWMAP_FEDERATED_APPLICATION: + activityFlowTemplate.ToType = "Federated Application"; + break; + default: activityFlowTemplate.ToName = getStringValueFromJToken(entityConnection, "targetNode"); activityFlowTemplate.ToType = getStringValueFromJToken(entityConnection["targetNodeDefinition"], "entityType"); @@ -711,6 +757,20 @@ private List convertFlowmapTier(APMTier tier, JobTarget jobTarget, { ActivityFlow activityFlow = activityFlowTemplate.Clone(); + // Inbound call is from Application, let's come up with the call chain that would match the outbound call from an application + // Here we have to look up Tier name from APM Entities index because flowmap only includes its ID + if (activityFlowTemplate.FromType == APMApplication.ENTITY_TYPE) + { + if (isTokenPropertyNull(entityConnectionStat, "upstreamCrossAppCallingEntity") == false) + { + APMTier tierLookup = null; + if (tiersDictionary.TryGetValue(String.Format("{0}/{1}", activityFlowTemplate.FromEntityID, getLongValueFromJToken(entityConnectionStat["upstreamCrossAppCallingEntity"], "entityId")), out tierLookup) == true) + { + activityFlow.FromName = String.Format("{{{0}}}->({1})-{{{2}}}", activityFlow.FromName, tierLookup.TierName, activityFlow.ApplicationName); + } + } + } + activityFlow.CallType = getStringValueFromJToken(entityConnectionStat["exitPointCall"], "exitPointType"); if (activityFlow.CallType.Length == 0) { @@ -779,7 +839,7 @@ private List convertFlowmapTier(APMTier tier, JobTarget jobTarget, return activityFlowsList; } - private List convertFlowmapNode(APMNode node, JobTarget jobTarget, JobTimeRange jobTimeRange) + private List convertFlowmapNode(APMNode node, Dictionary tiersDictionary, JobTarget jobTarget, JobTimeRange jobTimeRange) { JObject flowmapData = FileIOHelper.LoadJObjectFromFile(FilePathMap.NodeFlowmapDataFilePath(jobTarget, jobTimeRange, node)); if (flowmapData == null) @@ -863,6 +923,10 @@ private List convertFlowmapNode(APMNode node, JobTarget jobTarget, activityFlowTemplate.FromLink = String.Format(DEEPLINK_APM_APPLICATION, activityFlowTemplate.Controller, activityFlowTemplate.FromEntityID, DEEPLINK_THIS_TIMERANGE); break; + case ENTITY_TYPE_FLOWMAP_FEDERATED_APPLICATION: + activityFlowTemplate.FromType = "Federated Application"; + break; + default: activityFlowTemplate.FromName = getStringValueFromJToken(entityConnection, "sourceNode"); activityFlowTemplate.FromType = getStringValueFromJToken(entityConnection["sourceNodeDefinition"], "entityType"); @@ -893,10 +957,16 @@ private List convertFlowmapNode(APMNode node, JobTarget jobTarget, break; case ENTITY_TYPE_FLOWMAP_APPLICATION: + // Let's build a pretty call chain that can be matched on the downstream application flowmap + activityFlowTemplate.ToName = String.Format("{{{0}}}->({1})-{{{2}}}", activityFlowTemplate.ApplicationName, activityFlowTemplate.TierName, activityFlowTemplate.ToName); activityFlowTemplate.ToType = APMApplication.ENTITY_TYPE; activityFlowTemplate.ToLink = String.Format(DEEPLINK_APM_APPLICATION, activityFlowTemplate.Controller, activityFlowTemplate.ToEntityID, DEEPLINK_THIS_TIMERANGE); break; + case ENTITY_TYPE_FLOWMAP_FEDERATED_APPLICATION: + activityFlowTemplate.ToType = "Federated Application"; + break; + default: activityFlowTemplate.ToName = getStringValueFromJToken(entityConnection, "targetNode"); activityFlowTemplate.ToType = getStringValueFromJToken(entityConnection["targetNodeDefinition"], "entityType"); @@ -918,6 +988,20 @@ private List convertFlowmapNode(APMNode node, JobTarget jobTarget, { ActivityFlow activityFlow = activityFlowTemplate.Clone(); + // Inbound call is from Application, let's come up with the call chain that would match the outbound call from an application + // Here we have to look up Tier name from APM Entities index because flowmap only includes its ID + if (activityFlowTemplate.FromType == APMApplication.ENTITY_TYPE) + { + if (isTokenPropertyNull(entityConnectionStat, "upstreamCrossAppCallingEntity") == false) + { + APMTier tierLookup = null; + if (tiersDictionary.TryGetValue(String.Format("{0}/{1}", activityFlowTemplate.FromEntityID, getLongValueFromJToken(entityConnectionStat["upstreamCrossAppCallingEntity"], "entityId")), out tierLookup) == true) + { + activityFlow.FromName = String.Format("{{{0}}}->({1})-{{{2}}}", activityFlow.FromName, tierLookup.TierName, activityFlow.ApplicationName); + } + } + } + activityFlow.CallType = getStringValueFromJToken(entityConnectionStat["exitPointCall"], "exitPointType"); if (activityFlow.CallType.Length == 0) { @@ -1178,7 +1262,7 @@ private List convertFlowmapBackend(APMBackend backend, JobTarget j return activityFlowsList; } - private List convertFlowmapsBusinessTransaction(APMBusinessTransaction businessTransaction, JobTarget jobTarget, JobTimeRange jobTimeRange) + private List convertFlowmapsBusinessTransaction(APMBusinessTransaction businessTransaction, Dictionary tiersDictionary, JobTarget jobTarget, JobTimeRange jobTimeRange) { JObject flowmapData = FileIOHelper.LoadJObjectFromFile(FilePathMap.BusinessTransactionFlowmapDataFilePath(jobTarget, jobTimeRange, businessTransaction)); if (flowmapData == null) @@ -1260,6 +1344,10 @@ private List convertFlowmapsBusinessTransaction(APMBusinessTransac activityFlowTemplate.FromLink = String.Format(DEEPLINK_APM_APPLICATION, activityFlowTemplate.Controller, activityFlowTemplate.FromEntityID, DEEPLINK_THIS_TIMERANGE); break; + case ENTITY_TYPE_FLOWMAP_FEDERATED_APPLICATION: + activityFlowTemplate.FromType = "Federated Application"; + break; + default: activityFlowTemplate.FromName = getStringValueFromJToken(entityConnection, "sourceNode"); activityFlowTemplate.FromType = getStringValueFromJToken(entityConnection["sourceNodeDefinition"], "entityType"); @@ -1285,10 +1373,16 @@ private List convertFlowmapsBusinessTransaction(APMBusinessTransac break; case ENTITY_TYPE_FLOWMAP_APPLICATION: + // Let's build a pretty call chain that can be matched on the downstream application flowmap + activityFlowTemplate.ToName = String.Format("{{{0}}}->({1})-{{{2}}}", activityFlowTemplate.ApplicationName, activityFlowTemplate.FromName, activityFlowTemplate.ToName); activityFlowTemplate.ToType = APMApplication.ENTITY_TYPE; activityFlowTemplate.ToLink = String.Format(DEEPLINK_APM_APPLICATION, activityFlowTemplate.Controller, activityFlowTemplate.ToEntityID, DEEPLINK_THIS_TIMERANGE); break; + case ENTITY_TYPE_FLOWMAP_FEDERATED_APPLICATION: + activityFlowTemplate.ToType = "Federated Application"; + break; + default: activityFlowTemplate.ToName = getStringValueFromJToken(entityConnection, "targetNode"); activityFlowTemplate.ToType = getStringValueFromJToken(entityConnection["targetNodeDefinition"], "entityType"); @@ -1313,6 +1407,18 @@ private List convertFlowmapsBusinessTransaction(APMBusinessTransac { ActivityFlow activityFlow = activityFlowTemplate.Clone(); + if (activityFlowTemplate.FromType == APMApplication.ENTITY_TYPE) + { + if (isTokenPropertyNull(entityConnectionStat, "upstreamCrossAppCallingEntity") == false) + { + APMTier tierLookup = null; + if (tiersDictionary.TryGetValue(String.Format("{0}/{1}", activityFlowTemplate.FromEntityID, getLongValueFromJToken(entityConnectionStat["upstreamCrossAppCallingEntity"], "entityId")), out tierLookup) == true) + { + activityFlow.FromName = String.Format("{{{0}}}->({1})-{{{2}}}", activityFlow.FromName, tierLookup.TierName, activityFlow.ApplicationName); + } + } + } + activityFlow.CallType = getStringValueFromJToken(entityConnectionStat["exitPointCall"], "exitPointType"); if (activityFlow.CallType.Length == 0) { diff --git a/ProcessingSteps/Index/IndexAPMSnapshots.cs b/ProcessingSteps/Index/IndexAPMSnapshots.cs index ceeee83..5d82375 100644 --- a/ProcessingSteps/Index/IndexAPMSnapshots.cs +++ b/ProcessingSteps/Index/IndexAPMSnapshots.cs @@ -536,8 +536,11 @@ public override bool Execute(ProgramOptions programOptions, JobConfiguration job loggerConsole.Info("Fold Stacks for Nodes and Business Transactions"); + int j = 1; foreach (JobTimeRange jobTimeRange in jobConfiguration.Input.HourlyTimeRanges) { + Console.WriteLine("Processing range {0}/{1} for hour starting {2:o}", j, jobConfiguration.Input.HourlyTimeRanges.Count, jobTimeRange.From); + // Go through BTs foreach (APMBusinessTransaction businessTransaction in businessTransactionsList) { @@ -584,6 +587,8 @@ public override bool Execute(ProgramOptions programOptions, JobConfiguration job } } } + + j++; } #endregion @@ -2736,10 +2741,10 @@ private MethodCallLine convertCallGraphChildren_Recursion( exitCallForThisExit.ToEntityName = getStringValueFromJToken(goingToProperty, "value"); } goingToProperty = exitCallToken["properties"].Where(p => getStringValueFromJToken(p, "name") == "from").FirstOrDefault(); - string callChainForThisSegment = "(Generated From Call Graph:Unknown)"; + string callChainForThisSegment = "(CallGraph:Unknown)"; if (goingToProperty != null) { - callChainForThisSegment = String.Format("(Generated From Call Graph:{0})", getStringValueFromJToken(goingToProperty, "value")); + callChainForThisSegment = String.Format("(CallGraph:{0})", getStringValueFromJToken(goingToProperty, "value")); } if (exitCallForThisExit.IsAsync == false) { @@ -3105,10 +3110,10 @@ private List convertCallGraphChildren_Stack( exitCallForThisExit.ToEntityName = getStringValueFromJToken(goingToProperty, "value"); } goingToProperty = exitCallToken["properties"].Where(p => getStringValueFromJToken(p, "name") == "from").FirstOrDefault(); - string callChainForThisSegment = "(Generated From Call Graph:Unknown)"; + string callChainForThisSegment = "(CallGraph:Unknown)"; if (goingToProperty != null) { - callChainForThisSegment = String.Format("(Generated From Call Graph:{0})", getStringValueFromJToken(goingToProperty, "value")); + callChainForThisSegment = String.Format("(CallGraph:{0})", getStringValueFromJToken(goingToProperty, "value")); } if (exitCallForThisExit.IsAsync == false) { diff --git a/ProcessingSteps/Index/JobStepIndexBase.cs b/ProcessingSteps/Index/JobStepIndexBase.cs index e27f66e..de064e3 100644 --- a/ProcessingSteps/Index/JobStepIndexBase.cs +++ b/ProcessingSteps/Index/JobStepIndexBase.cs @@ -72,6 +72,7 @@ public class JobStepIndexBase : JobStepBase #region Constants for various strings mapping to Entity types in Flowmaps and Events internal const string ENTITY_TYPE_FLOWMAP_APPLICATION = "APPLICATION"; + internal const string ENTITY_TYPE_FLOWMAP_FEDERATED_APPLICATION = "FEDERATED_APPLICATION"; internal const string ENTITY_TYPE_FLOWMAP_APPLICATION_MOBILE = "MOBILE_APPLICATION"; internal const string ENTITY_TYPE_FLOWMAP_TIER = "APPLICATION_COMPONENT"; internal const string ENTITY_TYPE_FLOWMAP_NODE = "APPLICATION_COMPONENT_NODE"; diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 3bc144a..0e28a94 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.39.0")] -[assembly: AssemblyFileVersion("1.2.39.0")] +[assembly: AssemblyVersion("1.2.40.0")] +[assembly: AssemblyFileVersion("1.2.40.0")] diff --git a/ReportObjects/FlameGraph/FoldedStackLine.cs b/ReportObjects/FlameGraph/FoldedStackLine.cs index cc43123..e7b549e 100644 --- a/ReportObjects/FlameGraph/FoldedStackLine.cs +++ b/ReportObjects/FlameGraph/FoldedStackLine.cs @@ -147,15 +147,20 @@ public void AddFoldedStackLine(FoldedStackLine foldedStackLineToAdd) { // Append two exit calls by decoding them, squishing them together, and then reencoding them string exitsInThisFoldedStack = Encoding.UTF8.GetString(Convert.FromBase64String(this.ExitCallsArray[i])); - string exitsInIncomingFoldedStack = Encoding.UTF8.GetString(Convert.FromBase64String(foldedStackLineToAdd.ExitCallsArray[i])); - List allExits = new List(); - allExits.AddRange(exitsInThisFoldedStack.Split('\n').ToList()); - allExits.AddRange(exitsInIncomingFoldedStack.Split('\n').ToList()); + // Only append if the exit size hasn't grown larger then what we want to see + if (exitsInThisFoldedStack.Length < 400) + { + string exitsInIncomingFoldedStack = Encoding.UTF8.GetString(Convert.FromBase64String(foldedStackLineToAdd.ExitCallsArray[i])); - // Only append unique values - List uniqueExits = allExits.Distinct().ToList(); - this.ExitCallsArray[i] = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(String.Join("\n", uniqueExits))); + List allExits = new List(); + allExits.AddRange(exitsInThisFoldedStack.Split('\n').ToList()); + allExits.AddRange(exitsInIncomingFoldedStack.Split('\n').ToList()); + + // Only append unique values + List uniqueExits = allExits.Distinct().ToList(); + this.ExitCallsArray[i] = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(String.Join("\n", uniqueExits))); + } } else if ( this.ExitCallsArray[i] != null && @@ -177,8 +182,7 @@ public void AddFoldedStackLine(FoldedStackLine foldedStackLineToAdd) (foldedStackLineToAdd.ExitCallsArray[i] == null || foldedStackLineToAdd.ExitCallsArray[i].Length == 0)) { // Do nothing, nothing to join - } - + } } } } diff --git a/VisualAnalytics/PowerBI/Design/EntityMetricsDesign.pbix b/VisualAnalytics/PowerBI/Design/EntityMetricsDesign.pbix index b481ca9..2cf75e6 100644 Binary files a/VisualAnalytics/PowerBI/Design/EntityMetricsDesign.pbix and b/VisualAnalytics/PowerBI/Design/EntityMetricsDesign.pbix differ diff --git a/VisualAnalytics/PowerBI/Templates/EntityMetrics.pbit b/VisualAnalytics/PowerBI/Templates/EntityMetrics.pbit index a4bdd04..1749ff4 100644 Binary files a/VisualAnalytics/PowerBI/Templates/EntityMetrics.pbit and b/VisualAnalytics/PowerBI/Templates/EntityMetrics.pbit differ