From 669d34b47e1eabbc99d9584d0d462333d37f4775 Mon Sep 17 00:00:00 2001 From: legendecas Date: Thu, 5 May 2022 23:30:07 +0800 Subject: [PATCH] feat(host-metrics): upgrade api-metrics to v0.28.0 (#990) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gerhard Stöbich Co-authored-by: Rauno Viskus --- .github/component_owners.yml | 2 +- .../opentelemetry-host-metrics/package.json | 4 +- .../src/BaseMetrics.ts | 4 +- .../opentelemetry-host-metrics/src/metric.ts | 53 ++- .../test/metric.test.ts | 311 +++++++++--------- 5 files changed, 188 insertions(+), 186 deletions(-) diff --git a/.github/component_owners.yml b/.github/component_owners.yml index 84524973621..586c55da0c7 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -12,7 +12,7 @@ components: metapackages/auto-instrumentations-web: - obecny packages/opentelemetry-host-metrics: - - obecny + - legendecas packages/opentelemetry-id-generator-aws-xray: - NathanielRN - willarmiros diff --git a/packages/opentelemetry-host-metrics/package.json b/packages/opentelemetry-host-metrics/package.json index eb03391e76a..ccba8a57bd0 100644 --- a/packages/opentelemetry-host-metrics/package.json +++ b/packages/opentelemetry-host-metrics/package.json @@ -59,9 +59,9 @@ "typescript": "4.3.5" }, "dependencies": { - "@opentelemetry/api-metrics": "^0.27.0", + "@opentelemetry/api-metrics": "^0.28.0", "@opentelemetry/core": "^1.0.0", - "@opentelemetry/sdk-metrics-base": "^0.27.0", + "@opentelemetry/sdk-metrics-base": "^0.28.0", "systeminformation": "^5.0.0" } } diff --git a/packages/opentelemetry-host-metrics/src/BaseMetrics.ts b/packages/opentelemetry-host-metrics/src/BaseMetrics.ts index 41abd598051..46fe7a04bcc 100644 --- a/packages/opentelemetry-host-metrics/src/BaseMetrics.ts +++ b/packages/opentelemetry-host-metrics/src/BaseMetrics.ts @@ -36,7 +36,7 @@ export interface MetricsCollectorConfig { url?: string; } -const DEFAULT_MAX_TIMEOUT_UPDATE_MS = 500; +export const DEFAULT_MAX_TIMEOUT_UPDATE_MS = 500; const DEFAULT_NAME = 'opentelemetry-host-metrics'; /** @@ -45,7 +45,7 @@ const DEFAULT_NAME = 'opentelemetry-host-metrics'; export abstract class BaseMetrics { protected _logger = api.diag; protected _maxTimeoutUpdateMS: number; - protected _meter: metrics.Meter; + protected _meter: apiMetrics.Meter; private _name: string; constructor(config: MetricsCollectorConfig) { diff --git a/packages/opentelemetry-host-metrics/src/metric.ts b/packages/opentelemetry-host-metrics/src/metric.ts index 555eec41470..59c634fc749 100644 --- a/packages/opentelemetry-host-metrics/src/metric.ts +++ b/packages/opentelemetry-host-metrics/src/metric.ts @@ -166,73 +166,73 @@ export class HostMetrics extends BaseMetrics { protected _createMetrics(): void { this._meter.createObservableCounter( enums.METRIC_NAMES.CPU_TIME, - { - description: 'Cpu time in seconds', - unit: 's', - }, observableResult => { const cpuUsageData = this._getCpuUsageData(); this._updateCpuTime(observableResult, cpuUsageData); + }, + { + description: 'Cpu time in seconds', + unit: 's', } ); this._meter.createObservableGauge( enums.METRIC_NAMES.CPU_UTILIZATION, - { - description: 'Cpu usage time 0-1', - }, observableResult => { const cpuUsageData = this._getCpuUsageData(); this._updateCpuUtilisation(observableResult, cpuUsageData); + }, + { + description: 'Cpu usage time 0-1', } ); - this._meter.createObservableUpDownCounter( + this._meter.createObservableGauge( enums.METRIC_NAMES.MEMORY_USAGE, - { - description: 'Memory usage in bytes', - }, observableResult => { const memoryUsageData = this._getMemoryData(); this._updateMemUsage(observableResult, memoryUsageData); + }, + { + description: 'Memory usage in bytes', } ); this._meter.createObservableGauge( enums.METRIC_NAMES.MEMORY_UTILIZATION, - { - description: 'Memory usage 0-1', - }, observableResult => { const memoryUsageData = this._getMemoryData(); this._updateMemUtilization(observableResult, memoryUsageData); + }, + { + description: 'Memory usage 0-1', } ); this._meter.createObservableCounter( enums.METRIC_NAMES.NETWORK_DROPPED, - { - description: 'Network dropped packets', - }, async observableResult => { const networkData = await this._getNetworkData(); this._updateNetworkDropped(observableResult, networkData); + }, + { + description: 'Network dropped packets', } ); this._meter.createObservableCounter( enums.METRIC_NAMES.NETWORK_ERRORS, - { - description: 'Network errors counter', - }, async observableResult => { const networkData = await this._getNetworkData(); this._updateNetworkErrors(observableResult, networkData); + }, + { + description: 'Network errors counter', } ); this._meter.createObservableCounter( enums.METRIC_NAMES.NETWORK_IO, - { - description: 'Network transmit and received bytes', - }, async observableResult => { const networkData = await this._getNetworkData(); this._updateNetworkIO(observableResult, networkData); + }, + { + description: 'Network transmit and received bytes', } ); } @@ -241,12 +241,7 @@ export class HostMetrics extends BaseMetrics { * Starts collecting metrics */ start() { - // initial collection - Promise.all([getMemoryData(), getCpuUsageData(), getNetworkData()]).then( - () => { - this._createMetrics(); - } - ); + this._createMetrics(); } private _getMemoryData = throttle(getMemoryData, this._maxTimeoutUpdateMS); diff --git a/packages/opentelemetry-host-metrics/test/metric.test.ts b/packages/opentelemetry-host-metrics/test/metric.test.ts index bbbb28a7c91..09bd450d0b1 100644 --- a/packages/opentelemetry-host-metrics/test/metric.test.ts +++ b/packages/opentelemetry-host-metrics/test/metric.test.ts @@ -15,34 +15,27 @@ */ const SI = require('systeminformation'); -import { ExportResult } from '@opentelemetry/core'; +import { MetricAttributes } from '@opentelemetry/api-metrics'; import { + DataPoint, Histogram, MeterProvider, - MetricExporter, - MetricRecord, + MetricData, + MetricReader, } from '@opentelemetry/sdk-metrics-base'; import * as assert from 'assert'; import * as os from 'os'; import * as sinon from 'sinon'; -import { HostMetrics } from '../src'; +import { DEFAULT_MAX_TIMEOUT_UPDATE_MS, HostMetrics } from '../src'; const cpuJson = require('./mocks/cpu.json'); const networkJson = require('./mocks/network.json'); -class NoopExporter implements MetricExporter { - export( - metrics: MetricRecord[], - resultCallback: (result: ExportResult) => void - ): void {} - - shutdown(): Promise { - return Promise.resolve(); - } +class TestMetricReader extends MetricReader { + protected async onForceFlush(): Promise {} + protected async onShutdown(): Promise {} } -const originalSetTimeout = setTimeout; - let countSI = 0; const mockedSI = { networkStats: function () { @@ -63,185 +56,199 @@ const mockedSI = { }); }, }; -let memoryCallCount = 0; + const mockedOS = { freemem: function () { - memoryCallCount++; - return 7179869184 + 1024 * memoryCallCount; + return 1024; }, totalmem: function () { - return 17179869184; + return 1024 * 1024; }, }; const INTERVAL = 3000; describe('Host Metrics', () => { - let sandbox: sinon.SinonSandbox; - let hostMetrics: any; - let exporter: MetricExporter; - let exportSpy: any; + let meterProvider: MeterProvider; - beforeEach(done => { - sandbox = sinon.createSandbox(); - sandbox.useFakeTimers(); + afterEach(async () => { + await meterProvider?.shutdown(); + }); - sandbox.stub(os, 'freemem').callsFake(() => { - return mockedOS.freemem(); - }); - sandbox.stub(os, 'totalmem').returns(mockedOS.totalmem()); - sandbox.stub(os, 'cpus').returns(cpuJson); - sandbox.stub(process, 'uptime').returns(0); - sandbox.stub(SI, 'networkStats').callsFake(() => { - return mockedSI.networkStats(); + describe('constructor', () => { + it('should create a new instance', () => { + const hostMetrics = new HostMetrics({ + name: 'opentelemetry-host-metrics', + }); + assert.ok(hostMetrics instanceof HostMetrics); }); - exporter = new NoopExporter(); - exportSpy = sandbox.stub(exporter, 'export'); + it('should create a new instance with default meter provider', () => { + meterProvider = new MeterProvider(); - const meterProvider = new MeterProvider({ - interval: INTERVAL, - exporter, + const hostMetrics = new HostMetrics({ + meterProvider, + name: 'opentelemetry-host-metrics', + }); + hostMetrics.start(); + assert.ok(hostMetrics instanceof HostMetrics); }); + }); - hostMetrics = new HostMetrics({ - meterProvider, - name: 'opentelemetry-host-metrics', - }); - hostMetrics.start(); - - countSI = 0; - - // sinon fake doesn't work fine with setImmediate - originalSetTimeout(() => { - // move the clock with the same value as interval - sandbox.clock.tick(INTERVAL * 1); - // move to "real" next tick so that async batcher observer will start - // processing metrics - originalSetTimeout(() => { - // allow all callbacks to finish correctly as they are finishing in - // next tick due to async - sandbox.clock.tick(1); - originalSetTimeout(() => { - done(); - }); + describe('metrics', () => { + let sandbox: sinon.SinonSandbox; + let hostMetrics: HostMetrics; + let reader: TestMetricReader; + + beforeEach(async () => { + sandbox = sinon.createSandbox(); + sandbox.useFakeTimers(); + + sandbox.stub(os, 'freemem').callsFake(() => { + return mockedOS.freemem(); + }); + sandbox.stub(os, 'totalmem').returns(mockedOS.totalmem()); + sandbox.stub(os, 'cpus').returns(cpuJson); + sandbox.stub(process, 'uptime').returns(0); + sandbox.stub(SI, 'networkStats').callsFake(() => { + return mockedSI.networkStats(); + }); + + reader = new TestMetricReader(); + + meterProvider = new MeterProvider(); + meterProvider.addMetricReader(reader); + + hostMetrics = new HostMetrics({ + meterProvider, + name: 'opentelemetry-host-metrics', }); + await hostMetrics.start(); + + const dateStub = sandbox + .stub(Date.prototype, 'getTime') + .returns(process.uptime() * 1000 + 1); + // Drop first frame cpu metrics, see + // src/common.ts getCpuUsageData + await reader.collect(); + dateStub.returns(process.uptime() * 1000 + INTERVAL); + + // invalidates throttles + sandbox.clock.tick(DEFAULT_MAX_TIMEOUT_UPDATE_MS); + countSI = 0; + }); + afterEach(() => { + sandbox.restore(); }); - }); - afterEach(() => { - sandbox.restore(); - }); - it('should create a new instance', () => { - assert.ok(hostMetrics instanceof HostMetrics); - }); + it('should export CPU time metrics', async () => { + const metric = await getRecords(reader, 'system.cpu.time'); - it('should create a new instance with default meter provider', () => { - const meterProvider = new MeterProvider({ - interval: INTERVAL, - exporter, + ensureValue(metric, { state: 'user', cpu: '0' }, 90713.56); + ensureValue(metric, { state: 'system', cpu: '0' }, 63192.630000000005); + ensureValue(metric, { state: 'idle', cpu: '0' }, 374870.7); + ensureValue(metric, { state: 'interrupt', cpu: '0' }, 0); + ensureValue(metric, { state: 'nice', cpu: '0' }, 0); + + ensureValue(metric, { state: 'user', cpu: '1' }, 11005.42); + ensureValue(metric, { state: 'system', cpu: '1' }, 7678.12); + ensureValue(metric, { state: 'idle', cpu: '1' }, 510034.8); + ensureValue(metric, { state: 'interrupt', cpu: '1' }, 0); + ensureValue(metric, { state: 'nice', cpu: '1' }, 0); }); - hostMetrics = new HostMetrics({ - meterProvider, - name: 'opentelemetry-host-metrics', + it('should export CPU utilization metrics', async () => { + const metric = await getRecords(reader, 'system.cpu.utilization'); + + ensureValue(metric, { state: 'user', cpu: '0' }, 30247.935978659552); + ensureValue(metric, { state: 'system', cpu: '0' }, 21071.23374458153); + ensureValue(metric, { state: 'idle', cpu: '0' }, 124998.56618872957); + ensureValue(metric, { state: 'interrupt', cpu: '0' }, 0); + ensureValue(metric, { state: 'nice', cpu: '0' }, 0); + + ensureValue(metric, { state: 'user', cpu: '1' }, 3669.6965655218405); + ensureValue(metric, { state: 'system', cpu: '1' }, 2560.2267422474156); + ensureValue(metric, { state: 'idle', cpu: '1' }, 170068.28942980993); + ensureValue(metric, { state: 'interrupt', cpu: '1' }, 0); + ensureValue(metric, { state: 'nice', cpu: '1' }, 0); }); - hostMetrics.start(true); - assert.ok(hostMetrics instanceof HostMetrics); - }); - it('should export CPU time metrics', () => { - const records = getRecords(exportSpy.args[0][0], 'system.cpu.time'); - assert.strictEqual(records.length, 10); - - ensureValue(records[0], { state: 'user', cpu: '0' }, 90713.56); - ensureValue(records[1], { state: 'system', cpu: '0' }, 63192.630000000005); - ensureValue(records[2], { state: 'idle', cpu: '0' }, 374870.7); - ensureValue(records[3], { state: 'interrupt', cpu: '0' }, 0); - ensureValue(records[4], { state: 'nice', cpu: '0' }, 0); - - ensureValue(records[5], { state: 'user', cpu: '1' }, 11005.42); - ensureValue(records[6], { state: 'system', cpu: '1' }, 7678.12); - ensureValue(records[7], { state: 'idle', cpu: '1' }, 510034.8); - ensureValue(records[8], { state: 'interrupt', cpu: '1' }, 0); - ensureValue(records[9], { state: 'nice', cpu: '1' }, 0); - }); + it('should export Memory usage metrics', async () => { + const metric = await getRecords(reader, 'system.memory.usage'); - it('should export CPU utilization metrics', () => { - const records = getRecords(exportSpy.args[0][0], 'system.cpu.utilization'); - assert.strictEqual(records.length, 10); - - ensureValue(records[0], { state: 'user', cpu: '0' }, 30237.853333333333); - ensureValue(records[1], { state: 'system', cpu: '0' }, 21064.210000000003); - ensureValue(records[2], { state: 'idle', cpu: '0' }, 124956.90000000001); - ensureValue(records[3], { state: 'interrupt', cpu: '0' }, 0); - ensureValue(records[4], { state: 'nice', cpu: '0' }, 0); - - ensureValue(records[5], { state: 'user', cpu: '1' }, 3668.4733333333334); - ensureValue(records[6], { state: 'system', cpu: '1' }, 2559.3733333333334); - ensureValue(records[7], { state: 'idle', cpu: '1' }, 170011.6); - ensureValue(records[8], { state: 'interrupt', cpu: '1' }, 0); - ensureValue(records[9], { state: 'nice', cpu: '1' }, 0); - }); + ensureValue(metric, { state: 'used' }, 1024 * 1024 - 1024); + ensureValue(metric, { state: 'free' }, 1024); + }); - it('should export Memory usage metrics', done => { - const records = getRecords(exportSpy.args[0][0], 'system.memory.usage'); - assert.strictEqual(records.length, 2); - ensureValue(records[0], { state: 'used' }, 9999988736); - ensureValue(records[1], { state: 'free' }, 7179880448); - done(); - }); + it('should export Memory utilization metrics', async () => { + const metric = await getRecords(reader, 'system.memory.utilization'); - it('should export Memory utilization metrics', done => { - const records = getRecords( - exportSpy.args[0][0], - 'system.memory.utilization' - ); - assert.strictEqual(records.length, 2); - ensureValue(records[0], { state: 'used' }, 0.582075834274292); - ensureValue(records[1], { state: 'free' }, 0.417924165725708); - done(); - }); + ensureValue(metric, { state: 'used' }, 0.9990234375); + ensureValue(metric, { state: 'free' }, 0.0009765625); + }); - it('should export Network io dropped', done => { - const records = getRecords(exportSpy.args[0][0], 'system.network.dropped'); - assert.strictEqual(records.length, 2); - ensureValue(records[0], { direction: 'receive', device: 'eth0' }, 1200); - ensureValue(records[1], { direction: 'transmit', device: 'eth0' }, 12); - done(); - }); + it('should export Network io dropped', async () => { + const metric = await getRecords(reader, 'system.network.dropped'); - it('should export Network io errors', done => { - const records = getRecords(exportSpy.args[0][0], 'system.network.errors'); - assert.strictEqual(records.length, 2); - ensureValue(records[0], { direction: 'receive', device: 'eth0' }, 3); - ensureValue(records[1], { direction: 'transmit', device: 'eth0' }, 15); - done(); - }); + ensureValue(metric, { direction: 'receive', device: 'eth0' }, 1200); + ensureValue(metric, { direction: 'transmit', device: 'eth0' }, 12); + }); + + it('should export Network io errors', async () => { + const metric = await getRecords(reader, 'system.network.errors'); + + ensureValue(metric, { direction: 'receive', device: 'eth0' }, 3); + ensureValue(metric, { direction: 'transmit', device: 'eth0' }, 15); + }); - it('should export Network io bytes', done => { - const records = getRecords(exportSpy.args[0][0], 'system.network.io'); - assert.strictEqual(records.length, 2); - ensureValue(records[0], { direction: 'receive', device: 'eth0' }, 123123); - ensureValue(records[1], { direction: 'transmit', device: 'eth0' }, 321321); - done(); + it('should export Network io bytes', async () => { + const metric = await getRecords(reader, 'system.network.io'); + + ensureValue(metric, { direction: 'receive', device: 'eth0' }, 123123); + ensureValue(metric, { direction: 'transmit', device: 'eth0' }, 321321); + }); }); }); -function getRecords(records: MetricRecord[], name: string): MetricRecord[] { - return records.filter(record => record.descriptor.name === name); +async function getRecords( + metricReader: MetricReader, + name: string +): Promise { + const resourceMetrics = await metricReader.collect(); + assert(resourceMetrics != null); + assert.strictEqual(resourceMetrics.instrumentationLibraryMetrics.length, 1); + const instrumentationLibraryMetrics = + resourceMetrics.instrumentationLibraryMetrics[0]; + const metricDataList = instrumentationLibraryMetrics.metrics.filter( + metric => metric.descriptor.name === name + ); + assert.strictEqual(metricDataList.length, 1); + return metricDataList[0]; } function ensureValue( - record: MetricRecord, - attributes: Record, + metric: MetricData, + attributes: MetricAttributes, value: number ) { - assert.deepStrictEqual(record.attributes, attributes); - const point = record.aggregator.toPoint(); + const attrHash = hashAttributes(attributes); + const matches = (metric.dataPoints as DataPoint[]).filter(it => { + return attrHash === hashAttributes(it.attributes); + }); + assert.strictEqual(matches.length, 1); + const point = matches[0]; const aggValue = typeof point.value === 'number' ? point.value : (point.value as Histogram).sum; assert.strictEqual(aggValue, value); } + +function hashAttributes(attributes: MetricAttributes) { + return Object.entries(attributes) + .sort(([a], [b]) => { + return a < b ? -1 : 1; + }) + .map(pair => `${pair[0]}:${pair[1]}`) + .join('#'); +}