Repository: pinpoint-apm/pinpoint
Issue: Add Transaction metric collection
Labels: enhancement

Add per-agent transaction count metric collection support for all 4 types of transactions:
1. Sampled New
2. Unsampled New
3. Sampled Continuation
4. Unsampled Continuation


Issue comments:
Currently tps is calculated by factoring in the following situations:
1. When a new trace is created and should be sampled.
2. When a new trace is created and should not be sampled.
3. When a trace is continued with sampling set false.

Need to consider 1 more situation:
1. When a trace is continued, and sampling is set true.

Should continue after #1103 

Changing spec to collect the number of transaction over a certain period of time.
Tps calculation will be done when querying using this data.

Reopening to remove `version` field.

Reopening for UI


Pull request review comments:

Patch that resolved the issue:
diff --git a/web/src/main/webapp/common/help/help-content-en.js b/web/src/main/webapp/common/help/help-content-en.js
index 57b9ed66761a..76154867f583 100644
--- a/web/src/main/webapp/common/help/help-content-en.js
+++ b/web/src/main/webapp/common/help/help-content-en.js
@@ -467,7 +467,19 @@
 						desc: "Both the JVM's and the system's CPU usage are collected"
 					}]
 				}]
-			}
+			},
+            tps: {
+                mainStyle: "",
+                title: "TPS",
+                desc: "Transactions per second received by the server",
+                category: [{
+                    title: "[Legend]",
+                    items: [{
+                        name: "TPS",
+                        desc: "Number of transactions received by the server every second"
+                    }]
+                }]
+            }
 		},
 		callTree: {
 			column: {
diff --git a/web/src/main/webapp/common/help/help-content-ko.js b/web/src/main/webapp/common/help/help-content-ko.js
index 6c7871afe53c..daefa105c6e7 100644
--- a/web/src/main/webapp/common/help/help-content-ko.js
+++ b/web/src/main/webapp/common/help/help-content-ko.js
@@ -486,7 +486,19 @@
 						desc: "JVM과 전체 시스템의 CPU 사용량 모두 수집됩니다."
 					}]
 				}]
-			}
+			},
+            tps: {
+                mainStyle: "",
+                title: "TPS",
+                desc: "서버로 인입된 초당 트랜잭션 수",
+                category: [{
+                    title: "[범례]",
+                    items: [{
+                        name: "TPS",
+                        desc: "초당 트랜잭션 수"
+                    }]
+                }]
+            }
 		},
 		callTree: {
 			column: {
diff --git a/web/src/main/webapp/common/services/agent-dao.service.js b/web/src/main/webapp/common/services/agent-dao.service.js
index 897939699447..4c4c0fd96736 100644
--- a/web/src/main/webapp/common/services/agent-dao.service.js
+++ b/web/src/main/webapp/common/services/agent-dao.service.js
@@ -167,6 +167,38 @@
 	            
 	            return newData;
 	        };
+    
+            /**
+             * parse tps chart data for amcharts
+             * @param tps
+             * @param agentStat
+             * @returns {Array}
+             */
+	        this.parseTpsChartDataForAmcharts = function (tps, agentStat) {
+	            // TPS data availability check
+	            var totalTpsData = agentStat.charts['TPS_TOTAL'];
+	            if (totalTpsData) {
+	                tps.isAvailable = true;
+	            } else {
+	                return;
+	            }
+	            var newData = [],
+	            DATA_UNAVAILABLE = -1,
+	            pointsTotalTps = totalTpsData.points;
+	            
+	            for (var i = 0; i < pointsTotalTps.length; ++i) {
+	                var thisData = {
+	                        time: moment(pointsTotalTps[i].timestamp).toString('YYYY-MM-dd HH:mm:ss')
+	                };
+	                var totalTps = typeof agentStat.charts['TPS_TOTAL'].points[i].avgVal == "number" ? agentStat.charts['TPS_TOTAL'].points[i].avgVal.toFixed(2) : 0.00;
+	                if (!(totalTps < 0)) {
+	                    thisData.totalTps = totalTps;
+	                }
+	                newData.push(thisData);
+	            }
+	            
+	            return newData;
+	        }
 	    }
 	]);
 })();
\ No newline at end of file
diff --git a/web/src/main/webapp/features/agentInfo/agent-info.directive.js b/web/src/main/webapp/features/agentInfo/agent-info.directive.js
index fbee377c5ff2..218c93177b85 100644
--- a/web/src/main/webapp/features/agentInfo/agent-info.directive.js
+++ b/web/src/main/webapp/features/agentInfo/agent-info.directive.js
@@ -24,7 +24,7 @@
 	
 	                // define private variables of methods
 	                var getAgentStat, getLink, initServiceInfo, showCharts, parseMemoryChartDataForAmcharts, parseCpuLoadChartDataForAmcharts,
