Skip to content

Commit 3cc6d41

Browse files
committed
initial commit: emit gauge metrics to Levitate
0 parents  commit 3cc6d41

8 files changed

Lines changed: 735 additions & 0 deletions

File tree

.github/workflows/docker-image.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Publish docker image
2+
3+
on:
4+
release:
5+
types:
6+
- created
7+
8+
jobs:
9+
publish:
10+
name: Build and publish docker image
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Check out the repo
15+
uses: actions/checkout@v2
16+
17+
- name: Set IMAGE_TAG
18+
run: |
19+
VERSION=$(cat package.json | grep version | awk -F '\"' '{ print $4 }')
20+
TS=$(date +'%d%m%y%H%M')
21+
IMAGE_TAG="${VERSION}-${TS}"
22+
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
23+
24+
- name: Set up docker hub credentials
25+
env:
26+
LAST9_DOCKER_AUTH: ${{secrets.LAST9_DOCKER_AUTH}}
27+
run: |
28+
mkdir -p ~/.docker
29+
cat > ~/.docker/config.json <<EOF
30+
{
31+
"auths": {
32+
"docker-registry.last9.io": {
33+
"auth": "${LAST9_DOCKER_AUTH}"
34+
}
35+
}
36+
}
37+
EOF
38+
39+
- name: Build new docker image
40+
run: |
41+
docker build -t docker-registry.last9.io/last9-nodejs-otel-example/nodejs-otel:${IMAGE_TAG} .
42+
docker build -t docker-registry.last9.io/last9-nodejs-otel-example/nodejs-otel:latest .
43+
44+
- name: Publish the new docker image to internal registry
45+
run: |
46+
docker login docker-registry.last9.io
47+
docker push docker-registry.last9.io/last9-nodejs-otel-example/nodejs-otel:${IMAGE_TAG}
48+
docker push docker-registry.last9.io/last9-nodejs-otel-example/nodejs-otel:latest

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
.idea

Dockerfile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Use the official Node.js 18 image as a parent image
2+
FROM node:18
3+
4+
# Set the working directory in the container
5+
WORKDIR /usr/src/app
6+
7+
# Copy package.json and package-lock.json
8+
COPY package*.json ./
9+
10+
# Install dependencies
11+
RUN npm install
12+
13+
# Copy the application code to the container
14+
COPY . .
15+
16+
CMD [ "node", "app.js" ]

