├── .idea ├── .name ├── scopes │ └── scope_settings.xml ├── vcs.xml ├── ant.xml ├── encodings.xml ├── libraries │ ├── testng.xml │ ├── Servlet_Api.xml │ ├── Test_Api.xml │ ├── Common_Impl.xml │ ├── log4j.xml │ ├── standard_jar.xml │ ├── idea_annotations_jar.xml │ ├── Test_Api_libs.xml │ ├── Agent_Api.xml │ ├── Server_Api.xml │ ├── TeamCity_agent_runtime.xml │ ├── TeamCity_server_runtime.xml │ ├── spring.xml │ ├── Common_Api.xml │ ├── Idea_OpenApi.xml │ └── jmock.xml ├── artifacts │ ├── deploy.xml │ ├── common_jar.xml │ ├── jmeter_plugin.xml │ ├── idea_resolve_helper.xml │ └── jmeter.xml ├── webContexts.xml ├── copyright │ ├── profiles_settings.xml │ └── JetBrains_open_sourced.xml ├── modules.xml ├── compiler.xml ├── runConfigurations │ ├── All_Tests.xml │ └── Tomcat.xml ├── misc.xml ├── projectCodeStyle.xml └── uiDesigner.xml ├── lib ├── jmock │ ├── jmock-2.5.1.jar │ ├── jmock-core-1.2.0.jar │ ├── hamcrest-core-1.1.jar │ ├── jmock-2.5.1-javadoc.zip │ └── hamcrest-library-1.1.jar └── testng │ ├── testng-5.14.1.jar │ └── testng-5.14.1-javadoc.jar ├── .gitignore ├── server ├── src │ ├── perf_statistic │ │ └── server │ │ │ ├── remote_monitoring │ │ │ ├── graph │ │ │ │ ├── BaseSeries.java │ │ │ │ ├── Series.java │ │ │ │ └── Graph.java │ │ │ ├── RemotePerfMonChart.java │ │ │ ├── RemotePerfMonTab.java │ │ │ └── RemotePerfMonLogDataProvider.java │ │ │ ├── ProblemTypeProviderPerformanceTestFailed.java │ │ │ ├── ProblemTypeProviderPerformanceDecline.java │ │ │ ├── FeatureRemotePerfMon.java │ │ │ ├── perf_test_charts │ │ │ ├── types │ │ │ │ ├── ResponseCodeCompositeVT.java │ │ │ │ ├── PerformanceMetricCompositeVT.java │ │ │ │ └── AbstractCompositeVT.java │ │ │ ├── PerformanceChartController.java │ │ │ └── PerformanceStatisticTab.java │ │ │ ├── PerformancePostChartController.java │ │ │ ├── FeaturePerformanceStatistic.java │ │ │ ├── perf_tests │ │ │ ├── PerformanceTestRun.java │ │ │ └── PerformanceBuildMetadataProvider.java │ │ │ └── ref_data │ │ │ └── BuildHistoryRefCheckAdapter.java │ └── META-INF │ │ └── build-server-plugin-jmeter.xml ├── resources │ ├── monitoring │ │ ├── remotePerfMon.styles.css │ │ ├── remotePerfMon.format.js │ │ └── remotePerfMon.jsp │ ├── statistic │ │ ├── perfChartsCustom.jsp │ │ ├── perfChartsTC.jsp │ │ └── perfChartsCustom.js │ ├── editFeatureRemoteMonitoring.jsp │ └── flot │ │ ├── jquery.flot.time.min.js │ │ ├── jquery.flot.crosshair.js │ │ └── jquery.flot.stack.js └── server.iml ├── agent ├── src │ ├── perf_statistic │ │ └── agent │ │ │ ├── metric_aggregation │ │ │ ├── counting │ │ │ │ ├── TestAggregation.java │ │ │ │ ├── TestsReport.java │ │ │ │ ├── TestsGroupAggregation.java │ │ │ │ ├── BaseAggregation.java │ │ │ │ └── Item.java │ │ │ ├── AggregationProperties.java │ │ │ └── FilevaluesChecker.java │ │ │ ├── common │ │ │ ├── BaseFileReader.java │ │ │ └── PerformanceLogger.java │ │ │ └── remote_monitoring │ │ │ ├── RemoteMonitoringProperties.java │ │ │ ├── RemoteMonitoringAgentAdapter.java │ │ │ └── RemoteMonitoring.java │ └── META-INF │ │ └── build-agent-plugin-jmeter.xml └── agent.iml ├── teamcity-plugin.xml ├── common ├── common.iml └── src │ └── perf_statistic │ └── common │ ├── StringUtils.java │ ├── PerformanceMessage.java │ ├── PerformanceStatisticMetrics.java │ ├── PluginConstants.java │ └── PerformanceMessageParser.java ├── idea-resolve-helper.iml └── readme.txt /.idea/.name: -------------------------------------------------------------------------------- 1 | jmeter -------------------------------------------------------------------------------- /lib/jmock/jmock-2.5.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtorgan/jmeter_plugin/HEAD/lib/jmock/jmock-2.5.1.jar -------------------------------------------------------------------------------- /lib/testng/testng-5.14.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtorgan/jmeter_plugin/HEAD/lib/testng/testng-5.14.1.jar -------------------------------------------------------------------------------- /lib/jmock/jmock-core-1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtorgan/jmeter_plugin/HEAD/lib/jmock/jmock-core-1.2.0.jar -------------------------------------------------------------------------------- /lib/jmock/hamcrest-core-1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtorgan/jmeter_plugin/HEAD/lib/jmock/hamcrest-core-1.1.jar -------------------------------------------------------------------------------- /lib/jmock/jmock-2.5.1-javadoc.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtorgan/jmeter_plugin/HEAD/lib/jmock/jmock-2.5.1-javadoc.zip -------------------------------------------------------------------------------- /lib/jmock/hamcrest-library-1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtorgan/jmeter_plugin/HEAD/lib/jmock/hamcrest-library-1.1.jar -------------------------------------------------------------------------------- /lib/testng/testng-5.14.1-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtorgan/jmeter_plugin/HEAD/lib/testng/testng-5.14.1-javadoc.jar -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/ant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/libraries/testng.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/Servlet_Api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/Test_Api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/Common_Impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/standard_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/artifacts/deploy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | C:/ProgramData/JetBrains/TeamCity/plugins 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/libraries/idea_annotations_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/artifacts/common_jar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/out/artifacts/common_jar 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/libraries/Test_Api_libs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/artifacts/jmeter_plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/out/artifacts/jmeter_plugin 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/Agent_Api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Server_Api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/TeamCity_agent_runtime.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/workspace.xml 2 | .idea/codeStyleSettings.xml 3 | .idea/runConfigurations/Tomcat.xml 4 | .idea/modules.xml 5 | 6 | out 7 | tests 8 | 9 | # Compiled source # 10 | ################### 11 | *.class 12 | 13 | # Packages # 14 | *.zip 15 | 16 | # Logs and databases # 17 | *.log 18 | 19 | # Directories # 20 | bin/ 21 | dist/ 22 | temp/ 23 | _tmp/ 24 | .classpath 25 | .project 26 | 27 | -------------------------------------------------------------------------------- /.idea/libraries/TeamCity_server_runtime.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/webContexts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Common_Api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/remote_monitoring/graph/BaseSeries.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.remote_monitoring.graph; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * simplest realization of Series 9 | */ 10 | public class BaseSeries extends Series { 11 | public BaseSeries(@NotNull String label) { 12 | super(label); 13 | } 14 | 15 | @Override 16 | public List> getValues() { 17 | return toListFormat(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.idea/artifacts/idea_resolve_helper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/out/artifacts/idea_resolve_helper 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/metric_aggregation/counting/TestAggregation.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.metric_aggregation.counting; 2 | 3 | import perf_statistic.agent.metric_aggregation.AggregationProperties; 4 | 5 | public class TestAggregation extends BaseAggregation { 6 | 7 | public TestAggregation(Item item, AggregationProperties properties) { 8 | super(item.getTestName(), properties); 9 | addItem(item); 10 | } 11 | 12 | public void addItem(Item item) { 13 | super.addItem(item); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 10 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/libraries/Idea_OpenApi.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /teamcity-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | jmeter 6 | JMeter 7 | @version@ 8 | Performance test analysis(JMeter and other tests) 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /common/common.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /agent/src/META-INF/build-agent-plugin-jmeter.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.idea/libraries/jmock.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/copyright/JetBrains_open_sourced.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/ProblemTypeProviderPerformanceTestFailed.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server; 2 | 3 | import jetbrains.buildServer.BuildProblemData; 4 | import jetbrains.buildServer.serverSide.SBuild; 5 | import jetbrains.buildServer.serverSide.problems.BaseBuildProblemTypeDetailsProvider; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import perf_statistic.common.PluginConstants; 9 | 10 | public class ProblemTypeProviderPerformanceTestFailed extends BaseBuildProblemTypeDetailsProvider { 11 | @Nullable 12 | public String getStatusText(@NotNull final BuildProblemData buildProblem, @NotNull final SBuild build) { 13 | return PluginConstants.ASSERTION_FAILED_PROBLEM_TYPE; 14 | } 15 | 16 | @NotNull 17 | @Override 18 | public String getType() { 19 | return PluginConstants.ASSERTION_FAILED_PROBLEM_TYPE; 20 | } 21 | 22 | @Nullable 23 | @Override 24 | public String getTypeDescription() { 25 | return PluginConstants.ASSERTION_FAILED_PROBLEM_TYPE; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/remote_monitoring/RemotePerfMonChart.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.remote_monitoring; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import perf_statistic.server.remote_monitoring.graph.BaseSeries; 5 | import perf_statistic.server.remote_monitoring.graph.Graph; 6 | import perf_statistic.server.remote_monitoring.graph.Series; 7 | 8 | public class RemotePerfMonChart extends Graph { 9 | public static final RemotePerfMonChart UNKNOWN_GRAPH = new RemotePerfMonChart("unknown", "unknown", "time", "", 0); 10 | 11 | public RemotePerfMonChart(String id, String title, String xMode, String yMode, int orderNumber) { 12 | super(id, title, xMode, yMode, orderNumber); 13 | } 14 | 15 | @Override 16 | public void addValue(long timestamp, long value, @NotNull String label) { 17 | Series series = mySeries.get(label); 18 | if (series == null) { 19 | series = new BaseSeries(label); 20 | } 21 | series.addValue(timestamp, value); 22 | mySeries.put(label, series); 23 | setMax(value); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/ProblemTypeProviderPerformanceDecline.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server; 2 | 3 | import jetbrains.buildServer.BuildProblemData; 4 | import jetbrains.buildServer.serverSide.SBuild; 5 | import jetbrains.buildServer.serverSide.problems.BaseBuildProblemTypeDetailsProvider; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import perf_statistic.common.PluginConstants; 9 | 10 | public class ProblemTypeProviderPerformanceDecline extends BaseBuildProblemTypeDetailsProvider { 11 | 12 | @Nullable 13 | public String getStatusText(@NotNull final BuildProblemData buildProblem, @NotNull final SBuild build) { 14 | return PluginConstants.CRITICAL_PERFORMANCE_PROBLEM_TYPE; 15 | } 16 | 17 | @NotNull 18 | @Override 19 | public String getType() { 20 | return PluginConstants.CRITICAL_PERFORMANCE_PROBLEM_TYPE; 21 | } 22 | 23 | @Nullable 24 | @Override 25 | public String getTypeDescription() { 26 | return PluginConstants.CRITICAL_PERFORMANCE_PROBLEM_TYPE; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/metric_aggregation/counting/TestsReport.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.metric_aggregation.counting; 2 | 3 | import perf_statistic.agent.metric_aggregation.AggregationProperties; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class TestsReport { 9 | private final Map myTestsGroups; 10 | private final AggregationProperties myProperties; 11 | 12 | 13 | public TestsReport(AggregationProperties properties) { 14 | myProperties = properties; 15 | myTestsGroups = new HashMap(); 16 | } 17 | public Map getTestsGroups() { 18 | return myTestsGroups; 19 | } 20 | 21 | public TestsGroupAggregation getTestGroup(String testGroupName) { 22 | return myTestsGroups.get(testGroupName); 23 | } 24 | 25 | public void addItem(Item item) { 26 | TestsGroupAggregation testsGroup = myTestsGroups.get(item.getTestGroupName()); 27 | if (testsGroup == null) { 28 | testsGroup = new TestsGroupAggregation(myProperties); 29 | } 30 | testsGroup.addItem(item); 31 | myTestsGroups.put(item.getTestGroupName(), testsGroup); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.idea/artifacts/jmeter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/out/artifacts/jmeter 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/remote_monitoring/graph/Series.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.remote_monitoring.graph; 2 | 3 | import com.intellij.util.containers.SortedList; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.*; 7 | 8 | /** 9 | * Presents api for performance series 10 | */ 11 | public abstract class Series { 12 | protected final String myLabel; 13 | protected Map myValues; 14 | 15 | public Series(@NotNull String label) { 16 | this.myLabel = label; 17 | this.myValues = new HashMap(); 18 | } 19 | 20 | public String getLabel(){ 21 | return myLabel; 22 | } 23 | 24 | public long getValue(long key) { 25 | Long value = myValues.get(key); 26 | return value != null ? value : 0; 27 | } 28 | 29 | public void addValue(long key, long value) { 30 | myValues.put(key, value); 31 | } 32 | 33 | protected List> toListFormat() { 34 | List> values = new SortedList>(new Comparator>() { 35 | @Override 36 | public int compare(List o1, List o2) { 37 | return o2.get(0).equals(o1.get(0)) ? 0 : o1.get(0) < o2.get(0) ? -1 : 1; 38 | } 39 | }); 40 | for (long time: myValues.keySet()) { 41 | values.add(Arrays.asList(time, myValues.get(time))); 42 | } 43 | return values; 44 | } 45 | 46 | public abstract List> getValues(); 47 | } 48 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/FeatureRemotePerfMon.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server; 2 | 3 | import jetbrains.buildServer.serverSide.BuildFeature; 4 | import jetbrains.buildServer.serverSide.PropertiesProcessor; 5 | import jetbrains.buildServer.web.openapi.PluginDescriptor; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import perf_statistic.common.PluginConstants; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | * Created by Yuliya.Torhan on 1/16/14. 14 | */ 15 | public class FeatureRemotePerfMon extends BuildFeature { 16 | private final String myEditUrl; 17 | 18 | public FeatureRemotePerfMon(@NotNull final PluginDescriptor descriptor) { 19 | myEditUrl = descriptor.getPluginResourcesPath("editFeatureRemoteMonitoring.jsp"); 20 | } 21 | 22 | @NotNull 23 | @Override 24 | public String getType() { 25 | return PluginConstants.FEATURE_TYPE_REMOTE_MONITORING; 26 | } 27 | 28 | @NotNull 29 | @Override 30 | public String getDisplayName() { 31 | return "Performance Remote Monitoring"; 32 | } 33 | 34 | @Nullable 35 | @Override 36 | public String getEditParametersUrl() { 37 | return myEditUrl; 38 | } 39 | 40 | @Nullable 41 | public PropertiesProcessor getParametersProcessor() { 42 | // todo: 43 | return null; 44 | } 45 | @NotNull 46 | public String describeParameters(@NotNull Map params) { 47 | // todo: 48 | return ""; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.idea/runConfigurations/All_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 23 | 32 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/perf_test_charts/types/ResponseCodeCompositeVT.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.perf_test_charts.types; 2 | 3 | import jetbrains.buildServer.serverSide.SBuildServer; 4 | import jetbrains.buildServer.serverSide.statistics.ChartSettings; 5 | import jetbrains.buildServer.serverSide.statistics.ValueProviderRegistry; 6 | import jetbrains.buildServer.serverSide.statistics.build.BuildDataStorage; 7 | import org.jetbrains.annotations.NotNull; 8 | import perf_statistic.common.PluginConstants; 9 | 10 | public final class ResponseCodeCompositeVT extends AbstractCompositeVT { 11 | 12 | public ResponseCodeCompositeVT(BuildDataStorage buildDataStorage, ValueProviderRegistry valueProviderRegistry, SBuildServer server, String key) { 13 | super(buildDataStorage, valueProviderRegistry, server, key); 14 | } 15 | 16 | @Override 17 | public String getSeriesName(String subKey, int idx) { 18 | return idx < subKeys.length ? subKeys[idx] : subKey; 19 | } 20 | 21 | @NotNull 22 | @Override 23 | public String getDescription(ChartSettings chartSettings) { 24 | return "Response Code"; 25 | } 26 | 27 | @Override 28 | public String getValueFormat() { 29 | return "integer"; 30 | } 31 | 32 | @Override 33 | public String getSeriesGenericName() { 34 | return "Code"; 35 | } 36 | 37 | @Override 38 | public String getSubKeysStorageKey() { 39 | return PluginConstants.STORAGE_KEY_CODE; 40 | } 41 | 42 | @Override 43 | public String getSubKey(String subKey) { 44 | return currentBuildTypeID + '_' + getKey() + subKey; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /agent/agent.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | file://$MODULE_DIR$/src/META-INF/build-agent-plugin-jmeter.xml 8 | jar://$TeamCityDistribution$/buildAgent/lib/agent.jar!/META-INF/buildAgentSpring.xml 9 | jar://$TeamCityDistribution$/buildAgent/lib/agent.jar!/META-INF/buildAgentPlugins.xml 10 | file://$MODULE_DIR$/fake-teamcity-agent-plugin-context.xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/metric_aggregation/counting/TestsGroupAggregation.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.metric_aggregation.counting; 2 | 3 | import perf_statistic.agent.metric_aggregation.AggregationProperties; 4 | import perf_statistic.common.PluginConstants; 5 | 6 | import java.util.*; 7 | 8 | public class TestsGroupAggregation extends BaseAggregation { 9 | protected final Map tests; 10 | 11 | public TestsGroupAggregation(AggregationProperties properties) { 12 | super(PluginConstants.AGGREGATION_TOTAL_NAME, properties); 13 | 14 | tests = new HashMap(); 15 | } 16 | 17 | public void addItem(Item item) { 18 | if (myProperties.isCalculateTotal()) { 19 | super.addItem(item); 20 | } 21 | 22 | TestAggregation testAggregation = tests.get(item.getTestName()); 23 | if (testAggregation == null) { 24 | testAggregation = new TestAggregation(item, myProperties); 25 | } 26 | testAggregation.addItem(item); 27 | tests.put(item.getTestName(), testAggregation); 28 | } 29 | 30 | 31 | public Map getTests() { 32 | return tests; 33 | } 34 | 35 | public TestAggregation getTest(String testName) { 36 | return tests.get(testName); 37 | } 38 | 39 | 40 | /* String checkValue(@NotNull String sampler, @NotNull PerformanceStatisticMetrics metric, double referenceValue, double variation) { 41 | BaseAggregation aggregation = tests.get(sampler); 42 | if (aggregation != null) 43 | return aggregation.checkValue(metric, referenceValue, variation); 44 | return checkValue(metric, referenceValue, variation); 45 | }*/ 46 | 47 | } 48 | -------------------------------------------------------------------------------- /common/src/perf_statistic/common/StringUtils.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.common; 2 | 3 | import jetbrains.buildServer.BuildProblemData; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | public class StringUtils { 8 | public static final String EMPTY = ""; 9 | 10 | public static final Pattern non_word_pattern = Pattern.compile("\\W"); 11 | public static final Pattern sharp_pattern = Pattern.compile("#"); 12 | 13 | public static String checkTestName(String testName) { 14 | if (testName.contains("#")) { 15 | return sharp_pattern.split(testName)[1].trim(); 16 | } 17 | return testName; 18 | } 19 | 20 | public static String replaceNonWordSymbols(String str) { 21 | return non_word_pattern.matcher(str).replaceAll(""); 22 | } 23 | 24 | public static String getBuildProblemId(final String metricKey, final String testName) { 25 | return cutLongBuildProblemIdentity(non_word_pattern.matcher(metricKey + testName).replaceAll("")); 26 | } 27 | 28 | public static String getTestNameIdFromIdentity(final String buildProblemIdentity) { 29 | return buildProblemIdentity.split("_")[0]; 30 | } 31 | 32 | public static boolean isPerformanceBuildProblemReferToTest(final String buildProblemIdentity, final String testName) { 33 | String formattedName = cutLongBuildProblemIdentity(non_word_pattern.matcher(testName).replaceAll("")); 34 | return buildProblemIdentity.startsWith(formattedName); 35 | } 36 | 37 | public static String cutLongBuildProblemIdentity(final String identity) { 38 | return identity.length() > BuildProblemData.MAX_IDENTITY_LENGTH ? identity.substring(0, BuildProblemData.MAX_IDENTITY_LENGTH - 1) : identity; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/common/BaseFileReader.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.common; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileNotFoundException; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | 8 | public abstract class BaseFileReader { 9 | private static final String FILE_NOT_FOUND = "file_not_found"; 10 | private static final String FILE_CAN_NOT_READ = "file_cant_read"; 11 | private static final String FILE_CAN_NOT_CLOSE = "file_cant_close"; 12 | 13 | protected final PerformanceLogger myLogger; 14 | 15 | protected BaseFileReader(PerformanceLogger logger) { 16 | myLogger = logger; 17 | } 18 | protected abstract void processLine(String line) throws FileFormatException; 19 | 20 | public abstract void logProcessingResults(); 21 | 22 | public void processFile(String file) throws FileFormatException { 23 | BufferedReader reader = null; 24 | try { 25 | reader = new BufferedReader(new FileReader(file)); 26 | 27 | String line; 28 | while (reader.ready() && !(line = reader.readLine()).isEmpty()) { 29 | processLine(line); 30 | } 31 | } catch (FileNotFoundException e) { 32 | myLogger.logBuildProblem(FILE_NOT_FOUND, FILE_NOT_FOUND, "Not found log file! Path - " + file); 33 | } catch (IOException e) { 34 | myLogger.logBuildProblem(FILE_CAN_NOT_READ, FILE_CAN_NOT_READ, "Can not read log file! Path - " + file); 35 | } finally { 36 | if (reader != null) { 37 | try { 38 | reader.close(); 39 | } catch (IOException e) { 40 | myLogger.logBuildProblem(FILE_CAN_NOT_CLOSE, FILE_CAN_NOT_CLOSE, "Can not close log file! Path - " + file); 41 | } 42 | } 43 | } 44 | } 45 | 46 | public static class FileFormatException extends Exception { 47 | public FileFormatException(String msg) { 48 | super(msg); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 26 | 27 | 30 | 31 | http://www.w3.org/1999/xhtml 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/remote_monitoring/RemoteMonitoringProperties.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.remote_monitoring; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | import perf_statistic.common.PluginConstants; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by Yuliya.Torhan on 1/17/14. 11 | */ 12 | public class RemoteMonitoringProperties { 13 | private final String remotePerfMonBuildStep; 14 | private final String remotePerfMonHost; 15 | private final int remotePerfMonPort; 16 | private final String remoteInterval; 17 | private final long remoteSystemClockDelay; 18 | 19 | public RemoteMonitoringProperties(@NotNull Map params) { 20 | remotePerfMonBuildStep = params.get(PluginConstants.PARAMS_BUILD_STEP_TO_ANALYZE); 21 | remotePerfMonHost = params.get(PluginConstants.PARAMS_REMOTE_PERF_MON_HOST); 22 | remotePerfMonPort = params.get(PluginConstants.PARAMS_REMOTE_PERF_MON_PORT) == null ? 4444 : Integer.parseInt(params.get(PluginConstants.PARAMS_REMOTE_PERF_MON_PORT)); 23 | remoteInterval = params.get(PluginConstants.PARAMS_REMOTE_INTERVAL); 24 | remoteSystemClockDelay = params.get(PluginConstants.PARAMS_REMOTE_CLOCK_DELAY) == null ? 0 : Long.parseLong(params.get(PluginConstants.PARAMS_REMOTE_CLOCK_DELAY)); 25 | } 26 | 27 | @NotNull 28 | public String getBuildStepToMonitor() { 29 | return remotePerfMonBuildStep; 30 | } 31 | 32 | @NotNull 33 | public String getRemoteMonitoringHost() { 34 | return remotePerfMonHost == null ? "localhost" : remotePerfMonHost; 35 | } 36 | 37 | public int getRemoteMonitoringPort() { 38 | return remotePerfMonPort; 39 | } 40 | 41 | @Nullable 42 | public String getRemoteInterval() { 43 | return remoteInterval; 44 | } 45 | 46 | public long getRemoteClockDelay() { 47 | return remoteSystemClockDelay; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.idea/projectCodeStyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/perf_test_charts/PerformanceChartController.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.perf_test_charts; 2 | 3 | import jetbrains.buildServer.controllers.BaseController; 4 | import jetbrains.buildServer.serverSide.SBuild; 5 | import jetbrains.buildServer.serverSide.SBuildServer; 6 | import jetbrains.buildServer.web.openapi.WebControllerManager; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | import org.springframework.web.servlet.ModelAndView; 10 | import perf_statistic.server.perf_tests.PerformanceTestProvider; 11 | import perf_statistic.server.perf_tests.PerformanceTestRun; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.util.Map; 16 | 17 | public class PerformanceChartController extends BaseController { 18 | private final PerformanceTestProvider myTestHolder; 19 | 20 | public PerformanceChartController(@NotNull WebControllerManager webControllerManager, @NotNull SBuildServer server, 21 | @NotNull final PerformanceTestProvider testHolder) { 22 | super(server); 23 | webControllerManager.registerController("/performanceCharts.html", this); 24 | myTestHolder = testHolder; 25 | } 26 | 27 | @Nullable 28 | @Override 29 | protected ModelAndView doHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws Exception { 30 | String buildId = request.getParameter("buildId"); 31 | String testName = request.getParameter("testName"); 32 | 33 | SBuild build = myServer.findBuildInstanceById(Long.parseLong(buildId)); 34 | 35 | final ModelAndView mv = new ModelAndView(request.getParameter("perfjsp")); 36 | 37 | Map myModel = mv.getModel(); 38 | 39 | if (build != null) { 40 | PerformanceTestRun test = myTestHolder.findTestByName(build, testName); 41 | myModel.put("logTitles", myTestHolder.fillLogItems(build, test)); 42 | myModel.put("test", test); 43 | } 44 | return mv; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/src/perf_statistic/common/PerformanceMessage.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.common; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | public class PerformanceMessage { 7 | public static final String EMPTY = ""; 8 | 9 | private String metric; 10 | private String value; 11 | private String testName; 12 | private String codeLabel; 13 | private boolean warning; 14 | 15 | private String currValue; 16 | private String variation; 17 | 18 | private String testsGroupName; 19 | 20 | @NotNull 21 | public String getMetric() { 22 | return metric; 23 | } 24 | 25 | @NotNull 26 | public String getTestName() { 27 | return testName == null ? EMPTY : testName; 28 | } 29 | 30 | @NotNull 31 | public String getValue() { 32 | return value; 33 | } 34 | 35 | @NotNull 36 | public String getTestsGroupName() { 37 | return testsGroupName == null ? EMPTY : testsGroupName; 38 | } 39 | @Nullable 40 | public String getCode() { 41 | return codeLabel == null ? EMPTY : codeLabel; 42 | } 43 | 44 | public boolean isWarning() { 45 | return warning; 46 | } 47 | 48 | // PerformanceMessageParser only can set values 49 | void setMetric(@NotNull final String metric) { 50 | this.metric = metric; 51 | } 52 | void setTestName(@NotNull final String testName) { 53 | this.testName = testName; 54 | } 55 | void setValue(@NotNull final String value) { 56 | this.value = value; 57 | } 58 | void setTestsGroupName(String testsGroupName) { 59 | this.testsGroupName = testsGroupName; 60 | } 61 | public void setCodeLabel(String label) { 62 | this.codeLabel = label; 63 | } 64 | public void setWarning(boolean warning) { 65 | this.warning = warning; 66 | } 67 | 68 | public String getCurrValue() { 69 | return currValue; 70 | } 71 | 72 | public void setCurrValue(String currValue) { 73 | this.currValue = currValue; 74 | } 75 | 76 | public String getVariation() { 77 | return variation; 78 | } 79 | 80 | public void setVariation(String variation) { 81 | this.variation = variation; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/remote_monitoring/graph/Graph.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.remote_monitoring.graph; 2 | 3 | import java.util.Collection; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * Presents api for performance metrics (perfmon graphs) 9 | */ 10 | public abstract class Graph implements Comparable { 11 | protected final String myId; 12 | protected final String myTitle; 13 | protected final String myYAxisMode; 14 | protected final String myXAxisMode; 15 | 16 | protected final int myOrderNumber; 17 | 18 | protected String state; 19 | 20 | protected Map mySeries; 21 | protected long max = Long.MAX_VALUE; 22 | 23 | public Graph(String id, String title, String xMode, String yMode, int orderNumber) { 24 | this.myId = id; 25 | this.myTitle = title; 26 | this.myYAxisMode = yMode; 27 | this.myXAxisMode = xMode; 28 | this.myOrderNumber = orderNumber; 29 | this.mySeries = new HashMap(); 30 | } 31 | 32 | public String getId() { 33 | return myId; 34 | } 35 | 36 | public String getTitle() { 37 | return myTitle; 38 | } 39 | 40 | public String getYAxisMode() { 41 | return myYAxisMode; 42 | } 43 | 44 | public String getXAxisMode() { 45 | return myXAxisMode; 46 | } 47 | 48 | public int getOrderNumber() { 49 | return myOrderNumber; 50 | } 51 | 52 | public void setState(String state) { 53 | this.state = state; 54 | } 55 | public String getState() { 56 | return state; 57 | } 58 | public abstract void addValue(long timestamp, long value, String label); 59 | 60 | public Collection getKeys() { 61 | return mySeries.keySet(); 62 | } 63 | 64 | public Collection getSeries() { 65 | return mySeries.values(); 66 | } 67 | 68 | protected void setMax(long value) { 69 | if (max == Integer.MAX_VALUE || value > max) 70 | max = value; 71 | } 72 | 73 | public double getMax() { 74 | return myYAxisMode.equals("%") ? 100 : max; 75 | } 76 | 77 | public int compareTo(Graph o) { 78 | return o != null ? myOrderNumber - o.getOrderNumber() : -1; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /server/resources/monitoring/remotePerfMon.styles.css: -------------------------------------------------------------------------------- 1 | #jmeterPerfmon { 2 | width: 1300px; 3 | } 4 | 5 | #jmeterPerfmon td { 6 | vertical-align: top; 7 | padding: 0; 8 | } 9 | 10 | #jmeterPerfmon .rightBar { 11 | padding: 2px 0 0 20px; 12 | } 13 | 14 | .chart { 15 | float: left; 16 | margin: 0 5px; 17 | height: 200px !important; 18 | width: 950px; 19 | } 20 | 21 | .chart_title { 22 | width: 100%; 23 | text-align: center; 24 | padding-left: 5px; 25 | 26 | } 27 | .legend { 28 | float: left; 29 | padding: 5px !important; 30 | background: #f5f5f5; 31 | width: 320px; 32 | overflow-y: scroll; 33 | max-height: 200px; 34 | } 35 | 36 | .legendHint { 37 | color: #888888; 38 | padding: 5px !important; 39 | } 40 | 41 | .legendLabel { 42 | width: 200px; 43 | white-space: nowrap; 44 | overflow: hidden; 45 | text-overflow: ellipsis; 46 | display: inline-block; 47 | vertical-align: bottom; 48 | } 49 | 50 | #jmeterPerfmonLog { 51 | } 52 | 53 | #jmeterLogMarker { 54 | margin-bottom: 3px; 55 | } 56 | 57 | #jmeterLogContainerOuter { 58 | max-height: 1000px; 59 | max-width: 600; 60 | height: auto; 61 | width: auto; 62 | overflow: hidden; 63 | } 64 | 65 | #jmeterLogContainer { 66 | max-height: inherit; 67 | overflow: auto; 68 | } 69 | 70 | #jmeterTimePeriod { 71 | } 72 | 73 | #jmeterLogContainer table td { 74 | padding: 0 5px; 75 | border-bottom: 1px solid #f5f5f5; 76 | } 77 | 78 | #jmeterLogTitle th { 79 | padding: 0 5px; 80 | background-color: #f5f5f5; 81 | } 82 | 83 | #filters { 84 | padding: 20px 0; 85 | vertical-align: top; 86 | color: #888888; 87 | } 88 | 89 | #filterTitle { 90 | padding-top: 25px; 91 | } 92 | 93 | .expandAll { 94 | float: right; 95 | display: block; 96 | cursor: pointer; 97 | } 98 | 99 | .hideWarmUp { 100 | cursor: pointer; 101 | } 102 | 103 | .collapse { 104 | margin-left: 10px; 105 | cursor: pointer; 106 | /*background-image: url("../img/blockExpanded.png");*/ 107 | /*background-repeat: no-repeat;*/ 108 | /*color: #FFFFFF;*/ 109 | /*position: relative;*/ 110 | /*display: inline-block;*/ 111 | } -------------------------------------------------------------------------------- /server/resources/monitoring/remotePerfMon.format.js: -------------------------------------------------------------------------------- 1 | if (!window.$j) { 2 | window.$j = window.jQuery; 3 | } 4 | 5 | Format = { 6 | // X-axis formatting 7 | formatTime: function (value, includeMs) { 8 | var date = new Date(value); 9 | var time = date.getHours() + ":" + this.toString2(date.getMinutes()) + ":" + this.toString2(date.getSeconds()); 10 | if (includeMs) { 11 | time = time + ":" + date.getMilliseconds(); 12 | } 13 | return time; 14 | }, 15 | 16 | toString2: function (num) { 17 | if (num < 10) { 18 | return "0" + num; 19 | } 20 | return num; 21 | }, 22 | 23 | // Y-axis and legend crosshair formatting 24 | bytes: "byte", 25 | percent: "%", 26 | ms: "ms", 27 | number: "", 28 | 29 | getFormatByMax: function(max, format) { 30 | if (format == this.bytes) { 31 | return this._getBytesFormat(max); 32 | } 33 | return this._getNumberFormat(max) + format; 34 | }, 35 | 36 | _getBytesFormat: function(max) { 37 | if (max >= 1073741824) 38 | return 'Gb'; 39 | if (max >= 1048576) 40 | return 'Mb'; 41 | if (max >= 1024) 42 | return 'Kb'; 43 | return 'bytes'; 44 | }, 45 | 46 | _getNumberFormat: function(max) { 47 | // if (max > 1000000) 48 | // return 'M'; 49 | // if (max > 10000) 50 | // return 'K'; 51 | return ''; 52 | }, 53 | 54 | format: function(value, format, isBytes, precision) { 55 | if (isBytes) { 56 | return this._formatBytes(value, format); 57 | } 58 | return this._formatNumber(value, format, precision); 59 | }, 60 | 61 | _formatBytes: function(bytes, format) { 62 | if (format == 'Gb') 63 | bytes = (bytes / 1073741824); 64 | if (format == 'Mb') 65 | bytes = (bytes / 1048576); 66 | if (format == 'Kb') 67 | bytes = (bytes / 1024); 68 | return bytes.toFixed(2) + ' ' + format; 69 | }, 70 | 71 | _formatNumber: function(number, format, precision) { 72 | // if (getMetricType.indexOf('M') == 0) { 73 | // number = (number / 1000000).toFixed(0); 74 | // } else if (getMetricType.indexOf('K') == 0) { 75 | // number = (number / 1000).toFixed(0); 76 | // } else { 77 | number = number.toFixed(precision); 78 | return number + ' ' + format; 79 | } 80 | } -------------------------------------------------------------------------------- /server/src/perf_statistic/server/perf_test_charts/types/PerformanceMetricCompositeVT.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.perf_test_charts.types; 2 | 3 | import jetbrains.buildServer.serverSide.SBuildServer; 4 | import jetbrains.buildServer.serverSide.SBuildType; 5 | import jetbrains.buildServer.serverSide.statistics.ChartSettings; 6 | import jetbrains.buildServer.serverSide.statistics.ValueProviderRegistry; 7 | import jetbrains.buildServer.serverSide.statistics.build.BuildDataStorage; 8 | import org.jetbrains.annotations.NotNull; 9 | import perf_statistic.common.PerformanceStatisticMetrics; 10 | import perf_statistic.common.PluginConstants; 11 | 12 | public final class PerformanceMetricCompositeVT extends AbstractCompositeVT { 13 | 14 | public PerformanceMetricCompositeVT(BuildDataStorage buildDataStorage, ValueProviderRegistry valueProviderRegistry, SBuildServer server, String key) { 15 | super(buildDataStorage, valueProviderRegistry, server, key); 16 | } 17 | 18 | @NotNull 19 | @Override 20 | public String getDescription(final ChartSettings chartSettings) { 21 | SBuildType buildType = myServer.getProjectManager().findBuildTypeByExternalId(currentBuildTypeID); 22 | String title = null; 23 | if (buildType != null && getKey() != null) { 24 | title = buildType.getCustomDataStorage(PluginConstants.STORAGE_ID_TEST_ALIAS).getValue(getKey()); 25 | } 26 | return title != null ? title : getKey(); 27 | } 28 | 29 | @Override 30 | public String getValueFormat() { 31 | return "duration"; 32 | } 33 | 34 | @Override 35 | public String getSeriesGenericName() { 36 | return "Metric"; 37 | } 38 | 39 | @Override 40 | public String getSubKeysStorageKey() { 41 | return PluginConstants.STORAGE_KEY_METRIC; 42 | } 43 | 44 | @Override 45 | public String getSubKey(String subKey) { 46 | return currentBuildTypeID + '_' + subKey + '_' + getKey(); 47 | } 48 | 49 | @Override 50 | public String getSeriesName(String subKey, int idx) { 51 | return PerformanceStatisticMetrics.getTitleByKey(subKeys[idx]); 52 | } 53 | 54 | public String getSeriesColor(String s) { 55 | if (s != null) { 56 | for(String key : subKeys) { 57 | if (s.contains(key)) { 58 | PerformanceStatisticMetrics metric = PerformanceStatisticMetrics.getMetricByKey(key); 59 | return metric == null ? null : metric.getColor(s.contains("Reference")); 60 | } 61 | } 62 | } 63 | return null; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/remote_monitoring/RemoteMonitoringAgentAdapter.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.remote_monitoring; 2 | 3 | import jetbrains.buildServer.agent.*; 4 | import jetbrains.buildServer.util.EventDispatcher; 5 | import org.jetbrains.annotations.NotNull; 6 | import perf_statistic.common.PluginConstants; 7 | 8 | import java.io.File; 9 | import java.util.Collection; 10 | 11 | 12 | public class RemoteMonitoringAgentAdapter extends AgentLifeCycleAdapter { 13 | private volatile boolean featureSwitchON = false; 14 | 15 | private RemoteMonitoringProperties myProperties; 16 | 17 | private RemoteMonitoring myRemoteMonitoring; 18 | 19 | public RemoteMonitoringAgentAdapter(@NotNull final EventDispatcher agentDispatcher) { 20 | agentDispatcher.addListener(this); 21 | } 22 | 23 | /** 24 | * Set features properties for running build 25 | * @param runningBuild 26 | */ 27 | public void buildStarted(@NotNull AgentRunningBuild runningBuild) { 28 | final Collection features = runningBuild.getBuildFeaturesOfType(PluginConstants.FEATURE_TYPE_REMOTE_MONITORING); 29 | featureSwitchON = !features.isEmpty(); 30 | if (!features.isEmpty()) { 31 | myProperties = new RemoteMonitoringProperties(features.iterator().next().getParameters()); 32 | } 33 | } 34 | 35 | 36 | /** 37 | * start monitoring for test build step 38 | * @param runner 39 | */ 40 | public void beforeRunnerStart(@NotNull final BuildRunnerContext runner) { 41 | final Collection features = runner.getBuild().getBuildFeaturesOfType(PluginConstants.FEATURE_TYPE_REMOTE_MONITORING); 42 | if (!features.isEmpty() && checkBuildRunnerForMonitoring(runner.getName())) { 43 | final String resultFile = runner.getWorkingDirectory().getAbsolutePath() + File.separator + PluginConstants.MONITORING_RESULT_FILE; 44 | myRemoteMonitoring = new RemoteMonitoring(myProperties, resultFile); 45 | myRemoteMonitoring.start(); 46 | } 47 | } 48 | 49 | /** 50 | * stop monitoring for test build step 51 | * @param runner 52 | */ 53 | public void runnerFinished(@NotNull final BuildRunnerContext runner, @NotNull final BuildFinishedStatus status) { 54 | final Collection features = runner.getBuild().getBuildFeaturesOfType(PluginConstants.FEATURE_TYPE_REMOTE_MONITORING); 55 | if (!features.isEmpty() && checkBuildRunnerForMonitoring(runner.getName()) && myRemoteMonitoring != null) { 56 | myRemoteMonitoring.stop(); 57 | } 58 | } 59 | 60 | private boolean checkBuildRunnerForMonitoring(@NotNull final String runnerName) { 61 | return myProperties != null && runnerName.startsWith(myProperties.getBuildStepToMonitor()); 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /server/resources/statistic/perfChartsCustom.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="stats" tagdir="/WEB-INF/tags/graph" %> 2 | <%@ taglib prefix="bs" tagdir="/WEB-INF/tags" %> 3 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 4 | <%@ taglib prefix="forms" tagdir="/WEB-INF/tags/forms" %> 5 | 6 | 7 | <%--@elvariable id="test" type="perf_statistic.server.perf_tests.PerformanceTestRun"--%> 8 | <%--@elvariable id="logTitles" type="java.util.Collection"--%> 9 | 10 | 11 | 12 |
13 | 23 | 24 |
25 | Server Response Time 26 |
27 | 28 | 47 |
48 | 49 |
50 | Requests per seconds 51 |
52 | 53 | 58 |
59 |
60 | 61 | -------------------------------------------------------------------------------- /idea-resolve-helper.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | file://$TeamCityDistribution$/webapps/ROOT/WEB-INF/build-server-plugin-agents-statistics-web.xml 8 | file://$TeamCityDistribution$/webapps/ROOT/WEB-INF/build-server-plugin-buildFailures.xml 9 | file://$TeamCityDistribution$/webapps/ROOT/WEB-INF/build-server-plugin-dotnet-pdb.xml 10 | file://$TeamCityDistribution$/webapps/ROOT/WEB-INF/build-server-plugin-freespace.xml 11 | file://$TeamCityDistribution$/webapps/ROOT/WEB-INF/buildServerConfigurator.xml 12 | file://$TeamCityDistribution$/webapps/ROOT/WEB-INF/buildServerPluginsSupportWeb.xml 13 | file://$TeamCityDistribution$/webapps/ROOT/WEB-INF/buildServerSpringStatistics.xml 14 | file://$TeamCityDistribution$/webapps/ROOT/WEB-INF/buildServerSpringWeb.xml 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /common/src/perf_statistic/common/PerformanceStatisticMetrics.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.common; 2 | 3 | import jetbrains.buildServer.util.StringUtil; 4 | 5 | public enum PerformanceStatisticMetrics { 6 | AVERAGE("Average", "Average time", "#162EAE", "#0095d9"), 7 | MAX("Max", "Max time", "#95002B", "#e55751"), 8 | MIN("Min", "Min time", "#6A0AAB", "#bb79f4"), 9 | LINE90("90Line", "90% line", "#BFBC30", "#4dbf6a"), 10 | 11 | RESPONSE_CODE("ResponseCode", "Response codes", null, null); 12 | 13 | 14 | private final String title; 15 | private final String key; 16 | private final String color; 17 | private final String referenceColor; 18 | 19 | private volatile boolean selected = true; 20 | 21 | PerformanceStatisticMetrics(String key, String title, String color, String referenceColor) { 22 | this.title = title; 23 | this.key = key; 24 | this.color = color; 25 | this.referenceColor = referenceColor; 26 | } 27 | 28 | public void setIsSelected(boolean selected) { 29 | this.selected = selected; 30 | } 31 | 32 | public boolean isSelected() { 33 | return selected; 34 | } 35 | 36 | public String getKey() { 37 | return key; 38 | } 39 | public String getTitle() { 40 | return title; 41 | } 42 | 43 | public String getColor(boolean isReference) { 44 | return isReference ? referenceColor : color; 45 | } 46 | 47 | public String getReferenceTitle() { 48 | return "Reference data: " + title; 49 | } 50 | public String getReferenceKey() { 51 | return getKey() + "_reference"; 52 | } 53 | public static PerformanceStatisticMetrics getMetricByReferenceKey(String key) { 54 | key = key.substring(0, key.length() - 10); 55 | return getMetricByKey(key); 56 | } 57 | 58 | public static PerformanceStatisticMetrics getMetricByKey(String key) { 59 | for(PerformanceStatisticMetrics metric : PerformanceStatisticMetrics.values()) { 60 | if (metric.key.equals(key)) { 61 | return metric; 62 | } 63 | } 64 | return null; 65 | } 66 | public static String getTitleByKey(String key) { 67 | boolean isReferenceMetric = key.indexOf("_reference") != -1; 68 | String searchKey = isReferenceMetric ? key.split("_")[0] : key; 69 | PerformanceStatisticMetrics metric = getMetricByKey(searchKey); 70 | if (metric != null) { 71 | return isReferenceMetric ? metric.getReferenceTitle() : metric.getTitle(); 72 | } 73 | return StringUtil.EMPTY; 74 | } 75 | 76 | public static String getNonReferenceTitleByKey(String key) { 77 | boolean isReferenceMetric = key.indexOf("_reference") != -1; 78 | String searchKey = isReferenceMetric ? key.split("_")[0] : key; 79 | PerformanceStatisticMetrics metric = getMetricByKey(searchKey); 80 | if (metric != null) { 81 | return metric.getTitle(); 82 | } 83 | return StringUtil.EMPTY; 84 | } 85 | 86 | public static boolean isReferenceKey(String key) { 87 | return key.indexOf("_reference") != -1; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/PerformancePostChartController.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server; 2 | 3 | import jetbrains.buildServer.controllers.BaseController; 4 | import jetbrains.buildServer.serverSide.CustomDataStorage; 5 | import jetbrains.buildServer.serverSide.SBuildServer; 6 | import jetbrains.buildServer.serverSide.SBuildType; 7 | import jetbrains.buildServer.util.StringUtil; 8 | import jetbrains.buildServer.web.openapi.WebControllerManager; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | import org.springframework.web.servlet.ModelAndView; 12 | import perf_statistic.common.PerformanceStatisticMetrics; 13 | import perf_statistic.common.PluginConstants; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.util.Map; 18 | 19 | public class PerformancePostChartController extends BaseController { 20 | 21 | enum RequestType { 22 | CHANGE_STATE, 23 | SAVE_DESELECTED 24 | } 25 | 26 | public PerformancePostChartController(@NotNull final SBuildServer server, @NotNull final WebControllerManager manager) { 27 | super(server); 28 | manager.registerController("/app/performance_test_analyzer/**", this); 29 | } 30 | 31 | @Nullable 32 | @Override 33 | protected ModelAndView doHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws Exception { 34 | String buildTypeId = request.getParameter("buildTypeId"); 35 | SBuildType buildType = myServer.getProjectManager().findBuildTypeByExternalId(buildTypeId); 36 | if (buildType != null) { 37 | String requestType = request.getParameter("reqtype"); 38 | if (requestType != null && requestType.equals(RequestType.SAVE_DESELECTED.name().toLowerCase())) { 39 | updateDefaultDeselectedMetrics(buildType, request.getParameter("deselected").split(",")); 40 | } else { 41 | String graphID = request.getParameter("graphId"); 42 | String state = request.getParameter("state"); 43 | CustomDataStorage stateStorage = buildType.getCustomDataStorage("teamcity.jmeter.graph.states"); 44 | if (!StringUtil.isEmpty(graphID)) { 45 | stateStorage.putValue(graphID, state); 46 | } 47 | else { 48 | Map states = stateStorage.getValues(); 49 | if (states != null) { 50 | for (String key : states.keySet()) { 51 | stateStorage.putValue(key, state); 52 | } 53 | } 54 | } 55 | stateStorage.flush(); 56 | } 57 | } 58 | return null; 59 | } 60 | 61 | private void updateDefaultDeselectedMetrics(SBuildType buildType, String[] deselectedSeries) { 62 | CustomDataStorage stateStorage = buildType.getCustomDataStorage(PluginConstants.STORAGE_ID_DEFAULT_DESELECTED_SERIES); 63 | for (PerformanceStatisticMetrics metric: PerformanceStatisticMetrics.values()) { 64 | stateStorage.putValue(metric.getKey(), "false"); 65 | } 66 | if (deselectedSeries.length > 0 && !deselectedSeries[0].isEmpty()) { 67 | for (String series: deselectedSeries) { 68 | stateStorage.putValue(series, "true"); 69 | } 70 | } 71 | stateStorage.flush(); 72 | } 73 | } -------------------------------------------------------------------------------- /server/resources/statistic/perfChartsTC.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> 2 | <%@ taglib prefix="stats" tagdir="/WEB-INF/tags/graph" %> 3 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 4 | 5 | 6 | 7 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 62 | 63 |
64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /server/resources/editFeatureRemoteMonitoring.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="props" tagdir="/WEB-INF/tags/props" %> 2 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 3 | <%@ taglib prefix="bs" tagdir="/WEB-INF/tags" %> 4 | <%@ taglib prefix="forms" tagdir="/WEB-INF/tags/forms" %> 5 | <%@ taglib prefix="l" tagdir="/WEB-INF/tags/layout" %> 6 | 7 | 8 | 9 | <%--Remote performance monitoring--%> 10 | 11 | 12 | 13 | Enable to run tests from another machine (non-Build Agent). 14 | 15 | 16 | 17 | Build step to analyze: 18 | 19 | 20 | The name of the build step with performance tests 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 41 | 42 | 43 | 44 | 48 | 49 |
31 | 32 | 33 |
38 | 39 | 40 |
45 | 46 | in seconds. Set to sync the time of monitoring between the build agent and the test machine 47 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | in seconds 58 | 59 | 60 | 61 | 62 | 63 | 64 | Collected metrics: (system) cpu system/user/all; memory used; disk i/o reads/writes;
65 | (jmx) memory heap, pools commited/usage; gc time; class loaded; 66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /server/src/META-INF/build-server-plugin-jmeter.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /server/server.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | file://$MODULE_DIR$/fake-teamcity-server-plugin-context.xml 8 | jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/server.jar!/META-INF/build-server-declarative-extensibility.xml 9 | jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/server.jar!/META-INF/build-server-plugin-agents-statistics.xml 10 | jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/server.jar!/META-INF/buildServerCompatMode.xml 11 | jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/server.jar!/META-INF/buildServerPermissions.xml 12 | jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/server.jar!/META-INF/buildServerPluginsSupport.xml 13 | jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/server.jar!/META-INF/buildServerSpring.xml 14 | jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/common-impl.jar!/META-INF/shared-spring-sub-container.xml 15 | jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/common-impl.jar!/META-INF/plugin-model-shared-spring.xml 16 | file://$TeamCityDistribution$/webapps/ROOT/WEB-INF/buildServerSpringWeb.xml 17 | file://$MODULE_DIR$/src/META-INF/build-server-plugin-jmeter.xml 18 | jar://$TeamCityDistribution$/buildAgent/lib/common-impl.jar!/META-INF/per-plugin-shared-spring.xml 19 | jar://$TeamCityDistribution$/buildAgent/lib/common-impl.jar!/META-INF/plugin-model-shared-spring.xml 20 | jar://$TeamCityDistribution$/buildAgent/lib/common-impl.jar!/META-INF/shared-spring-sub-container.xml 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/metric_aggregation/counting/BaseAggregation.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.metric_aggregation.counting; 2 | 3 | import com.intellij.util.containers.SortedList; 4 | import org.jetbrains.annotations.NotNull; 5 | import perf_statistic.agent.metric_aggregation.AggregationProperties; 6 | import perf_statistic.common.PerformanceStatisticMetrics; 7 | 8 | import java.util.Comparator; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * Base class to count aggregate values 15 | */ 16 | public abstract class BaseAggregation { 17 | protected boolean isAggregationCalculated = false; 18 | 19 | protected final String myTitle; 20 | 21 | protected final Map codes; 22 | protected final AggregationProperties myProperties; 23 | 24 | protected double sum; 25 | protected int count; 26 | protected double mean; 27 | protected double min = Double.MAX_VALUE; 28 | protected double max = Double.MIN_VALUE; 29 | protected List responseTimesToAggregate; //to calculate quantiles 30 | 31 | protected BaseAggregation(String title, AggregationProperties properties) { 32 | myTitle = title; 33 | myProperties = properties; 34 | codes = new HashMap(); 35 | responseTimesToAggregate = new SortedList(new Comparator() { 36 | @Override 37 | public int compare(Double o1, Double o2) { 38 | return o1.compareTo(o2); 39 | } 40 | }); 41 | } 42 | 43 | public String getTitle() { 44 | return myTitle; 45 | } 46 | 47 | public void addItem(Item item) { 48 | if (!myProperties.isCheckAssertions() || item.isSuccessful()) { 49 | addCalculatedValue(item.getResponseTime()); 50 | } 51 | if (myProperties.isCalculateResponseCodes()) { 52 | Long count = codes.get(item.getResponseCode()); 53 | codes.put(item.getResponseCode(), count != null ? ++count : 1); 54 | } 55 | } 56 | 57 | private void addCalculatedValue(double itemValue) { 58 | isAggregationCalculated = true; 59 | count++; 60 | sum += itemValue; 61 | mean = sum / count; 62 | min = Math.min(itemValue, min); 63 | max = Math.max(itemValue, max); 64 | responseTimesToAggregate.add(itemValue); 65 | } 66 | 67 | public Double getAggregateValue(@NotNull final PerformanceStatisticMetrics param) 68 | { 69 | switch (param) { 70 | case AVERAGE: 71 | return mean; 72 | case MAX: 73 | return max; 74 | case MIN: 75 | return min; 76 | case LINE90: { 77 | int ind90 = (int) Math.round(responseTimesToAggregate.size() * 0.9d); 78 | return responseTimesToAggregate.get(ind90 - 1); 79 | } 80 | default: 81 | return null; 82 | } 83 | } 84 | 85 | public Map getCodes() { 86 | return codes; 87 | } 88 | 89 | public boolean isAggregationCalculated() { 90 | return isAggregationCalculated; 91 | } 92 | /* public String checkValue(@NotNull final PerformanceStatisticMetrics metric, double referenceValue, double variation) { 93 | Double currentValue = getAggregateValue(metric); 94 | if (currentValue > referenceValue * (1 + variation)) { 95 | return "Metric - " + metric.getTitle() + "; testName - " + myTitle 96 | + "; \nreference value: " + Math.round(referenceValue) 97 | + "; current value: " + Math.round(currentValue) 98 | + "; variation: " + variation; 99 | } 100 | return null; 101 | }*/ 102 | } 103 | -------------------------------------------------------------------------------- /server/resources/monitoring/remotePerfMon.jsp: -------------------------------------------------------------------------------- 1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 2 | <%@ taglib prefix="bs" tagdir="/WEB-INF/tags" %> 3 | <%@ taglib prefix="forms" tagdir="/WEB-INF/tags/forms" %> 4 | 5 | <%--@elvariable id="metrics" type="java.util.Collection"--%> 6 | <%--@elvariable id="build" type="jetbrains.buildServer.serverSide.SBuild>"--%> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 |
25 | [Show all] 26 |
27 | 28 |
29 | 30 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 61 | 62 |
35 |
36 | ${metric.title} 37 | 38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 | 51 | 52 |
53 | 54 | 55 |
56 |
57 | 58 |
59 |
60 |
63 | 64 | 75 |
76 |
77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /common/src/perf_statistic/common/PluginConstants.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.common; 2 | 3 | public class PluginConstants { 4 | private PluginConstants() {} 5 | public static final String FEATURE_TYPE_AGGREGATION = "performance_test_analyzer"; 6 | public static final String FEATURE_TYPE_REMOTE_MONITORING = "performance_remote_monitoring"; 7 | 8 | 9 | //Feature's parameters 10 | public static final String PARAMS_AGGREGATE_FILE = "perfTest.agg.file"; 11 | public static final String PARAMS_METRIC_MAX = "perfTest.agg.max"; 12 | public static final String PARAMS_METRIC_MIN = "perfTest.agg.min"; 13 | public static final String PARAMS_METRIC_AVG = "perfTest.agg.avg"; 14 | public static final String PARAMS_METRIC_LINE90 = "perfTest.agg.90line"; 15 | public static final String PARAMS_HTTP_RESPONSE_CODE = "perfTest.agg.respCode"; 16 | public static final String PARAMS_USED_TEST_FORMAT = "perfTest.agg.testFormat"; 17 | public static final String PARAMS_CALC_TOTAL = "perfTest.agg.total"; 18 | public static final String PARAMS_CHECK_ASSERT = "perfTest.agg.assert"; 19 | public static final String PARAMS_TEST_GROUPS = "perfTest.agg.testGroups"; 20 | 21 | public static final String PARAMS_REF_CHECK = "perfTest.check.ref.data"; 22 | 23 | public static final String PARAMS_REF_TYPE_FILE = "perfTest.ref.type.file"; 24 | public static final String PARAMS_REF_DATA_FILE = "perfTest.ref.data"; 25 | 26 | public static final String PARAMS_REF_TYPE_BUILD_HISTORY = "perfTest.ref.type.builds"; 27 | public static final String PARAMS_REF_BUILD_COUNT = "perfTest.ref.buildCount"; 28 | public static final String PARAMS_REF_METRIC_MAX = "perfTest.agg.ref.max"; 29 | public static final String PARAMS_REF_METRIC_AVG = "perfTest.agg.ref.avg"; 30 | public static final String PARAMS_REF_METRIC_LINE90 = "perfTest.agg.ref.90line"; 31 | 32 | public static final String PARAMS_REF_TYPE = "perfTest.ref.type"; 33 | 34 | public static final String PARAMS_VARIATION_CRITICAL = "perfTest.ref.variation"; 35 | public static final String PARAMS_VARIATION_WARN = "perfTest.ref.warn.variation"; 36 | 37 | public static final String PARAMS_BUILD_STEP_TO_ANALYZE = "perfTest.buildStep"; 38 | public static final String PARAMS_REMOTE_PERF_MON_HOST = "perfTest.mon.host"; 39 | public static final String PARAMS_REMOTE_PERF_MON_PORT = "perfTest.mon.port"; 40 | public static final String PARAMS_REMOTE_INTERVAL = "perfTest.mon.interval"; 41 | public static final String PARAMS_REMOTE_CLOCK_DELAY = "perfTest.mon.clock.delay"; 42 | 43 | public static final String AGGREGATION_TOTAL_NAME = "Total"; 44 | public static final String MONITORING_RESULT_FILE = "monitoring.csv"; 45 | 46 | // build problem type 47 | public static final String CRITICAL_PERFORMANCE_PROBLEM_TYPE = "Performance worsened (critical)"; 48 | public static final String WARN_PERFORMANCE_PROBLEM_TYPE = "Performance worsened"; 49 | public static final String ASSERTION_FAILED_PROBLEM_TYPE = "Assertion failed"; 50 | 51 | // custom build type storage constants 52 | public static final String STORAGE_ID_COMMON_JMETER = "teamcity.jmeter.statistic"; 53 | public static final String STORAGE_ID_TEST_ALIAS = "teamcity.jmeter.sample.keys"; 54 | public static final String STORAGE_ID_DEFAULT_DESELECTED_SERIES = "teamcity.jmeter.default.deselected.series"; 55 | public static final String STORAGE_ID_WARNINGS = "teamcity.jmeter.build.warnings"; 56 | 57 | 58 | public static final String STORAGE_KEY_METRIC = "Metrics"; 59 | public static final String STORAGE_KEY_SAMPLES = "Samples"; 60 | public static final String STORAGE_KEY_CODE = "Codes"; 61 | 62 | // Log constants 63 | public static final String PERFORMANCE_TESTS_ACTIVITY_NAME = "Performance tests statistic"; 64 | public static final String CHECK_REFERENCE_ACTIVITY_NAME = "Check reference values from FILE"; 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/common/PerformanceLogger.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.common; 2 | 3 | import jetbrains.buildServer.BuildProblemData; 4 | import jetbrains.buildServer.agent.BuildProgressLogger; 5 | import jetbrains.buildServer.messages.DefaultMessagesInfo; 6 | import org.jetbrains.annotations.NotNull; 7 | import perf_statistic.common.PerformanceMessageParser; 8 | import perf_statistic.common.StringUtils; 9 | 10 | 11 | public class PerformanceLogger { 12 | private final BuildProgressLogger logger; 13 | 14 | public PerformanceLogger(@NotNull BuildProgressLogger logger) { 15 | this.logger = logger; 16 | } 17 | 18 | public void logMessage(final String ... messageArrays) { 19 | StringBuilder builder = new StringBuilder(); 20 | for (String value : messageArrays) { 21 | builder.append(value); 22 | } 23 | logMessage(builder.toString()); 24 | } 25 | 26 | public void logMessage(final String message) { 27 | logger.logMessage(DefaultMessagesInfo.createTextMessage(message)); 28 | } 29 | 30 | public void logMessage(final String testGroup, final String testName, final String metricName, final long value, final String code, final boolean warning) { 31 | String message = PerformanceMessageParser.createJMeterMessage(testGroup, testName, metricName, value, code, warning); 32 | logger.logMessage(DefaultMessagesInfo.createTextMessage(message)); 33 | } 34 | 35 | public void logWarningMessage(final String testGroup, final String testName, final String metricName, final long value, final String code, long currValue, double variation) { 36 | String message = PerformanceMessageParser.createJMeterWarningMessage(testGroup, testName, metricName, value, code, currValue, variation); 37 | logger.logMessage(DefaultMessagesInfo.createTextMessage(message)); 38 | logger.warning("Exceed variation:" + testGroup + " " + testName + "; metric = " + metricName); 39 | } 40 | 41 | public void logWarningBuildStatus() { 42 | logger.logMessage(DefaultMessagesInfo.createTextMessage("##teamcity[buildStatus text='{build.status.text}; Warning: exceed non-critical variation!']")); 43 | } 44 | 45 | public void logBuildProblem(final String metric, final String testName, final String type, final String description) { 46 | BuildProblemData buildProblem = BuildProblemData.createBuildProblem(StringUtils.getBuildProblemId(metric, testName), type, description, testName); 47 | System.out.println(buildProblem); 48 | System.out.println("Additional data: " + buildProblem.getAdditionalData()); 49 | logger.logBuildProblem(buildProblem); 50 | } 51 | 52 | public void logBuildProblem(final String identity, final String type, final String description) { 53 | BuildProblemData buildProblem = BuildProblemData.createBuildProblem(StringUtils.cutLongBuildProblemIdentity(identity), type, description); 54 | logger.logBuildProblem(buildProblem); 55 | } 56 | 57 | public void activityStarted(String activityName) { 58 | logger.activityStarted(activityName, DefaultMessagesInfo.BLOCK_TYPE_MODULE); 59 | } 60 | 61 | public void activityFinished(String activityName) { 62 | logger.activityFinished(activityName, DefaultMessagesInfo.BLOCK_TYPE_MODULE); 63 | } 64 | 65 | 66 | public void logTestStarted(String testName) { 67 | logger.logTestStarted(testName); 68 | } 69 | 70 | public void logTestFailed(String testName, String typeError, String msg) { 71 | logger.logTestFailed(testName, typeError, msg); 72 | } 73 | 74 | public void logTestFinished(String testName) { 75 | logger.logTestFinished(testName); 76 | } 77 | 78 | public void testsGroupStarted(String testGroupName) { 79 | if (testGroupName != null && !testGroupName.isEmpty()) 80 | logger.logSuiteStarted(testGroupName); 81 | } 82 | public void testsGroupFinished(String testGroupName) { 83 | if (testGroupName != null && !testGroupName.isEmpty()) 84 | logger.logSuiteFinished(testGroupName); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/metric_aggregation/counting/Item.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.metric_aggregation.counting; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | import perf_statistic.agent.common.BaseFileReader; 6 | import perf_statistic.agent.metric_aggregation.AggregationProperties; 7 | import perf_statistic.common.PerformanceMessageParser; 8 | import perf_statistic.common.StringUtils; 9 | 10 | import java.util.Arrays; 11 | 12 | public class Item { 13 | // protected final String startTime; 14 | private final long responseTime; 15 | private final String testName; 16 | private String responseCode = null; 17 | private boolean isSuccessful; 18 | private String testGroupName = StringUtils.EMPTY; 19 | 20 | // save only if check assertions and item is not successful 21 | protected String logLine; 22 | 23 | public Item(String line, AggregationProperties properties) throws IllegalItemFormatException { 24 | String[] values = PerformanceMessageParser.DELIMITER_PATTERN.split(line); 25 | 26 | if (values == null || values.length < 3) { //failureMessage may be empty 27 | throw new IllegalItemFormatException("", values); 28 | } 29 | // startTime = values[0]; 30 | responseTime = Long.parseLong(values[1]); 31 | if (properties.isUsedTestGroups()) { 32 | String[] testNameParts = values[2].split(":"); 33 | if (testNameParts.length < 2) { 34 | throw new IllegalItemFormatException("Test label must contains thread group name separated to test name by ':' ! Find: " + values[2]); 35 | } 36 | testGroupName = testNameParts[0].trim(); 37 | testName = StringUtils.checkTestName(testNameParts[1].trim()); 38 | } else { 39 | testName = StringUtils.checkTestName(values[2]); 40 | } 41 | 42 | boolean codes = properties.isCalculateResponseCodes(); 43 | boolean assets = properties.isCheckAssertions(); 44 | 45 | if (assets && !isSuccessful) { 46 | logLine = line; 47 | } 48 | if(codes && assets) { 49 | if (values.length < 5) 50 | throw new IllegalItemFormatException("\tresponseCode\tisSuccess", values); 51 | responseCode = values[3]; 52 | isSuccessful = values[4].equals("1") || values[4].equalsIgnoreCase("true"); 53 | } else if (codes) { 54 | if (values.length < 4) 55 | throw new IllegalItemFormatException("\tresponseCode", values); 56 | responseCode = values[3]; 57 | } else if (assets) { 58 | if (values.length < 4) 59 | throw new IllegalItemFormatException("\tisSuccess", values); 60 | isSuccessful = values[3].equals("1") || values[3].equalsIgnoreCase("true"); 61 | } 62 | } 63 | 64 | // public String getStartTime() { 65 | // return startTime; 66 | // } 67 | 68 | public long getResponseTime() { 69 | return responseTime; 70 | } 71 | 72 | public String getTestName() { 73 | return testName; 74 | } 75 | 76 | public String getResponseCode() { 77 | return responseCode; 78 | } 79 | 80 | public boolean isSuccessful() { 81 | return isSuccessful; 82 | } 83 | 84 | @NotNull 85 | public String getTestGroupName() { 86 | return testGroupName != null ? testGroupName : StringUtils.EMPTY; 87 | } 88 | 89 | @Nullable 90 | public String getLogLine() { 91 | return logLine; 92 | } 93 | 94 | public String toString(){ 95 | return "_Item_: responseTime=[" + responseTime 96 | + "] testName=[" + testName 97 | + "] responseCode=[" + responseCode 98 | + "] isSuccessful=[" + isSuccessful 99 | + "] testGroupName=[" + testGroupName; 100 | } 101 | 102 | public static class IllegalItemFormatException extends BaseFileReader.FileFormatException { 103 | public IllegalItemFormatException(String message) { 104 | super(message); 105 | } 106 | public IllegalItemFormatException(String wrongFiledNames, String[] actualData) { 107 | super("Result item format must included asserted result. Format: startTime\tresponseTime\ttestName" + wrongFiledNames + "...\nFound" + Arrays.toString(actualData)); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/remote_monitoring/RemotePerfMonTab.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.remote_monitoring; 2 | 3 | import jetbrains.buildServer.controllers.BuildDataExtensionUtil; 4 | import jetbrains.buildServer.serverSide.CustomDataStorage; 5 | import jetbrains.buildServer.serverSide.SBuild; 6 | import jetbrains.buildServer.serverSide.SBuildServer; 7 | import jetbrains.buildServer.serverSide.SBuildType; 8 | import jetbrains.buildServer.serverSide.artifacts.BuildArtifact; 9 | import jetbrains.buildServer.serverSide.artifacts.BuildArtifactsViewMode; 10 | import jetbrains.buildServer.web.openapi.PagePlaces; 11 | import jetbrains.buildServer.web.openapi.PlaceId; 12 | import jetbrains.buildServer.web.openapi.PluginDescriptor; 13 | import jetbrains.buildServer.web.openapi.SimpleCustomTab; 14 | import org.jetbrains.annotations.NotNull; 15 | import perf_statistic.common.PluginConstants; 16 | import perf_statistic.server.remote_monitoring.graph.Graph; 17 | 18 | import javax.servlet.http.HttpServletRequest; 19 | import java.io.File; 20 | import java.util.ArrayList; 21 | import java.util.Collection; 22 | import java.util.Map; 23 | 24 | /** 25 | * Remote performance monitoring tab, locates on the build page 26 | */ 27 | public class RemotePerfMonTab extends SimpleCustomTab { 28 | private BuildArtifact monitoringLogFile; 29 | 30 | protected final SBuildServer myServer; 31 | 32 | public RemotePerfMonTab(@NotNull final PagePlaces pagePlaces, @NotNull final SBuildServer server, @NotNull final PluginDescriptor descriptor) { 33 | super(pagePlaces, PlaceId.BUILD_RESULTS_TAB, "jmeter", descriptor.getPluginResourcesPath("monitoring/remotePerfMon.jsp"), "RemotePerfMon"); 34 | myServer = server; 35 | 36 | addJsFile(descriptor.getPluginResourcesPath("flot/excanvas.js")); 37 | addJsFile(descriptor.getPluginResourcesPath("flot/jquery.flot.js")); 38 | addJsFile(descriptor.getPluginResourcesPath("flot/jquery.flot.stack.js")); 39 | addJsFile(descriptor.getPluginResourcesPath("flot/jquery.flot.crosshair.js")); 40 | addJsFile(descriptor.getPluginResourcesPath("flot/jquery.flot.selection.js")); 41 | 42 | addJsFile(descriptor.getPluginResourcesPath("monitoring/remotePerfMon.format.js")); 43 | addJsFile(descriptor.getPluginResourcesPath("monitoring/remotePerfMon.plots.js")); 44 | addCssFile(descriptor.getPluginResourcesPath("monitoring/remotePerfMon.styles.css")); 45 | register(); 46 | } 47 | 48 | @Override 49 | public boolean isAvailable(@NotNull final HttpServletRequest request) { 50 | final SBuild build = BuildDataExtensionUtil.retrieveBuild(request, myServer); 51 | if (build == null) { 52 | return false; 53 | } 54 | boolean hasFeature = build.getBuildType() != null && !build.getBuildType().getBuildFeaturesOfType(PluginConstants.FEATURE_TYPE_REMOTE_MONITORING).isEmpty(); 55 | 56 | monitoringLogFile = build.getArtifacts(BuildArtifactsViewMode.VIEW_ALL).getArtifact(PluginConstants.MONITORING_RESULT_FILE); 57 | return hasFeature && monitoringLogFile != null && monitoringLogFile.isFile(); 58 | } 59 | 60 | public void fillModel(@NotNull Map model, @NotNull HttpServletRequest request) { 61 | SBuild build = BuildDataExtensionUtil.retrieveBuild(request, myServer); 62 | 63 | Collection data = new ArrayList(); 64 | if (monitoringLogFile != null && build != null) { 65 | RemotePerfMonLogDataProvider provider = new RemotePerfMonLogDataProvider(); 66 | data = provider.getGraphs(new File(build.getArtifactsDirectory().getAbsolutePath() + File.separator + monitoringLogFile.getRelativePath())); 67 | } 68 | 69 | if (build != null && build.getBuildType() != null) { 70 | setState(data, build.getBuildType()); 71 | } 72 | model.put("metrics", data); 73 | model.put("build", build); 74 | } 75 | 76 | 77 | /** 78 | * retrieve and set graph state (hidden/shown) 79 | * @param graphs 80 | * @param buildType 81 | */ 82 | private void setState(Collection graphs, @NotNull SBuildType buildType) { 83 | CustomDataStorage stateStorage = buildType.getCustomDataStorage("teamcity.jmeter.graph.states"); 84 | for (Graph graph : graphs) { 85 | String state = stateStorage.getValue(graph.getId()); 86 | graph.setState(state == null ? "shown" : state); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/FeaturePerformanceStatistic.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server; 2 | 3 | import jetbrains.buildServer.serverSide.BuildFeature; 4 | import jetbrains.buildServer.serverSide.InvalidProperty; 5 | import jetbrains.buildServer.serverSide.PropertiesProcessor; 6 | import jetbrains.buildServer.web.openapi.PluginDescriptor; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | import perf_statistic.common.PluginConstants; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Collection; 13 | import java.util.Map; 14 | 15 | /** 16 | * Created by Yuliya.Torhan on 1/16/14. 17 | */ 18 | public class FeaturePerformanceStatistic extends BuildFeature { 19 | private final String myEditUrl; 20 | 21 | public FeaturePerformanceStatistic(@NotNull final PluginDescriptor descriptor) { 22 | myEditUrl = descriptor.getPluginResourcesPath("editFeaturePerformanceStatistic.jsp"); 23 | } 24 | 25 | @NotNull 26 | @Override 27 | public String getType() { 28 | return PluginConstants.FEATURE_TYPE_AGGREGATION; 29 | } 30 | 31 | @NotNull 32 | @Override 33 | public String getDisplayName() { 34 | return "Performance Metrics Calculation"; 35 | } 36 | 37 | @Nullable 38 | @Override 39 | public String getEditParametersUrl() { 40 | return myEditUrl; 41 | } 42 | 43 | @NotNull 44 | @Override 45 | public String describeParameters(@NotNull final Map params) { 46 | StringBuilder result = new StringBuilder(); 47 | result.append("Log file to aggregate values=[").append(params.get(PluginConstants.PARAMS_AGGREGATE_FILE)) 48 | .append("]; metrics=[ "); 49 | if ("true".equalsIgnoreCase(params.get(PluginConstants.PARAMS_METRIC_MIN))) 50 | result.append("min "); 51 | if ("true".equalsIgnoreCase(params.get(PluginConstants.PARAMS_METRIC_MAX))) 52 | result.append("max "); 53 | if ("true".equalsIgnoreCase(params.get(PluginConstants.PARAMS_METRIC_AVG))) 54 | result.append("avg "); 55 | if ("true".equalsIgnoreCase(params.get(PluginConstants.PARAMS_METRIC_LINE90))) 56 | result.append("line90"); 57 | result.append(" ]"); 58 | 59 | if ("true".equalsIgnoreCase(params.get(PluginConstants.PARAMS_HTTP_RESPONSE_CODE))) 60 | result.append(" + response codes"); 61 | if ("true".equalsIgnoreCase(params.get(PluginConstants.PARAMS_CHECK_ASSERT))) 62 | result.append(" + assertions"); 63 | if ("true".equalsIgnoreCase(params.get(PluginConstants.PARAMS_CALC_TOTAL))) 64 | result.append(" + total."); 65 | 66 | if ("true".equalsIgnoreCase(params.get(PluginConstants.PARAMS_TEST_GROUPS))) 67 | result.append(" Starts on different threads."); 68 | if ("true".equalsIgnoreCase(params.get(PluginConstants.PARAMS_REF_CHECK))) 69 | result.append(" Fail if check reference values not passed."); 70 | return result.toString(); 71 | } 72 | 73 | public PropertiesProcessor getParametersProcessor() { 74 | return new PropertiesProcessor() { 75 | @Override 76 | public Collection process(Map params) { 77 | Collection invalidProperties = new ArrayList(); 78 | if (params.get(PluginConstants.PARAMS_AGGREGATE_FILE) == null) { 79 | invalidProperties.add(new InvalidProperty(PluginConstants.PARAMS_AGGREGATE_FILE, "File with data to aggregate can't be empty!")); 80 | } 81 | if (Boolean.parseBoolean(params.get(PluginConstants.PARAMS_REF_CHECK))) { 82 | if (Boolean.parseBoolean(params.get(PluginConstants.PARAMS_REF_TYPE_BUILD_HISTORY)) && params.get(PluginConstants.PARAMS_REF_BUILD_COUNT) == null) { 83 | invalidProperties.add(new InvalidProperty(PluginConstants.PARAMS_REF_BUILD_COUNT, "Set count of build to calculate reference value!")); 84 | } 85 | if (Boolean.parseBoolean(params.get(PluginConstants.PARAMS_REF_TYPE_FILE)) && params.get(PluginConstants.PARAMS_REF_DATA_FILE) == null) { 86 | invalidProperties.add(new InvalidProperty(PluginConstants.PARAMS_REF_DATA_FILE, "Set checkout file with references values!")); 87 | } 88 | } 89 | if (!Boolean.parseBoolean(params.get(PluginConstants.PARAMS_METRIC_AVG)) && !Boolean.parseBoolean(params.get(PluginConstants.PARAMS_METRIC_MAX)) 90 | && !Boolean.parseBoolean(params.get(PluginConstants.PARAMS_METRIC_MIN)) && !Boolean.parseBoolean(params.get(PluginConstants.PARAMS_METRIC_LINE90))) { 91 | invalidProperties.add(new InvalidProperty("perfTest.metrics", "Please, choose at least one metric!")); 92 | } 93 | return invalidProperties; 94 | } 95 | }; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Tomcat.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 93 | -------------------------------------------------------------------------------- /server/resources/flot/jquery.flot.time.min.js: -------------------------------------------------------------------------------- 1 | /* Pretty handling of time axes. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | Set axis.mode to "time" to enable. See the section "Time series data" in 7 | API.txt for details. 8 | 9 | */(function(e){function n(e,t){return t*Math.floor(e/t)}function r(e,t,n,r){if(typeof e.strftime=="function")return e.strftime(t);var i=function(e,t){return e=""+e,t=""+(t==null?"0":t),e.length==1?t+e:e},s=[],o=!1,u=e.getHours(),a=u<12;n==null&&(n=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]),r==null&&(r=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]);var f;u>12?f=u-12:u==0?f=12:f=u;for(var l=0;l=u)break;var h=l[c][0],p=l[c][1];if(p=="year"){if(i.minTickSize!=null&&i.minTickSize[1]=="year")h=Math.floor(i.minTickSize[0]);else{var d=Math.pow(10,Math.floor(Math.log(e.delta/o.year)/Math.LN10)),v=e.delta/o.year/d;v<1.5?h=1:v<3?h=2:v<7.5?h=5:h=10,h*=d}h<1&&(h=1)}e.tickSize=i.tickSize||[h,p];var m=e.tickSize[0];p=e.tickSize[1];var g=m*o[p];p=="second"?r.setSeconds(n(r.getSeconds(),m)):p=="minute"?r.setMinutes(n(r.getMinutes(),m)):p=="hour"?r.setHours(n(r.getHours(),m)):p=="month"?r.setMonth(n(r.getMonth(),m)):p=="quarter"?r.setMonth(3*n(r.getMonth()/3,m)):p=="year"&&r.setFullYear(n(r.getFullYear(),m)),r.setMilliseconds(0),g>=o.minute&&r.setSeconds(0),g>=o.hour&&r.setMinutes(0),g>=o.day&&r.setHours(0),g>=o.day*4&&r.setDate(1),g>=o.month*2&&r.setMonth(n(r.getMonth(),3)),g>=o.quarter*2&&r.setMonth(n(r.getMonth(),6)),g>=o.year&&r.setMonth(0);var y=0,b=Number.NaN,w;do{w=b,b=r.getTime(),t.push(b);if(p=="month"||p=="quarter")if(m<1){r.setDate(1);var E=r.getTime();r.setMonth(r.getMonth()+(p=="quarter"?3:1));var S=r.getTime();r.setTime(b+y*o.hour+(S-E)*m),y=r.getHours(),r.setHours(0)}else r.setMonth(r.getMonth()+m*(p=="quarter"?3:1));else p=="year"?r.setFullYear(r.getFullYear()+m):r.setTime(b+g)}while(b { 12 | private static final Pattern non_word_pattern = Pattern.compile("\\W"); 13 | 14 | enum PerformanceStatus { 15 | OK, CRITICAL_DECLINE, DECLINE 16 | } 17 | private final STestRun myTest; 18 | private final String myChartKey; 19 | private final String myTestName; 20 | private final String myGroupName; 21 | 22 | private String myWarning = ""; 23 | private List myPerformanceProblems; 24 | 25 | private List> mySRTValues; 26 | private Map myRPSValues; 27 | private Map myLogLines; 28 | 29 | public PerformanceTestRun(STestRun test) { 30 | myTest = test; 31 | myChartKey = non_word_pattern.matcher(myTest.getTest().getName().getAsString()).replaceAll(""); 32 | TestName name = myTest.getTest().getName(); 33 | myTestName = name.getNameWithoutSuite(); 34 | myGroupName = name.getSuite().replace(":", ""); 35 | } 36 | 37 | public int getID() { 38 | return myTest.getTestRunId(); 39 | } 40 | 41 | public String getTestName() { 42 | return myTestName; 43 | } 44 | 45 | public String getTestsGroupName() { 46 | return myGroupName; 47 | } 48 | 49 | public String getFullName() { 50 | return myTest.getTest().getName().getAsString(); 51 | } 52 | 53 | public String getChartKey() { 54 | return myChartKey; 55 | } 56 | 57 | public STestRun getTestRun() { 58 | return myTest; 59 | } 60 | 61 | public String getTestStatus() { 62 | return myTest.getStatus().toString(); 63 | } 64 | 65 | public String getPerformanceStatus() { 66 | if (myPerformanceProblems != null && !myPerformanceProblems.isEmpty()) 67 | return PerformanceStatus.CRITICAL_DECLINE.toString(); 68 | if (!myWarning.isEmpty()) 69 | return PerformanceStatus.DECLINE.toString(); 70 | return PerformanceStatus.OK.toString(); 71 | } 72 | 73 | public Collection getPerformanceProblems() { 74 | return myPerformanceProblems; 75 | } 76 | 77 | 78 | 79 | public void setPerformanceProblems(List performanceProblems) { 80 | myPerformanceProblems = performanceProblems; 81 | } 82 | 83 | public String getWarning() { 84 | return myWarning; 85 | } 86 | 87 | public void setWarning(String warning) { 88 | myWarning = warning; 89 | } 90 | 91 | public List> getSRTValues() { 92 | return mySRTValues; 93 | } 94 | 95 | public List> getRPSValues() { 96 | if (myRPSValues != null) { 97 | List> values = new SortedList>(new Comparator>() { 98 | @Override 99 | public int compare(List o1, List o2) { 100 | return o2.get(0).equals(o1.get(0)) ? 0 : o1.get(0) < o2.get(0) ? -1 : 1; 101 | } 102 | }); 103 | for (long time: myRPSValues.keySet()) { 104 | values.add(Arrays.asList(time, myRPSValues.get(time))); 105 | } 106 | return values; 107 | } 108 | return null; 109 | } 110 | 111 | public Map getLogLines() { 112 | return myLogLines; 113 | } 114 | 115 | public void addTimeValue(long time, long value) { 116 | if (mySRTValues == null) { 117 | mySRTValues = new SortedList>(new Comparator>() { 118 | @Override 119 | public int compare(List o1, List o2) { 120 | return o2.get(0).equals(o1.get(0)) ? 0 : o1.get(0) < o2.get(0) ? -1 : 1; 121 | } 122 | }); 123 | } 124 | mySRTValues.add(Arrays.asList(time, value)); 125 | 126 | if (myRPSValues == null) { 127 | myRPSValues = new HashMap(); 128 | } 129 | time = time - time % 1000; 130 | Long count = myRPSValues.get(time); 131 | myRPSValues.put(time, count == null ? 1 : ++count); 132 | } 133 | 134 | public void addLogLine(long time, String[] lineParts) { 135 | if (myLogLines == null) { 136 | myLogLines = new HashMap(); 137 | } 138 | myLogLines.put(time, lineParts); 139 | } 140 | 141 | 142 | 143 | @Override 144 | public int compareTo(PerformanceTestRun o) { 145 | return this.myGroupName.compareTo(((PerformanceTestRun) o).myGroupName); 146 | /* if (this.myGroupName == null && o.myGroupName == null) { 147 | if (this.myTestName.equals("Total")) return 1; 148 | if (o.myTestName.equals("Total")) return -1; 149 | if (o.myTestName.equals("Total")) return this.myTestName.compareTo(o.myTestName); 150 | }*/ 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /common/src/perf_statistic/common/PerformanceMessageParser.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.common; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class PerformanceMessageParser { 6 | public static final String DELIMITER = "\t"; 7 | public static final Pattern DELIMITER_PATTERN = Pattern.compile(DELIMITER); 8 | 9 | private static final String MESSAGE_START = "performance["; 10 | private static final String MESSAGE_END = "]"; 11 | private static final String MESSAGE_KEY_METRIC = "metric="; 12 | private static final String MESSAGE_KEY_TEST_NAME = "test="; 13 | private static final String MESSAGE_KEY_CODE_LABEL = "code="; 14 | private static final String MESSAGE_KEY_VALUE = "value="; 15 | private static final String MESSAGE_KEY_TESTS_GROUP_NAME = "testsGroup="; 16 | private static final String MESSAGE_KEY_WARNING = "warning="; 17 | private static final String MESSAGE_KEY_WARNING_CURRENT_VALUE = "currValue="; 18 | private static final String MESSAGE_KEY_WARNING_VARIATION = "variation="; 19 | 20 | 21 | public static PerformanceMessage getPerformanceTestingMessage(String message) { 22 | if (message.startsWith(MESSAGE_START) && message.endsWith(MESSAGE_END)) { 23 | message = message.substring(12, message.length() - 1).trim(); 24 | PerformanceMessage perfTestMessage = new PerformanceMessage(); 25 | for (String attribute : DELIMITER_PATTERN.split(message)) { 26 | if (attribute.startsWith(MESSAGE_KEY_METRIC)) { 27 | perfTestMessage.setMetric(attribute.substring(7)); 28 | continue; 29 | } 30 | if (attribute.startsWith(MESSAGE_KEY_TEST_NAME)) { 31 | perfTestMessage.setTestName(attribute.substring(5)); 32 | continue; 33 | } 34 | if (attribute.startsWith(MESSAGE_KEY_CODE_LABEL)) { 35 | perfTestMessage.setCodeLabel(attribute.substring(5)); 36 | continue; 37 | } 38 | if (attribute.startsWith(MESSAGE_KEY_VALUE)) { 39 | perfTestMessage.setValue(attribute.substring(6)); 40 | continue; 41 | } 42 | if (attribute.startsWith(MESSAGE_KEY_TESTS_GROUP_NAME)) { 43 | perfTestMessage.setTestsGroupName(attribute.substring(11)); 44 | } 45 | if (attribute.startsWith(MESSAGE_KEY_WARNING)) { 46 | perfTestMessage.setWarning(Boolean.parseBoolean(attribute.substring(8))); 47 | } 48 | if (attribute.startsWith(MESSAGE_KEY_WARNING_CURRENT_VALUE)) { 49 | perfTestMessage.setCurrValue(attribute.substring(10)); 50 | } 51 | if (attribute.startsWith(MESSAGE_KEY_WARNING_VARIATION)) { 52 | perfTestMessage.setVariation(attribute.substring(10)); 53 | } 54 | // todo: temporary to support old format; will be removed in future 55 | if (attribute.startsWith("label=")) { 56 | perfTestMessage.setTestName(attribute.substring(6)); 57 | } 58 | } 59 | return perfTestMessage; 60 | } 61 | return null; 62 | } 63 | 64 | public static String createJMeterMessage(final String testGroup, final String testName, final String metric, final long value, final String code, final boolean warning) { 65 | StringBuilder message = new StringBuilder(MESSAGE_START); 66 | if (testGroup != null && !testGroup.isEmpty()) { 67 | message.append(MESSAGE_KEY_TESTS_GROUP_NAME).append(testGroup).append(DELIMITER); 68 | } 69 | message.append(MESSAGE_KEY_TEST_NAME).append(testName).append(DELIMITER) 70 | .append(MESSAGE_KEY_METRIC).append(metric).append(DELIMITER); 71 | if (code != null && !code.isEmpty()) { 72 | message.append(MESSAGE_KEY_CODE_LABEL).append(code).append(DELIMITER); 73 | } 74 | 75 | message.append(MESSAGE_KEY_WARNING).append(warning).append(DELIMITER); 76 | message.append(MESSAGE_KEY_VALUE).append(value).append(MESSAGE_END); 77 | 78 | return message.toString(); 79 | } 80 | 81 | public static String createJMeterWarningMessage(final String testGroup, final String testName, final String metric, final long value, final String code, final long currValue, final double variation) { 82 | StringBuilder message = new StringBuilder(MESSAGE_START); 83 | if (testGroup != null && !testGroup.isEmpty()) { 84 | message.append(MESSAGE_KEY_TESTS_GROUP_NAME).append(testGroup).append(DELIMITER); 85 | } 86 | message.append(MESSAGE_KEY_TEST_NAME).append(testName).append(DELIMITER) 87 | .append(MESSAGE_KEY_METRIC).append(metric).append(DELIMITER); 88 | if (code != null && !code.isEmpty()) { 89 | message.append(MESSAGE_KEY_CODE_LABEL).append(code).append(DELIMITER); 90 | } 91 | 92 | message.append(MESSAGE_KEY_WARNING).append(true).append(DELIMITER); 93 | message.append(MESSAGE_KEY_WARNING_CURRENT_VALUE).append(currValue).append(DELIMITER); 94 | message.append(MESSAGE_KEY_WARNING_VARIATION).append(variation).append(DELIMITER); 95 | message.append(MESSAGE_KEY_VALUE).append(value).append(MESSAGE_END); 96 | 97 | return message.toString(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/remote_monitoring/RemotePerfMonLogDataProvider.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.remote_monitoring; 2 | 3 | import com.intellij.util.containers.SortedList; 4 | import jetbrains.buildServer.log.Loggers; 5 | import jetbrains.buildServer.util.StringUtil; 6 | import org.jetbrains.annotations.NotNull; 7 | import perf_statistic.common.PerformanceMessageParser; 8 | import perf_statistic.common.PluginConstants; 9 | import perf_statistic.server.remote_monitoring.graph.Graph; 10 | 11 | import java.io.*; 12 | import java.util.*; 13 | import java.util.regex.Pattern; 14 | 15 | public class RemotePerfMonLogDataProvider { 16 | protected static Pattern delimiter = PerformanceMessageParser.DELIMITER_PATTERN; 17 | 18 | protected Map metrics; 19 | 20 | public RemotePerfMonLogDataProvider() { 21 | metrics = new HashMap(); 22 | metrics.put("memory", new RemotePerfMonChart("memory", "Memory", "time", "byte", 2)); 23 | metrics.put("cpu", new RemotePerfMonChart("cpu", "CPU", "time", "%", 0)); 24 | metrics.put("disks", new RemotePerfMonChart("disks", "Disks", "time", "ops", 1)); 25 | metrics.put("jmx_gc", new RemotePerfMonChart("jmx_gc","JMX: garbage collection", "time", "ms", 3)); 26 | metrics.put("jmx_class_count", new RemotePerfMonChart("jmx_class_count", "JMX: class count", "time", "", 4)); 27 | } 28 | protected void readLines(@NotNull final File file) { 29 | BufferedReader reader = null; 30 | try { 31 | reader = new BufferedReader(new FileReader(file)); 32 | while (reader.ready()) { 33 | String[] items = delimiter.split(reader.readLine().trim()); 34 | if (checkItem(items)) { 35 | processLine(items); 36 | } 37 | } 38 | } catch (FileNotFoundException e) { 39 | Loggers.STATS.error(PluginConstants.FEATURE_TYPE_REMOTE_MONITORING + " plugin error. File " + file.getAbsolutePath() + " not found!", e); 40 | } catch (IOException e) { 41 | Loggers.STATS.error(PluginConstants.FEATURE_TYPE_REMOTE_MONITORING + " plugin error. Error reading file " + file.getAbsolutePath(), e); 42 | } finally { 43 | if (reader != null) { 44 | try { 45 | reader.close(); 46 | } catch (IOException e) { 47 | Loggers.STATS.error(PluginConstants.FEATURE_TYPE_REMOTE_MONITORING + " plugin error. Error closing file " + file.getAbsolutePath(), e); 48 | } 49 | } 50 | } 51 | } 52 | 53 | private boolean checkItem(String[] values) { 54 | if (values.length < 3) { 55 | Loggers.STATS.error(PluginConstants.FEATURE_TYPE_REMOTE_MONITORING + " plugin error. \nItem: timestamp\tresultValue\ttestName \n Found: " + Arrays.toString(values)); 56 | return false; 57 | } 58 | return (values[0].matches("\\d+") && values[1].matches("[0-9]*\\.?[0-9]*([Ee][+-]?[0-9]+)?")); 59 | } 60 | 61 | 62 | public void processLine(String... itemValues) { 63 | long timeStamp = Long.parseLong(itemValues[0]); 64 | if (!StringUtil.isEmpty(itemValues[1])) { 65 | double value = Double.parseDouble(itemValues[1]); 66 | String label = itemValues[2]; 67 | 68 | // TODO old format support: remove code after all older monitoring results will be removed; 69 | if (label.contains("labs.intellij.net")) { 70 | label = correctOldPerfMonFormat(label); 71 | value = value / 1000; 72 | } 73 | 74 | Graph metric = getMetric(label); 75 | metric.addValue(timeStamp, Math.round(value), label); 76 | } 77 | } 78 | 79 | private String correctOldPerfMonFormat(String label) { 80 | if (label != null) { 81 | label = label.toLowerCase(); 82 | if (label.contains("memory")) { 83 | label = (label.contains("jmx") ? "jmx" : "") + label.substring(label.indexOf("memory")); 84 | } 85 | if (label.contains("cpu")) { 86 | label = label.substring(label.indexOf("cpu")); 87 | } 88 | if (label.contains("disks")) { 89 | label = label.substring(label.indexOf("disks")); 90 | } 91 | if (label.contains("gc-time")) { 92 | label = "jmx gc-time"; 93 | } 94 | if (label.contains("class-count")) { 95 | label = "jmx class-count"; 96 | } 97 | } 98 | return label; 99 | } 100 | 101 | private Graph getMetric(String label) { 102 | if (label != null && (label.contains("memory") || label.contains("swap"))) { 103 | return metrics.get("memory"); 104 | } 105 | if (label != null && label.contains("cpu")) { 106 | return metrics.get("cpu"); 107 | } 108 | if (label != null && label.contains("disks")) { 109 | return metrics.get("disks"); 110 | } 111 | if (label != null && label.contains("gc-time")) { 112 | return metrics.get("jmx_gc"); 113 | } 114 | if (label != null && label.contains("class-count")) { 115 | return metrics.get("jmx_class_count"); 116 | } 117 | return RemotePerfMonChart.UNKNOWN_GRAPH; 118 | } 119 | 120 | public Collection getGraphs(@NotNull final File file) { 121 | readLines(file); 122 | List sortedMetricDescriptors = new SortedList(new Comparator() { 123 | @Override 124 | public int compare(Graph o1, Graph o2) { 125 | return o1.compareTo(o2); 126 | } 127 | }); 128 | sortedMetricDescriptors.addAll(metrics.values()); 129 | return sortedMetricDescriptors; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /server/resources/statistic/perfChartsCustom.js: -------------------------------------------------------------------------------- 1 | if (!window.$j) { 2 | window.$j = window.jQuery; 3 | } 4 | 5 | BS.PerfStat = { 6 | initPlot: function (container, data, yformat, label, logLines, logTitles) { 7 | container.css({ 8 | width: 1400, 9 | height: 150 10 | }); 11 | 12 | var settings = this.initSettings(yformat); 13 | 14 | var plot = window.$j.plot(container, data, settings); 15 | plot.unhighlightSerie = function (seriess) { }; 16 | plot.showToolTip = function (seriess) { }; 17 | 18 | function showTooltip(x, y, contents) { 19 | $j('
' + contents + '
').css({ 20 | position: 'absolute', 21 | display: 'none', 22 | top: y + 6, 23 | left: x + 12, 24 | border: '1px solid rgba(0, 0, 0, 0.2)', 25 | padding: '2px', 26 | 'background-color': '#ffffff', 27 | 'box-shadow': '0 5px 10px rgba(0, 0, 0, 0.2)' 28 | }).appendTo("body").fadeIn(200); 29 | } 30 | 31 | var previousPoint = null; 32 | 33 | container.unbind("plothover").bind("plothover", function (event, pos, item) { 34 | if (item) { 35 | if (previousPoint != item.dataIndex) { 36 | previousPoint = item.dataIndex; 37 | $j("#tooltip").remove(); 38 | var value = item.datapoint[1]; 39 | var msg = label + " " + value + " " + yformat + " "; 40 | if (logLines && logTitles && logLines[item.datapoint[0]]) { 41 | msg = msg + "
Log line details:"; 42 | var parts = logLines[item.datapoint[0]]; 43 | for (var i = 0; i < logTitles.length; i++) { 44 | if (i == 0) { 45 | msg = msg + "
" + logTitles[i] + ": " + BS.PerfStat.Format.formatTime(Number(parts[i])) + " (" + parts[i] + ")"; 46 | } else { 47 | msg = msg + "
" + logTitles[i] + ": " + parts[i]; 48 | } 49 | } 50 | } 51 | showTooltip(item.pageX, item.pageY, msg); 52 | } 53 | } else { 54 | $j("#tooltip").remove(); 55 | previousPoint = null; 56 | } 57 | }); 58 | 59 | container.unbind("plotselected").bind("plotselected", function (event, ranges) { 60 | if (ranges.xaxis) { 61 | var start = parseInt(ranges.xaxis.from.toFixed()); 62 | var end = parseInt(ranges.xaxis.to.toFixed()); 63 | 64 | plot = $j.plot(container, data, 65 | $j.extend(true, {xaxis: {min: start, max: end}}, settings)); 66 | plot.unhighlightSerie = function (seriess) { }; 67 | plot.showToolTip = function (seriess) { }; 68 | } else { 69 | plot.clearSelection(); 70 | } 71 | }); 72 | 73 | container.unbind("plotunselected").bind("plotunselected", function () { 74 | plot = $j.plot(container, data, settings); 75 | plot.unhighlightSerie = function (seriess) { }; 76 | plot.showToolTip = function (seriess) { }; 77 | }); 78 | 79 | container.unbind("plotclick"); 80 | }, 81 | 82 | initSettings: function (yformat) { 83 | return { 84 | series: { 85 | lines: { show: true }, 86 | points: { show: true, radius: 1 } 87 | }, 88 | grid: { 89 | hoverable: true, 90 | clickable: true 91 | }, 92 | xaxis: { 93 | mode: 'time', 94 | tickFormatter: function (val) { 95 | return BS.PerfStat.Format.formatTime(val); 96 | } 97 | }, 98 | yaxis: { 99 | tickFormatter: function (val) { 100 | return BS.PerfStat.Format.format(val, yformat, false, 0); 101 | }, 102 | labelWidth: 50 103 | }, 104 | legend: { 105 | show: false 106 | }, 107 | selection: { 108 | mode: "x" 109 | }, 110 | BSChart: { 111 | format: "duration", 112 | onHighlightSerie: function (series) { 113 | } 114 | } 115 | }; 116 | } 117 | }; 118 | 119 | BS.PerfStat.Format = { 120 | formatTime: function (value) { 121 | var date = new Date(value); 122 | return date.getHours() + ":" + this.toString2(date.getMinutes()) + ":" + this.toString2(date.getSeconds()); 123 | }, 124 | 125 | toString2: function (num) { 126 | if (num < 10) { 127 | return "0" + num; 128 | } 129 | return num; 130 | }, 131 | // Y-axis and legend crosshair formatting 132 | bytes: "byte", 133 | percent: "%", 134 | ms: "ms", 135 | number: "", 136 | 137 | format: function(value, format, isBytes, precision) { 138 | if (isBytes) { 139 | return this._formatBytes(value, format); 140 | } 141 | return this._formatNumber(value, format, precision); 142 | }, 143 | _formatBytes: function(bytes, format) { 144 | if (format == 'Gb') 145 | bytes = (bytes / 1073741824); 146 | if (format == 'Mb') 147 | bytes = (bytes / 1048576); 148 | if (format == 'Kb') 149 | bytes = (bytes / 1024); 150 | return bytes.toFixed(2) + ' ' + format; 151 | }, 152 | 153 | _formatNumber: function(number, format, precision) { 154 | number = number.toFixed(precision); 155 | return number + ' ' + format; 156 | } 157 | }; 158 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/perf_test_charts/PerformanceStatisticTab.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.perf_test_charts; 2 | 3 | import jetbrains.buildServer.controllers.BuildDataExtensionUtil; 4 | import jetbrains.buildServer.serverSide.SBuild; 5 | import jetbrains.buildServer.serverSide.SBuildServer; 6 | import jetbrains.buildServer.serverSide.metadata.BuildMetadataEntry; 7 | import jetbrains.buildServer.serverSide.metadata.MetadataStorage; 8 | import jetbrains.buildServer.serverSide.statistics.ValueProviderRegistry; 9 | import jetbrains.buildServer.serverSide.statistics.build.BuildDataStorage; 10 | import jetbrains.buildServer.web.openapi.PagePlaces; 11 | import jetbrains.buildServer.web.openapi.PlaceId; 12 | import jetbrains.buildServer.web.openapi.PluginDescriptor; 13 | import jetbrains.buildServer.web.openapi.SimpleCustomTab; 14 | import jetbrains.buildServer.web.statistics.graph.BuildGraphHelper; 15 | import org.jetbrains.annotations.NotNull; 16 | import perf_statistic.common.PluginConstants; 17 | import perf_statistic.server.perf_tests.PerformanceBuildMetadataProvider; 18 | import perf_statistic.server.perf_tests.PerformanceTestProvider; 19 | import perf_statistic.server.perf_tests.PerformanceTestRun; 20 | 21 | import javax.servlet.http.HttpServletRequest; 22 | import java.util.Collection; 23 | import java.util.Iterator; 24 | import java.util.Map; 25 | 26 | public class PerformanceStatisticTab extends SimpleCustomTab { 27 | private static final String resourceURL = "statistic/tabPerfStat.jsp"; 28 | 29 | private final BuildGraphHelper myGraphHelper; 30 | 31 | private final ValueProviderRegistry myRegistry; 32 | private final SBuildServer myServer; 33 | private final BuildDataStorage myStorage; 34 | private final MetadataStorage myMetadataStorage; 35 | 36 | 37 | private final PerformanceTestProvider myPerformanceTestHolder; 38 | 39 | public PerformanceStatisticTab(@NotNull final PagePlaces pagePlaces, @NotNull final SBuildServer server, final BuildDataStorage storage, 40 | @NotNull final ValueProviderRegistry valueProviderRegistry, @NotNull final BuildGraphHelper buildGraphHelper, 41 | @NotNull final PerformanceTestProvider testHolder, @NotNull final PluginDescriptor descriptor, @NotNull MetadataStorage metadataStorage) { 42 | super(pagePlaces, PlaceId.BUILD_RESULTS_TAB, "performanceTests", descriptor.getPluginResourcesPath(resourceURL), "Performance Statistics"); 43 | myServer = server; 44 | myRegistry = valueProviderRegistry; 45 | myStorage = storage; 46 | myGraphHelper = buildGraphHelper; 47 | myPerformanceTestHolder = testHolder; 48 | myMetadataStorage = metadataStorage; 49 | 50 | addCssFile("/css/buildGraph.css"); 51 | 52 | addJsFile(descriptor.getPluginResourcesPath("flot/jquery.flot.js")); 53 | addJsFile(descriptor.getPluginResourcesPath("flot/excanvas.js")); 54 | addJsFile(descriptor.getPluginResourcesPath("flot/jquery.flot.selection.js")); 55 | addJsFile(descriptor.getPluginResourcesPath("flot/jquery.flot.time.min.js")); 56 | 57 | addJsFile(descriptor.getPluginResourcesPath("statistic/perfChartsCustom.js")); 58 | 59 | register(); 60 | } 61 | 62 | @Override 63 | public boolean isAvailable(@NotNull final HttpServletRequest request) { 64 | SBuild build = BuildDataExtensionUtil.retrieveBuild(request, myServer); 65 | return build != null && build.getBuildType() != null && !build.getBuildType().getBuildFeaturesOfType(PluginConstants.FEATURE_TYPE_AGGREGATION).isEmpty(); 66 | } 67 | 68 | public void fillModel(@NotNull Map model, @NotNull HttpServletRequest request) { 69 | request.setAttribute("buildGraphHelper", myGraphHelper); 70 | 71 | SBuild build = BuildDataExtensionUtil.retrieveBuild(request, myServer); 72 | model.put("deselectedSeries", build.getBuildType().getCustomDataStorage(PluginConstants.STORAGE_ID_DEFAULT_DESELECTED_SERIES).getValues()); 73 | 74 | 75 | Collection successTests = myPerformanceTestHolder.getSuccessTestRuns(build); 76 | 77 | for (Iterator it = myMetadataStorage.getBuildEntry(build.getBuildId(), PerformanceBuildMetadataProvider.PERFORMANCE_META_DATA_PROVIDER_ID); it.hasNext();) { 78 | BuildMetadataEntry entry = it.next(); 79 | if (PerformanceBuildMetadataProvider.KEY_META_DATA_WARNINGS.equals(entry.getKey())) { 80 | Map warnings = entry.getMetadata(); 81 | if (!warnings.isEmpty()) { 82 | for (PerformanceTestRun test : successTests) { 83 | String testID = test.getFullName(); 84 | if (warnings.containsKey(testID)) { 85 | test.setWarning(warnings.get(testID)); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | model.put("performanceOKTests", successTests); 93 | model.put("performanceFailedTests", myPerformanceTestHolder.getFailedTestRuns(build)); 94 | 95 | model.put("allTestNames", myPerformanceTestHolder.getAllTestNames(build)); 96 | model.put("allTestGroups", myPerformanceTestHolder.getAllThreadGroups(build)); 97 | 98 | model.put("build", build); 99 | model.put("statistic", build.getFullStatistics()); 100 | model.put("isShowResponseCodes", build.getParametersProvider().get(PluginConstants.PARAMS_HTTP_RESPONSE_CODE) == null ? false : build.getParametersProvider().get(PluginConstants.PARAMS_HTTP_RESPONSE_CODE)); 101 | model.put("isLogSaved", myPerformanceTestHolder.isLogAvailable(build)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/perf_test_charts/types/AbstractCompositeVT.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.perf_test_charts.types; 2 | 3 | import jetbrains.buildServer.serverSide.CustomDataStorage; 4 | import jetbrains.buildServer.serverSide.SBuild; 5 | import jetbrains.buildServer.serverSide.SBuildServer; 6 | import jetbrains.buildServer.serverSide.SBuildType; 7 | import jetbrains.buildServer.serverSide.statistics.BuildValueProvider; 8 | import jetbrains.buildServer.serverSide.statistics.ChartSettings; 9 | import jetbrains.buildServer.serverSide.statistics.ValueProviderRegistry; 10 | import jetbrains.buildServer.serverSide.statistics.build.*; 11 | import org.jetbrains.annotations.NotNull; 12 | import perf_statistic.common.PerformanceStatisticMetrics; 13 | import perf_statistic.common.PluginConstants; 14 | 15 | import java.util.*; 16 | import java.util.regex.Pattern; 17 | 18 | public abstract class AbstractCompositeVT extends CompositeVTB { 19 | protected static final Pattern comma_pattern = Pattern.compile(","); 20 | 21 | protected String[] subKeys; 22 | protected volatile String currentBuildTypeID; 23 | 24 | protected AbstractCompositeVT(BuildDataStorage buildDataStorage, ValueProviderRegistry valueProviderRegistry, SBuildServer server, String key) { 25 | super(buildDataStorage, valueProviderRegistry, server, key); 26 | } 27 | 28 | public abstract String getValueFormat(); 29 | public abstract String getSeriesGenericName(); 30 | public abstract String getSubKeysStorageKey(); 31 | public abstract String getSubKey(String subKey); 32 | 33 | 34 | @Override 35 | public String[] getSubKeys() { 36 | if (subKeys != null) { 37 | String[] fullSubKeys = new String[subKeys.length]; 38 | int i = 0; 39 | for(String subKey : subKeys) { 40 | fullSubKeys[i++] = getSubKey(subKey); 41 | } 42 | return fullSubKeys; 43 | } 44 | return new String[0]; 45 | } 46 | 47 | @Override 48 | @NotNull 49 | public List getDataSet(@NotNull final ChartSettings _chartSettings) { 50 | if (_chartSettings instanceof BuildChartSettings) { 51 | if (subKeys == null) { 52 | BuildChartSettings settings = (BuildChartSettings) _chartSettings; 53 | updateKeys(settings.getBuildTypeId()); 54 | setDefaultDeselectedKeys(settings); 55 | } 56 | 57 | return super.getDataSet(_chartSettings); 58 | } 59 | return Collections.emptyList(); 60 | } 61 | 62 | protected void updateKeys(final String buildTypeID) { 63 | if (buildTypeID != null) { 64 | currentBuildTypeID = buildTypeID; 65 | SBuildType buildType = myServer.getProjectManager().findBuildTypeByExternalId(currentBuildTypeID); 66 | if (buildType != null) { 67 | CustomDataStorage storage = buildType.getCustomDataStorage(PluginConstants.STORAGE_ID_COMMON_JMETER); 68 | String value = storage.getValue(getSubKeysStorageKey()); 69 | if (value != null) { 70 | subKeys = comma_pattern.split(value); 71 | } 72 | } 73 | 74 | } 75 | } 76 | 77 | @Override 78 | protected BuildValueProvider createValueProviderForSubkey(@NotNull final String subKey) { 79 | return new BuildValueTypeBase(myServer, myStorage, myValueProviderRegistry, subKey) { 80 | @NotNull 81 | public String getDescription(final ChartSettings chartSettings) { 82 | return subKey; 83 | } 84 | @NotNull 85 | public List getDataSet(@NotNull final ChartSettings chartSettings) { 86 | List values = myStorage.getDataSet(getKey(), (BuildChartSettings)chartSettings, getValueProcessor()); 87 | 88 | for (ListIterator it = values.listIterator(); it.hasNext();) { 89 | BuildValue value = it.next(); 90 | SBuild build = myServer.findBuildInstanceById(value.getBuildId()); 91 | // clear data with removed builds or null values 92 | if (value.getBuildNumber() == null || value.getValue() == null || build == null) { 93 | it.remove(); 94 | } 95 | } 96 | return values; 97 | } 98 | }; 99 | } 100 | 101 | private void setDefaultDeselectedKeys(@NotNull final BuildChartSettings chartSettings){ 102 | if (chartSettings.isResetToDefaultRequest() || !chartSettings.isUpdateRequest()) { 103 | SBuildType buildType = myServer.getProjectManager().findBuildTypeByExternalId(currentBuildTypeID); 104 | Set deselectedKeys = new HashSet(); 105 | 106 | Map storage = buildType.getCustomDataStorage(PluginConstants.STORAGE_ID_DEFAULT_DESELECTED_SERIES).getValues(); 107 | 108 | if (storage != null) { 109 | for (String key : storage.keySet()) { 110 | if ("true".equals(storage.get(key))) { 111 | PerformanceStatisticMetrics metric = PerformanceStatisticMetrics.getMetricByKey(key); 112 | if (metric != null) { 113 | deselectedKeys.add(metric.getTitle()); 114 | deselectedKeys.add(metric.getReferenceTitle()); 115 | } 116 | } 117 | } 118 | } 119 | chartSettings.setAll(BuildChartSettings.FILTER_S, deselectedKeys.toArray(new String[deselectedKeys.size()])); 120 | chartSettings.setSingle("_resetDefaults", ""); // hack, because default setting show all series 121 | } 122 | } 123 | 124 | @Override 125 | public boolean hasData(final ChartSettings buildChartSettings) { 126 | BuildChartSettings settings = (BuildChartSettings) buildChartSettings; 127 | updateKeys(settings.getBuildTypeId()); 128 | setDefaultDeselectedKeys(settings); 129 | return subKeys != null && subKeys.length != 0; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/metric_aggregation/AggregationProperties.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.metric_aggregation; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import perf_statistic.common.PerformanceStatisticMetrics; 5 | import perf_statistic.common.PluginConstants; 6 | 7 | import java.io.File; 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class AggregationProperties { 14 | // Aggregate parameters 15 | private final String aggregateFile; 16 | private final boolean[] metrics = new boolean[5]; 17 | 18 | private final boolean responseCodes; 19 | private final boolean assertions; 20 | private final boolean total; 21 | 22 | private final boolean usedTestGroups; 23 | private final boolean logResultsAsTests; 24 | 25 | 26 | // Check references values 27 | private final boolean checkReferences; 28 | private double criticalVariation; 29 | private double variation; 30 | 31 | private boolean isFileValues; 32 | private String referencesDataFile; 33 | 34 | private boolean isBuildHistoryValues; 35 | private int buildCount; 36 | private boolean[] refMetrics; 37 | 38 | 39 | public AggregationProperties(@NotNull Map params) { 40 | aggregateFile = params.get(PluginConstants.PARAMS_AGGREGATE_FILE); 41 | 42 | metrics[0] = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_METRIC_AVG)); 43 | metrics[1] = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_METRIC_MAX)); 44 | metrics[2] = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_METRIC_MIN)); 45 | metrics[3] = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_METRIC_LINE90)); 46 | metrics[4] = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_HTTP_RESPONSE_CODE)); 47 | 48 | responseCodes = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_HTTP_RESPONSE_CODE)); 49 | assertions = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_CHECK_ASSERT)); 50 | total = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_CALC_TOTAL)); 51 | usedTestGroups = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_TEST_GROUPS)); 52 | 53 | logResultsAsTests = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_USED_TEST_FORMAT)); 54 | 55 | checkReferences = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_REF_CHECK)); 56 | if (checkReferences) { 57 | String tmp = params.get(PluginConstants.PARAMS_VARIATION_CRITICAL); 58 | criticalVariation = tmp == null ? 0.05 : Double.parseDouble(tmp); 59 | tmp = params.get(PluginConstants.PARAMS_VARIATION_WARN); 60 | variation = tmp == null ? Double.NEGATIVE_INFINITY : Double.parseDouble(tmp); 61 | 62 | isFileValues = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_REF_TYPE_FILE)); 63 | isBuildHistoryValues = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_REF_TYPE_BUILD_HISTORY)); 64 | if (isFileValues) { 65 | referencesDataFile = params.get(PluginConstants.PARAMS_REF_DATA_FILE); 66 | } 67 | if (isBuildHistoryValues) { 68 | buildCount = Integer.parseInt(params.get(PluginConstants.PARAMS_REF_BUILD_COUNT)); 69 | refMetrics = new boolean[3]; 70 | refMetrics[0] = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_REF_METRIC_AVG)); 71 | refMetrics[1] = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_REF_METRIC_LINE90)); 72 | refMetrics[2] = Boolean.parseBoolean(params.get(PluginConstants.PARAMS_REF_METRIC_MAX)); 73 | } 74 | } 75 | } 76 | 77 | //Aggregate parameter's getters 78 | @NotNull 79 | public String getAggregateDataFile(@NotNull final String workingDir) { 80 | if (aggregateFile == null) { 81 | throw new IllegalArgumentException("File for aggregation must not be null!"); 82 | } 83 | return workingDir + File.separator + aggregateFile; 84 | } 85 | @NotNull 86 | public String getAggregateDataFile() { 87 | return aggregateFile; 88 | } 89 | 90 | public List getSelectedMetrics() { 91 | PerformanceStatisticMetrics[] values = PerformanceStatisticMetrics.values(); 92 | if (metrics.length > values.length) { 93 | return Collections.emptyList(); // Can not remove 94 | } 95 | List metricList = new ArrayList(); 96 | for(int i = 0 ; i < metrics.length; i++) { 97 | if (metrics[i]) 98 | metricList.add(values[i]); 99 | } 100 | return metricList; 101 | } 102 | 103 | public boolean isCalculateResponseCodes() { 104 | return responseCodes; 105 | } 106 | public boolean isCalculateTotal() { 107 | return total; 108 | } 109 | public boolean isCheckAssertions() { 110 | return assertions; 111 | } 112 | 113 | public boolean isUsedTestGroups() { 114 | return usedTestGroups; 115 | } 116 | 117 | public boolean isLogResultsAsTests() { 118 | return logResultsAsTests; 119 | } 120 | 121 | public boolean isCheckReferences() { 122 | return checkReferences; 123 | } 124 | 125 | public boolean isFileValues() { 126 | return isFileValues; 127 | } 128 | 129 | public boolean isBuildHistoryValues() { 130 | return isBuildHistoryValues; 131 | } 132 | 133 | public int getBuildCount() { 134 | return buildCount; 135 | } 136 | 137 | public boolean isCountMaxReference() { 138 | return refMetrics[2]; 139 | } 140 | public boolean isCountAverageReference() { 141 | return refMetrics[0]; 142 | } 143 | public boolean isCount90LineReference() { 144 | return refMetrics[1]; 145 | } 146 | 147 | public String getReferencesDataFile(String workingDir) { 148 | if (aggregateFile == null) { 149 | throw new IllegalArgumentException("File with references values must not be null!"); 150 | } 151 | return workingDir + File.separator + referencesDataFile; 152 | } 153 | 154 | public double getCriticalVariation() { 155 | return criticalVariation; 156 | } 157 | 158 | public double getVariation() { 159 | return variation; 160 | } 161 | 162 | 163 | } 164 | -------------------------------------------------------------------------------- /server/resources/flot/jquery.flot.crosshair.js: -------------------------------------------------------------------------------- 1 | /* 2 | Flot plugin for showing crosshairs, thin lines, when the mouse hovers 3 | over the plot. 4 | 5 | crosshair: { 6 | mode: null or "x" or "y" or "xy" 7 | color: color 8 | lineWidth: number 9 | } 10 | 11 | Set the mode to one of "x", "y" or "xy". The "x" mode enables a 12 | vertical crosshair that lets you trace the values on the x axis, "y" 13 | enables a horizontal crosshair and "xy" enables them both. "color" is 14 | the color of the crosshair (default is "rgba(170, 0, 0, 0.80)"), 15 | "lineWidth" is the width of the drawn lines (default is 1). 16 | 17 | The plugin also adds four public methods: 18 | 19 | - setCrosshair(pos) 20 | 21 | Set the position of the crosshair. Note that this is cleared if 22 | the user moves the mouse. "pos" is in coordinates of the plot and 23 | should be on the form { x: xpos, y: ypos } (you can use x2/x3/... 24 | if you're using multiple axes), which is coincidentally the same 25 | format as what you get from a "plothover" event. If "pos" is null, 26 | the crosshair is cleared. 27 | 28 | - clearCrosshair() 29 | 30 | Clear the crosshair. 31 | 32 | - lockCrosshair(pos) 33 | 34 | Cause the crosshair to lock to the current location, no longer 35 | updating if the user moves the mouse. Optionally supply a position 36 | (passed on to setCrosshair()) to move it to. 37 | 38 | Example usage: 39 | var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; 40 | $("#graph").bind("plothover", function (evt, position, item) { 41 | if (item) { 42 | // Lock the crosshair to the data point being hovered 43 | myFlot.lockCrosshair({ x: item.datapoint[0], y: item.datapoint[1] }); 44 | } 45 | else { 46 | // Return normal crosshair operation 47 | myFlot.unlockCrosshair(); 48 | } 49 | }); 50 | 51 | - unlockCrosshair() 52 | 53 | Free the crosshair to move again after locking it. 54 | */ 55 | 56 | (function ($) { 57 | var options = { 58 | crosshair: { 59 | mode: null, // one of null, "x", "y" or "xy", 60 | color: "rgba(170, 0, 0, 0.80)", 61 | lineWidth: 1 62 | } 63 | }; 64 | 65 | function init(plot) { 66 | // position of crosshair in pixels 67 | var crosshair = { x: -1, y: -1, locked: false }; 68 | 69 | plot.setCrosshair = function setCrosshair(pos) { 70 | if (!pos) 71 | crosshair.x = -1; 72 | else { 73 | var o = plot.p2c(pos); 74 | crosshair.x = Math.max(0, Math.min(o.left, plot.width())); 75 | crosshair.y = Math.max(0, Math.min(o.top, plot.height())); 76 | } 77 | 78 | plot.triggerRedrawOverlay(); 79 | }; 80 | 81 | plot.clearCrosshair = plot.setCrosshair; // passes null for pos 82 | 83 | plot.lockCrosshair = function lockCrosshair(pos) { 84 | if (pos) 85 | plot.setCrosshair(pos); 86 | crosshair.locked = true; 87 | } 88 | 89 | plot.unlockCrosshair = function unlockCrosshair() { 90 | crosshair.locked = false; 91 | } 92 | 93 | function onMouseOut(e) { 94 | if (crosshair.locked) 95 | return; 96 | 97 | if (crosshair.x != -1) { 98 | crosshair.x = -1; 99 | plot.triggerRedrawOverlay(); 100 | } 101 | } 102 | 103 | function onMouseMove(e) { 104 | if (crosshair.locked) 105 | return; 106 | 107 | if (plot.getSelection && plot.getSelection()) { 108 | crosshair.x = -1; // hide the crosshair while selecting 109 | return; 110 | } 111 | 112 | var offset = plot.offset(); 113 | crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); 114 | crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); 115 | plot.triggerRedrawOverlay(); 116 | } 117 | 118 | plot.hooks.bindEvents.push(function (plot, eventHolder) { 119 | if (!plot.getOptions().crosshair.mode) 120 | return; 121 | 122 | eventHolder.mouseout(onMouseOut); 123 | eventHolder.mousemove(onMouseMove); 124 | }); 125 | 126 | plot.hooks.drawOverlay.push(function (plot, ctx) { 127 | var c = plot.getOptions().crosshair; 128 | if (!c.mode) 129 | return; 130 | 131 | var plotOffset = plot.getPlotOffset(); 132 | 133 | ctx.save(); 134 | ctx.translate(plotOffset.left, plotOffset.top); 135 | 136 | if (crosshair.x != -1) { 137 | ctx.strokeStyle = c.color; 138 | ctx.lineWidth = c.lineWidth; 139 | ctx.lineJoin = "round"; 140 | 141 | ctx.beginPath(); 142 | if (c.mode.indexOf("x") != -1) { 143 | ctx.moveTo(crosshair.x, 0); 144 | ctx.lineTo(crosshair.x, plot.height()); 145 | } 146 | if (c.mode.indexOf("y") != -1) { 147 | ctx.moveTo(0, crosshair.y); 148 | ctx.lineTo(plot.width(), crosshair.y); 149 | } 150 | ctx.stroke(); 151 | } 152 | ctx.restore(); 153 | }); 154 | 155 | plot.hooks.shutdown.push(function (plot, eventHolder) { 156 | eventHolder.unbind("mouseout", onMouseOut); 157 | eventHolder.unbind("mousemove", onMouseMove); 158 | }); 159 | } 160 | 161 | $.plot.plugins.push({ 162 | init: init, 163 | options: options, 164 | name: 'crosshair', 165 | version: '1.0' 166 | }); 167 | })(jQuery); 168 | -------------------------------------------------------------------------------- /server/src/perf_statistic/server/perf_tests/PerformanceBuildMetadataProvider.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.server.perf_tests; 2 | 3 | import jetbrains.buildServer.serverSide.CustomDataStorage; 4 | import jetbrains.buildServer.serverSide.SBuild; 5 | import jetbrains.buildServer.serverSide.SBuildType; 6 | import jetbrains.buildServer.serverSide.buildLog.BuildLog; 7 | import jetbrains.buildServer.serverSide.buildLog.LogMessage; 8 | import jetbrains.buildServer.serverSide.metadata.BuildMetadataProvider; 9 | import jetbrains.buildServer.serverSide.metadata.MetadataStorageWriter; 10 | import jetbrains.buildServer.serverSide.statistics.build.BuildDataStorage; 11 | import org.jetbrains.annotations.NotNull; 12 | import perf_statistic.common.PerformanceMessage; 13 | import perf_statistic.common.PerformanceMessageParser; 14 | import perf_statistic.common.PerformanceStatisticMetrics; 15 | import perf_statistic.common.PluginConstants; 16 | 17 | import java.math.BigDecimal; 18 | import java.util.*; 19 | import java.util.regex.Pattern; 20 | 21 | public class PerformanceBuildMetadataProvider implements BuildMetadataProvider { 22 | private static final Pattern comma_pattern = Pattern.compile(","); 23 | private static final Pattern non_word_pattern = Pattern.compile("\\W"); 24 | 25 | public static final String PERFORMANCE_META_DATA_PROVIDER_ID = "performanceMetaData"; 26 | 27 | public static final String KEY_META_DATA_WARNINGS = "performance.warnings"; 28 | private final BuildDataStorage myStorage; 29 | 30 | 31 | public PerformanceBuildMetadataProvider(final BuildDataStorage storage) { 32 | myStorage = storage; 33 | } 34 | 35 | @NotNull 36 | @Override 37 | public String getProviderId() { 38 | return PERFORMANCE_META_DATA_PROVIDER_ID; 39 | } 40 | 41 | @Override 42 | public void generateMedatadata(@NotNull SBuild build, @NotNull MetadataStorageWriter metadataStorageWriter) { 43 | SBuildType buildType = build.getBuildType(); 44 | if (buildType == null || buildType.getBuildFeaturesOfType(PluginConstants.FEATURE_TYPE_AGGREGATION).isEmpty()) 45 | return; 46 | 47 | Map warnings = new HashMap(); 48 | 49 | List serviceMessages = getJMeterServiceMessages(build.getBuildLog()); 50 | if (!serviceMessages.isEmpty()) { 51 | long buildId = build.getBuildId(); 52 | String buildTypeId = build.getBuildTypeExternalId(); 53 | 54 | CustomDataStorage sampleAliasStorage = buildType.getCustomDataStorage(PluginConstants.STORAGE_ID_TEST_ALIAS); 55 | 56 | Set metrics = new HashSet(); 57 | Set codes = new HashSet(); 58 | Set samples = new HashSet(); 59 | 60 | for (PerformanceMessage message : serviceMessages) { 61 | String testsGroup = message.getTestsGroupName(); 62 | String testName = message.getTestName(); 63 | String metricValue = message.getMetric(); 64 | String code = message.getCode(); 65 | String value = message.getValue(); 66 | boolean warning = message.isWarning(); 67 | 68 | if (warning) { 69 | String key = !testsGroup.isEmpty() ? testsGroup + " : " + testName : testName; 70 | String warn = warnings.get(key); 71 | String msg = "Exceed variation: metric - " + PerformanceStatisticMetrics.getNonReferenceTitleByKey(metricValue) 72 | + "; reference value - " + value + "; current value - " + message.getCurrValue() + "; variation - " + message.getVariation(); 73 | warnings.put(key, warn == null ? msg : warn + "
" + msg); 74 | } 75 | 76 | String alias; 77 | if (testsGroup.isEmpty()) { 78 | alias = non_word_pattern.matcher(testName).replaceAll(""); 79 | updateStorageValue(sampleAliasStorage, alias, testName); 80 | } else { 81 | alias = non_word_pattern.matcher(testsGroup + testName).replaceAll(""); 82 | updateStorageValue(sampleAliasStorage, alias, testsGroup + ": " + testName); 83 | } 84 | 85 | if (code != null && !code.isEmpty()) { 86 | alias += non_word_pattern.matcher(code).replaceAll(""); 87 | } 88 | myStorage.publishValue(buildTypeId + '_' + metricValue + '_' + alias, buildId, new BigDecimal(value)); 89 | 90 | if (metricValue.equals(PerformanceStatisticMetrics.RESPONSE_CODE.getKey())) { 91 | codes.add(code); 92 | } else { 93 | metrics.add(metricValue); 94 | samples.add(alias); 95 | } 96 | } 97 | // updateStorageValue(sampleOrderStorage, PerformanceStatisticMetrics.RESPONSE_CODE.getKey(), "0"); // response codes is the first 98 | 99 | CustomDataStorage commonStorage = buildType.getCustomDataStorage(PluginConstants.STORAGE_ID_COMMON_JMETER); 100 | updateStorageValue(commonStorage, PluginConstants.STORAGE_KEY_METRIC, metrics); 101 | updateStorageValue(commonStorage, PluginConstants.STORAGE_KEY_CODE, codes); 102 | updateStorageValue(commonStorage, PluginConstants.STORAGE_KEY_SAMPLES, samples); 103 | } 104 | 105 | if (!warnings.isEmpty()) { 106 | metadataStorageWriter.addParameters(KEY_META_DATA_WARNINGS, warnings); 107 | } 108 | } 109 | 110 | /** 111 | * parses build log, returns collection of service messages provided by jmeter agent runner 112 | * 113 | * @param log 114 | * @return 115 | */ 116 | private List getJMeterServiceMessages(@NotNull BuildLog log) { 117 | List messages = new ArrayList(); 118 | for (Iterator iterator = log.getMessagesIterator(); iterator.hasNext(); ) { 119 | PerformanceMessage message = PerformanceMessageParser.getPerformanceTestingMessage(iterator.next().getText().trim()); 120 | if (message != null) { 121 | messages.add(message); 122 | } 123 | } 124 | return messages; 125 | } 126 | 127 | private void updateStorageValue(@NotNull CustomDataStorage storage, @NotNull String key, @NotNull String value) { 128 | String oldValue = storage.getValue(key); 129 | if (oldValue == null || !value.equals(oldValue)) { 130 | storage.putValue(key, value); 131 | storage.flush(); 132 | } 133 | } 134 | 135 | private void updateStorageValue(@NotNull CustomDataStorage storage, @NotNull String key, @NotNull Collection values) { 136 | String parametersValues = storage.getValue(key); 137 | if (parametersValues != null) { 138 | Collections.addAll(values, comma_pattern.split(parametersValues)); 139 | } 140 | StringBuilder builder = new StringBuilder(); 141 | for (String value : values) { 142 | builder.append(value).append(','); 143 | } 144 | storage.putValue(key, builder.toString()); 145 | storage.flush(); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /agent/src/perf_statistic/agent/remote_monitoring/RemoteMonitoring.java: -------------------------------------------------------------------------------- 1 | package perf_statistic.agent.remote_monitoring; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.io.*; 7 | import java.net.Socket; 8 | import java.util.Queue; 9 | import java.util.concurrent.ConcurrentLinkedQueue; 10 | 11 | public final class RemoteMonitoring { 12 | public static final Logger CLASS_LOGGER = Logger.getLogger(RemoteMonitoring.class); 13 | 14 | private static final String METRICS_COMMAND = "metrics:cpu:combined\tcpu:user\tcpu:system\tcpu:iowait\t" 15 | + "memory:used\t" 16 | + "disks:reads\tdisks:writes\t" 17 | + "swap:used\t" 18 | + "jmx:url=localhost\\:4711:gc-time\t" 19 | + "jmx:url=localhost\\:4711:class-count\t" 20 | // + "jmx:url=localhost\\:4711:memory-usage\t" 21 | // + "jmx:url=localhost\\:4711:memory-committed\t" 22 | + "jmx:url=localhost\\:4711:memorypool-usage\t" 23 | + "jmx:url=localhost\\:4711:memorypool-committed\t\n"; 24 | 25 | private volatile boolean stopped; 26 | @NotNull private final Queue monitoringResults; 27 | @NotNull private final Thread getter; 28 | @NotNull private final Thread processor; 29 | 30 | 31 | public RemoteMonitoring(final RemoteMonitoringProperties properties, final String resultFile) { 32 | stopped = false; 33 | monitoringResults = new ConcurrentLinkedQueue(); 34 | 35 | getter = new Thread(){ 36 | private long interval = 0; 37 | 38 | private Socket socket = null; 39 | private BufferedWriter writer = null; 40 | private BufferedReader reader = null; 41 | 42 | @Override 43 | public void run() { 44 | try { 45 | socket = new Socket(properties.getRemoteMonitoringHost(), properties.getRemoteMonitoringPort()); 46 | writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); 47 | reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 48 | 49 | if (socket.isConnected()) { 50 | writer.write("test\n"); 51 | writer.flush(); 52 | String testResult = reader.readLine(); 53 | if (testResult != null && "Yep".equals(testResult)) { 54 | setInterval(properties.getRemoteInterval()); 55 | writer.write(METRICS_COMMAND); 56 | writer.flush(); 57 | 58 | while (!stopped && socket.isConnected()) { 59 | processResults(); 60 | } 61 | } 62 | writer.write("shutdown\n"); 63 | } 64 | } catch (IOException e) { 65 | CLASS_LOGGER.error(e); 66 | } finally { 67 | if (reader != null) { 68 | try { 69 | writer.close(); 70 | } catch (IOException e) { 71 | CLASS_LOGGER.error(e); 72 | } 73 | } 74 | if (writer != null) { 75 | try { 76 | writer.close(); 77 | } catch (IOException e) { 78 | CLASS_LOGGER.error(e); 79 | } 80 | } 81 | if (socket != null && !socket.isClosed()) { 82 | try { 83 | socket.close(); 84 | } catch (IOException e) { 85 | CLASS_LOGGER.error(e); 86 | } 87 | } 88 | } 89 | } 90 | 91 | private void setInterval(String strInterval) throws IOException { 92 | if (strInterval != null) { 93 | interval = Long.parseLong(strInterval) * 1000; 94 | writer.write("interval:" + strInterval + "\n"); 95 | writer.flush(); 96 | } 97 | } 98 | 99 | private void processResults() throws IOException { 100 | long start = System.currentTimeMillis(); 101 | String line = reader.readLine(); 102 | monitoringResults.add(line + start); 103 | long spend = System.currentTimeMillis() - start; 104 | if (spend < interval) { 105 | try { 106 | Thread.sleep(interval - spend); 107 | } catch (InterruptedException e) { 108 | CLASS_LOGGER.error(e); 109 | } 110 | } 111 | } 112 | }; 113 | 114 | processor = new Thread(){ 115 | private long interval = 1000; 116 | private long delay = properties.getRemoteClockDelay(); 117 | 118 | @Override 119 | public void run() { 120 | if (properties.getRemoteInterval() != null) { 121 | interval = Long.parseLong(properties.getRemoteInterval()) * 1000; 122 | } 123 | BufferedWriter resultWriter = null; 124 | try { 125 | resultWriter = new BufferedWriter(new FileWriter(resultFile)); 126 | while (true) { 127 | if (!monitoringResults.isEmpty()) { 128 | processLine(resultWriter, monitoringResults.poll()); 129 | } else if (!stopped) { 130 | try { 131 | Thread.sleep(interval); 132 | } catch (InterruptedException e) { 133 | CLASS_LOGGER.error(e); 134 | } 135 | } else { 136 | break; 137 | } 138 | } 139 | } catch (IOException e) { 140 | CLASS_LOGGER.error(e); 141 | } finally { 142 | if (resultWriter != null) { 143 | try { 144 | resultWriter.close(); 145 | } catch (IOException e) { 146 | CLASS_LOGGER.error(e); 147 | } 148 | } 149 | } 150 | } 151 | 152 | private void processLine(BufferedWriter writer, String line) throws IOException { 153 | String[] parts = line.split("\t"); 154 | if (parts.length < 13) { 155 | return; 156 | } 157 | long time = Long.parseLong(parts[12]) + delay; 158 | 159 | writer.write(String.valueOf(time) + "\t" + parts[0] + "\tcpu\t\n"); 160 | writer.write(String.valueOf(time) + "\t" + parts[1] + "\tcpu user\t\n"); 161 | writer.write(String.valueOf(time) + "\t" + parts[2] + "\tcpu system\t\n"); 162 | writer.write(String.valueOf(time) + "\t" + parts[3] + "\tcpu iowait\t\n"); 163 | writer.write(String.valueOf(time) + "\t" + parts[4] + "\tmemory used\t\n"); 164 | writer.write(String.valueOf(time) + "\t" + parts[5] + "\tdisks reads\t\n"); 165 | writer.write(String.valueOf(time) + "\t" + parts[6] + "\tdisks writes\t\n"); 166 | writer.write(String.valueOf(time) + "\t" + parts[7] + "\tswap used\t\n"); 167 | writer.write(String.valueOf(time) + "\t" + parts[8] + "\tjmx gc-time\t\n"); 168 | writer.write(String.valueOf(time) + "\t" + parts[9] + "\tjmx class-count\t\n"); 169 | // writer.write(String.valueOf(time) + "\t" + parts[9] + "\tjmx memory-usage\t\n"); 170 | // writer.write(String.valueOf(time) + "\t" + parts[10] + "\tjmx memory-committed\t\n"); 171 | writer.write(String.valueOf(time) + "\t" + parts[10] + "\tjmx memorypool-usage\t\n"); 172 | writer.write(String.valueOf(time) + "\t" + parts[11] + "\tjmx memorypool-committed\t\n"); 173 | writer.flush(); 174 | } 175 | }; 176 | 177 | } 178 | 179 | public void start() { 180 | getter.start(); 181 | processor.start(); 182 | } 183 | 184 | public void stop() { 185 | stopped = true; 186 | try { 187 | getter.join(); 188 | processor.join(); 189 | } catch (InterruptedException e) { 190 | CLASS_LOGGER.error(e); 191 | } 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | Performance tests analysis plugin for TeamCity 2 | ============================================== 3 | This is plugin for TeamCity 8.0 that helps to organize simplest performance testing in CI. 4 | It has the ability to aggregate results from a log file, calculate metrics, 5 | compare results with reference values, monitor a remote machine with the tested application. 6 | Additionally, it allows viewing all the results of performance tests as charts. 7 | 8 | Last version: https://teamcity.jetbrains.com/repository/download/TeamCityPluginsByJetBrains_JMeterPlugin_Build/.lastSuccessful/jmeter.zip 9 | 10 | How it works 11 | ============== 12 | The plugin has two features: 13 | 1) Performance Metrics Calculation - a build feature to configure settings for calcuation performance metrics; 14 | 2) Performance Statistic - a tab with values of average metrics per test by builds; 15 | 16 | 3) Performance Remote Monitoring - a build feature to configure plugin settings for build configuration; 17 | 4) RemotePerfMon - a tab with monitoring results for the build. 18 | 19 | 20 | Performance Metrics Calculation 21 | =========================================== 22 | required - * 23 | 24 | Aggregation: 25 | ------------------------------------------- 26 | File to aggregate results: * 27 | a relative path to the file with the raw test results to calculate average metrics; 28 | Aggregate metrics: * 29 | average, min, max, 90% line, 30 | response codes: it allows to calculated count of each code in test results - http code, or some other test result. 31 | (for example you can log some id for exception occurred during test run: OK, InternalError, NPE) 32 | note: response code NOT fail build, 33 | to DETECT errors or non-200 http codes and fail build in this case use assertions! 34 | Format settings: 35 | total - calculate total average values for all tests; Note: if thread groups are used, total will be calculated by groups 36 | assertions - fail the build if any assertion check fails, 37 | thread groups - you can group tests in thread groups(20,100 threads..), in this case, test name must have the specific format at the log 38 | 39 | used TeamCity tests format 40 | This option must be set if your tests are not created and run using Test framework, like TestNG, JUnit; and you aren't use service messages to log test result in build log. 41 | 42 | Check reference data (optionally): 43 | The build will be failed if the aggregated values exceed reference values considering variation. 44 | ------------------------------------------- 45 | Get reference values from: * 46 | - file - set if you have a static reference values. In this case, set relative path to reference data; 47 | - builds - not supported yet 48 | Variation: 49 | the value of variation [0..1] in decimal format; default - 0.05 (5%); 50 | 51 | 52 | 53 | 54 | 55 | 56 | Performance Remote Monitoring: 57 | =========================================== 58 | allows monitoring some system and jvm statistics on the remote machine with the tested application. 59 | ------------------------------------------- 60 | Build step to analyze: * 61 | the name of the build step which starts tests. 62 | Note: The plugin uses Server Agent(http://jmeter-plugins.org/wiki/PerfMonAgent/) to collect metric values on the remoted machine. So, the agent must be run on the remote machine before the build step begins. 63 | 64 | Remote machine: * 65 | host *, port * - settings for access to the running agent; 66 | clock delay - if tests will be performed from another machine(non-BuildAgent), set system clock delay between build agent and test machine to sync time of monitoring. 67 | 68 | Monitoring interval: * - in seconds, to collect metrics from agent; 69 | 70 | Monitored parameters, supported by plugin: 71 | - CPU system/user/all/iowait (all in percent) 72 | - Memory used (bytes) 73 | - Disk I/O reads/writes (ops) 74 | - JMX memory heap, pools commited/usage; gc time; class loaded; 75 | 76 | 77 | 78 | Formats of the file to aggregate results: 79 | ========================================== 80 | The first line must contain the titles of columns. 81 | Delimiter - \t 82 | Label is the same test/sample name; 83 | If thread groups are used; label must have format: :