forked from openstf/stf
-
Notifications
You must be signed in to change notification settings - Fork 567
feat: Prometheus metrics #860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
matanbaruch
wants to merge
9
commits into
DeviceFarmer:master
Choose a base branch
from
matanbaruch:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
0a783ad
feat: Implement Prometheus metrics collection and update hooks for re…
matanbaruch 145db2e
fix: Update ESLint configuration for better compatibility and improve…
matanbaruch bd23bc0
feat: Expose getDevices function in dbapi module
matanbaruch c73c31f
feat: Add secure device metrics aggregation function and update metri…
matanbaruch c44a40c
fix: Update device metrics query to use group instead of groupBy for …
matanbaruch 8e22469
fix: Update metrics collection to use $window instead of window for b…
matanbaruch ffcb820
Fix Copilot suggestions in metrics-hooks.js
matanbaruch 89225ad
fix: Add $window dependency to GroupListCtrl for improved compatibility
matanbaruch 83ef628
Merge branch 'master' into master
matanbaruch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* Copyright © 2025 STF Metrics Controller - Licensed under the Apache license 2.0 | ||
* | ||
* Prometheus metrics endpoint controller | ||
*/ | ||
|
||
// Fix for Node.js versions where util.isError was removed | ||
const util = require('util') | ||
if (!util.isError) { | ||
util.isError = function(e) { | ||
return e && typeof e === 'object' && e instanceof Error | ||
} | ||
} | ||
|
||
const metrics = require('../../../util/metrics') | ||
const logger = require('../../../util/logger') | ||
const log = logger.createLogger('api:controllers:metrics') | ||
|
||
/** | ||
* GET /metrics | ||
* | ||
* Returns Prometheus metrics in the expected format | ||
* @param {Object} req - Express request object | ||
* @param {Object} res - Express response object | ||
* @returns {void} | ||
*/ | ||
function getMetrics(req, res) { | ||
// Set the content type to plain text as expected by Prometheus | ||
res.set('Content-Type', metrics.register.contentType) | ||
|
||
// Return the metrics (handle Promise from prom-client v15+) | ||
metrics.register.metrics() | ||
.then(metricsData => { | ||
res.end(metricsData) | ||
log.debug('Served Prometheus metrics') | ||
}) | ||
.catch(error => { | ||
log.error('Error serving metrics:', error) | ||
res.status(500).json({ | ||
success: false | ||
, description: 'Internal server error while fetching metrics' | ||
}) | ||
}) | ||
} | ||
|
||
module.exports = { | ||
getMetrics | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/** | ||
* Copyright © 2025 STF Metrics Collector - Licensed under the Apache license 2.0 | ||
* | ||
* Service for collecting STF metrics from database and external sources | ||
*/ | ||
|
||
const logger = require('./logger') | ||
const dbapi = require('../db/api') | ||
const metrics = require('./metrics') | ||
|
||
const log = logger.createLogger('metrics-collector') | ||
|
||
class MetricsCollector { | ||
constructor(options = {}) { | ||
this.interval = options.interval || 30000 // 30 seconds default | ||
this.timer = null | ||
this.isRunning = false | ||
} | ||
|
||
start() { | ||
if (!this.isRunning) { | ||
log.info('Starting metrics collection with interval:', this.interval + 'ms') | ||
this.isRunning = true | ||
this.collectMetrics() // Collect immediately | ||
this.timer = setInterval(() => this.collectMetrics(), this.interval) | ||
} | ||
} | ||
|
||
stop() { | ||
if (this.isRunning) { | ||
log.info('Stopping metrics collection') | ||
this.isRunning = false | ||
if (this.timer) { | ||
clearInterval(this.timer) | ||
this.timer = null | ||
} | ||
} | ||
} | ||
|
||
async collectMetrics() { | ||
try { | ||
log.debug('Collecting metrics...') | ||
|
||
const [ | ||
deviceData | ||
, userData | ||
, groupData | ||
] = await Promise.all([ | ||
this.collectDeviceMetrics() | ||
, this.collectUserMetrics() | ||
, this.collectGroupMetrics() | ||
]) | ||
|
||
// Update the metrics | ||
metrics.updateDeviceMetrics(deviceData) | ||
metrics.updateUserMetrics(userData) | ||
metrics.updateGroupMetrics(groupData) | ||
|
||
log.debug('Metrics collection completed') | ||
} | ||
catch (error) { | ||
log.error('Error during metrics collection:', error) | ||
} | ||
} | ||
|
||
async collectDeviceMetrics() { | ||
try { | ||
// Get device statistics from database using secure aggregation function | ||
// This avoids access control bypass by not exposing individual device data | ||
const deviceStats = await dbapi.getDeviceMetrics() | ||
return deviceStats | ||
} | ||
catch (error) { | ||
log.error('Error collecting device metrics:', error) | ||
return { | ||
total: 0 | ||
, usable: 0 | ||
, busy: 0 | ||
, providers: 0 | ||
, byStatus: {} | ||
} | ||
} | ||
} | ||
|
||
async collectUserMetrics() { | ||
try { | ||
// Get user statistics from database | ||
const users = await dbapi.getUsers() | ||
|
||
return { | ||
total: users.length | ||
} | ||
} | ||
catch (error) { | ||
log.error('Error collecting user metrics:', error) | ||
return { | ||
total: 0 | ||
} | ||
} | ||
} | ||
|
||
async collectGroupMetrics() { | ||
try { | ||
// Get group statistics from database | ||
const groups = await dbapi.getGroups() | ||
|
||
const groupStats = { | ||
total: groups.length | ||
, active: groups.filter(g => g.state === 'active').length | ||
, ready: groups.filter(g => g.state === 'ready').length | ||
, pending: groups.filter(g => g.state === 'pending').length | ||
} | ||
|
||
return groupStats | ||
} | ||
catch (error) { | ||
log.error('Error collecting group metrics:', error) | ||
return { | ||
total: 0 | ||
, active: 0 | ||
, ready: 0 | ||
, pending: 0 | ||
} | ||
} | ||
} | ||
|
||
// Method to collect quota metrics for a specific user | ||
async collectUserQuotaMetrics(user) { | ||
try { | ||
// This would depend on how quotas are implemented in STF | ||
// For now, return placeholder data | ||
const quotaTypes = ['devices', 'duration'] | ||
|
||
quotaTypes.forEach(quotaType => { | ||
// Example: Get quota usage from database | ||
const consumed = 0 // Would be actual consumed amount | ||
const allocated = 10 // Would be actual allocated amount | ||
|
||
metrics.updateUserQuota(user, quotaType, consumed, allocated) | ||
}) | ||
} | ||
catch (error) { | ||
log.error('Error collecting user quota metrics:', error) | ||
} | ||
} | ||
} | ||
|
||
module.exports = MetricsCollector |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.