app.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
'use strict';
2+
3+
const {DiagConsoleLogger, DiagLogLevel, ValueType, diag} = require('@opentelemetry/api');
4+
const {OTLPMetricExporter} = require('@opentelemetry/exporter-metrics-otlp-proto');
5+
6+
const {
7+
ExponentialHistogramAggregation, MeterProvider, PeriodicExportingMetricReader, View,
8+
} = require('@opentelemetry/sdk-metrics');
9+
const {Resource} = require('@opentelemetry/resources');
10+
const {
11+
SemanticResourceAttributes,
12+
} = require('@opentelemetry/semantic-conventions');
13+
const os = require('os');
14+
const {v4: uuidv4} = require('uuid');
15+
const metricPrefix = process.env.METRIC_PREFIX
16+
const workflowIDCount = parseInt(process.env.WORKFLOW_ID_COUNT);
17+
const customerIDCount = parseInt(process.env.CUSTOMER_ID_COUNT);
18+
const otlpEndpoint = process.env.OTLP_ENDPOINT;
19+
const serviceName = process.env.SERVICE_NAME;
20+
const otelMetricExporterFrequency = parseInt(process.env.OTLP_METRIC_EXPORTER_FREQUENCY);
21+
const otelMeterName = process.env.OTLP_METER_NAME;
22+
const environment = process.env.ENVIRONMENT;
23+
24+
// Global variables
25+
// Toggle this number below to explode cardinality.
26+
const workflowIDs = Array.from({length: workflowIDCount}, () => uuidv4());
27+
const customerIDs = Array.from({length: customerIDCount}, () => uuidv4());
28+
29+
30+
// Optional and only needed to see the internal diagnostic logging (during development)
31+
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
32+
33+
const metricExporter = new OTLPMetricExporter({
34+
url: otlpEndpoint
35+
});
36+
37+
// Create an instance of the metric provider
38+
const meterProvider = new MeterProvider({
39+
resource: new Resource({
40+
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
41+
})
42+
});
43+
44+
meterProvider.addMetricReader(new PeriodicExportingMetricReader({
45+
exporter: metricExporter, // exporter: new ConsoleMetricExporter(),
46+
exportIntervalMillis: otelMetricExporterFrequency,
47+
}));
48+
49+
const meter = meterProvider.getMeter(otelMeterName);
50+
51+
// Create Observable Gauges
52+
const memoryUsageGauge = meter.createObservableGauge(`${metricPrefix}_memory_usage`, {
53+
description: 'Tracks the memory usage of the application', valueType: ValueType.DOUBLE
54+
});
55+
56+
const concurrencyGauge = meter.createObservableGauge(`${metricPrefix}_concurrency`, {
57+
description: 'Current concurrency level', valueType: ValueType.INT
58+
});
59+
60+
const cpuUsageGauge = meter.createObservableGauge(`${metricPrefix}_cpu_usage`, {
61+
description: 'CPU usage percentage', valueType: ValueType.DOUBLE
62+
});
63+
64+
const runInTimeGauge = meter.createObservableGauge(`${metricPrefix}_run_time`, {
65+
description: 'Run time in seconds', valueType: ValueType.INT
66+
});
67+
68+
// Base Label Sets
69+
const attributes = {pid: process.pid, environment: environment};
70+
71+
// Callbacks for Observable Gauges
72+
concurrencyGauge.addCallback((observableResult) => {
73+
workflowIDs.forEach(workflow_id => {
74+
customerIDs.forEach(customer_id => {
75+
let concurrency = Math.floor(Math.random() * 10) + 1;
76+
observableResult.observe(concurrency, {
77+
...attributes, 'workflow_id': workflow_id, 'customer_id': customer_id
78+
});
79+
});
80+
});
81+
});
82+
83+
cpuUsageGauge.addCallback((observableResult) => {
84+
workflowIDs.forEach(workflow_id => {
85+
customerIDs.forEach(customer_id => {
86+
let cpuUsage = os.loadavg()[0]; // Load average for 1 minute; adjust as needed
87+
observableResult.observe(cpuUsage, {
88+
...attributes, 'workflow_id': workflow_id, 'customer_id': customer_id
89+
});
90+
});
91+
});
92+
});
93+
94+
runInTimeGauge.addCallback((observableResult) => {
95+
workflowIDs.forEach(workflow_id => {
96+
customerIDs.forEach(customer_id => {
97+
let run_time = Math.floor(Math.random() * 60);
98+
observableResult.observe(run_time, {
99+
...attributes, 'workflow_id': workflow_id, 'customer_id': customer_id
100+
});
101+
});
102+
});
103+
});
104+
105+
memoryUsageGauge.addCallback((observableResult) => {
106+
workflowIDs.forEach(workflow_id => {
107+
customerIDs.forEach(customer_id => {
108+
let usedMemory = process.memoryUsage().heapUsed / 1024 / 1024; // Convert bytes to megabytes
109+
observableResult.observe(usedMemory, {
110+
...attributes, 'workflow_id': workflow_id, 'customer_id': customer_id, 'unit': 'MB'
111+
});
112+
});
113+
});
114+
});
115+
116+
117+
// Periodically update global variables if needed
118+
setInterval(() => {
119+
console.log("Job Executed")
120+
}, 15000); // Adjust as needed

docker-compose.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: '3.8'
2+
services:
3+
app:
4+
build: .
5+
environment:
6+
WORKFLOW_ID_COUNT: 100 # Adjust as needed
7+
CUSTOMER_ID_COUNT: 40 # Adjust as needed
8+
OTLP_ENDPOINT: 'http://localhost:8429/opentelemetry/api/v1/push'
9+
SERVICE_NAME: 'custom-metric-service'
10+
OTLP_METRIC_EXPORTER_FREQUENCY: 15000
11+
OTLP_METER_NAME: 'cms-exporter-collector'
12+
ENVIRONMENT: 'staging'

0 commit comments

Comments
 (0)