/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.stream.module.metrics.memory; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.joda.time.Chronology; import org.joda.time.DateTime; import org.joda.time.Days; import org.joda.time.Duration; import org.joda.time.Interval; import org.joda.time.Months; import org.joda.time.Years; import org.springframework.cloud.stream.module.metrics.AggregateCounter; import org.springframework.cloud.stream.module.metrics.AggregateCounterResolution; import org.springframework.cloud.stream.module.metrics.MetricUtils; import org.springframework.util.Assert; /** * A counter that tracks integral values but also remembers how its value was distributed over time. * * <p> * This core class only holds data structures. Depending on backing stores, logic for computing totals may be * implemented in a specialization of this class or at the repository level. * </p> * * @author Luke Taylor * @author Eric Bottard * @author Ilayaperumal Gopinathan */ class InMemoryAggregateCounter { private final String name; private long value; private Map<Integer, long[]> monthCountsByYear = new HashMap<Integer, long[]>(); private Map<Integer, long[]> dayCountsByYear = new HashMap<Integer, long[]>(); private Map<Integer, long[]> hourCountsByDay = new HashMap<Integer, long[]>(); private Map<Integer, long[]> minuteCountsByDay = new HashMap<Integer, long[]>(); public InMemoryAggregateCounter(String name, long value) { this.name = name; this.value = value; } public InMemoryAggregateCounter(String name) { this.name = name; this.value = 0l; } public AggregateCounter getCounts(int nCounts, DateTime endDate, AggregateCounterResolution resolution) { Assert.notNull(endDate, "endDate must not be null"); return getCounts(new Interval(resolution.minus(endDate, nCounts-1), endDate), resolution); } public AggregateCounter getCounts(Interval interval, AggregateCounterResolution resolution) { DateTime start = interval.getStart(); DateTime end = interval.getEnd(); Chronology c = interval.getChronology(); long[] counts; if (resolution == AggregateCounterResolution.minute) { List<long[]> days = accumulateDayCounts(minuteCountsByDay, start, end, 60 * 24); counts = MetricUtils.concatArrays(days, interval.getStart().getMinuteOfDay(), interval.toPeriod().toStandardMinutes().getMinutes() + 1); } else if (resolution == AggregateCounterResolution.hour) { List<long[]> days = accumulateDayCounts(hourCountsByDay, start, end, 24); counts = MetricUtils.concatArrays(days, interval.getStart().getHourOfDay(), interval.toPeriod().toStandardHours().getHours() + 1); } else if (resolution == AggregateCounterResolution.day) { DateTime startDay = new DateTime(c.dayOfYear().roundFloor(start.getMillis())); DateTime endDay = new DateTime(c.dayOfYear().roundFloor(end.plusDays(1).getMillis())); int nDays = Days.daysBetween(startDay, endDay).getDays(); DateTime cursor = new DateTime(c.year().roundFloor(interval.getStartMillis())); List<long[]> yearDays = new ArrayList<long[]>(); DateTime endYear = new DateTime(c.year().roundCeiling(end.getMillis())); while (cursor.isBefore(endYear)) { long[] dayCounts = dayCountsByYear.get(cursor.getYear()); if (dayCounts == null) { // Querying where we have no data dayCounts = new long[daysInYear(cursor.getYear())]; } yearDays.add(dayCounts); cursor = cursor.plusYears(1); } counts = MetricUtils.concatArrays(yearDays, startDay.getDayOfYear() - 1, nDays); } else if (resolution == AggregateCounterResolution.month) { DateTime startMonth = new DateTime(c.monthOfYear().roundFloor(interval.getStartMillis())); DateTime endMonth = new DateTime(c.monthOfYear().roundFloor(end.plusMonths(1).getMillis())); int nMonths = Months.monthsBetween(startMonth, endMonth).getMonths(); DateTime cursor = new DateTime(c.year().roundFloor(interval.getStartMillis())); List<long[]> yearMonths = new ArrayList<long[]>(); DateTime endYear = new DateTime(c.year().roundCeiling(end.getMillis())); while (cursor.isBefore(endYear)) { long[] monthCounts = monthCountsByYear.get(cursor.getYear()); if (monthCounts == null) { monthCounts = new long[12]; } yearMonths.add(monthCounts); cursor = cursor.plusYears(1); } counts = MetricUtils.concatArrays(yearMonths, startMonth.getMonthOfYear() - 1 , nMonths); } else if (resolution == AggregateCounterResolution.year) { DateTime startYear = new DateTime(interval.getStart().getYear(), 1, 1, 0, 0); DateTime endYear = new DateTime(end.getYear() + 1, 1, 1, 0, 0); int nYears = Years.yearsBetween(startYear, endYear).getYears(); counts = new long[nYears]; for (int i = 0; i < nYears; i++) { long[] monthCounts = monthCountsByYear.get(startYear.plusYears(i).getYear()); counts[i] = MetricUtils.sum(monthCounts); } } else { throw new IllegalStateException("Shouldn't happen. Unhandled resolution: " + resolution); } return new AggregateCounter(this.name, interval, counts, resolution); } private static List<long[]> accumulateDayCounts(Map<Integer, long[]> fromDayCounts, DateTime start, DateTime end, int subSize) { List<long[]> days = new ArrayList<long[]>(); Duration step = Duration.standardDays(1); long[] emptySubArray = new long[subSize]; end = end.plusDays(1); // Need to account for an interval which crosses days for (DateTime now = start; now.isBefore(end); now = now.plus(step)) { int countsByDayKey = now.getYear() * 1000 + now.getDayOfYear(); long[] dayCounts = fromDayCounts.get(countsByDayKey); if (dayCounts == null) { // Use an empty array if we don't have data dayCounts = emptySubArray; } days.add(dayCounts); } return days; } private int daysInYear(int year) { Duration d = new Duration(new DateTime(year, 1, 1, 0, 0), new DateTime(year + 1, 1, 1, 0, 0)); return (int)d.getStandardDays(); } synchronized long increment(long amount) { return this.value += amount; } synchronized long increment(long amount, DateTime dateTime) { int year = dateTime.getYear(); int month = dateTime.getMonthOfYear(); int day = dateTime.getDayOfYear(); int hour = dateTime.getHourOfDay(); int minute = dateTime.getMinuteOfDay(); long[] monthCounts = monthCountsByYear.get(year); long[] dayCounts = dayCountsByYear.get(year); if (monthCounts == null) { monthCounts = new long[12]; monthCountsByYear.put(year, monthCounts); dayCounts = new long[daysInYear(year)]; dayCountsByYear.put(year, dayCounts); } int countsByDayKey = year * 1000 + day; long[] hourCounts = hourCountsByDay.get(countsByDayKey); if (hourCounts == null) { hourCounts = new long[24]; hourCountsByDay.put(countsByDayKey, hourCounts); } long[] minuteCounts = minuteCountsByDay.get(countsByDayKey); if (minuteCounts == null) { minuteCounts = new long[60 * 24]; minuteCountsByDay.put(countsByDayKey, minuteCounts); } minuteCounts[minute] += amount; monthCounts[month-1] += amount; dayCounts[day-1] += amount; hourCounts[hour] += amount; return increment(amount); } }