blob: a558caf17c4127fb52385c09fda0bb1ffde456f5 [file]
/*!
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*
*/
import * as grpc from '@grpc/grpc-js';
import * as os from 'os';
import * as packageInfo from '../../../../package.json';
import config from '../../../config/AgentConfig';
import { createLogger, throttled } from '../../../logging';
import BootService from '../boot/BootService';
import ServiceManager from '../boot/ServiceManager';
import { ManagementServiceClient } from '../../../proto/management/Management_grpc_pb';
import { InstancePingPkg, InstanceProperties } from '../../../proto/management/Management_pb';
import { KeyStringValuePair } from '../../../proto/common/Common_pb';
import GRPCChannelManager from './GRPCChannelManager';
import { GRPCChannelListener } from './GRPCChannelListener';
import { GRPCChannelStatus } from './GRPCChannelStatus';
const logger = createLogger(__filename);
const logHeartbeatError = throttled(logger, 'error', 30000);
export default class ServiceManagementClient implements BootService, GRPCChannelListener {
private closed = false;
private channelManager?: GRPCChannelManager;
private status = GRPCChannelStatus.DISCONNECT;
private managementServiceClient?: ManagementServiceClient;
private heartbeatTimer?: NodeJS.Timeout;
private keepAlivePkg?: InstancePingPkg;
private instanceProperties?: InstanceProperties;
private sendPropertiesCounter = 0;
/** Same default as Java Config.Collector.PROPERTIES_REPORT_PERIOD_FACTOR (10). */
private static readonly PROPERTIES_REPORT_PERIOD_FACTOR = 10;
prepare(): void {
this.channelManager = ServiceManager.INSTANCE.findService(GRPCChannelManager);
this.channelManager?.addChannelListener(this);
}
boot(): void {
if (this.heartbeatTimer) {
logger.warn(`
The heartbeat timer has already been scheduled,
this may be a potential bug, please consider reporting
this to ${packageInfo.bugs.url}
`);
return;
}
this.keepAlivePkg = new InstancePingPkg().setService(config.serviceName).setServiceinstance(config.serviceInstance);
this.instanceProperties = new InstanceProperties()
.setService(config.serviceName)
.setServiceinstance(config.serviceInstance)
.setPropertiesList([
new KeyStringValuePair().setKey('language').setValue('NodeJS'),
new KeyStringValuePair().setKey('OS Name').setValue(os.platform()),
new KeyStringValuePair().setKey('hostname').setValue(os.hostname()),
new KeyStringValuePair().setKey('Process No.').setValue(`${process.pid}`),
]);
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), 20000) as NodeJS.Timeout;
this.heartbeatTimer.unref();
}
onComplete(): void {}
shutdown(): void {
this.closed = true;
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = undefined;
}
this.managementServiceClient = undefined;
this.channelManager = undefined;
logger.info('ServiceManagementClient destroyed and resources cleaned up');
}
priority(): number {
return 0;
}
statusChanged(status: GRPCChannelStatus): void {
this.status = status;
this.managementServiceClient = status === GRPCChannelStatus.CONNECTED ? this.createManagementClient() : undefined;
}
private sendHeartbeat(): void {
if (
this.closed ||
this.status !== GRPCChannelStatus.CONNECTED ||
!this.managementServiceClient ||
!this.instanceProperties ||
!this.keepAlivePkg
) {
return;
}
const options = { deadline: Date.now() + config.traceTimeout };
const reportProperties =
Math.abs(this.sendPropertiesCounter++) % ServiceManagementClient.PROPERTIES_REPORT_PERIOD_FACTOR === 0;
if (reportProperties) {
this.managementServiceClient.reportInstanceProperties(
this.instanceProperties,
new grpc.Metadata(),
options,
(error) => {
if (error) {
logHeartbeatError('Failed to send instance properties', error);
this.reportGrpcError(error);
}
},
);
return;
}
this.managementServiceClient.keepAlive(this.keepAlivePkg, new grpc.Metadata(), options, (error) => {
if (error) {
logHeartbeatError('Failed to send heartbeat', error);
this.reportGrpcError(error);
}
});
}
private createManagementClient(): ManagementServiceClient | undefined {
if (!this.channelManager) {
return undefined;
}
return new ManagementServiceClient(
config.collectorAddress,
grpc.credentials.createInsecure(),
this.channelManager.getClientOptions(),
);
}
private reportGrpcError(error: unknown): void {
if (this.closed) {
return;
}
this.channelManager?.reportError(error);
}
flush(): Promise<unknown> | null {
logger.warn('ServiceManagementClient does not need flush().');
return null;
}
}