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

Commit a716e61

Browse files
stepanvMarek Potociar
authored andcommitted
JERSEY-2854: Jersey monitoring fixes.
- Collection concurrency issues solved by using custom SlidingWindowTimeReservoir implementation together with Dropwizard's metrics customized objects. Custom implementation of SlidingWindowTimeReservoir allows to put values that occurred in past. Data integrity should be guarantied on 100% by switching off the trim on update/snapshot during the snapshot creation operation. Change-Id: Ibe246c5f5ad8bc650e2748fbd5d07e4b96770726 Signed-off-by: Marek Potociar <[email protected]>
1 parent 2b27635 commit a716e61

File tree

8 files changed

+1038
-232
lines changed

8 files changed

+1038
-232
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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+
58+
package org.glassfish.jersey.server.internal.monitoring;
59+
60+
import java.util.Collection;
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+
/**
68+
* A {@link TimeReservoir} implementation backed by a sliding window that stores only the measurements made in the last {@code N}
69+
* seconds (or other startTime unit) and allows an update with data that happened in past (which is what makes it different from
70+
* Dropwizard's Metrics SlidingTimeWindowReservoir.
71+
* <p/>
72+
* The snapshot this reservoir returns has limitations as mentioned in {@link TimeReservoir}.
73+
* <p/>
74+
* This reservoir is capable to store up to 2^{@link #COLLISION_BUFFER_POWER}, that is 256, in a granularity of nanoseconds. In
75+
* other words, up to 256 values that occurred at the same nanosecond can be stored in this reservoir. For particular nanosecond,
76+
* if the collision buffer exceeds, newly added values are thrown away.
77+
*
78+
* @author Stepan Vavra (stepan.vavra at oracle.com)
79+
* @see <pre><a href="https://github.com/dropwizard/metrics/blob/master/metrics-core/src/main/java/io/dropwizard/metrics/SlidingTimeWindowReservoir.java">Dropwizrd's Metrics SlidingTimeWindowReservoir</a></pre>
80+
*/
81+
public class SlidingWindowTimeReservoir implements TimeReservoir {
82+
83+
// allow for 2^that many duplicate ticks before throwing away measurements
84+
private static final int COLLISION_BUFFER_POWER = 8;
85+
private static final int COLLISION_BUFFER = 1 << COLLISION_BUFFER_POWER; // 256
86+
// only trim on updating once every N
87+
private static final int TRIM_THRESHOLD = 256;
88+
89+
private final ConcurrentNavigableMap<Long, Long> measurements;
90+
private final long window;
91+
private final AtomicLong greatestTick;
92+
private final AtomicLong updateCount;
93+
private final long startTick;
94+
private final AtomicInteger trimOff;
95+
96+
/**
97+
* Creates a new {@link SlidingWindowTimeReservoir} with the start time and window of startTime.
98+
*
99+
* @param window the window of startTime
100+
* @param windowUnit the unit of {@code window}
101+
*/
102+
public SlidingWindowTimeReservoir(long window, TimeUnit windowUnit, long startTime, TimeUnit startTimeUnit) {
103+
this.measurements = new ConcurrentSkipListMap<>();
104+
this.window = windowUnit.toNanos(window) << COLLISION_BUFFER_POWER;
105+
this.startTick = tick(startTime, startTimeUnit);
106+
this.greatestTick = new AtomicLong(startTick);
107+
this.updateCount = new AtomicLong(0);
108+
this.trimOff = new AtomicInteger(0);
109+
}
110+
111+
@Override
112+
public int size(long time, TimeUnit timeUnit) {
113+
conditionallyUpdateGreatestTick(tick(time, timeUnit));
114+
trim();
115+
return measurements.size();
116+
}
117+
118+
@Override
119+
public void update(long value, long time, TimeUnit timeUnit) {
120+
if (updateCount.incrementAndGet() % TRIM_THRESHOLD == 0) {
121+
trim();
122+
}
123+
124+
long tick = tick(time, timeUnit);
125+
if (greatestTick.get() - window > tick) {
126+
// the value is too old that it doesn't even fit into the window
127+
return;
128+
}
129+
for (int i = 0; i < COLLISION_BUFFER; ++i) {
130+
if (measurements.putIfAbsent(tick, value) == null) {
131+
conditionallyUpdateGreatestTick(tick);
132+
break;
133+
}
134+
// increase the tick, there should be up to COLLISION_BUFFER empty slots
135+
// where to put the value for given 'time'
136+
// if empty slot is not found, throw it away as we're getting inaccurate statistics anyway
137+
tick++;
138+
}
139+
}
140+
141+
private long conditionallyUpdateGreatestTick(final long tick) {
142+
for (;;) {
143+
final long currentGreatestTick = greatestTick.get();
144+
if (tick <= currentGreatestTick) {
145+
// the tick is too small, return the greatest one
146+
return currentGreatestTick;
147+
}
148+
if (greatestTick.compareAndSet(currentGreatestTick, tick)) {
149+
// successfully updated greatestTick with the tick
150+
return tick;
151+
}
152+
}
153+
}
154+
155+
@Override
156+
public UniformTimeSnapshot getSnapshot(long time, TimeUnit timeUnit) {
157+
trimOff.incrementAndGet();
158+
final long baselineTick = conditionallyUpdateGreatestTick(tick(time, timeUnit));
159+
try {
160+
// now, with the 'baselineTick' we can be sure that no trim will be performed
161+
// we just cannot guarantee that 'time' will correspond with the 'baselineTick' which is what the API warns about
162+
final long measuredTickInterval = Math.min(baselineTick - startTick, window);
163+
final Collection<Long> values = measurements
164+
.subMap((roundTick(baselineTick)) - measuredTickInterval, true, baselineTick, true)
165+
.values();
166+
return new UniformTimeSnapshot(values, measuredTickInterval >> COLLISION_BUFFER_POWER, TimeUnit.NANOSECONDS);
167+
} finally {
168+
trimOff.decrementAndGet();
169+
trim(baselineTick);
170+
}
171+
}
172+
173+
private long tick(long time, TimeUnit timeUnit) {
174+
return timeUnit.toNanos(time) << COLLISION_BUFFER_POWER;
175+
}
176+
177+
private void trim() {
178+
trim(greatestTick.get());
179+
}
180+
181+
private void trim(final long baselineTick) {
182+
if (trimEnabled()) {
183+
measurements.headMap(roundTick(baselineTick) - window).clear();
184+
}
185+
}
186+
187+
private boolean trimEnabled() {
188+
return trimOff.get() == 0;
189+
}
190+
191+
/**
192+
* The purpose of this method is to deal with the fact that data for the same nanosecond can be distributed in an interval
193+
* [0,256). By rounding the tick, we get the tick to which all the other ticks from the same interval belong.
194+
*
195+
* @param tick The tick
196+
* @return The rounded tick
197+
*/
198+
private long roundTick(final long tick) {
199+
// tick / COLLISION_BUFFER * COLLISION_BUFFER
200+
return tick >> COLLISION_BUFFER_POWER << COLLISION_BUFFER_POWER;
201+
}
202+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
58+
package org.glassfish.jersey.server.internal.monitoring;
59+
60+
import java.util.concurrent.TimeUnit;
61+
62+
/**
63+
* A statistically representative reservoir of a data stream in time.
64+
* <p/>
65+
* Comparing to Dropwizard's Reservoir, this interface adds a possibility to work with data that is associated with a specific
66+
* time. It may not be possible; however, to obtain a snapshot or size at some moment in past due to performance optimizations.
67+
*
68+
* @author Stepan Vavra (stepan.vavra at oracle.com)
69+
* @see <a href="https://github.com/dropwizard/metrics">https://github.com/dropwizard/metrics</a>
70+
*/
71+
public interface TimeReservoir {
72+
73+
/**
74+
* Returns the number of values recorded at given time or newer. It may not be supported to return a size in past due to
75+
* performance optimizations.
76+
*
77+
* @param time The time to get the size for
78+
* @param timeUnit Time unit of the provided time
79+
* @return the number of values recorded for given time or newer
80+
*/
81+
int size(long time, TimeUnit timeUnit);
82+
83+
/**
84+
* Adds a new recorded value to the reservoir bound to a given time.
85+
*
86+
* @param value a new recorded value
87+
* @param time The time the recorded value occurred at
88+
* @param timeUnit Time unit of the provided time
89+
*/
90+
void update(long value, long time, TimeUnit timeUnit);
91+
92+
/**
93+
* Returns a snapshot of the reservoir's values at given time or newer. It may not be supported to return a snapshot in past
94+
* due to performance optimizations.
95+
*
96+
* @param time The time for which to get the snapshot
97+
* @param timeUnit Time unit of the provided time
98+
* @return a snapshot of the reservoir's values for given time or newer
99+
*/
100+
UniformTimeSnapshot getSnapshot(long time, TimeUnit timeUnit);
101+
}

0 commit comments

Comments
 (0)