-	                broadcastToCpuLoadChart, resetServerMetaDataDiv, initTooltip;
+	                broadcastToCpuLoadChart, broadcastToTpsChart, resetServerMetaDataDiv, initTooltip;
 	
 	                // initialize
 	                scope.agentInfoTemplate = 'features/agentInfo/agentInfoReady.html';
@@ -55,6 +55,13 @@
 			                	position: "top",
 			                	trigger: "click"
 			                });
+			                jQuery('.tpsTooltip').tooltipster({
+			                    content: function() {
+			                        return helpContentTemplate(helpContentService.inspector.tps);
+			                    },
+			                    position: "top",
+			                    trigger: "click"
+			                });
 	                	}
 	                };
 	
@@ -112,26 +119,30 @@
 	                 */
 	                showCharts = function (agentStat) {
 	
-	                    var heap = { id: 'heap', title: 'Heap', span: 'span12', line: [
+	                    var heap = { id: 'heap', title: 'Heap Usage', span: 'span12', line: [
 	                        { id: 'JVM_MEMORY_HEAP_USED', key: 'Used', values: [], isFgc: false },
 	                        { id: 'JVM_MEMORY_HEAP_MAX', key: 'Max', values: [], isFgc: false },
 	                        { id: 'fgc', key: 'FGC', values: [], bar: true, isFgc: true }
 	                    ]};
 	
-	                    var nonheap = { id: 'nonheap', title: 'PermGen', span: 'span12', line: [
+	                    var nonheap = { id: 'nonheap', title: 'PermGen Usage', span: 'span12', line: [
 	                        { id: 'JVM_MEMORY_NON_HEAP_USED', key: 'Used', values: [], isFgc: false },
 	                        { id: 'JVM_MEMORY_NON_HEAP_MAX', key: 'Max', values: [], isFgc: false },
 	                        { id: 'fgc', key: 'FGC', values: [], bar: true, isFgc: true }
 	                    ]};
 	                    
 	                    var cpuLoad = { id: 'cpuLoad', title: 'JVM/System Cpu Usage', span: 'span12', isAvailable: false};
+	                    
+	                    var tps = { id: 'tps', title: 'Transactions Per Second', span: 'span12', isAvailable: false };
 	
 	                    scope.memoryGroup = [ heap, nonheap ];
 	                    scope.cpuLoadChart = cpuLoad;
+	                    scope.tpsChart = tps;
 	
 	                    scope.$broadcast('jvmMemoryChartDirective.initAndRenderWithData.forHeap', AgentDaoService.parseMemoryChartDataForAmcharts(heap, agentStat), '100%', '270px');
 	                    scope.$broadcast('jvmMemoryChartDirective.initAndRenderWithData.forNonHeap', AgentDaoService.parseMemoryChartDataForAmcharts(nonheap, agentStat), '100%', '270px');
 	                    scope.$broadcast('cpuLoadChartDirective.initAndRenderWithData.forCpuLoad', AgentDaoService.parseCpuLoadChartDataForAmcharts(cpuLoad, agentStat), '100%', '270px');
+	                    scope.$broadcast('tpsChartDirective.initAndRenderWithData.forTps', AgentDaoService.parseTpsChartDataForAmcharts(tps, agentStat), '100%', '270px');
 	                };
 	                
 	                /**
@@ -182,6 +193,12 @@
 	                	}
 	                }
 	                
+	                broadcastToTpsChart = function(e, event) {
+	                    if (scope.tpsChart.isAvailable) {
+	                        scope.$broadcast('tpsChartDirective.showCursorAt.forTps', event.index);
+	                    }
+	                }
+	                
 	                scope.openDetail = function() {
 	                	$('#serverMetaDataDiv').modal({});
 	                };
@@ -202,6 +219,7 @@
 	                scope.$on('jvmMemoryChartDirective.cursorChanged.forHeap', function (e, event) {
 	                    scope.$broadcast('jvmMemoryChart.showCursorAt.forNonHeap', event.index);
 	                    broadcastToCpuLoadChart(e, event);
+	                    broadcastToTpsChart(e, event);
 	                });
 	
 	                /**
@@ -210,6 +228,7 @@
 	                scope.$on('jvmMemoryChartDirective.cursorChanged.forNonHeap', function (e, event) {
 	                    scope.$broadcast('jvmMemoryChartDirective.showCursorAt.forHeap', event.index);
 	                    broadcastToCpuLoadChart(e, event);
+	                    broadcastToTpsChart(e, event);
 	                });
 	
 	                /**
@@ -218,7 +237,17 @@
 	                scope.$on('cpuLoadChartDirective.cursorChanged.forCpuLoad', function (e, event) {
 	                    scope.$broadcast('jvmMemoryChartDirective.showCursorAt.forHeap', event.index);
 	                    scope.$broadcast('jvmMemoryChartDirective.showCursorAt.forNonHeap', event.index);
+	                    broadcastToTpsChart(e, event);
 	                });
+    
+                    /**
+                     * scope event on tpsChart.cursorChanged.forTps
+                     */
+                    scope.$on('tpsChartDirective.cursorChanged.forTps', function (e, event) {
+                        scope.$broadcast('jvmMemoryChartDirective.showCursorAt.forHeap', event.index);
+                        scope.$broadcast('jvmMemoryChartDirective.showCursorAt.forNonHeap', event.index);
+                        broadcastToCpuLoadChart(e, event);
+                    });
 	            }
 	        };
 	    }
diff --git a/web/src/main/webapp/features/agentInfo/agentInfoMain.html b/web/src/main/webapp/features/agentInfo/agentInfoMain.html
index 2c54ba0b5582..d62599d493e6 100644
--- a/web/src/main/webapp/features/agentInfo/agentInfoMain.html
+++ b/web/src/main/webapp/features/agentInfo/agentInfoMain.html
@@ -56,25 +56,26 @@ <h3>Information</h3>
         </div>
     </div>
     <div>
-        <h3>Memory & Garbage Collection</h3>
         <div class="row">
             <div class="col-md-6">
-                <h5><span class="glyphicon glyphicon-question-sign heapTooltip" style="cursor:pointer;float:right;"></span> {{memoryGroup[0].title}}</h5>
+                <h4><span class="glyphicon glyphicon-question-sign heapTooltip" style="cursor:pointer;float:right;"></span> {{memoryGroup[0].title}}</h4>
                 <jvm-memory-chart-directive namespace="forHeap"></jvm-memory-chart-directive>
             </div>
             <div class="col-md-6">
-                <h5><span class="glyphicon glyphicon-question-sign permGenTooltip" style="cursor:pointer;float:right;"></span> {{memoryGroup[1].title}}</h5>
+                <h4><span class="glyphicon glyphicon-question-sign permGenTooltip" style="cursor:pointer;float:right;"></span> {{memoryGroup[1].title}}</h4>
                 <jvm-memory-chart-directive namespace="forNonHeap"></jvm-memory-chart-directive>
             </div>
         </div>
-    </div>
-    <div>
-        <h3>Cpu Usage</h3>
+        <hr>
         <div class="row">
             <div class="col-md-6">
-                <h5><span class="glyphicon glyphicon-question-sign cpuUsageTooltip" style="cursor:pointer;float:right;"></span> {{cpuLoadChart.title}}</h5>
+                <h4><span class="glyphicon glyphicon-question-sign cpuUsageTooltip" style="cursor:pointer;float:right;"></span> {{cpuLoadChart.title}}</h4>
                 <cpu-load-chart-directive namespace="forCpuLoad"></cpu-load-chart-directive>
             </div>
+            <div class="col-md-6">
+                <h4><span class="glyphicon glyphicon-question-sign tpsTooltip" style="cursor:pointer;float:right;"></span> {{tpsChart.title}}</h4>
+                <tps-chart-directive namespace="forTps"></tps-chart-directive>
+            </div>
         </div>
     </div>
     <div class="modal fade bs-modal-lg center" id="serverMetaDataDiv" tabindex="-1" role="dialog" aria-labelledby="myServerMetaDataDiv"
diff --git a/web/src/main/webapp/features/tpsChart/tps-chart.directive.js b/web/src/main/webapp/features/tpsChart/tps-chart.directive.js
new file mode 100644
index 000000000000..3e4e4dad6bc5
--- /dev/null
+++ b/web/src/main/webapp/features/tpsChart/tps-chart.directive.js
@@ -0,0 +1,164 @@
+(function() {
+    'use strict';
+    /**
+     * (en)tpsChartDirective 
+     * @ko tpsChartDirective
+     * @group Directive
+     * @name tpsChartDirective
+     * @class
+     */ 
+    angular.module('pinpointApp').directive('tpsChartDirective', ['$timeout',
+        function ($timeout) {
+            return {
+                template: '<div></div>',
+                replace: true,
+                restrict: 'E',
+                scope: {
+                    namespace: '@' // string value
+                },
+                link: function postLink(scope, element, attrs) {
+    
+                    // define variables
+                    var sId, oChart;
+    
+                    // define variables of methods
+                    var setIdAutomatically, setWidthHeight, render, showCursorAt, resize;
+    
+                    /**
+                     * set id automatically
+                     */
+                    setIdAutomatically = function () {
+                        sId = 'multipleValueAxesId-' + scope.namespace;
+                        element.attr('id', sId);
+                    };
+    
+                    /**
+                     * set width height
+                     * @param w
+                     * @param h
+                     */
+                    setWidthHeight = function (w, h) {
+                        if (w) element.css('width', w);
+                        if (h) element.css('height', h);
+                    };
+    
+                    /**
+                     * render
+                     * @param chartData
+                     */
+                    render = function (chartData) {
+                        var options = {
+                            "type": "serial",
+                            "theme": "light",
+                            "autoMargins": false,
+                            "marginTop": 10,
+                            "marginLeft": 70,
+                            "marginRight": 70,
+                            "marginBottom": 30,
+                            "legend": {
+                                "useGraphSettings": true,
+                                "autoMargins": true,
+                                "align" : "right",
+                                "position": "top",
+                                "valueWidth": 70
+                            },
+                            "usePrefixes": true,
+                            "dataProvider": chartData,
+                            "valueAxes": [
+                                {
+                                    "id": "v1",
+                                    "gridAlpha": 0,
+                                    "axisAlpha": 1,
+                                    "position": "left",
+                                    "title": "TPS",
+                                    "minimum" : 0
+                                },
+                            ],
+                            "graphs": [
+                                {
+                                    "valueAxis": "v1",
+                                    "balloonText": "[[value]]",
+                                    "legendValueText": "[[value]]",
+                                    "lineColor": "rgb(31, 119, 180)",
+                                    "fillColor": "rgb(31, 119, 180)",
+                                    "title": "TPS",
+                                    "valueField": "totalTps",
+                                    "fillAlphas": 0.4,
+                                    "connect": false
+                                }
+                            ],
+                            "chartCursor": {
+                                "categoryBalloonAlpha": 0.7,
+                                "fullWidth": true,
+                                "cursorAlpha": 0.1
+                            },
+                            "categoryField": "time",
+                            "categoryAxis": {
+                                "axisColor": "#DADADA",
+                                "startOnAxis": true,
+                                "gridPosition": "start",
+                                "labelFunction": function (valueText, serialDataItem, categoryAxis) {
+                                    return moment(valueText).format("HH:mm:ss");
+                                }
+                            }
+                        };
+                        $timeout(function () {
+                            oChart = AmCharts.makeChart(sId, options);
+                            oChart.chartCursor.addListener('changed', function (event) {
+                                scope.$emit('tpsChartDirective.cursorChanged.' + scope.namespace, event);
+                            });
+                        });
+                    };
+
+                    /**
+                     * show cursor at
+                     * @param category
+                     */
+                    showCursorAt = function (category) {
+                        if (category) {
+                            if (angular.isNumber(category)) {
+                                category = oChart.dataProvider[category].time;
+                            }
+                            oChart.chartCursor.showCursorAt(category);
+                        } else {
+                            oChart.chartCursor.hideCursor();
+                        }
+                    };
+
+                    /**
+                     * resize
+                     */
+                    resize = function () {
+                        if (oChart) {
+                            oChart.validateNow();
+                            oChart.validateSize();
+                        }
+                    };
+
+                    /**
+                     * scope event on tpsChartDirective.initAndRenderWithData.namespace
+                     */
+                    scope.$on('tpsChartDirective.initAndRenderWithData.' + scope.namespace, function (event, data, w, h) {
+                        setIdAutomatically();
+                        setWidthHeight(w, h);
+                        render(data);
+                    });
+
+                    /**
+                     * scope event on tpsChartDirective.showCursorAt.namespace
+                     */
+                    scope.$on('tpsChartDirective.showCursorAt.' + scope.namespace, function (event, category) {
+                        showCursorAt(category)
+                    });
+
+                    /**
+                     * scope event on tpsChartDirective.resize.namespace
+                     */
+                    scope.$on('tpsChartDirective.resize.' + scope.namespace, function (event) {
+                        resize();
+                    });
+                }
+            };
+        }
+    ]);
+})();
\ No newline at end of file
diff --git a/web/src/main/webapp/index.html b/web/src/main/webapp/index.html
index 7aee7b539ac0..3af87681f428 100644
--- a/web/src/main/webapp/index.html
+++ b/web/src/main/webapp/index.html
@@ -493,6 +493,7 @@ <h4>한글</h4>
 <script src="features/loadChart/load-chart.directive.js?v=${buildTime}"></script>
 <script src="features/jvmMemoryChart/jvm-memory-chart.directive.js?v=${buildTime}"></script>
 <script src="features/cpuLoadChart/cpu-load-chart.directive.js?v=${buildTime}"></script>
+<script src="features/tpsChart/tps-chart.directive.js?v=${buildTime}"></script>
 <script src="features/loading/loading.directive.js?v=${buildTime}"></script>
 <script src="features/configuration/configuration.controller.js?v=${buildTime}"></script>
 <script src="features/configuration/help/help.controller.js?v=${buildTime}"></script>


Test patch: