blob: f6acac39f6dd69fe8ed1c6a905c5ec9828dffbd7 [file] [log] [blame]
Madan Jampania14047d2015-02-25 12:23:02 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Madan Jampania14047d2015-02-25 12:23:02 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Madan Jampanibd6845d2015-02-25 11:43:48 -080016package org.onosproject.store.cluster.impl;
17
18import static com.google.common.base.Preconditions.checkArgument;
19import static com.google.common.base.Preconditions.checkNotNull;
20
21import java.util.Map;
22
23import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
24import org.onosproject.cluster.NodeId;
25
26import com.google.common.collect.Maps;
27
28/**
29 * Phi Accrual failure detector.
30 * <p>
31 * Based on a paper titled: "The φ Accrual Failure Detector" by Hayashibara, et al.
32 */
33public class PhiAccrualFailureDetector {
34 private final Map<NodeId, History> states = Maps.newConcurrentMap();
35
sangyun-hanf98df542016-03-24 20:28:03 +090036 // Default value
37 private static final int DEFAULT_WINDOW_SIZE = 250;
38 private static final int DEFAULT_MIN_SAMPLES = 25;
Jordan Halterman4a082ec2018-05-14 15:00:42 -070039 private static final long DEFAULT_MIN_STANDARD_DEVIATION_MILLIS = 50;
Madan Jampanibd6845d2015-02-25 11:43:48 -080040
41 // If a node does not have any heartbeats, this is the phi
42 // value to report. Indicates the node is inactive (from the
43 // detectors perspective.
sangyun-hanf98df542016-03-24 20:28:03 +090044 private static final double DEFAULT_BOOTSTRAP_PHI_VALUE = 100.0;
45
Jordan Halterman4a082ec2018-05-14 15:00:42 -070046 private final int minSamples;
47 private final long minStandardDeviationMillis;
48 private final double bootstrapPhiValue = DEFAULT_BOOTSTRAP_PHI_VALUE;
sangyun-hanf98df542016-03-24 20:28:03 +090049
Jordan Halterman4a082ec2018-05-14 15:00:42 -070050 public PhiAccrualFailureDetector() {
51 this(DEFAULT_MIN_SAMPLES, DEFAULT_MIN_STANDARD_DEVIATION_MILLIS);
52 }
53
54 public PhiAccrualFailureDetector(long minStandardDeviationMillis) {
55 this(DEFAULT_MIN_SAMPLES, minStandardDeviationMillis);
56 }
57
58 public PhiAccrualFailureDetector(int minSamples, long minStandardDeviationMillis) {
59 checkArgument(minSamples > 0, "minSamples must be positive");
60 checkArgument(minStandardDeviationMillis > 0, "minStandardDeviationMillis must be positive");
61 this.minSamples = minSamples;
62 this.minStandardDeviationMillis = minStandardDeviationMillis;
63 }
Madan Jampanibd6845d2015-02-25 11:43:48 -080064
65 /**
Jordan Haltermane7062532018-01-22 11:22:46 -080066 * Returns the last heartbeat time for the given node.
67 *
68 * @param nodeId the node identifier
69 * @return the last heartbeat time for the given node
70 */
71 public long getLastHeartbeatTime(NodeId nodeId) {
72 History nodeState = states.computeIfAbsent(nodeId, key -> new History());
73 return nodeState.latestHeartbeatTime();
74 }
75
76 /**
Madan Jampanibd6845d2015-02-25 11:43:48 -080077 * Report a new heart beat for the specified node id.
78 * @param nodeId node id
79 */
80 public void report(NodeId nodeId) {
81 report(nodeId, System.currentTimeMillis());
82 }
83
84 /**
85 * Report a new heart beat for the specified node id.
86 * @param nodeId node id
87 * @param arrivalTime arrival time
88 */
89 public void report(NodeId nodeId, long arrivalTime) {
90 checkNotNull(nodeId, "NodeId must not be null");
91 checkArgument(arrivalTime >= 0, "arrivalTime must not be negative");
Jordan Halterman4a082ec2018-05-14 15:00:42 -070092 History nodeState = states.computeIfAbsent(nodeId, key -> new History());
Madan Jampanibd6845d2015-02-25 11:43:48 -080093 synchronized (nodeState) {
94 long latestHeartbeat = nodeState.latestHeartbeatTime();
95 if (latestHeartbeat != -1) {
96 nodeState.samples().addValue(arrivalTime - latestHeartbeat);
97 }
98 nodeState.setLatestHeartbeatTime(arrivalTime);
99 }
100 }
101
Jordan Halterman1315d3e2018-01-16 19:41:31 -0800102 /**
103 * Resets the failure detector for the given node.
104 *
105 * @param nodeId node identifier for the node for which to reset the failure detector
106 */
107 public void reset(NodeId nodeId) {
Jordan Haltermane7062532018-01-22 11:22:46 -0800108 states.remove(nodeId);
Jordan Halterman1315d3e2018-01-16 19:41:31 -0800109 }
sangyun-hanf98df542016-03-24 20:28:03 +0900110
Madan Jampanibd6845d2015-02-25 11:43:48 -0800111 /**
112 * Compute phi for the specified node id.
113 * @param nodeId node id
114 * @return phi value
115 */
Madan Jampania14047d2015-02-25 12:23:02 -0800116 public double phi(NodeId nodeId) {
117 checkNotNull(nodeId, "NodeId must not be null");
Madan Jampanibd6845d2015-02-25 11:43:48 -0800118 if (!states.containsKey(nodeId)) {
sangyun-hanf98df542016-03-24 20:28:03 +0900119 return bootstrapPhiValue;
Madan Jampanibd6845d2015-02-25 11:43:48 -0800120 }
Madan Jampanibd6845d2015-02-25 11:43:48 -0800121 History nodeState = states.get(nodeId);
122 synchronized (nodeState) {
123 long latestHeartbeat = nodeState.latestHeartbeatTime();
124 DescriptiveStatistics samples = nodeState.samples();
sangyun-hanf98df542016-03-24 20:28:03 +0900125 if (latestHeartbeat == -1 || samples.getN() < minSamples) {
Madan Jampanibd6845d2015-02-25 11:43:48 -0800126 return 0.0;
127 }
128 return computePhi(samples, latestHeartbeat, System.currentTimeMillis());
129 }
130 }
131
132 private double computePhi(DescriptiveStatistics samples, long tLast, long tNow) {
Jordan Halterman4a082ec2018-05-14 15:00:42 -0700133 long elapsedTime = tNow - tLast;
134 double meanMillis = samples.getMean();
135 double y = (elapsedTime - meanMillis) / Math.max(samples.getStandardDeviation(), minStandardDeviationMillis);
136 double e = Math.exp(-y * (1.5976 + 0.070566 * y * y));
137 if (elapsedTime > meanMillis) {
138 return -Math.log10(e / (1.0 + e));
139 } else {
140 return -Math.log10(1.0 - 1.0 / (1.0 + e));
141 }
Madan Jampanibd6845d2015-02-25 11:43:48 -0800142 }
143
144 private static class History {
Jordan Halterman4a082ec2018-05-14 15:00:42 -0700145 DescriptiveStatistics samples = new DescriptiveStatistics(DEFAULT_WINDOW_SIZE);
Madan Jampanibd6845d2015-02-25 11:43:48 -0800146 long lastHeartbeatTime = -1;
147
Jordan Halterman4a082ec2018-05-14 15:00:42 -0700148 DescriptiveStatistics samples() {
Madan Jampanibd6845d2015-02-25 11:43:48 -0800149 return samples;
150 }
151
Jordan Halterman4a082ec2018-05-14 15:00:42 -0700152 long latestHeartbeatTime() {
Madan Jampanibd6845d2015-02-25 11:43:48 -0800153 return lastHeartbeatTime;
154 }
155
Jordan Halterman4a082ec2018-05-14 15:00:42 -0700156 void setLatestHeartbeatTime(long value) {
Madan Jampanibd6845d2015-02-25 11:43:48 -0800157 lastHeartbeatTime = value;
158 }
159 }
160}