[JAVA-9604] Refactor and code clean-up for Metrics article

This commit is contained in:
Haroon Khan
2022-01-31 12:45:28 +00:00
parent 30c54f2623
commit 01ca4575ba
25 changed files with 373 additions and 343 deletions
@@ -1,38 +1,19 @@
package com.baeldung.spring;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.context.request.RequestContextListener;
/**
* Main Application Class - uses Spring Boot. Just run this as a normal Java
* class to run up a Jetty Server (on http://localhost:8082/spring-rest-full)
*
*/
@EnableScheduling
@EnableAutoConfiguration
@ComponentScan("com.baeldung")
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
@Override
public void onStartup(ServletContext sc) throws ServletException {
// Manages the lifecycle of the root application context
sc.addListener(new RequestContextListener());
}
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
@@ -1,36 +0,0 @@
package com.baeldung.spring;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@ComponentScan("com.baeldung.web")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
public WebConfig() {
super();
}
@Bean
public ViewResolver viewResolver() {
final InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
// API
@Override
public void addViewControllers(final ViewControllerRegistry registry) {
registry.addViewController("/graph.html");
registry.addViewController("/homepage.html");
}
}
@@ -1,52 +0,0 @@
package com.baeldung.web.controller;
import java.util.Map;
import com.baeldung.web.metric.IActuatorMetricService;
import com.baeldung.web.metric.IMetricService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(value = "/auth/")
public class RootController {
@Autowired
private IMetricService metricService;
@Autowired
private IActuatorMetricService actMetricService;
public RootController() {
super();
}
// API
@RequestMapping(value = "/metric", method = RequestMethod.GET)
@ResponseBody
public Map getMetric() {
return metricService.getFullMetric();
}
@RequestMapping(value = "/status-metric", method = RequestMethod.GET)
@ResponseBody
public Map getStatusMetric() {
return metricService.getStatusMetric();
}
@RequestMapping(value = "/metric-graph", method = RequestMethod.GET)
@ResponseBody
public Object[][] drawMetric() {
final Object[][] result = metricService.getGraphData();
for (int i = 1; i < result[0].length; i++) {
result[0][i] = result[0][i].toString();
}
return result;
}
}
@@ -1,110 +0,0 @@
package com.baeldung.web.metric;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
@Service
public class ActuatorMetricService implements IActuatorMetricService {
@Autowired
private MeterRegistry publicMetrics;
private final List<ArrayList<Integer>> statusMetricsByMinute;
private final List<String> statusList;
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public ActuatorMetricService() {
super();
statusMetricsByMinute = new ArrayList<ArrayList<Integer>>();
statusList = new ArrayList<String>();
}
@Override
public Object[][] getGraphData() {
final Date current = new Date();
final int colCount = statusList.size() + 1;
final int rowCount = statusMetricsByMinute.size() + 1;
final Object[][] result = new Object[rowCount][colCount];
result[0][0] = "Time";
int j = 1;
for (final String status : statusList) {
result[0][j] = status;
j++;
}
for (int i = 1; i < rowCount; i++) {
result[i][0] = dateFormat.format(new Date(current.getTime() - (60000 * (rowCount - i))));
}
List<Integer> minuteOfStatuses;
List<Integer> last = new ArrayList<Integer>();
for (int i = 1; i < rowCount; i++) {
minuteOfStatuses = statusMetricsByMinute.get(i - 1);
for (j = 1; j <= minuteOfStatuses.size(); j++) {
result[i][j] = minuteOfStatuses.get(j - 1) - (last.size() >= j ? last.get(j - 1) : 0);
}
while (j < colCount) {
result[i][j] = 0;
j++;
}
last = minuteOfStatuses;
}
return result;
}
// Non - API
@Scheduled(fixedDelay = 60000)
private void exportMetrics() {
final ArrayList<Integer> lastMinuteStatuses = initializeStatuses(statusList.size());
for (final Meter counterMetric : publicMetrics.getMeters()) {
updateMetrics(counterMetric, lastMinuteStatuses);
}
statusMetricsByMinute.add(lastMinuteStatuses);
}
private ArrayList<Integer> initializeStatuses(final int size) {
final ArrayList<Integer> counterList = new ArrayList<Integer>();
for (int i = 0; i < size; i++) {
counterList.add(0);
}
return counterList;
}
private void updateMetrics(final Meter counterMetric, final ArrayList<Integer> statusCount) {
String status = "";
int index = -1;
int oldCount = 0;
if (counterMetric.getId().getName().contains("counter.status.")) {
status = counterMetric.getId().getName().substring(15, 18); // example 404, 200
appendStatusIfNotExist(status, statusCount);
index = statusList.indexOf(status);
oldCount = statusCount.get(index) == null ? 0 : statusCount.get(index);
statusCount.set(index, (int)((Counter) counterMetric).count() + oldCount);
}
}
private void appendStatusIfNotExist(final String status, final ArrayList<Integer> statusCount) {
if (!statusList.contains(status)) {
statusList.add(status);
statusCount.add(0);
}
}
//
}
@@ -1,92 +0,0 @@
package com.baeldung.web.metric;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.search.Search;
@Service
public class CustomActuatorMetricService implements ICustomActuatorMetricService {
@Autowired
private MeterRegistry registry;
private final List<ArrayList<Integer>> statusMetricsByMinute;
private final List<String> statusList;
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public CustomActuatorMetricService() {
super();
statusMetricsByMinute = new ArrayList<ArrayList<Integer>>();
statusList = new ArrayList<String>();
}
// API
@Override
public void increaseCount(final int status) {
String counterName = "counter.status." + status;
registry.counter(counterName).increment(1);
if (!statusList.contains(counterName)) {
statusList.add(counterName);
}
}
@Override
public Object[][] getGraphData() {
final Date current = new Date();
final int colCount = statusList.size() + 1;
final int rowCount = statusMetricsByMinute.size() + 1;
final Object[][] result = new Object[rowCount][colCount];
result[0][0] = "Time";
int j = 1;
for (final String status : statusList) {
result[0][j] = status;
j++;
}
for (int i = 1; i < rowCount; i++) {
result[i][0] = dateFormat.format(new Date(current.getTime() - (60000 * (rowCount - i))));
}
List<Integer> minuteOfStatuses;
for (int i = 1; i < rowCount; i++) {
minuteOfStatuses = statusMetricsByMinute.get(i - 1);
for (j = 1; j <= minuteOfStatuses.size(); j++) {
result[i][j] = minuteOfStatuses.get(j - 1);
}
while (j < colCount) {
result[i][j] = 0;
j++;
}
}
return result;
}
// Non - API
@Scheduled(fixedDelay = 60000)
private void exportMetrics() {
final ArrayList<Integer> statusCount = new ArrayList<Integer>();
for (final String status : statusList) {
Search search = registry.find(status);
if (search != null) {
Counter counter = search.counter();
statusCount.add(counter != null ? ((int) counter.count()) : 0);
registry.remove(counter);
} else {
statusCount.add(0);
}
}
statusMetricsByMinute.add(statusCount);
}
}
@@ -1,5 +0,0 @@
package com.baeldung.web.metric;
public interface IActuatorMetricService {
Object[][] getGraphData();
}
@@ -1,8 +0,0 @@
package com.baeldung.web.metric;
public interface ICustomActuatorMetricService {
void increaseCount(final int status);
Object[][] getGraphData();
}
@@ -1,14 +0,0 @@
package com.baeldung.web.metric;
import java.util.Map;
public interface IMetricService {
void increaseCount(final String request, final int status);
Map getFullMetric();
Map getStatusMetric();
Object[][] getGraphData();
}
@@ -1,49 +0,0 @@
package com.baeldung.web.metric;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
@Component
public class MetricFilter implements Filter {
@Autowired
private IMetricService metricService;
@Autowired
private ICustomActuatorMetricService actMetricService;
@Override
public void init(final FilterConfig config) throws ServletException {
if (metricService == null || actMetricService == null) {
metricService = (IMetricService) WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext()).getBean("metricService");
actMetricService = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext()).getBean(CustomActuatorMetricService.class);
}
}
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws java.io.IOException, ServletException {
final HttpServletRequest httpRequest = ((HttpServletRequest) request);
final String req = httpRequest.getMethod() + " " + httpRequest.getRequestURI();
chain.doFilter(request, response);
final int status = ((HttpServletResponse) response).getStatus();
metricService.increaseCount(req, status);
actMetricService.increaseCount(status);
}
@Override
public void destroy() {
}
}
@@ -1,122 +0,0 @@
package com.baeldung.web.metric;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.stereotype.Service;
@Service
public class MetricService implements IMetricService {
private ConcurrentMap<String, ConcurrentHashMap<Integer, Integer>> metricMap;
private ConcurrentMap<Integer, Integer> statusMetric;
private ConcurrentMap<String, ConcurrentHashMap<Integer, Integer>> timeMap;
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public MetricService() {
super();
metricMap = new ConcurrentHashMap<String, ConcurrentHashMap<Integer, Integer>>();
statusMetric = new ConcurrentHashMap<Integer, Integer>();
timeMap = new ConcurrentHashMap<String, ConcurrentHashMap<Integer, Integer>>();
}
// API
@Override
public void increaseCount(final String request, final int status) {
increaseMainMetric(request, status);
increaseStatusMetric(status);
updateTimeMap(status);
}
@Override
public Map getFullMetric() {
return metricMap;
}
@Override
public Map getStatusMetric() {
return statusMetric;
}
@Override
public Object[][] getGraphData() {
final int colCount = statusMetric.keySet().size() + 1;
final Set<Integer> allStatus = statusMetric.keySet();
final int rowCount = timeMap.keySet().size() + 1;
final Object[][] result = new Object[rowCount][colCount];
result[0][0] = "Time";
int j = 1;
for (final int status : allStatus) {
result[0][j] = status;
j++;
}
int i = 1;
ConcurrentMap<Integer, Integer> tempMap;
for (final Entry<String, ConcurrentHashMap<Integer, Integer>> entry : timeMap.entrySet()) {
result[i][0] = entry.getKey();
tempMap = entry.getValue();
for (j = 1; j < colCount; j++) {
result[i][j] = tempMap.get(result[0][j]);
if (result[i][j] == null) {
result[i][j] = 0;
}
}
i++;
}
return result;
}
// NON-API
private void increaseMainMetric(final String request, final int status) {
ConcurrentHashMap<Integer, Integer> statusMap = metricMap.get(request);
if (statusMap == null) {
statusMap = new ConcurrentHashMap<Integer, Integer>();
}
Integer count = statusMap.get(status);
if (count == null) {
count = 1;
} else {
count++;
}
statusMap.put(status, count);
metricMap.put(request, statusMap);
}
private void increaseStatusMetric(final int status) {
final Integer statusCount = statusMetric.get(status);
if (statusCount == null) {
statusMetric.put(status, 1);
} else {
statusMetric.put(status, statusCount + 1);
}
}
private void updateTimeMap(final int status) {
final String time = dateFormat.format(new Date());
ConcurrentHashMap<Integer, Integer> statusMap = timeMap.get(time);
if (statusMap == null) {
statusMap = new ConcurrentHashMap<Integer, Integer>();
}
Integer count = statusMap.get(status);
if (count == null) {
count = 1;
} else {
count++;
}
statusMap.put(status, count);
timeMap.put(time, statusMap);
}
}