/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * See LICENSE.txt included in this distribution for the specific * language governing permissions and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at LICENSE.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. */ package org.opensolaris.opengrok.web; import java.util.Calendar; import java.util.Map; import java.util.TreeMap; import java.util.stream.Collectors; import java.util.stream.LongStream; import javax.servlet.http.HttpServletRequest; import org.json.simple.JSONArray; import org.json.simple.JSONObject; /** * * @author Krystof Tulinger */ public class Statistics { protected static final String STATISTIC_TIMING = "timing"; protected static final String STATISTIC_TIMING_MIN = "timing_min"; protected static final String STATISTIC_TIMING_MAX = "timing_max"; protected static final String STATISTIC_TIMING_AVG = "timing_avg"; protected static final String STATISTIC_REQUEST_CATEGORIES = "request_categories"; protected static final String STATISTIC_REQUESTS = "requests"; protected static final String STATISTIC_MINUTES = "minutes"; protected static final String STATISTIC_REQUESTS_PER_MINUTE = "requests_per_minute"; protected static final String STATISTIC_REQUESTS_PER_MINUTE_MIN = "requests_per_minute_min"; protected static final String STATISTIC_REQUESTS_PER_MINUTE_MAX = "requests_per_minute_max"; protected static final String STATISTIC_REQUESTS_PER_MINUTE_AVG = "requests_per_minute_avg"; protected static final String STATISTIC_DAY_HISTOGRAM = "day_histogram"; protected static final String STATISTIC_MONTH_HISTOGRAM = "month_histogram"; private Map<String, Long> requestCategories = new TreeMap<>(); private Map<String, Long> timing = new TreeMap<>(); private Map<String, Long> timingMin = new TreeMap<>(); private Map<String, Long> timingMax = new TreeMap<>(); private long[] dayHistogram = new long[24]; private long[] monthHistogram = new long[31]; private long timeStart = System.currentTimeMillis(); private long requests = 0; private long minutes = 1; private long requestsPerMinute = 0; private long requestsPerMinuteMin = Long.MAX_VALUE; private long requestsPerMinuteMax = Long.MIN_VALUE; /** * Adds a single request into all requests. */ synchronized public void addRequest(HttpServletRequest req) { maybeRefresh(); requestsPerMinute++; requests++; if (requestsPerMinute > requestsPerMinuteMax) { requestsPerMinuteMax = requestsPerMinute; } if (requestsPerMinute < requestsPerMinuteMin) { requestsPerMinuteMin = requestsPerMinute; } dayHistogram[Calendar.getInstance().get(Calendar.HOUR_OF_DAY)]++; monthHistogram[Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1]++; } /** * Refreshes the last timestamp and number of minutes since start if needed. */ synchronized protected void maybeRefresh() { if (timeStart + 60 * 1000 <= System.currentTimeMillis()) { // several minutes have passed minutes += (System.currentTimeMillis() - timeStart) / (60 * 1000); timeStart = System.currentTimeMillis(); requestsPerMinute = 0; } } /** * Adds a request into the category * * @param req the given request * @param category category */ synchronized public void addRequest(HttpServletRequest req, String category) { Long val = requestCategories.get(category); if (val == null) { val = new Long(0); } val += 1; requestCategories.put(category, val); } /** * Adds a request's process time into given category. * * @param req the given request * @param category category * @param v time spent on processing this request */ synchronized public void addRequestTime(HttpServletRequest req, String category, long v) { addRequest(req, category); Long val = timing.get(category); Long min = timingMin.get(category); Long max = timingMax.get(category); if (val == null) { val = new Long(0); } if (max == null || v > max) { max = new Long(v); } if (min == null || v < min) { min = new Long(v); } val += v; timing.put(category, val); timingMin.put(category, min); timingMax.put(category, max); } public Map<String, Long> getRequestCategories() { return requestCategories; } public Map<String, Long> getTiming() { return timing; } public Map<String, Long> getTimingMin() { return timingMin; } public Map<String, Long> getTimingMax() { return timingMax; } /** * Get timing average for all requests. * * @see #getRequestCategories() * @return map of averages for each category */ public Map<String, Double> getTimingAvg() { Map<String, Double> timingAvg = new TreeMap<>(); for (Map.Entry<String, Long> entry : timing.entrySet()) { timingAvg.put(entry.getKey(), entry.getValue().doubleValue() / requestCategories.get(entry.getKey())); } return timingAvg; } synchronized public void setRequestCategories(Map<String, Long> requestCategories) { this.requestCategories = requestCategories; } synchronized public void setTiming(Map<String, Long> timing) { this.timing = timing; } synchronized public void setTimingMin(Map<String, Long> timing_min) { this.timingMin = timing_min; } synchronized public void setTimingMax(Map<String, Long> timing_max) { this.timingMax = timing_max; } public long getTimeStart() { return timeStart; } synchronized public void setTimeStart(long timeStart) { this.timeStart = timeStart; } public long getRequests() { return requests; } synchronized public void setRequests(long requests) { this.requests = requests; } public long getMinutes() { maybeRefresh(); return minutes; } synchronized public void setMinutes(long minutes) { this.minutes = minutes; } public long getRequestsPerMinute() { maybeRefresh(); return requestsPerMinute; } synchronized public void setRequestsPerMinute(long requestsPerMinute) { this.requestsPerMinute = requestsPerMinute; } public long getRequestsPerMinuteMin() { if (getRequests() <= 0) { return 0; } return requestsPerMinuteMin; } synchronized public void setRequestsPerMinuteMin(long requestsPerMinuteMin) { this.requestsPerMinuteMin = requestsPerMinuteMin; } public long getRequestsPerMinuteMax() { if (getRequests() <= 0) { return 0; } return requestsPerMinuteMax; } synchronized public void setRequestsPerMinuteMax(long requestsPerMinuteMax) { this.requestsPerMinuteMax = requestsPerMinuteMax; } public double getRequestsPerMinuteAvg() { maybeRefresh(); return requests / (double) minutes; } public long[] getDayHistogram() { return dayHistogram; } synchronized public void setDayHistogram(long[] dayHistogram) { this.dayHistogram = dayHistogram; } public long[] getMonthHistogram() { return monthHistogram; } synchronized public void setMonthHistogram(long[] monthHistogram) { this.monthHistogram = monthHistogram; } /** * Convert this statistics object into JSONObject. * * @return the json object */ public JSONObject toJson() { return toJson(this); } /** * Convert JSONObject object into statistics. * * @param input object containing statistics * @return the statistics object */ @SuppressWarnings("unchecked") public static Statistics from(JSONObject input) { Statistics stats = new Statistics(); Object o; if ((o = input.get(STATISTIC_REQUEST_CATEGORIES)) != null) { stats.setRequestCategories((Map<String, Long>) o); } if ((o = input.get(STATISTIC_TIMING)) != null) { stats.setTiming((Map<String, Long>) o); } if ((o = input.get(STATISTIC_TIMING_MIN)) != null) { stats.setTimingMin((Map<String, Long>) o); } if ((o = input.get(STATISTIC_TIMING_MAX)) != null) { stats.setTimingMax((Map<String, Long>) o); } if ((o = input.get(STATISTIC_REQUESTS)) != null) { stats.setRequests((long) o); } if ((o = input.get(STATISTIC_MINUTES)) != null) { stats.setMinutes((long) o); } if ((o = input.get(STATISTIC_REQUESTS_PER_MINUTE)) != null) { stats.setRequestsPerMinute((long) o); } if ((o = input.get(STATISTIC_REQUESTS_PER_MINUTE_MIN)) != null) { stats.setRequestsPerMinuteMin((long) o); } if ((o = input.get(STATISTIC_REQUESTS_PER_MINUTE_MAX)) != null) { stats.setRequestsPerMinuteMax((long) o); } if ((o = input.get(STATISTIC_DAY_HISTOGRAM)) != null) { stats.setDayHistogram(convertJSONArrayToArray((JSONArray) o, stats.getDayHistogram())); } if ((o = input.get(STATISTIC_MONTH_HISTOGRAM)) != null) { stats.setMonthHistogram(convertJSONArrayToArray((JSONArray) o, stats.getMonthHistogram())); } stats.setTimeStart(System.currentTimeMillis()); return stats; } /** * Convert statistics object into JSONObject. * * @param stats the statistics object * @return the json object or empty json object if there was no request */ @SuppressWarnings("unchecked") public static JSONObject toJson(Statistics stats) { JSONObject output = new JSONObject(); if (stats.getRequests() == 0) { return output; } output.put(STATISTIC_REQUEST_CATEGORIES, new JSONObject(stats.getRequestCategories())); output.put(STATISTIC_TIMING, new JSONObject(stats.getTiming())); output.put(STATISTIC_TIMING_MIN, new JSONObject(stats.getTimingMin())); output.put(STATISTIC_TIMING_MAX, new JSONObject(stats.getTimingMax())); output.put(STATISTIC_TIMING_AVG, new JSONObject(stats.getTimingAvg())); output.put(STATISTIC_MINUTES, stats.getMinutes()); output.put(STATISTIC_REQUESTS, stats.getRequests()); output.put(STATISTIC_REQUESTS_PER_MINUTE, stats.getRequestsPerMinute()); output.put(STATISTIC_REQUESTS_PER_MINUTE_MIN, stats.getRequestsPerMinuteMin()); output.put(STATISTIC_REQUESTS_PER_MINUTE_MAX, stats.getRequestsPerMinuteMax()); output.put(STATISTIC_REQUESTS_PER_MINUTE_AVG, stats.getRequestsPerMinuteAvg()); output.put(STATISTIC_DAY_HISTOGRAM, convertArrayToJSONArray(stats.getDayHistogram())); output.put(STATISTIC_MONTH_HISTOGRAM, convertArrayToJSONArray(stats.getMonthHistogram())); return output; } /** * Converts an array into json array. * * @param array the input array * @return the output json array */ @SuppressWarnings("unchecked") private static JSONArray convertArrayToJSONArray(long[] array) { JSONArray ret = new JSONArray(); for (long o : array) { ret.add(o); } return ret; } /** * Converts an json array into an array. * * @param dest the input json array * @param target the output array * @return target */ private static long[] convertJSONArrayToArray(JSONArray dest, long[] target) { for (int i = 0; i < target.length && i < dest.size(); i++) { target[i] = (long) dest.get(i); } return target; } @Override public String toString() { return "requestCategories = " + getRequestCategories().toString() + "\ntiming = " + getTiming().toString() + "\nntimingMin = " + getTimingMin().toString() + "\nntimingMax = " + getTimingMax().toString() + "\nntimingAvg = " + getTimingAvg().toString() + "\nminutes = " + getMinutes() + "\nrequests = " + getRequests() + "\nrequestsPerMinute = " + getRequestsPerMinute() + "\nrequestsPerMinuteMin = " + getRequestsPerMinuteMin() + "\nrequestsPerMinuteMax = " + getRequestsPerMinuteMax() + "\nrequestsPerMinuteAvg = " + getRequestsPerMinuteAvg() + "\ndayHistogram = " + LongStream.of(getDayHistogram()).mapToObj(a -> Long.toString(a)).map(a -> a.toString()).collect(Collectors.joining(", ")) + "\nmonthHistogram = " + LongStream.of(getMonthHistogram()).mapToObj(a -> Long.toString(a)).map(a -> a.toString()).collect(Collectors.joining(", ")); } }