Skip to content

Commit

Permalink
[OGUI-1432] Load on interval and store calibration runs in-memory (#2170
Browse files Browse the repository at this point in the history
)

Calibration Runs data changes often after a run has been started and users need to be able to see especially what is the CALIBRATION status of it. Thus, this PR:
- registers the run service to refresh the data at a specified rate, default 10s;
- refactors the intervals service to not have to know what methods are registered with it
  • Loading branch information
graduta authored Oct 23, 2023
1 parent 1173ab5 commit fb0dfcd
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 51 deletions.
1 change: 1 addition & 0 deletions Control/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ It communicates with [Control agent](https://github.com/AliceO2Group/Control) ov
### Bookkeeping
* `url` - URL which points to Bookkeeping API: `<protocol>://<instance>:<port>`, `<protocol>://<domain_name>`
* `token` - token needed for permissions to retrieve data from Bookkeeping
* `[refreshRate = 10000]` - number representing how often should the data from Bookkeeping be refreshed in ms;

Bookkeeping is going to be used as the source of latest `CALIBRATION` runs as per the [definition](https://github.com/AliceO2Group/Bookkeeping/blob/main/docs/RUN_DEFINITIONS.md). Detectors may need these run before stable beams, with some needing _none_, some only _one_ run and others _multiple_ ones defined by the `RUN TYPE` attribute. As this can vary depending on the period, the types corresponding to a detector will be defined and retrieved from the KV store of [O2Apricot](https://github.com/AliceO2Group/Control/tree/master/apricot) (key and value TBD).

Expand Down
4 changes: 3 additions & 1 deletion Control/config-default.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ module.exports = {
url: 'qcg.cern.ch'
},
bookkeepingGui: {
url: 'ali-bookkeeping.cern.ch'
url: 'ali-bookkeeping.cern.ch',
token: 'token',
refreshRate: 10000,
},
utils: {
refreshTask: 10000, // how often should task list page should refresh its content
Expand Down
30 changes: 28 additions & 2 deletions Control/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ module.exports.setup = (http, ws) => {
);
const statusController = new StatusController(statusService);

const intervals = new Intervals(statusService);
intervals.initializeIntervals();
const intervals = new Intervals();
initializeIntervals(intervals, statusService, runService, bkpService);

const coreMiddleware = [
ctrlService.isConnectionReady.bind(ctrlService),
Expand Down Expand Up @@ -164,3 +164,29 @@ module.exports.setup = (http, ws) => {
http.get('/consul/crus/aliases', validateService, consulController.getCRUsAlias.bind(consulController));
http.post('/consul/crus/config/save', validateService, consulController.saveCRUsConfiguration.bind(consulController));
};

/**
* Method to register services at the start of the server
* @param {Intervals} intervalsService - wrapper for storing intervals
* @param {StatusService} statusService - service used for retrieving status on dependent services
* @param {RunService} runService - service for retrieving and building information on runs
* @param {BookkeepingService} bkpService - service for retrieving information on runs from Bookkeeping
* @return {void}
*/
function initializeIntervals(intervalsService, statusService, runService, bkpService) {
const SERVICES_REFRESH_RATE = 10000;
const CALIBRATION_RUNS_REFRESH_RATE = bkpService.refreshRate;

intervalsService.register(statusService.retrieveConsulStatus.bind(statusService), SERVICES_REFRESH_RATE);
intervalsService.register(statusService.retrieveAliEcsCoreInfo.bind(statusService), SERVICES_REFRESH_RATE);
intervalsService.register(statusService.retrieveApricotStatus.bind(statusService), SERVICES_REFRESH_RATE);
intervalsService.register(statusService.retrieveGrafanaStatus.bind(statusService), SERVICES_REFRESH_RATE);
intervalsService.register(statusService.retrieveSystemCompatibility.bind(statusService), SERVICES_REFRESH_RATE);
intervalsService.register(statusService.retrieveNotificationSystemStatus.bind(statusService), SERVICES_REFRESH_RATE);
intervalsService.register(statusService.retrieveAliECSIntegratedInfo.bind(statusService), SERVICES_REFRESH_RATE);

intervalsService.register(
runService.retrieveCalibrationRunsGroupedByDetector.bind(runService),
CALIBRATION_RUNS_REFRESH_RATE
);
}
5 changes: 2 additions & 3 deletions Control/lib/controllers/Run.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ class RunController {
* @param {Response} res - HTTP Response object
* @returns {void}
*/
async getCalibrationRunsHandler(_, res) {
getCalibrationRunsHandler(_, res) {
try {
const response = await this._runService.retrieveCalibrationRunsGroupedByDetector();
res.status(200).json(response);
res.status(200).json(this._runService.calibrationRunsPerDetector);
} catch (error) {
this._logger.debug(error);
updateExpressResponseFromNativeError(res, error);
Expand Down
20 changes: 17 additions & 3 deletions Control/lib/services/Bookkeeping.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
const {Log} = require('@aliceo2/web-ui');
const {httpGetJson} = require('./../utils.js');
const RunSummaryAdapter = require('./../adapters/RunSummaryAdapter.js');
const DEFAULT_REFRESH_RATE = 10000;

/**
* BookkeepingService class to be used to retrieve data from Bookkeeping
Expand All @@ -24,14 +25,15 @@ class BookkeepingService {
* Constructor for configuring the service to retrieve data via Bookkeeping HTTP API
* @param {Object} config = {url: string, token: string} - configuration for using BKP service
*/
constructor({url = '', token = ''}) {
this._url = url;
constructor({url, token, refreshRate}) {
this._url = url ?? null;
const {protocol, hostname, port} = url ? new URL(this._url) : {};
this._hostname = hostname;
this._port = port;
this._protocol = protocol;

this._token = token;
this._token = token ?? '';
this._refreshRate = refreshRate ?? DEFAULT_REFRESH_RATE;

this._logger = new Log(`${process.env.npm_config_log_label ?? 'cog'}/bkp-service`);
}
Expand Down Expand Up @@ -80,6 +82,18 @@ class BookkeepingService {
}
return {};
}

/**
* Getters & Setters
*/

/**
* Getter for retrieving the rate of refreshing data from Bookkeeping
* @return {Number}
*/
get refreshRate() {
return this._refreshRate;
}
}

module.exports = {BookkeepingService};
46 changes: 23 additions & 23 deletions Control/lib/services/Intervals.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,44 @@
* or submit itself to any jurisdiction.
*/

const DEFAULT_INTERVAL_RATE = 60 * 1000;

/**
* @class
* Class which deals with setting up intervals for retrieving information constantly
*/
class Intervals {
/**
* Expected services to be used to retrieve information
* @constructor
* Constructor for initializing a list of intervals
*/
constructor(statusService) {
this._statusService = statusService;

this._intervals = [];

constructor() {
/**
* @type {Object<Intervals>}
*/
this._intervals = {};
}

/**
* Method to initialize all intervals used by AlIECS GUI to acquire data
* Method to allow other services to register events that should trigger based on an interval rate
* @param {function} callback - function that should be called based on interval rate
* @param {number} intervalRate = 60 * 1000 - (ms) on how often the callback should be called
* @return {Symbol} - unique key for registered callback
*/
initializeIntervals() {
this._statusService && this._initializeStatusIntervals();
register(callback, intervalRate = DEFAULT_INTERVAL_RATE) {
const key = Symbol(Math.random());
this._intervals[key] = setInterval(callback, intervalRate);
return key;
}

/**
* Sets interval to use {StatusService} to get data about the components which interact with AliECS GUI
* Method to allow services to deregister and clear an interval
* @param {Symbol} key - key under which the interval was registered
* @return {void}
*/
_initializeStatusIntervals() {
this._intervals.push(
setInterval(() => {
this._statusService.retrieveConsulStatus();
this._statusService.retrieveAliEcsCoreInfo();
this._statusService.retrieveAliECSIntegratedInfo();
this._statusService.retrieveApricotStatus();
this._statusService.retrieveGrafanaStatus();
this._statusService.retrieveNotificationSystemStatus();
this._statusService.retrieveSystemCompatibility();
}, 10000)
);
deregister(key) {
clearInterval(this._intervals[key]);
}

}

exports.Intervals = Intervals;
27 changes: 21 additions & 6 deletions Control/lib/services/Run.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,15 @@ class RunService {
this._runTypes = {};

/**
* Contains an object with list of run types that should be fetched for each detector
* @type {Object<String, Array<String>>}
*/
this._calibrationPerDetectorMap = {};
this._runTypesPerDetectorStoredMapping = {};

/**
* @type {Object<String, Array<RunSummary>>}
*/
this._calibrationRunsPerDetector = {};

this._logger = new Log(`${process.env.npm_config_log_label ?? 'cog'}/run-service`);
}
Expand All @@ -62,8 +68,8 @@ class RunService {
* @return {void}
*/
async init() {
this._runTypesPerDetectorStoredMapping = await this._retrieveCalibrationForDetector();
this._runTypes = await this._bkpService.getRunTypes();
this._calibrationPerDetectorMap = await this._retrieveCalibrationForDetector();
this._calibrationRunsPerDetector = await this.retrieveCalibrationRunsGroupedByDetector();
}

Expand All @@ -73,8 +79,8 @@ class RunService {
*/
async retrieveCalibrationRunsGroupedByDetector() {
const calibrationRunsPerDetector = {};
for (const detector in this._calibrationPerDetectorMap) {
const runTypesPerDetector = this._calibrationPerDetectorMap[detector] ?? [];
for (const detector in this._runTypesPerDetectorStoredMapping) {
const runTypesPerDetector = this._runTypesPerDetectorStoredMapping[detector] ?? [];
calibrationRunsPerDetector[detector] = [];
for (const runType of runTypesPerDetector) {
const runTypeId = this._runTypes[runType];
Expand All @@ -84,6 +90,7 @@ class RunService {
}
}
}
this._calibrationRunsPerDetector = calibrationRunsPerDetector;
return calibrationRunsPerDetector;
}

Expand Down Expand Up @@ -127,8 +134,16 @@ class RunService {
* Return the object containing a KV object with detector and its corresponding run types needed for calibration runs
* @return {Object<String, Array<String>>}
*/
get calibrationPerDetectorMap() {
return this._calibrationPerDetectorMap;
get runTypesPerDetectorStoredMapping() {
return this._runTypesPerDetectorStoredMapping;
}

/**
* Return the object containing a KV object with detector and its corresponding last calibration runs
* @return {Object<String, Array<RunSummary>>}
*/
get calibrationRunsPerDetector() {
return this._calibrationRunsPerDetector;
}
}

Expand Down
20 changes: 9 additions & 11 deletions Control/test/lib/controllers/mocha-run-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,27 @@ describe(`'RunController' test suite`, () => {
json: sinon.stub()
}

describe(`'getCalibrationRunsHandler' test suite`, async () => {
it('should successfully return calibrations runs grouped by detector', async () => {
describe(`'getCalibrationRunsHandler' test suite`, () => {
it('should successfully return calibrations runs grouped by detector', () => {
const runs = {
TPC: [
{runNumber: 1},
{runNumber: 2},
]
};
const runController = new RunController({
retrieveCalibrationRunsGroupedByDetector: sinon.stub().resolves(runs)
calibrationRunsPerDetector: runs
});
await runController.getCalibrationRunsHandler({}, res);
runController.getCalibrationRunsHandler({}, res);
assert.ok(res.status.calledWith(200));
assert.ok(res.json.calledWith(runs));
});

it('should return 500 response as there was a problem internally', async () => {
const runController = new RunController({
retrieveCalibrationRunsGroupedByDetector: sinon.stub().rejects(new Error('Something went wrong'))
});
await runController.getCalibrationRunsHandler({}, res);
assert.ok(res.status.calledWith(500));
assert.ok(res.json.calledWith({message: 'Something went wrong'}));
it('should return an empty object if no runs were loaded', () => {
const runController = new RunController({calibrationRunsPerDetector: {}});
runController.getCalibrationRunsHandler({}, res);
assert.ok(res.status.calledWith(200));
assert.ok(res.json.calledWith({}));
});
});

Expand Down
4 changes: 2 additions & 2 deletions Control/test/lib/services/mocha-run-service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ describe(`'RunService' test suite`, async () => {
PULSE: 1,
SOMEOTHER: 2,
};
runSrv._calibrationPerDetectorMap = {
runSrv._runTypesPerDetectorStoredMapping = {
TPC: ['NOISE', 'PULSE'],
ABC: ['SOMEOTHER', 'PULSE'],
XYZ: ['NONEXISTENT', 'PULSE'], // detector with no run found or nonexistent type
}
};
const result = await runSrv.retrieveCalibrationRunsGroupedByDetector();
assert.deepStrictEqual(result, {
TPC: [
Expand Down

0 comments on commit fb0dfcd

Please sign in to comment.