mirror of https://github.com/grpc/grpc-node.git
grpc-js: Add outlier detection tracing and enable it in interop tests
This commit is contained in:
parent
586f7c6db9
commit
edbdc570c7
|
@ -33,6 +33,6 @@ COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/
|
||||||
COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/
|
COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/
|
||||||
|
|
||||||
ENV GRPC_VERBOSITY="DEBUG"
|
ENV GRPC_VERBOSITY="DEBUG"
|
||||||
ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds
|
ENV GRPC_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds,outlier_detection
|
||||||
|
|
||||||
ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ]
|
ENTRYPOINT [ "node", "/node/src/grpc-node/packages/grpc-js-xds/build/interop/xds-interop-client" ]
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
import { ChannelOptions, connectivityState, StatusObject } from ".";
|
import { ChannelOptions, connectivityState, StatusObject } from ".";
|
||||||
import { Call } from "./call-stream";
|
import { Call } from "./call-stream";
|
||||||
import { ConnectivityState } from "./connectivity-state";
|
import { ConnectivityState } from "./connectivity-state";
|
||||||
import { Status } from "./constants";
|
import { LogVerbosity, Status } from "./constants";
|
||||||
import { durationToMs, isDuration, msToDuration } from "./duration";
|
import { durationToMs, isDuration, msToDuration } from "./duration";
|
||||||
import { ChannelControlHelper, createChildChannelControlHelper, registerLoadBalancerType } from "./experimental";
|
import { ChannelControlHelper, createChildChannelControlHelper, registerLoadBalancerType } from "./experimental";
|
||||||
import { BaseFilter, Filter, FilterFactory } from "./filter";
|
import { BaseFilter, Filter, FilterFactory } from "./filter";
|
||||||
|
@ -28,7 +28,13 @@ import { PickArgs, Picker, PickResult, PickResultType, QueuePicker, UnavailableP
|
||||||
import { Subchannel } from "./subchannel";
|
import { Subchannel } from "./subchannel";
|
||||||
import { SubchannelAddress, subchannelAddressToString } from "./subchannel-address";
|
import { SubchannelAddress, subchannelAddressToString } from "./subchannel-address";
|
||||||
import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from "./subchannel-interface";
|
import { BaseSubchannelWrapper, ConnectivityStateListener, SubchannelInterface } from "./subchannel-interface";
|
||||||
|
import * as logging from './logging';
|
||||||
|
|
||||||
|
const TRACER_NAME = 'outlier_detection';
|
||||||
|
|
||||||
|
function trace(text: string): void {
|
||||||
|
logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text);
|
||||||
|
}
|
||||||
|
|
||||||
const TYPE_NAME = 'outlier_detection';
|
const TYPE_NAME = 'outlier_detection';
|
||||||
|
|
||||||
|
@ -412,6 +418,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
if (!successRateConfig) {
|
if (!successRateConfig) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
trace('Running success rate check');
|
||||||
// Step 1
|
// Step 1
|
||||||
const targetRequestVolume = successRateConfig.request_volume;
|
const targetRequestVolume = successRateConfig.request_volume;
|
||||||
let addresesWithTargetVolume = 0;
|
let addresesWithTargetVolume = 0;
|
||||||
|
@ -424,6 +431,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
successRates.push(successes/(successes + failures));
|
successRates.push(successes/(successes + failures));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
trace('Found ' + addresesWithTargetVolume + ' success rate candidates; currentEjectionPercent=' + this.getCurrentEjectionPercent() + ' successRates=[' + successRates + ']');
|
||||||
if (addresesWithTargetVolume < successRateConfig.minimum_hosts) {
|
if (addresesWithTargetVolume < successRateConfig.minimum_hosts) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -438,9 +446,10 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
const successRateVariance = successRateDeviationSum / successRates.length;
|
const successRateVariance = successRateDeviationSum / successRates.length;
|
||||||
const successRateStdev = Math.sqrt(successRateVariance);
|
const successRateStdev = Math.sqrt(successRateVariance);
|
||||||
const ejectionThreshold = successRateMean - successRateStdev * (successRateConfig.stdev_factor / 1000);
|
const ejectionThreshold = successRateMean - successRateStdev * (successRateConfig.stdev_factor / 1000);
|
||||||
|
trace('stdev=' + successRateStdev + ' ejectionThreshold=' + ejectionThreshold);
|
||||||
|
|
||||||
// Step 3
|
// Step 3
|
||||||
for (const mapEntry of this.addressMap.values()) {
|
for (const [address, mapEntry] of this.addressMap.entries()) {
|
||||||
// Step 3.i
|
// Step 3.i
|
||||||
if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) {
|
if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) {
|
||||||
break;
|
break;
|
||||||
|
@ -453,9 +462,12 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
}
|
}
|
||||||
// Step 3.iii
|
// Step 3.iii
|
||||||
const successRate = successes / (successes + failures);
|
const successRate = successes / (successes + failures);
|
||||||
|
trace('Checking candidate ' + address + ' successRate=' + successRate);
|
||||||
if (successRate < ejectionThreshold) {
|
if (successRate < ejectionThreshold) {
|
||||||
const randomNumber = Math.random() * 100;
|
const randomNumber = Math.random() * 100;
|
||||||
|
trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + successRateConfig.enforcement_percentage);
|
||||||
if (randomNumber < successRateConfig.enforcement_percentage) {
|
if (randomNumber < successRateConfig.enforcement_percentage) {
|
||||||
|
trace('Ejecting candidate ' + address);
|
||||||
this.eject(mapEntry, ejectionTimestamp);
|
this.eject(mapEntry, ejectionTimestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -470,13 +482,14 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
if (!failurePercentageConfig) {
|
if (!failurePercentageConfig) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
trace('Running failure percentage check. threshold=' + failurePercentageConfig.threshold + ' request volume threshold=' + failurePercentageConfig.request_volume);
|
||||||
// Step 1
|
// Step 1
|
||||||
if (this.addressMap.size < failurePercentageConfig.minimum_hosts) {
|
if (this.addressMap.size < failurePercentageConfig.minimum_hosts) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2
|
// Step 2
|
||||||
for (const mapEntry of this.addressMap.values()) {
|
for (const [address, mapEntry] of this.addressMap.entries()) {
|
||||||
// Step 2.i
|
// Step 2.i
|
||||||
if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) {
|
if (this.getCurrentEjectionPercent() > this.latestConfig.getMaxEjectionPercent()) {
|
||||||
break;
|
break;
|
||||||
|
@ -484,6 +497,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
// Step 2.ii
|
// Step 2.ii
|
||||||
const successes = mapEntry.counter.getLastSuccesses();
|
const successes = mapEntry.counter.getLastSuccesses();
|
||||||
const failures = mapEntry.counter.getLastFailures();
|
const failures = mapEntry.counter.getLastFailures();
|
||||||
|
trace('Candidate successes=' + successes + ' failures=' + failures);
|
||||||
if (successes + failures < failurePercentageConfig.request_volume) {
|
if (successes + failures < failurePercentageConfig.request_volume) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -491,7 +505,9 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
const failurePercentage = (failures * 100) / (failures + successes);
|
const failurePercentage = (failures * 100) / (failures + successes);
|
||||||
if (failurePercentage > failurePercentageConfig.threshold) {
|
if (failurePercentage > failurePercentageConfig.threshold) {
|
||||||
const randomNumber = Math.random() * 100;
|
const randomNumber = Math.random() * 100;
|
||||||
|
trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + failurePercentageConfig.enforcement_percentage);
|
||||||
if (randomNumber < failurePercentageConfig.enforcement_percentage) {
|
if (randomNumber < failurePercentageConfig.enforcement_percentage) {
|
||||||
|
trace('Ejecting candidate ' + address);
|
||||||
this.eject(mapEntry, ejectionTimestamp);
|
this.eject(mapEntry, ejectionTimestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -525,6 +541,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
|
|
||||||
private runChecks() {
|
private runChecks() {
|
||||||
const ejectionTimestamp = new Date();
|
const ejectionTimestamp = new Date();
|
||||||
|
trace('Ejection timer running');
|
||||||
|
|
||||||
this.switchAllBuckets();
|
this.switchAllBuckets();
|
||||||
|
|
||||||
|
@ -537,7 +554,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
this.runSuccessRateCheck(ejectionTimestamp);
|
this.runSuccessRateCheck(ejectionTimestamp);
|
||||||
this.runFailurePercentageCheck(ejectionTimestamp);
|
this.runFailurePercentageCheck(ejectionTimestamp);
|
||||||
|
|
||||||
for (const mapEntry of this.addressMap.values()) {
|
for (const [address, mapEntry] of this.addressMap.entries()) {
|
||||||
if (mapEntry.currentEjectionTimestamp === null) {
|
if (mapEntry.currentEjectionTimestamp === null) {
|
||||||
if (mapEntry.ejectionTimeMultiplier > 0) {
|
if (mapEntry.ejectionTimeMultiplier > 0) {
|
||||||
mapEntry.ejectionTimeMultiplier -= 1;
|
mapEntry.ejectionTimeMultiplier -= 1;
|
||||||
|
@ -548,6 +565,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
const returnTime = new Date(mapEntry.currentEjectionTimestamp.getTime());
|
const returnTime = new Date(mapEntry.currentEjectionTimestamp.getTime());
|
||||||
returnTime.setMilliseconds(returnTime.getMilliseconds() + Math.min(baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, Math.max(baseEjectionTimeMs, maxEjectionTimeMs)));
|
returnTime.setMilliseconds(returnTime.getMilliseconds() + Math.min(baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, Math.max(baseEjectionTimeMs, maxEjectionTimeMs)));
|
||||||
if (returnTime < new Date()) {
|
if (returnTime < new Date()) {
|
||||||
|
trace('Unejecting ' + address);
|
||||||
this.uneject(mapEntry);
|
this.uneject(mapEntry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -564,6 +582,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
}
|
}
|
||||||
for (const address of subchannelAddresses) {
|
for (const address of subchannelAddresses) {
|
||||||
if (!this.addressMap.has(address)) {
|
if (!this.addressMap.has(address)) {
|
||||||
|
trace('Adding map entry for ' + address);
|
||||||
this.addressMap.set(address, {
|
this.addressMap.set(address, {
|
||||||
counter: new CallCounter(),
|
counter: new CallCounter(),
|
||||||
currentEjectionTimestamp: null,
|
currentEjectionTimestamp: null,
|
||||||
|
@ -574,6 +593,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
}
|
}
|
||||||
for (const key of this.addressMap.keys()) {
|
for (const key of this.addressMap.keys()) {
|
||||||
if (!subchannelAddresses.has(key)) {
|
if (!subchannelAddresses.has(key)) {
|
||||||
|
trace('Removing map entry for ' + key);
|
||||||
this.addressMap.delete(key);
|
this.addressMap.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -585,15 +605,18 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
||||||
|
|
||||||
if (lbConfig.getSuccessRateEjectionConfig() || lbConfig.getFailurePercentageEjectionConfig()) {
|
if (lbConfig.getSuccessRateEjectionConfig() || lbConfig.getFailurePercentageEjectionConfig()) {
|
||||||
if (this.timerStartTime) {
|
if (this.timerStartTime) {
|
||||||
|
trace('Previous timer existed. Replacing timer');
|
||||||
clearTimeout(this.ejectionTimer);
|
clearTimeout(this.ejectionTimer);
|
||||||
const remainingDelay = lbConfig.getIntervalMs() - ((new Date()).getTime() - this.timerStartTime.getTime());
|
const remainingDelay = lbConfig.getIntervalMs() - ((new Date()).getTime() - this.timerStartTime.getTime());
|
||||||
this.startTimer(remainingDelay);
|
this.startTimer(remainingDelay);
|
||||||
} else {
|
} else {
|
||||||
|
trace('Starting new timer');
|
||||||
this.timerStartTime = new Date();
|
this.timerStartTime = new Date();
|
||||||
this.startTimer(lbConfig.getIntervalMs());
|
this.startTimer(lbConfig.getIntervalMs());
|
||||||
this.switchAllBuckets();
|
this.switchAllBuckets();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
trace('Counting disabled. Cancelling timer.');
|
||||||
this.timerStartTime = null;
|
this.timerStartTime = null;
|
||||||
clearTimeout(this.ejectionTimer);
|
clearTimeout(this.ejectionTimer);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue