Skip to content
This repository was archived by the owner on May 28, 2018. It is now read-only.

Commit 998c97e

Browse files
stepanvMarek Potociar
authored andcommitted
J-559: OOME fix in monitoring.
- Using sliding windows with aggregated measurements provided by aggregating trimmer in order to prevent extreme memory consumption and unnecessary processor demanding calculations. Change-Id: I062404dfe56dad7c6af03a964e5c929d14400bf5
1 parent f3aeb2e commit 998c97e

23 files changed

+1791
-510
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
/*
2+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3+
*
4+
* Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved.
5+
*
6+
* The contents of this file are subject to the terms of either the GNU
7+
* General Public License Version 2 only ("GPL") or the Common Development
8+
* and Distribution License("CDDL") (collectively, the "License"). You
9+
* may not use this file except in compliance with the License. You can
10+
* obtain a copy of the License at
11+
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
12+
* or packager/legal/LICENSE.txt. See the License for the specific
13+
* language governing permissions and limitations under the License.
14+
*
15+
* When distributing the software, include this License Header Notice in each
16+
* file and include the License file at packager/legal/LICENSE.txt.
17+
*
18+
* GPL Classpath Exception:
19+
* Oracle designates this particular file as subject to the "Classpath"
20+
* exception as provided by Oracle in the GPL Version 2 section of the License
21+
* file that accompanied this code.
22+
*
23+
* Modifications:
24+
* If applicable, add the following below the License Header, with the fields
25+
* enclosed by brackets [] replaced by your own identifying information:
26+
* "Portions Copyright [year] [name of copyright owner]"
27+
*
28+
* Contributor(s):
29+
* If you wish your version of this file to be governed by only the CDDL or
30+
* only the GPL Version 2, indicate your decision by adding "[Contributor]
31+
* elects to include this software in this distribution under the [CDDL or GPL
32+
* Version 2] license." If you don't indicate a single choice of license, a
33+
* recipient has the option to distribute your version of this file under
34+
* either the CDDL, the GPL Version 2 or to extend the choice of license to
35+
* its licensees as provided above. However, if you add GPL Version 2 code
36+
* and therefore, elected the GPL Version 2 license, then the option applies
37+
* only if the new code is made subject to such option by the copyright
38+
* holder.
39+
*
40+
* This file incorporates work covered by the following copyright and
41+
* permission notice:
42+
*
43+
* Copyright 2010-2013 Coda Hale and Yammer, Inc., 2014-2015 Dropwizard Team
44+
*
45+
* Licensed under the Apache License, Version 2.0 (the "License");
46+
* you may not use this file except in compliance with the License.
47+
* You may obtain a copy of the License at
48+
*
49+
* http://www.apache.org/licenses/LICENSE-2.0
50+
*
51+
* Unless required by applicable law or agreed to in writing, software
52+
* distributed under the License is distributed on an "AS IS" BASIS,
53+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
54+
* See the License for the specific language governing permissions and
55+
* limitations under the License.
56+
*/
57+
package org.glassfish.jersey.server.internal.monitoring;
58+
59+
import java.util.Collection;
60+
import java.util.Map;
61+
import java.util.concurrent.ConcurrentNavigableMap;
62+
import java.util.concurrent.ConcurrentSkipListMap;
63+
import java.util.concurrent.TimeUnit;
64+
import java.util.concurrent.atomic.AtomicInteger;
65+
import java.util.concurrent.atomic.AtomicLong;
66+
67+
import static org.glassfish.jersey.server.internal.monitoring.ReservoirConstants.COLLISION_BUFFER;
68+
import static org.glassfish.jersey.server.internal.monitoring.ReservoirConstants.COLLISION_BUFFER_POWER;
69+
70+
/**
71+
* An abstract {@link TimeReservoir} implementation backed by a sliding window that stores only the measurements made in the last
72+
* {@code N} seconds (or other startTime unit) and allows an update with data that happened in past (which is what makes it
73+
* different from Dropwizard's Metrics SlidingTimeWindowReservoir.
74+
* <p/>
75+
* The snapshot this reservoir returns has limitations as mentioned in {@link TimeReservoir}.
76+
* <p/>
77+
* This reservoir is capable to store up to 2^{@link ReservoirConstants#COLLISION_BUFFER_POWER}, that is 256, in a granularity of
78+
* nanoseconds. In other words, up to 256 values that occurred at the same nanosecond can be stored in this reservoir. For
79+
* particular nanosecond, if the collision buffer exceeds, newly added values are thrown away.
80+
*
81+
* @param <V> The type of values to store in this sliding window reservoir
82+
* @author Stepan Vavra (stepan.vavra at oracle.com)
83+
* @see <pre><a href="https://github.com/dropwizard/metrics/blob/master/metrics-core/src/main/java/io/dropwizard/metrics
84+
* /SlidingTimeWindowReservoir.java">Dropwizard's
85+
* Metrics SlidingTimeWindowReservoir</a></pre>
86+
*/
87+
abstract class AbstractSlidingWindowTimeReservoir<V> implements TimeReservoir<V> {
88+
89+
private final ConcurrentNavigableMap<Long, V> measurements;
90+
private final long window;
91+
private final AtomicLong greatestTick;
92+
private final AtomicLong updateCount;
93+
private final AtomicLong startTick;
94+
private final AtomicInteger trimOff;
95+
private final SlidingWindowTrimmer<V> trimmer;
96+
private final long interval;
97+
private final TimeUnit intervalUnit;
98+
99+
/**
100+
* Creates a new {@link SlidingWindowTimeReservoir} with the start time and window of startTime.
101+
*
102+
* @param window The window of startTime
103+
* @param windowUnit The unit of {@code window}
104+
* @param startTime The start time from which this reservoir calculates measurements
105+
* @param startTimeUnit The start time unit
106+
*/
107+
public AbstractSlidingWindowTimeReservoir(final long window,
108+
final TimeUnit windowUnit,
109+
final long startTime,
110+
final TimeUnit startTimeUnit) {
111+
this(window, windowUnit, startTime, startTimeUnit, null);
112+
}
113+
114+
/**
115+
* Creates a new base sliding time window reservoir with the start time and a specified time window.
116+
*
117+
* @param window The window of startTime.
118+
* @param windowUnit The unit of {@code window}.
119+
* @param startTime The start time from which this reservoir calculates measurements.
120+
* @param startTimeUnit The start time unit.
121+
* @param trimmer The trimmer to use for trimming, if {@code null}, default trimmer is used.
122+
*/
123+
@SuppressWarnings("unchecked")
124+
public AbstractSlidingWindowTimeReservoir(final long window,
125+
final TimeUnit windowUnit,
126+
final long startTime,
127+
final TimeUnit startTimeUnit,
128+
final SlidingWindowTrimmer<V> trimmer) {
129+
this.trimmer = trimmer != null ? trimmer : (SlidingWindowTrimmer<V>) DefaultSlidingWindowTrimmerHolder.INSTANCE;
130+
this.measurements = new ConcurrentSkipListMap<>();
131+
this.interval = window;
132+
this.intervalUnit = windowUnit;
133+
this.window = windowUnit.toNanos(window) << COLLISION_BUFFER_POWER;
134+
this.startTick = new AtomicLong(tick(startTime, startTimeUnit));
135+
this.greatestTick = new AtomicLong(startTick.get());
136+
this.updateCount = new AtomicLong(0);
137+
this.trimOff = new AtomicInteger(0);
138+
139+
this.trimmer.setTimeReservoir(this);
140+
}
141+
142+
@Override
143+
public int size(long time, TimeUnit timeUnit) {
144+
conditionallyUpdateGreatestTick(tick(time, timeUnit));
145+
trim();
146+
return measurements.size();
147+
}
148+
149+
@Override
150+
public void update(V value, long time, TimeUnit timeUnit) {
151+
if (updateCount.incrementAndGet() % ReservoirConstants.TRIM_THRESHOLD == 0) {
152+
trim();
153+
}
154+
155+
long tick = tick(time, timeUnit);
156+
for (int i = 0; i < COLLISION_BUFFER; ++i) {
157+
if (measurements.putIfAbsent(tick, value) == null) {
158+
conditionallyUpdateGreatestTick(tick);
159+
return;
160+
}
161+
// increase the tick, there should be up to COLLISION_BUFFER empty slots
162+
// where to put the value for given 'time'
163+
// if empty slot is not found, throw it away as we're getting inaccurate statistics anyway
164+
tick++;
165+
}
166+
}
167+
168+
@Override
169+
public long interval(final TimeUnit timeUnit) {
170+
return timeUnit.convert(interval, intervalUnit);
171+
}
172+
173+
private long conditionallyUpdateGreatestTick(final long tick) {
174+
while (true) {
175+
final long currentGreatestTick = greatestTick.get();
176+
if (tick <= currentGreatestTick) {
177+
// the tick is too small, return the greatest one
178+
return currentGreatestTick;
179+
}
180+
if (greatestTick.compareAndSet(currentGreatestTick, tick)) {
181+
// successfully updated greatestTick with the tick
182+
return tick;
183+
}
184+
}
185+
}
186+
187+
/**
188+
* Updates the startTick in case that the sliding window was created AFTER the time of a value that updated this window.
189+
*
190+
* @param firstEntry The first entry of the windowed measurments
191+
*/
192+
private void conditionallyUpdateStartTick(final Map.Entry<Long, V> firstEntry) {
193+
final Long firstEntryKey = firstEntry != null ? firstEntry.getKey() : null;
194+
if (firstEntryKey != null && firstEntryKey < startTick.get()) {
195+
while (true) {
196+
final long expectedStartTick = startTick.get();
197+
198+
if (startTick.compareAndSet(expectedStartTick, firstEntryKey)) {
199+
return;
200+
}
201+
}
202+
}
203+
}
204+
205+
/**
206+
* Subclasses are required to instantiate {@link UniformTimeSnapshot} on their own.
207+
*
208+
* @param values The values to create the snapshot from
209+
* @param timeInterval The time interval this snapshot conforms to
210+
* @param timeIntervalUnit The interval unit of the time interval
211+
* @param time The time of the request of the snapshot
212+
* @param timeUnit The unit of the time of the snapshot request
213+
* @return The snapshot
214+
*/
215+
abstract UniformTimeSnapshot snapshot(final Collection<V> values,
216+
final long timeInterval,
217+
final TimeUnit timeIntervalUnit,
218+
final long time,
219+
final TimeUnit timeUnit);
220+
221+
@Override
222+
public UniformTimeSnapshot getSnapshot(long time, TimeUnit timeUnit) {
223+
trimOff.incrementAndGet();
224+
final long baselineTick = conditionallyUpdateGreatestTick(tick(time, timeUnit));
225+
try {
226+
// now, with the 'baselineTick' we can be sure that no trim will be performed
227+
// we just cannot guarantee that 'time' will correspond with the 'baselineTick' which is what the API warns about
228+
final ConcurrentNavigableMap<Long, V> windowMap = measurements
229+
.subMap((roundTick(baselineTick)) - window, true, baselineTick, true);
230+
231+
// if the first update came with value lower that the 'startTick' we need to extend the window size so that the
232+
// calculation depending on the actual measured interval is not unnecessary boosted
233+
conditionallyUpdateStartTick(windowMap.firstEntry());
234+
235+
// calculate the actual measured interval
236+
final long measuredTickInterval = Math.min(baselineTick - startTick.get(), window);
237+
238+
return snapshot(windowMap.values(), measuredTickInterval >> COLLISION_BUFFER_POWER,
239+
TimeUnit.NANOSECONDS, time, timeUnit);
240+
} finally {
241+
trimOff.decrementAndGet();
242+
trim(baselineTick);
243+
}
244+
}
245+
246+
private long tick(long time, TimeUnit timeUnit) {
247+
return timeUnit.toNanos(time) << COLLISION_BUFFER_POWER;
248+
}
249+
250+
private void trim() {
251+
trim(greatestTick.get());
252+
}
253+
254+
private void trim(final long baselineTick) {
255+
if (trimEnabled()) {
256+
final long key = roundTick(baselineTick) - window;
257+
trimmer.trim(measurements, key);
258+
}
259+
}
260+
261+
private boolean trimEnabled() {
262+
return trimOff.get() == 0;
263+
}
264+
265+
/**
266+
* The purpose of this method is to deal with the fact that data for the same nanosecond can be distributed in an interval
267+
* [0,256). By rounding the tick, we get the tick to which all the other ticks from the same interval belong.
268+
*
269+
* @param tick The tick
270+
* @return The rounded tick
271+
*/
272+
private long roundTick(final long tick) {
273+
// tick / COLLISION_BUFFER * COLLISION_BUFFER
274+
return tick >> COLLISION_BUFFER_POWER << COLLISION_BUFFER_POWER;
275+
}
276+
277+
/**
278+
* The holder of the lazy loaded instance of the default trimmer.
279+
*/
280+
private static final class DefaultSlidingWindowTrimmerHolder {
281+
282+
/**
283+
* The default instance of sliding window trimmer.
284+
*/
285+
static final SlidingWindowTrimmer<Object> INSTANCE = new SlidingWindowTrimmer<Object>() {
286+
@Override
287+
public void trim(final ConcurrentNavigableMap<Long, Object> map, final long key) {
288+
map.headMap(key).clear();
289+
}
290+
291+
@Override
292+
public void setTimeReservoir(final TimeReservoir<Object> reservoir) {
293+
// not used
294+
}
295+
};
296+
}
297+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3+
*
4+
* Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved.
5+
*
6+
* The contents of this file are subject to the terms of either the GNU
7+
* General Public License Version 2 only ("GPL") or the Common Development
8+
* and Distribution License("CDDL") (collectively, the "License"). You
9+
* may not use this file except in compliance with the License. You can
10+
* obtain a copy of the License at
11+
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
12+
* or packager/legal/LICENSE.txt. See the License for the specific
13+
* language governing permissions and limitations under the License.
14+
*
15+
* When distributing the software, include this License Header Notice in each
16+
* file and include the License file at packager/legal/LICENSE.txt.
17+
*
18+
* GPL Classpath Exception:
19+
* Oracle designates this particular file as subject to the "Classpath"
20+
* exception as provided by Oracle in the GPL Version 2 section of the License
21+
* file that accompanied this code.
22+
*
23+
* Modifications:
24+
* If applicable, add the following below the License Header, with the fields
25+
* enclosed by brackets [] replaced by your own identifying information:
26+
* "Portions Copyright [year] [name of copyright owner]"
27+
*
28+
* Contributor(s):
29+
* If you wish your version of this file to be governed by only the CDDL or
30+
* only the GPL Version 2, indicate your decision by adding "[Contributor]
31+
* elects to include this software in this distribution under the [CDDL or GPL
32+
* Version 2] license." If you don't indicate a single choice of license, a
33+
* recipient has the option to distribute your version of this file under
34+
* either the CDDL, the GPL Version 2 or to extend the choice of license to
35+
* its licensees as provided above. However, if you add GPL Version 2 code
36+
* and therefore, elected the GPL Version 2 license, then the option applies
37+
* only if the new code is made subject to such option by the copyright
38+
* holder.
39+
*/
40+
package org.glassfish.jersey.server.internal.monitoring;
41+
42+
import java.util.concurrent.TimeUnit;
43+
44+
/**
45+
* Base implementation of {@code UniformTimeSnapshot}.
46+
*
47+
* @author Stepan Vavra (stepan.vavra at oracle.com)
48+
*/
49+
abstract class AbstractTimeSnapshot implements UniformTimeSnapshot {
50+
51+
private final long timeInterval;
52+
private final TimeUnit timeIntervalUnit;
53+
54+
/**
55+
* Constructor to be used by subclasses overriding the base abstract uniform time snapshot class.
56+
*
57+
* @param timeInterval The time interval of this snapshot.
58+
* @param timeIntervalUnit The time interval unit.
59+
*/
60+
protected AbstractTimeSnapshot(final long timeInterval, final TimeUnit timeIntervalUnit) {
61+
this.timeInterval = timeInterval;
62+
this.timeIntervalUnit = timeIntervalUnit;
63+
}
64+
65+
@Override
66+
public long getTimeInterval(TimeUnit timeUnit) {
67+
return timeUnit.convert(timeInterval, timeIntervalUnit);
68+
}
69+
70+
@Override
71+
public double getRate(TimeUnit timeUnit) {
72+
final double rateInNanos = (double) size() / getTimeInterval(TimeUnit.NANOSECONDS);
73+
final long multiplier = TimeUnit.NANOSECONDS.convert(1, timeUnit);
74+
return rateInNanos * multiplier;
75+
}
76+
}

0 commit comments

Comments
 (0)