Skip to content

Commit

Permalink
feat: Added gatekeeper status (#491)
Browse files Browse the repository at this point in the history
* Added status endpoint

* Added get-status command

* Added DID counts to status

* Renamed checkDb to checkDIDs

* Improved coverage

* Refactored reportStatus

* Improved coverage

* Added invalid DID count to status report
  • Loading branch information
macterra authored Dec 18, 2024
1 parent 9ce8d7f commit ffbe989
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 39 deletions.
36 changes: 30 additions & 6 deletions packages/gatekeeper/src/gatekeeper-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,23 +116,47 @@ export async function verifyDb(options = {}) {
return { total, verified, expired, invalid };
}

export async function checkDb() {
const dids = await getDIDs();
export async function checkDIDs(options = {}) {
let { chatty = false, dids } = options;

if (!dids) {
dids = await getDIDs();
}

const total = dids.length;
let n = 0;
let ephemeral = 0;
let invalid = 0;
const byRegistry = {};

for (const did of dids) {
n += 1;
try {
await resolveDID(did);
console.log(`resolved ${n}/${total} ${did} OK`);
const doc = await resolveDID(did);
if (chatty) {
console.log(`resolved ${n}/${total} ${did} OK`);
}

if (doc.mdip.validUntil) {
ephemeral += 1;
}

if (byRegistry[doc.mdip.registry]) {
byRegistry[doc.mdip.registry] += 1;
}
else {
byRegistry[doc.mdip.registry] = 1;
}
}
catch (error) {
console.log(`can't resolve ${n}/${total} ${did} ${error}`);
invalid += 1;
if (chatty) {
console.log(`can't resolve ${n}/${total} ${did} ${error}`);
}
}
}

return { total };
return { total, ephemeral, invalid, byRegistry };
}

export async function initRegistries(csvRegistries) {
Expand Down
10 changes: 10 additions & 0 deletions packages/gatekeeper/src/gatekeeper-sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ export async function getVersion() {
}
}

export async function getStatus() {
try {
const response = await axios.get(`${API}/status`);
return response.data;
}
catch (error) {
throwError(error);
}
}

export async function createDID(operation) {
try {
const response = await axios.post(`${API}/did/`, operation);
Expand Down
13 changes: 13 additions & 0 deletions scripts/admin-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,19 @@ program
}
});

program
.command('get-status')
.description('Report gatekeeper status')
.action(async () => {
try {
const response = await gatekeeper.getStatus();
console.log(JSON.stringify(response, null, 4));
}
catch (error) {
console.error(error);
}
});

async function run() {
gatekeeper.start({ url: gatekeeperURL });
await keymaster.start({ gatekeeper, wallet, cipher });
Expand Down
143 changes: 114 additions & 29 deletions services/gatekeeper/server/src/gatekeeper-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const db = (config.db === 'sqlite') ? db_sqlite
await db.start('mdip');
await gatekeeper.start({ db });

const startTime = new Date();
const app = express();
const v1router = express.Router();

Expand Down Expand Up @@ -53,6 +54,15 @@ v1router.get('/version', async (req, res) => {
}
});

v1router.get('/status', async (req, res) => {
try {
const status = await getStatus();
res.json(status);
} catch (error) {
res.status(500).send(error.toString());
}
});

v1router.post('/did', async (req, res) => {
try {
const operation = req.body;
Expand Down Expand Up @@ -259,20 +269,118 @@ async function gcLoop() {
setTimeout(gcLoop, config.gcInterval * 60 * 1000);
}

let didCheck;

async function checkDids() {
console.time('checkDIDs');
didCheck = await gatekeeper.checkDIDs();
console.timeEnd('checkDIDs');
}

async function getStatus() {
return {
uptimeSeconds: Math.round((new Date() - startTime) / 1000),
dids: didCheck,
memoryUsage: process.memoryUsage()
};
}

async function reportStatus() {
await checkDids();
const status = await getStatus();

console.log('Status -----------------------------');

console.log('DID Database:');
console.log(` Total: ${status.dids.total}`);
console.log(` By registry:`);
for (let registry in status.dids.byRegistry) {
console.log(` ${registry}: ${status.dids.byRegistry[registry]}`);
}
console.log(` Ephemeral: ${status.dids.ephemeral}`);
console.log(` Invalid: ${status.dids.invalid}`);

console.log('Memory Usage Report:');
console.log(` RSS: ${formatBytes(status.memoryUsage.rss)} (Resident Set Size - total memory allocated for the process)`);
console.log(` Heap Total: ${formatBytes(status.memoryUsage.heapTotal)} (Total heap allocated)`);
console.log(` Heap Used: ${formatBytes(status.memoryUsage.heapUsed)} (Heap actually used)`);
console.log(` External: ${formatBytes(status.memoryUsage.external)} (Memory used by C++ objects bound to JavaScript)`);
console.log(` Array Buffers: ${formatBytes(status.memoryUsage.arrayBuffers)} (Memory used by ArrayBuffer and SharedArrayBuffer)`);

console.log(`Uptime: ${status.uptimeSeconds}s (${formatDuration(status.uptimeSeconds)})`);

console.log('------------------------------------');
}

function formatDuration(seconds) {
const secPerMin = 60;
const secPerHour = secPerMin * 60;
const secPerDay = secPerHour * 24;

const days = Math.floor(seconds / secPerDay);
seconds %= secPerDay;

const hours = Math.floor(seconds / secPerHour);
seconds %= secPerHour;

const minutes = Math.floor(seconds / secPerMin);
seconds %= secPerMin;

let duration = "";

if (days > 0) {
if (days > 1) {
duration += `${days} days, `;
} else {
duration += `1 day, `;
}
}

if (hours > 0) {
if (hours > 1) {
duration += `${hours} hours, `;
} else {
duration += `1 hour, `;
}
}

if (minutes > 0) {
if (minutes > 1) {
duration += `${minutes} minutes, `;
} else {
duration += `1 minute, `;
}
}

if (seconds === 1) {
duration += `1 second`;
} else {
duration += `${seconds} seconds`;
}

return duration;
}

function formatBytes(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return '0 Byte';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
}

async function main() {
console.time('checkDb');
const check = await gatekeeper.checkDb();
console.timeEnd('checkDb');
console.log(`check: ${JSON.stringify(check)}`);
console.log(`Starting Gatekeeper with a db (${config.db}) check...`);
await reportStatus();
setInterval(reportStatus, 60 * 1000);

console.log(`Starting DID garbage collection in ${config.gcInterval} minutes`);
setTimeout(gcLoop, config.gcInterval * 60 * 1000);

const registries = await gatekeeper.initRegistries(config.registries);
console.log(`Supported registries: ${registries}`);

app.listen(config.port, () => {
console.log(`Server is running on port ${config.port}, persisting with ${config.db}`);
console.log(`Supported registries: ${registries}`);
console.log(`Server is running on port ${config.port}`);
serverReady = true;
});
}
Expand All @@ -286,26 +394,3 @@ process.on('uncaughtException', (error) => {
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection caught', reason, promise);
});

function reportMemoryUsage() {
const memoryUsage = process.memoryUsage();

console.log('Memory Usage Report:');
console.log(` RSS: ${formatBytes(memoryUsage.rss)} (Resident Set Size - total memory allocated for the process)`);
console.log(` Heap Total: ${formatBytes(memoryUsage.heapTotal)} (Total heap allocated)`);
console.log(` Heap Used: ${formatBytes(memoryUsage.heapUsed)} (Heap actually used)`);
console.log(` External: ${formatBytes(memoryUsage.external)} (Memory used by C++ objects bound to JavaScript)`);
console.log(` Array Buffers: ${formatBytes(memoryUsage.arrayBuffers)} (Memory used by ArrayBuffer and SharedArrayBuffer)`);

console.log('------------------------------------');
}

function formatBytes(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return '0 Byte';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${sizes[i]}`;
}

// Example: Report memory usage every 60 seconds
setInterval(reportMemoryUsage, 60 * 1000);
29 changes: 25 additions & 4 deletions tests/gatekeeper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2608,23 +2608,44 @@ describe('verifyDb', () => {
});
});

describe('checkDb', () => {
describe('checkDIDs', () => {
afterEach(() => {
mockFs.restore();
});

it('should check all DIDs in db', async () => {
mockFs({});

const keypair = cipher.generateRandomJwk();
const agentOp = await createAgentOp(keypair);
const agentDID = await gatekeeper.createDID(agentOp);
const assetOp = await createAssetOp(agentDID, keypair, 'local', new Date().toISOString());
await gatekeeper.createDID(assetOp);

const check = await gatekeeper.checkDIDs({ chatty: true });

expect(check.total).toBe(2);
expect(check.ephemeral).toBe(1);
expect(check.invalid).toBe(0);
});

it('should report invalid DIDs', async () => {
mockFs({});

const keypair = cipher.generateRandomJwk();
const agentOp = await createAgentOp(keypair);
const agentDID = await gatekeeper.createDID(agentOp);
const assetOp = await createAssetOp(agentDID, keypair);
await gatekeeper.createDID(assetOp);

const { total } = await gatekeeper.checkDb();
const dids = await gatekeeper.getDIDs();
dids.push('mock');

expect(total).toBe(2);
const check = await gatekeeper.checkDIDs({ chatty: true, dids });

expect(check.total).toBe(3);
expect(check.ephemeral).toBe(0);
expect(check.invalid).toBe(1);
});

it('should reset a corrupted db', async () => {
Expand All @@ -2638,7 +2659,7 @@ describe('checkDb', () => {

fs.writeFileSync('data/test.json', "{ dids: {");

const { total } = await gatekeeper.checkDb();
const { total } = await gatekeeper.checkDIDs();

expect(total).toBe(0);
});
Expand Down

0 comments on commit ffbe989

Please sign in to comment.