summaryrefslogtreecommitdiffstats
path: root/jdisc_core/src/main/java/com/yahoo/jdisc/core/ActiveContainerStatistics.java
blob: 43a57aece648175d4829f80f52f727cbe9c541fc (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
134
135
136
137
// 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();

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

    public 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.setTimeDeactived(Instant.now());
        }
    }

    public void outputMetrics(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);
        }
    }

    public 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.getKey() != null)
                    .filter(e -> !e.getValue().isDeactivated())
                    .map(e -> new DeactivatedContainer(e.getKey(), e.getValue().timeActivated, e.getValue().timeDeactived));
        }
    }

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

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

        public void setTimeDeactived(Instant instant) {
            this.timeDeactived = instant;
        }

        public boolean isDeactivated() {
            return timeDeactived == 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;
        }
    }
}