From 6c963ab15003a1437bf3a47a7503204014505b2d Mon Sep 17 00:00:00 2001 From: Alex Afshar Date: Thu, 5 Oct 2023 14:08:21 +0000 Subject: [PATCH] Add configuration analysis report enhancement * add Configuration Analysis Report generation option (--car) used by services team, issue #127 --- README.md | 47 +- VERSION | 2 +- backend/backend.py | 5 +- backend/core/Engine.py | 27 +- backend/output/PostProcessReport.py | 10 + .../reports/ConfigurationAnalysisReport.py | 477 ++++++++++++++++++ input/jobs/DefaultJob.json | 32 +- input/thresholds/DefaultThresholds.json | 2 +- 8 files changed, 570 insertions(+), 32 deletions(-) create mode 100644 backend/output/PostProcessReport.py create mode 100644 backend/output/reports/ConfigurationAnalysisReport.py diff --git a/README.md b/README.md index 794c3b4..b6d4a61 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ There are four options to run the tool: 1. [UI Method](https://github.com/Appdynamics/config-assessment-tool#ui-method) - Run jobs from a convenient web based UI - Easier way to configure jobs but requires Python and Docker installation -2. [Platform executable](https://github.com/Appdynamics/config-assessment-tool#platform-executable) +2. [Platform executable](https://github.com/Appdynamics/config-assessment-tool#platform-executable). (Preferred method for most users running Windows and Linux) - A self contained OS specific bundle if you are not using Docker and Python. Bundles available for Linux and Windows - Recommended for users that do not wish to install Python and Docker and/or can not access external repositories 3. [Directly via Docker](https://github.com/Appdynamics/config-assessment-tool#directly-via-docker) @@ -35,9 +35,9 @@ The tool expects ONLY the following permissions to be given: - Administrator (Default) - Analytics Administrator (Default) -### UI method +### 1) UI method -Obtain frontend and backend Docker images via: +This method will automatically create local Docker images if not already in the local registry and runs them using the newly built images: 1. Download or clone the latest `Source Code.zip` from [here](https://github.com/Appdynamics/config-assessment-tool/releases) 2. `cd config-assessment-tool` @@ -50,7 +50,7 @@ Add new Jobs or Thresholds to `config_assessment_tool/resources/jobs` and `confi Refresh the page to see the Jobs and Thresholds appear. -### Platform executable +### 2) Platform executable Use this method if you are not able to use Docker or Python in your target deployment environment. Currently, platform bundles are available for x86 Windows and Linux only. Currently arm architectures are not supported. @@ -79,12 +79,26 @@ config-assessment-tool--/ └── ... ``` +To see a list of options you may use the ```--help``` when running the executable command. Available options are listed below. +``` +Usage: config-assessment-tool [OPTIONS] -### Directly via Docker +Where below OPTIONS are available: -You can start the backend container with the following command: +Options: + -j, --job-file TEXT + -t, --thresholds-file TEXT + -d, --debug + -c, --concurrent-connections INTEGER + -u, --username TEXT Adds the option to put username dynamically + -p, --password TEXT Adds the option to put password dynamically + --car Generate the configration analysis report as part of the output + --help Show this help message and exit. +``` + +### 3) Directly via Docker -Unix +You can start the backend container with the following command: ``` docker run \ @@ -112,7 +126,7 @@ docker run ` ghcr.io/appdynamics/config-assessment-tool-backend-{platform}:{tag} -j DefaultJob -t DefaultThresholds ``` -### From Source +### 4) From Source #### Steps to run @@ -124,15 +138,22 @@ Required 4. `pipenv shell` 5. `python3 backend/backend.py -j DefaultJob -t DefaultThresholds` +To see a list of options you may use the ```--help``` flag when running the backend command. Available options are listed below. + ``` Usage: backend.py [OPTIONS] +Where below OPTIONS are available: + Options: -j, --job-file TEXT -t, --thresholds-file TEXT -d, --debug -c, --concurrent-connections INTEGER - --help Show this message and exit. + -u, --username TEXT Adds the option to put username dynamically + -p, --password TEXT Adds the option to put password dynamically + --car Generate the configration analysis report as part of the output + --help Show this help message and exit ``` Options `--job-file` and `--thresholds-file` will default to `DefaultJob` and `DefaultThresholds` respectively. @@ -165,6 +186,10 @@ This program will create the following files in the `out` directory. - Raw metrics which go into MaturityAssessment for BRUM report - `{jobName}-MaturityAssessmentRaw-mrum.xlsx` - Raw metrics which go into MaturityAssessment for MRUM report +- `{jobName}-HybridApplicationMonitoringUseCaseMaturityAssessment-presentation.pptx + - Primarily used by customers that have purchased the Hybrid App Monitoring(HAM) SKU's +- `{jobName}-ConfigurationAnalysisReport.xlsx` + - Configuration Analysis Report used primarily by AppD Services team, generated separately - `controllerData.json` - Contains all raw data used in analysis. - `info.json` @@ -247,9 +272,9 @@ For example: Use HTTPS_PROXY environment variable if your controller is accessib ## Support -For general feature requests or questions/feedback please create an issue in this Github repository. +For general feature requests or questions/feedback please create an issue in this Github repository. Ensure that no proprietary information is included in the issue or attachments as this is an open source project with public visibility. -If you are having difficulty running the tool email Alex Afshar at aleafsha@cisco.com and attach debug logs. +If you are having difficulty running the tool email Alex Afshar at aleafsha@cisco.com and attach any relevant information including debug logs. Debug logs can be taken by either: diff --git a/VERSION b/VERSION index 8e2e751..5363857 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.5.9.9 \ No newline at end of file +v1.6 \ No newline at end of file diff --git a/backend/backend.py b/backend/backend.py index 0814c0e..a08267e 100644 --- a/backend/backend.py +++ b/backend/backend.py @@ -14,10 +14,11 @@ @click.option("-c", "--concurrent-connections", type=int) @click.option("-u", "--username", default=None, help="Adds the option to put username dynamically") @click.option("-p", "--password", default=None, help="Adds the option to put password dynamically") +@click.option("--car", is_flag=True, help="Generate the configration analysis report as part of the output") @coro -async def main(job_file: str, thresholds_file: str, debug, concurrent_connections: int, username: str, password: str): +async def main(job_file: str, thresholds_file: str, debug, concurrent_connections: int, username: str, password: str, car: bool): initLogging(debug) - engine = Engine(job_file, thresholds_file, concurrent_connections, username, password) + engine = Engine(job_file, thresholds_file, concurrent_connections, username, password, car) await engine.run() diff --git a/backend/core/Engine.py b/backend/core/Engine.py index f096e2e..32d7085 100644 --- a/backend/core/Engine.py +++ b/backend/core/Engine.py @@ -9,6 +9,7 @@ from pathlib import Path import requests + from api.appd.AppDService import AppDService from extractionSteps.general.ControllerLevelDetails import ControllerLevelDetails from extractionSteps.general.CustomMetrics import CustomMetrics @@ -30,9 +31,11 @@ from extractionSteps.maturityAssessment.mrum.HealthRulesAndAlertingMRUM import HealthRulesAndAlertingMRUM from extractionSteps.maturityAssessment.mrum.NetworkRequestsMRUM import NetworkRequestsMRUM from extractionSteps.maturityAssessment.mrum.OverallAssessmentMRUM import OverallAssessmentMRUM +from output.PostProcessReport import PostProcessReport from output.presentations.cxPpt import createCxPpt from output.presentations.cxPptFsoUseCases import createCxHamUseCasePpt from output.reports.AgentMatrixReport import AgentMatrixReport +from output.reports.ConfigurationAnalysisReport import ConfigurationAnalysisReport from output.reports.CustomMetricsReport import CustomMetricsReport from output.reports.DashboardReport import DashboardReport from output.reports.LicenseReport import LicenseReport @@ -44,7 +47,10 @@ class Engine: - def __init__(self, jobFileName: str, thresholdsFileName: str, concurrentConnections: int, username: str, password: str): + def __init__(self, jobFileName: str, thresholdsFileName: str, concurrentConnections: int, username: str, password: str, car: bool): + + # should we run the configuration analysis report in post-processing? + self.car = car if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): # running as executable bundle @@ -62,6 +68,7 @@ def __init__(self, jobFileName: str, thresholdsFileName: str, concurrentConnecti self.codebaseVersion = open(f"VERSION").read() logging.info(f"Running Software Version: {self.codebaseVersion}") + # Validate jobFileName and thresholdFileName self.jobFileName = jobFileName self.thresholdsFileName = thresholdsFileName @@ -196,6 +203,7 @@ async def run(self): await self.validateThresholdsFile() await self.initControllers() await self.process() + await self.postProcess() self.finalize(startTime) except Exception as e: # catch exceptions here, so we can terminate coroutines before program exit @@ -396,3 +404,20 @@ async def abortAndCleanup(self, msg: str, error=True): if msg: logging.info(msg) sys.exit(0) + + async def postProcess(self): + """Post-processing reports that rely on the core generated excel reports. + Currently, for CAR(configuration analysis report) that consumes the core + reports post process + """ + logging.info(f"----------Post Process----------") + commands = [] + + if self.car: + commands.append(ConfigurationAnalysisReport()) + + for command in commands: + if isinstance(command, PostProcessReport): + await command.post_process(self.jobFileName) + + logging.info(f"----------Post Process Done----------") diff --git a/backend/output/PostProcessReport.py b/backend/output/PostProcessReport.py new file mode 100644 index 0000000..bb884f0 --- /dev/null +++ b/backend/output/PostProcessReport.py @@ -0,0 +1,10 @@ +from abc import ABC, abstractmethod + + +class PostProcessReport(ABC): + @abstractmethod + async def post_process(self, report_names): + """ + execute post processing reports + """ + pass diff --git a/backend/output/reports/ConfigurationAnalysisReport.py b/backend/output/reports/ConfigurationAnalysisReport.py new file mode 100644 index 0000000..13d9c7d --- /dev/null +++ b/backend/output/reports/ConfigurationAnalysisReport.py @@ -0,0 +1,477 @@ +import logging +import os.path + +import pandas as pd +import xlsxwriter + +from output.PostProcessReport import PostProcessReport + +light_font = '#FFFFFF' +dark_font = '#000000' + +medium_bg = '#A0A0A0' +dark_bg = '#000000' + + +class ConfigurationAnalysisReport(PostProcessReport): + + def __init__(self): + self.workbook = None + self.analysis_sheet = None + + async def post_process(self, jobFileName): + + logging.info(f"Running post-process command: Creating Configuration Analysis Workbook") + + directory = f"output/{jobFileName}" + file_prefix = f"{jobFileName}" + # input + self.analysis_sheet = os.path.join(directory, f"{file_prefix}-MaturityAssessment-apm.xlsx") + + if not os.path.exists(self.analysis_sheet): + logging.warn(f"Input file sheet {self.analysis_sheet} does not exist. " + f"Skipping post-process command: Creating Configuration Analysis Workbook") + return + + # output + self.workbook = xlsxwriter.Workbook(f"output/{jobFileName}/{jobFileName}-ConfigurationAnalysisReport.xlsx") + worksheets = self.generateHeaders() + applicationNames = self.getListOfApplications() + applicationData = [] + + for application in applicationNames: + taskList = [[], [], [], [], []] + ranking = self.performAnalysis(application, taskList) + if ranking != "Platinum": + for task in taskList: + if (len(task) > 0): + applicationData.append([application, ranking, taskList]) + break + else: + applicationData.append([application, ranking, []]) + + self.buildOutput(applicationData, worksheets) + logging.debug(f"Saving ConfigurationAnalysisReport Workbook") + self.workbook.close() + + def getValuesInColumn(self, sheet, col1_value): + values = [] + for column_cell in sheet.iter_cols(1, sheet.max_column): + if column_cell[0].value == col1_value: + j = 0 + for data in column_cell[1:]: + values.append(data.value) + break + return values + + def getListOfApplications(self): + frame = pd.read_excel(self.analysis_sheet, sheet_name='Analysis', engine='openpyxl').dropna() + return frame['name'].tolist() + + def overallAppStatus(self, application, tasklist): + frame = pd.read_excel(self.analysis_sheet, sheet_name='Analysis', engine='openpyxl') + frame = frame.drop('controller', axis=1) + appFrame = frame.loc[frame['name'] == application] + + # Overall Assessment + ranking = 'NA' + if (appFrame['OverallAssessment'] == 'bronze').any(): + ranking = 'Bronze' + elif (appFrame['OverallAssessment'] == 'silver').any(): + ranking = 'Silver' + elif (appFrame['OverallAssessment'] == 'gold').any(): + ranking = 'Gold' + elif (appFrame['OverallAssessment'] == 'platinum').any(): + ranking = 'Platinum' + return ranking + + def appAgentStatus(self, application, taskList): + # Sheet name may have changed to AppAgentsAPM + frame = pd.read_excel(self.analysis_sheet, sheet_name='AppAgentsAPM', engine='openpyxl') + frame = frame.drop('controller', axis=1) + appFrame = frame.loc[frame['application'] == application] + + # Agent Metric Limit + if (appFrame['metricLimitNotHit'] == False).any(): + taskList[2].append("Application Agent metric limit has been reached") + + # Agent Versions + if (appFrame['percentAgentsLessThan2YearsOld'] < 50).any(): + taskList[0].append(str(100 - int(appFrame['percentAgentsLessThan2YearsOld'])) + '% of Application Agents are 2+ years old') + elif (appFrame['percentAgentsLessThan1YearOld'] < 80).any(): + taskList[0].append(str(100 - int(appFrame['percentAgentsLessThan1YearOld'])) + '% of Application Agents are at least 1 year old') + + # Agents reporting data + if (appFrame['percentAgentsReportingData'] < 100).any(): + taskList[0].append(str(100 - int(appFrame['percentAgentsReportingData'])) + "% of Application Agents aren't reporting data") + + if (appFrame['percentAgentsRunningSameVersion'] < 100).any(): + taskList[0].append('Multiple Application Agent Versions') + + def machineAgentStatus(self, application, taskList): + frame = pd.read_excel(self.analysis_sheet, sheet_name='MachineAgentsAPM', engine='openpyxl') + frame = frame.drop('controller', axis=1) + appFrame = frame.loc[frame['application'] == application] + + def businessTranStatus(self, application, taskList): + frame = pd.read_excel(self.analysis_sheet, sheet_name='BusinessTransactionsAPM', engine='openpyxl') + frame.drop('controller', axis=1) + appFrame = frame.loc[frame['application'] == application] + + # Number of Business Transcations + if (appFrame['numberOfBTs'] > 200).any(): + taskList[1].append("Reduce amount of Business transactions from " + str(int(appFrame['numberOfBTs']))) + + # % of Business Transactions with load + if (appFrame['percentBTsWithLoad'] < 90).any(): + taskList[1].append(str(100 - int(appFrame['percentBTsWithLoad'])) + '% of Business Transactions have no load over the last 24 hours') + + # Business Transaction Lockdown + if (appFrame['btLockdownEnabled'] == False).any(): + taskList[1].append("Business Transaction Lockdown is disabled") + + # Number of Custom Match Rules + if (appFrame['numberCustomMatchRules'] < 3).any(): + if (appFrame['numberCustomMatchRules'] == 0).any(): + taskList[2].append('No Custom Match Rules') + else: + taskList[2].append('Only ' + str(int(appFrame['numberCustomMatchRules'])) + ' Custom Match Rules') + + def backendStatus(self, application, taskList): + frame = pd.read_excel(self.analysis_sheet, sheet_name='BackendsAPM', engine='openpyxl') + frame.drop('controller', axis=1) + appFrame = frame.loc[frame['application'] == application] + + # % of Backends with load + if (appFrame['percentBackendsWithLoad'] < 75).any(): + taskList[2].append(str(100 - int(appFrame['percentBackendsWithLoad'])) + '% of Backends have no load') + + # Backend limit not hit + if (appFrame['backendLimitNotHit'] == False).any(): + taskList[2].append('Backend limit has been reached') + + # Number of Custom Backend Rules + if (appFrame['numberOfCustomBackendRules'] == 0).any(): + taskList[2].append('No Custom Backend Rules') + + def overheadStatus(self, application, taskList): + frame = pd.read_excel(self.analysis_sheet, sheet_name='OverheadAPM', engine='openpyxl') + frame.drop('controller', axis=1) + appFrame = frame.loc[frame['application'] == application] + + # Developer Mode Not Enabled for any Business Transaction + if (appFrame['developerModeNotEnabledForAnyBT'] == False).any(): + taskList[2].append('Development Level monitoring is enabled for a Business Transaction') + + # find-entry-points not enabled + if (appFrame['findEntryPointsNotEnabled'] == False).any(): + taskList[2].append('Find-entry-points node property is enabled') + + # Aggressive Snapshotting not enabled + if (appFrame['aggressiveSnapshottingNotEnabled'] == False).any(): + taskList[2].append('Aggressive snapshot collection is enabled') + + # Developer Mode not enabled for an application + if (appFrame['developerModeNotEnabledForApplication'] == False).any(): + taskList[2].append('Development Level monitoring is enabled for an Application') + + def serviceEndpointStatus(self, application, taskList): + frame = pd.read_excel(self.analysis_sheet, sheet_name='ServiceEndpointsAPM', engine='openpyxl') + frame.drop('controller', axis=1) + appFrame = frame.loc[frame['application'] == application] + + # Number of Custom Service Endpoint Rules + if (appFrame['numberOfCustomServiceEndpointRules'] == 0).any(): + taskList[2].append('No Custom Service Endpoint rules') + + # Service Endpoint Limit not hit + if (appFrame['serviceEndpointLimitNotHit'] == False).any(): + taskList[2].append('Service Endpoint limit has been reached') + + # % of enabled Service Endpoints with load + if (appFrame['percentServiceEndpointsWithLoadOrDisabled'] < 75).any(): + taskList[2].append(str(100 - int(appFrame['percentServiceEndpointsWithLoadOrDisabled'])) + '% of enabled Service Endpoints have no load') + + def errorConfigurationStatus(self, application, taskList): + frame = pd.read_excel(self.analysis_sheet, sheet_name='ErrorConfigurationAPM', engine='openpyxl') + frame.drop('controller', axis=1) + appFrame = frame.loc[frame['application'] == application] + + # Sucess Percentage of Worst Transaction + if (appFrame['successPercentageOfWorstTransaction'] < 80).any(): + taskList[3].append('Some Business Transactions fail ' + str(100 - int(appFrame['successPercentageOfWorstTransaction'])) + '% of the time') + + # Number of Custom rules + if (appFrame['numberOfCustomRules'] == 0).any(): + taskList[2].append('No custom error configurations') + + def healthRulesAlertingStatus(self, application, taskList): + frame = pd.read_excel(self.analysis_sheet, sheet_name='HealthRulesAndAlertingAPM', engine='openpyxl') + frame.drop('controller', axis=1) + appFrame = frame.loc[frame['application'] == application] + + # Number of Health Rule Violations in last 24 hours + if (appFrame['numberOfHealthRuleViolations'] > 10).any(): + taskList[3].append(str(int(appFrame['numberOfHealthRuleViolations'])) + ' Health Rule Violations in 24 hours') + + # Number of modifications to default Health Rules + if (appFrame['numberOfDefaultHealthRulesModified'] < 2).any(): + if (appFrame['numberOfDefaultHealthRulesModified'] < 2).any(): + taskList[3].append('No modifications to the default Health Rules') + else: + taskList[3].append('Only ' + str(int(appFrame['numberOfDefaultHealthRulesModified'])) + ' modifications to the default Health Rules') + + # Number of actions bound to enabled policies + if (appFrame['numberOfActionsBoundToEnabledPolicies'] < 1).any(): + taskList[3].append('No actions bound to enabled policies') + + # Number of Custom Health Rules + if (appFrame['numberOfCustomHealthRules'] < 5).any(): + if (appFrame['numberOfCustomHealthRules'] == 0).any(): + taskList[3].append('No Custom Health Rules') + else: + taskList[3].append('Only ' + str(int(appFrame['numberOfCustomHealthRules'])) + ' Custom Health Rules') + + def dataCollectorStatus(self, application, taskList): + frame = pd.read_excel(self.analysis_sheet, sheet_name='DataCollectorsAPM', engine='openpyxl') + frame.drop('controller', axis=1) + appFrame = frame.loc[frame['application'] == application] + + # Number of data collector fields configured + if (appFrame['numberOfDataCollectorFieldsConfigured'] < 5).any(): + if (appFrame['numberOfDataCollectorFieldsConfigured'] == 0).any(): + taskList[2].append('No configured Data Collectors') + else: + taskList[2].append('Only ' + str(int(appFrame['numberOfDataCollectorFieldsConfigured'])) + ' configured Data Collectors') + + # Number of data collector fields colleced in snapshots in last 24 hours + if (appFrame['numberOfDataCollectorFieldsCollectedInSnapshots'] < 5).any(): + if (appFrame['numberOfDataCollectorFieldsCollectedInSnapshots'] == 0).any(): + taskList[2].append('No Data Collector fields collected in APM Snapshots in 24 hours') + else: + taskList[2].append('Only ' + str(int(appFrame['numberOfDataCollectorFieldsCollectedInSnapshots'])) + ' Data Collector fields collected in APM Snapshots in 24 hours') + + # Number of data collector fields collect in analytics in last 24 hours + if (appFrame['numberOfDataCollectorFieldsCollectedInAnalytics'] < 5).any(): + if (appFrame['numberOfDataCollectorFieldsCollectedInAnalytics'] == 0).any(): + taskList[2].append('No Data Collector fields collected in Analytics in 24 hours') + else: + taskList[2].append('Only ' + str(int(appFrame['numberOfDataCollectorFieldsCollectedInAnalytics'])) + ' Data Collector fields collected in Analytics in 24 hours') + + # BiQ enabled + if (appFrame['biqEnabled'] == False).any(): + taskList[2].append('BiQ is disabled') + + def apmDashBoardsStatus(self, application, taskList): + frame = pd.read_excel(self.analysis_sheet, sheet_name='DashboardsAPM', engine='openpyxl') + frame.drop('controller', axis=1) + appFrame = frame.loc[frame['application'] == application] + + # Number of custom dashboards + if (appFrame['numberOfDashboards'] < 5).any(): + if (appFrame['numberOfDashboards'] == 1).any(): + taskList[4].append('Only 1 Custom Dashboard') + elif (appFrame['numberOfDashboards'] == 0).any(): + taskList[4].append('No Custom Dashboards') + else: + taskList[4].append('Only ' + str(int(appFrame['numberOfDashboards'])) + ' Custom Dashboards') + + # % of Custom Dashboards modified in last 6 months + if (appFrame['percentageOfDashboardsModifiedLast6Months'] < 100).any(): + taskList[4].append(str(100 - int(appFrame['percentageOfDashboardsModifiedLast6Months'])) + '% of Custom Dashboards have not been updated in 6+ months') + + # Number of Custom Dashboards using BiQ + if (appFrame['numberOfDashboardsUsingBiQ'] == 0).any(): + taskList[4].append('No Custom Dashboards using BiQ') + + def performAnalysis(self, application, taskList): + overallRanking = self.overallAppStatus(application, taskList) + self.appAgentStatus(application, taskList) + self.machineAgentStatus(application, taskList) + self.businessTranStatus(application, taskList) + self.backendStatus(application, taskList) + self.overheadStatus(application, taskList) + self.serviceEndpointStatus(application, taskList) + self.errorConfigurationStatus(application, taskList) + self.healthRulesAlertingStatus(application, taskList) + self.dataCollectorStatus(application, taskList) + self.apmDashBoardsStatus(application, taskList) + + return overallRanking + + def buildOutput(self, applicationData, worksheets): + worksheet = None + + stepFormat = self.workbook.add_format() + stepFormat.set_align('center') + # row_num = 1 + row_counts = [1, 1, 1, 1] + + for application in range(0, len(applicationData)): + applicationName = applicationData[application][0] + applicationRank = applicationData[application][1] + currentApplication = applicationData[application][2] + + if applicationRank == 'Bronze': + worksheet = worksheets[0] + self.generateApplicationHeader(applicationName, applicationRank, worksheet, row_counts[0]) + row_counts[0] = row_counts[0] + 1 + elif applicationRank == 'Silver': + worksheet = worksheets[1] + self.generateApplicationHeader(applicationName, applicationRank, worksheet, row_counts[1]) + row_counts[1] = row_counts[1] + 1 + elif applicationRank == 'Gold': + worksheet = worksheets[2] + self.generateApplicationHeader(applicationName, applicationRank, worksheet, row_counts[2]) + row_counts[2] = row_counts[2] + 1 + elif applicationRank == 'Platinum': + worksheet = worksheets[3] + self.generateApplicationHeader(applicationName, applicationRank, worksheet, row_counts[3]) + row_counts[3] = row_counts[3] + 1 + + # row_num += 1 + task_num = 1 + for categoryIndex in range(0, len(currentApplication)): + for taskIndex in range(0, len(currentApplication[categoryIndex])): + + if applicationRank == 'Bronze': + worksheet.write(row_counts[0], 0, applicationName) + worksheet.write(row_counts[0], 1, task_num, stepFormat) + worksheet.write(row_counts[0], 3, currentApplication[categoryIndex][taskIndex]) + # worksheet.write(row_num, 4, applicationRank) + worksheet.write(row_counts[0], 4, " ") + + if categoryIndex == 0: + worksheet.write(row_counts[0], 2, "Agent Installation (APM, Machine, Server, etc.)") + elif categoryIndex == 1: + worksheet.write(row_counts[0], 2, "Business Transactions") + elif categoryIndex == 2: + worksheet.write(row_counts[0], 2, "Advanced Configurations") + elif categoryIndex == 3: + worksheet.write(row_counts[0], 2, "Health Rules & Alerts") + elif categoryIndex == 4: + worksheet.write(row_counts[0], 2, "Dashboard") + worksheet.set_row(row_counts[0], None, None, {'level': 1}) + row_counts[0] += 1 + task_num += 1 + elif applicationRank == 'Silver': + worksheet.write(row_counts[1], 0, applicationName) + worksheet.write(row_counts[1], 1, task_num, stepFormat) + worksheet.write(row_counts[1], 3, currentApplication[categoryIndex][taskIndex]) + # worksheet.write(row_num, 4, applicationRank) + worksheet.write(row_counts[1], 4, " ") + + if categoryIndex == 0: + worksheet.write(row_counts[1], 2, "Agent Installation (APM, Machine, Server, etc.)") + elif categoryIndex == 1: + worksheet.write(row_counts[1], 2, "Business Transactions") + elif categoryIndex == 2: + worksheet.write(row_counts[1], 2, "Advanced Configurations") + elif categoryIndex == 3: + worksheet.write(row_counts[1], 2, "Health Rules & Alerts") + elif categoryIndex == 4: + worksheet.write(row_counts[1], 2, "Dashboard") + worksheet.set_row(row_counts[1], None, None, {'level': 1}) + row_counts[1] += 1 + task_num += 1 + elif applicationRank == 'Gold': + worksheet.write(row_counts[2], 0, applicationName) + worksheet.write(row_counts[2], 1, task_num, stepFormat) + worksheet.write(row_counts[2], 3, currentApplication[categoryIndex][taskIndex]) + # worksheet.write(row_num, 4, applicationRank) + worksheet.write(row_counts[2], 4, " ") + + if categoryIndex == 0: + worksheet.write(row_counts[2], 2, "Agent Installation (APM, Machine, Server, etc.)") + elif categoryIndex == 1: + worksheet.write(row_counts[2], 2, "Business Transactions") + elif categoryIndex == 2: + worksheet.write(row_counts[2], 2, "Advanced Configurations") + elif categoryIndex == 3: + worksheet.write(row_counts[2], 2, "Health Rules & Alerts") + elif categoryIndex == 4: + worksheet.write(row_counts[2], 2, "Dashboard") + worksheet.set_row(row_counts[2], None, None, {'level': 1}) + row_counts[2] += 1 + task_num += 1 + elif applicationRank == 'Platinum': + worksheet.write(row_counts[3], 0, applicationName) + worksheet.write(row_counts[3], 1, task_num, stepFormat) + worksheet.write(row_counts[3], 3, currentApplication[categoryIndex][taskIndex]) + # worksheet.write(row_num, 4, applicationRank) + worksheet.write(row_counts[3], 4, " ") + + if categoryIndex == 0: + worksheet.write(row_counts[3], 2, "Agent Installation (APM, Machine, Server, etc.)") + elif categoryIndex == 1: + worksheet.write(row_counts[3], 2, "Business Transactions") + elif categoryIndex == 2: + worksheet.write(row_counts[3], 2, "Advanced Configurations") + elif categoryIndex == 3: + worksheet.write(row_counts[3], 2, "Health Rules & Alerts") + elif categoryIndex == 4: + worksheet.write(row_counts[3], 2, "Dashboard") + worksheet.set_row(row_counts[3], None, None, {'level': 1}) + row_counts[3] += 1 + task_num += 1 + + for i in range(len(worksheets)): + worksheets[i].autofilter('A1:D' + str(row_counts[i])) + + def generateApplicationHeader(self, applicationName, applicationRank, worksheet, row_num): + appHeaderFormat = self.workbook.add_format() + appHeaderFormat.set_bold() + appHeaderFormat.set_font_color(dark_font) + appHeaderFormat.set_bg_color(medium_bg) + + stepsHeaderFormat = self.workbook.add_format() + stepsHeaderFormat.set_bold() + stepsHeaderFormat.set_font_color(dark_font) + stepsHeaderFormat.set_bg_color(medium_bg) + stepsHeaderFormat.set_align('center') + + worksheet.write(row_num, 0, applicationName, appHeaderFormat) + worksheet.write(row_num, 1, " ", stepsHeaderFormat) + worksheet.write(row_num, 2, " ", appHeaderFormat) + worksheet.write(row_num, 3, " ", appHeaderFormat) + # worksheet.write(row_num, 4, applicationRank, appHeaderFormat) + worksheet.write(row_num, 4, " ", appHeaderFormat) + + def generateHeaders(self): + bronze_worksheet = self.workbook.add_worksheet('Bronze') + silver_worksheet = self.workbook.add_worksheet('Silver') + gold_worksheet = self.workbook.add_worksheet('Gold') + plat_worksheet = self.workbook.add_worksheet('Platinum') + + worksheets = [bronze_worksheet, silver_worksheet, gold_worksheet, plat_worksheet] + + headerFormat = self.workbook.add_format() + headerFormat.set_bold() + headerFormat.set_font_color(light_font) + headerFormat.set_bg_color(dark_bg) + + stepFormat = self.workbook.add_format() + stepFormat.set_bold() + stepFormat.set_font_color(light_font) + stepFormat.set_bg_color(dark_bg) + stepFormat.set_align('center') + + for sheet in range(len(worksheets)): + worksheets[sheet].write('A1', 'Application', headerFormat) + worksheets[sheet].write('B1', 'Steps', stepFormat) + worksheets[sheet].write('C1', 'Activity', headerFormat) + worksheets[sheet].write('D1', 'Task', headerFormat) + worksheets[sheet].write('E1', 'Target', headerFormat) + + # worksheet.write('E1', 'Overall Ranking', headerFormat) + + worksheets[sheet].set_column('A:A', 40) + worksheets[sheet].set_column('B:B', 7) + worksheets[sheet].set_column('C:C', 50) + worksheets[sheet].set_column('D:D', 65) + + # worksheet.set_column('E:E', 15) + + worksheets[sheet].freeze_panes(1, 0) + + return worksheets diff --git a/input/jobs/DefaultJob.json b/input/jobs/DefaultJob.json index 6a58c01..95ad376 100644 --- a/input/jobs/DefaultJob.json +++ b/input/jobs/DefaultJob.json @@ -1,18 +1,18 @@ [ - { - "host": "acme.saas.appdynamics.com", - "port": 443, - "ssl": true, - "account": "acme", - "username": "foo", - "pwd": "hunter1", - "verifySsl": true, - "useProxy": true, - "applicationFilter": { - "apm": ".*", - "mrum": ".*", - "brum": ".*" - }, - "timeRangeMins": 1440 - } + { + "host": "acme.saas.appdynamics.com", + "port": 443, + "ssl": true, + "account": "acme", + "username": "foo", + "pwd": "hunter1", + "verifySsl": true, + "useProxy": true, + "applicationFilter": { + "apm": ".*", + "mrum": ".*", + "brum": ".*" + }, + "timeRangeMins": 1440 + } ] \ No newline at end of file diff --git a/input/thresholds/DefaultThresholds.json b/input/thresholds/DefaultThresholds.json index fdf462a..502bfcb 100644 --- a/input/thresholds/DefaultThresholds.json +++ b/input/thresholds/DefaultThresholds.json @@ -1,5 +1,5 @@ { - "version": "v1.5.9.9", + "version": "v1.6", "apm": { "AppAgentsAPM": { "platinum": {