summaryrefslogtreecommitdiffstats
path: root/jdisc_core/src/main/java/com/yahoo/jdisc/core/ActiveContainerStatistics.java
blob: e5404d58ab82ce3ff56d9b40b3dcd7bc9e4b6f99 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.core;

import com.yahoo.jdisc.Metric;

import java.time.Instant;
import java.util.List;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

/**
 * Tracks statistics on stale {@link ActiveContainer} instances.
 *
 * @author bjorncs
 */
public class ActiveContainerStatistics {
    public interface Metrics {
        String TOTAL_DEACTIVATED_CONTAINERS = "jdisc.deactivated_containers.total";
        String DEACTIVATED_CONTAINERS_WITH_RETAINED_REFERENCES = "jdisc.deactivated_containers.with_retained_refs";
    }

    private static final Logger log = Logger.getLogger(ActiveContainerStatistics.class.getName());

    private final WeakHashMap<ActiveContainer, ActiveContainerStats> activeContainers = new WeakHashMap<>();
    private final Object lock = new Object();

    ActiveContainerStatistics() {} // Make class only constructible from this package

    public void emitMetrics(Metric metric) {
        synchronized (lock) {
            DeactivatedContainerMetrics metrics = deactivatedContainerStream()
                    .collect(
                            DeactivatedContainerMetrics::new,
                            DeactivatedContainerMetrics::aggregate,
                            DeactivatedContainerMetrics::merge);

            metric.set(Metrics.TOTAL_DEACTIVATED_CONTAINERS, metrics.deactivatedContainerCount, null);
            metric.set(Metrics.DEACTIVATED_CONTAINERS_WITH_RETAINED_REFERENCES, metrics.deactivatedContainersWithRetainedRefsCount, null);
        }
    }

    void onActivated(ActiveContainer activeContainer) {
        synchronized (lock) {
            activeContainers.put(activeContainer, new ActiveContainerStats(Instant.now()));
        }
    }

    void onDeactivated(ActiveContainer activeContainer) {
        synchronized (lock) {
            ActiveContainerStats containerStats = activeContainers.get(activeContainer);
            if (containerStats == null) {
                throw new IllegalStateException("onActivated() has not been called for container: " + activeContainer);
            }
            containerStats.timeDeactivated = Instant.now();
        }
    }

    void printSummaryToLog() {
        synchronized (lock) {
            List<DeactivatedContainer> deactivatedContainers = deactivatedContainerStream().collect(toList());
            if (deactivatedContainers.isEmpty()) return;

            log.warning(
                    "Multiple instances of ActiveContainer leaked! " + deactivatedContainers.size() +
                            " instances are still present.");
            deactivatedContainers.stream()
                    .map(c -> " - " + c.toSummaryString())
                    .forEach(log::warning);
        }
    }

    private Stream<DeactivatedContainer> deactivatedContainerStream() {
        synchronized (lock) {
            return activeContainers.entrySet().stream()
                    .filter(e -> e.getValue().isDeactivated())
                    .map(e -> new DeactivatedContainer(e.getKey(), e.getValue().timeActivated, e.getValue().timeDeactivated));
        }
    }

    private static class ActiveContainerStats {
        public final Instant timeActivated;
        public Instant timeDeactivated;

        public ActiveContainerStats(Instant timeActivated) {
            this.timeActivated = timeActivated;
        }

        public boolean isDeactivated() {
            return timeDeactivated != null;
        }
    }

    private static class DeactivatedContainer {
        public final ActiveContainer activeContainer;
        public final Instant timeActivated;
        public final Instant timeDeactivated;

        public DeactivatedContainer(ActiveContainer activeContainer, Instant timeActivated, Instant timeDeactivated) {
            this.activeContainer = activeContainer;
            this.timeActivated = timeActivated;
            this.timeDeactivated = timeDeactivated;
        }

        public String toSummaryString() {
            return String.format("%s: timeActivated=%s, timeDeactivated=%s, retainCount=%d",
                    activeContainer.toString(),
                    timeActivated.toString(),
                    timeDeactivated.toString(),
                    activeContainer.retainCount());
        }
    }

    private static class DeactivatedContainerMetrics {
        public int deactivatedContainerCount = 0;
        public int deactivatedContainersWithRetainedRefsCount = 0;

        public void aggregate(DeactivatedContainer deactivatedContainer) {
            ++deactivatedContainerCount;
            if (deactivatedContainer.activeContainer.retainCount() > 0) {
                ++deactivatedContainersWithRetainedRefsCount;
            }
        }

        public DeactivatedContainerMetrics merge(DeactivatedContainerMetrics other) {
            deactivatedContainerCount += other.deactivatedContainerCount;
            deactivatedContainersWithRetainedRefsCount += other.deactivatedContainersWithRetainedRefsCount;
            return this;
        }
    }
}