/* * Copyright 2015-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.slb; import com.google.common.base.Preconditions; import java.net.URI; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; public class ServerHealthState { private static final int MAX_STORED_SAMPLES = 100; private final int maxSamplesStored; private final URI server; private final List<LatencySample> pingLatencies; private final List<RequestSample> requests; public ServerHealthState(URI server) { this(server, MAX_STORED_SAMPLES); } public ServerHealthState(URI server, int maxSamplesStored) { Preconditions.checkArgument( maxSamplesStored > 0, "The maximum number of samples stored must be positive instead of [%s].", maxSamplesStored); this.maxSamplesStored = maxSamplesStored; this.server = server; this.pingLatencies = new LinkedList<>(); this.requests = new LinkedList<>(); } /** * NOTE: Assumes nowMillis is roughly non-decreasing in consecutive calls. * * @param nowMillis * @param latencyMillis */ public void reportPingLatency(long nowMillis, long latencyMillis) { synchronized (pingLatencies) { pingLatencies.add(new LatencySample(nowMillis, latencyMillis)); keepWithinSizeLimit(pingLatencies); } } /** * NOTE: Assumes nowMillis is roughly non-decreasing in consecutive calls. * * @param nowMillis */ public void reportRequestSuccess(long nowMillis) { reportRequest(nowMillis, true); } /** * NOTE: Assumes nowMillis is roughly non-decreasing in consecutive calls. * * @param nowMillis */ public void reportRequestError(long nowMillis) { reportRequest(nowMillis, false); } private void reportRequest(long nowMillis, boolean wasSuccessful) { synchronized (requests) { requests.add(new RequestSample(nowMillis, wasSuccessful)); keepWithinSizeLimit(requests); } } public int getPingLatencySampleCount() { synchronized (pingLatencies) { return pingLatencies.size(); } } public int getRequestSampleCount() { synchronized (requests) { return requests.size(); } } private <T> void keepWithinSizeLimit(List<T> list) { while (list.size() > maxSamplesStored) { // Assume list is time sorted; always remove oldest sample. list.remove(0); } } /** * @param nowMillis Current timestamp. * @param timeRangeMillis Time range for 'nowMillis' to compute the errorPercentage for. * @return Value in the interval [0.0, 1.0]. */ public float getErrorPercentage(long nowMillis, int timeRangeMillis) { int errorCount = 0; int requestCount = 0; long initialMillis = nowMillis - timeRangeMillis; synchronized (requests) { ListIterator<RequestSample> iterator = requests.listIterator(requests.size()); while (iterator.hasPrevious()) { RequestSample sample = iterator.previous(); long requestMillis = sample.getEpochMillis(); if (requestMillis >= initialMillis && requestMillis <= nowMillis) { if (!sample.wasSuccessful()) { ++errorCount; } ++requestCount; } } } if (requestCount == 0) { return 0; } else { return errorCount / ((float) requestCount); } } public URI getServer() { return server; } public long getPingLatencyMillis(long nowMillis, int timeRangeMillis) { int count = 0; int sum = 0; long initialMillis = nowMillis - timeRangeMillis; synchronized (pingLatencies) { ListIterator<LatencySample> iterator = pingLatencies.listIterator(pingLatencies.size()); while (iterator.hasPrevious()) { LatencySample sample = iterator.previous(); if (sample.getEpochMillis() >= initialMillis && sample.getEpochMillis() <= nowMillis) { sum += sample.getLatencyMillis(); ++count; } } } if (count > 0) { return sum / count; } else { return -1; } } public String toString(long nowMillis, int timeRangeMillis) { return "ServerHealthState{" + "server=" + server + ", latencyMillis=" + getPingLatencyMillis(nowMillis, timeRangeMillis) + ", errorCount=" + getErrorPercentage(nowMillis, timeRangeMillis) + '}'; } private static final class RequestSample { private final long epochMillis; private final boolean wasSuccessful; private RequestSample(long epochMillis, boolean wasSuccessful) { this.epochMillis = epochMillis; this.wasSuccessful = wasSuccessful; } public long getEpochMillis() { return epochMillis; } public boolean wasSuccessful() { return wasSuccessful; } } private static final class LatencySample { private final long latencyMillis; private final long epochMillis; public LatencySample(long epochMillis, long latencyMillis) { this.latencyMillis = latencyMillis; this.epochMillis = epochMillis; } public long getLatencyMillis() { return latencyMillis; } public long getEpochMillis() { return epochMillis; } } }