7e7283842bf326e8d1ff80f48ed077be81fc220c
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / ConcurrentDurationStatisticsTracker.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.util;
9
10 import com.google.common.primitives.UnsignedLong;
11 import java.lang.invoke.MethodHandles;
12 import java.lang.invoke.VarHandle;
13
14 /**
15  * Concurrent version of {@link DurationStatisticsTracker}.
16  */
17 // TODO: once DurationStatsTracker is gone make this class final
18 class ConcurrentDurationStatisticsTracker extends DurationStatisticsTracker {
19     private static final VarHandle SUM;
20     private static final VarHandle COUNT;
21     private static final VarHandle LONGEST;
22     private static final VarHandle SHORTEST;
23
24     static {
25         final var lookup = MethodHandles.lookup();
26         try {
27             SUM = lookup.findVarHandle(ConcurrentDurationStatisticsTracker.class, "sum", long.class);
28             COUNT = lookup.findVarHandle(ConcurrentDurationStatisticsTracker.class, "count", long.class);
29             LONGEST = lookup.findVarHandle(
30                 ConcurrentDurationStatisticsTracker.class, "longest", DurationWithTime.class);
31             SHORTEST = lookup.findVarHandle(
32                 ConcurrentDurationStatisticsTracker.class, "shortest", DurationWithTime.class);
33         } catch (NoSuchFieldException | IllegalAccessException e) {
34             throw new ExceptionInInitializerError(e);
35         }
36     }
37
38     private volatile long sum;
39     private volatile long count;
40     private volatile DurationWithTime longest;
41     private volatile DurationWithTime shortest;
42
43     ConcurrentDurationStatisticsTracker() {
44         // Hidden on purpose
45     }
46
47     @Override
48     public final void addDuration(final long duration) {
49         // First update the quick stats
50         SUM.getAndAdd(this, duration);
51         COUNT.getAndAdd(this, 1L);
52
53         /*
54          * Now the hairy 'min/max' things. The notion of "now" we cache, so the first time we use it, we do not call it
55          * twice. We populate it lazily, though.
56          *
57          * The longest/shortest stats both are encapsulated in an object, so we update them atomically and we minimize
58          * the number of volatile operations.
59          */
60         final var currentShortest = (DurationWithTime) SHORTEST.getAcquire(this);
61         if (currentShortest == null || duration < currentShortest.duration()) {
62             updateShortest(currentShortest, duration);
63         }
64         final var currentLongest = (DurationWithTime) LONGEST.getAcquire(this);
65         if (currentLongest == null || duration > currentLongest.duration()) {
66             updateLongest(currentLongest, duration);
67         }
68     }
69
70     private void updateShortest(final DurationWithTime prev, final long duration) {
71         final var newObj = new DurationWithTime(duration, System.currentTimeMillis());
72
73         var expected = prev;
74         while (true) {
75             final var witness = (DurationWithTime) SHORTEST.compareAndExchangeRelease(this, expected, newObj);
76             if (witness == expected || duration >= witness.duration()) {
77                 break;
78             }
79             expected = witness;
80         }
81     }
82
83     private void updateLongest(final DurationWithTime prev, final long duration) {
84         final var newObj = new DurationWithTime(duration, System.currentTimeMillis());
85
86         var expected = prev;
87         while (true) {
88             final var witness = (DurationWithTime) LONGEST.compareAndExchangeRelease(this, expected, newObj);
89             if (witness == expected || duration <= witness.duration()) {
90                 break;
91             }
92             expected = witness;
93         }
94     }
95
96     @Override
97     public final long getTotalDurations() {
98         return count;
99     }
100
101     @Override
102     public final double getAverageDuration() {
103         final long myCount = count;
104         return myCount == 0 ? 0 : UnsignedLong.fromLongBits(sum).doubleValue() / myCount;
105     }
106
107     @Override
108     public final synchronized void reset() {
109         // Synchronized is just to make sure we do not have concurrent resets :)
110         longest = null;
111         shortest = null;
112         count = 0;
113         sum = 0;
114     }
115
116     @Override
117     protected final DurationWithTime getLongest() {
118         return longest;
119     }
120
121     @Override
122     protected final DurationWithTime getShortest() {
123         return shortest;
124     }
125 }