Skip to content

Commit

Permalink
feat(telemetry): export swingstore vat object metrics to datadog
Browse files Browse the repository at this point in the history
  • Loading branch information
toliaqat committed Jul 14, 2023
1 parent 6e5b422 commit 74f48af
Show file tree
Hide file tree
Showing 6 changed files with 2,086 additions and 0 deletions.
25 changes: 25 additions & 0 deletions packages/SwingSet/misc-tools/store-stats/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Store Stats

## Build / Install

```
yarn install
```

# Usage

Gets stats from default sqlite database file and send them to datadog.
```
node src/index.js
```

Get stats from custom sqlite database file.

```
node src/index.js ~/_agstate/agoric-servers/dev/fake-chain/swingstore.sqlite
```

# Test
```
yarn test tests/test-index.js
```
29 changes: 29 additions & 0 deletions packages/SwingSet/misc-tools/store-stats/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "store-stats",
"version": "0.0.1",
"main": "src/index.js",
"author": "Agoric",
"license": "Apache-2.0",
"type": "module",
"scripts": {
"build": "exit 0",
"test": "ava --verbose"
},
"files": [
"src/**/*.js",
"test/**/*.js"
],
"dependencies": {
"better-sqlite3": "^8.4.0",
"hot-shots": "^10.0.0",
"sqlite3": "^5.1.6"
},
"devDependencies": {
"ava": "^5.3.1"
},
"ava": {
"files": [
"test/**/test-*.js"
]
}
}
43 changes: 43 additions & 0 deletions packages/SwingSet/misc-tools/store-stats/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import processAmbient from 'process';
import dbOpenAmbient from 'better-sqlite3';
import {
makeStatsSender,
makeStatsTransformer,
makeSwingstore,
} from './store-stats.js';

export const swingstorePath = '~/.agoric/data/agoric/swingstore.sqlite';

/**
* @param {string[]} argv
* @param {{ dbOpen: typeof import('better-sqlite3'), HOME?: string }} io
*/
const main = async (argv, { dbOpen }) => {
const [_node, _script, databasePath] = argv;
const fullPath = (databasePath || swingstorePath).replace(
/^~/,
processAmbient.env.HOME,
);
console.log(`Opening database at ${fullPath}`);
const kStore = makeSwingstore(dbOpen(fullPath, { readonly: true }));
const height = kStore.findHeight();
const vatItems = kStore.findVatItems();
const statsTransformer = makeStatsTransformer();
const stats = statsTransformer.rowsToStats(vatItems);
const statsSender = makeStatsSender();
statsSender.sendMetrics({ height, vatStats: stats });
console.log('Done');
};

processAmbient.exitCode = 1;
main(processAmbient.argv, {
dbOpen: dbOpenAmbient,
}).then(
() => {
processAmbient.exitCode = 0;
},
err => {
console.error('Failed with', err);
processAmbient.exit(processAmbient.exitCode || 1);
},
);
162 changes: 162 additions & 0 deletions packages/SwingSet/misc-tools/store-stats/src/store-stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import processAmbient from 'process';
import { StatsD } from 'hot-shots';
import dbOpenAmbient from 'better-sqlite3';

const dogstatsd = new StatsD({
errorHandler(error) {
console.log('StatsD socket errors: ', error);
},
});

/**
* @param {import('better-sqlite3').Database} db
*/
export const makeSwingstore = db => {
const dbTool = () => {
const prepare = (strings, ...params) => {
const dml = strings.join('?');
return { stmt: db.prepare(dml), params };
};
const sql = (strings, ...args) => {
const { stmt, params } = prepare(strings, ...args);
return stmt.all(...params);
};
sql.get = (strings, ...args) => {
const { stmt, params } = prepare(strings, ...args);
return stmt.get(...params);
};
sql.iterate = (strings, ...args) => {
const { stmt, params } = prepare(strings, ...args);
return stmt.iterate(...params);
};
return sql;
};

const sql = dbTool();

/** @param {string} key */
const kvGet = key => sql.get`select * from kvStore where key = ${key}`.value;

const kvAll = key => sql.iterate`select * from kvStore where key GLOB ${key}`;

return Object.freeze({
findHeight: () => {
console.log('Fetching block height from the database');
return kvGet('host.height');
},
findVatItems: () => {
console.log('Fetching vat items from the database');
return kvAll('v[0-9]*.*');
},
});
};

export const makeStatsSender = () => {
const sendMetrics = data => {
const { height, vatStats } = data;
console.log(
`Sending metrics of ${vatStats.size} vats to datadog for block ${height}`,
);
for (const [vatID, stats] of vatStats) {
const tags = [`height:${height}`, `vatID:${vatID}`];
dogstatsd.gauge(
'swingset.kvstore.vat_stats.object_exports_reachable',
stats.objectExportsReachable,
tags,
);
dogstatsd.gauge(
'swingset.kvstore.vat_stats.object_exports_recognizable',
stats.objectExportsRecognizable,
tags,
);
dogstatsd.gauge(
'swingset.kvstore.vat_stats.object_imports_reachable',
stats.objectImportsReachable,
tags,
);
dogstatsd.gauge(
'swingset.kvstore.vat_stats.object_imports_recognizable',
stats.objectImportsRecognizable,
tags,
);
dogstatsd.gauge(
'swingset.kvstore.vat_stats.promises',
stats.promises,
tags,
);
dogstatsd.gauge(
'swingset.kvstore.vat_stats.vat_store_keys',
stats.vatStoreKeys,
tags,
);
dogstatsd.increment('swingset.kvstore.stats');
}
dogstatsd.close();
console.log('Done sending metrics to datadog');
};

return Object.freeze({
sendMetrics,
});
};

export const makeStatsTransformer = () => {
// kvstore has pairs like:
// "v$NN.c.ko45": "R o+4" # reachable export
// "v$NN.c.ko46": "_ o+5" # merely-recognizable export
// "v$NN.c.ko47": "R o-6" # reachable import
// "v$NN.c.ko48": "_ o-7" # merely-recognizable import
// "v$NN.c.kp70": "R p+12" # promise (always "R", sometimes "p+" sometimes "p-")

const object = /^v\d+\.c\..*ko\d.*$/; // vNN.c.koNNNNN*
const exportReach = /^R o\+.*$/; // R o+*
const exportRecog = /^_ o\+.*$/; // _ o+*

const importReach = /^R o-.*$/; // R o-*
const importRecog = /^_ o-.*$/; // _ o-*

const vatPromise = /^v\d+\.c\..*kp\d.*$/; // vNN.c.kpNNNNN*
const vatStoreKeys = /^v\d+\.vs\..*$/; // vNN.vs.*

const newStat = () => {
return {
objectExportsReachable: 0,
objectExportsRecognizable: 0,
objectImportsReachable: 0,
objectImportsRecognizable: 0,
promises: 0,
vatStoreKeys: 0,
};
};

const rowsToStats = rows => {
console.log(`Transforming rows to stats`);
let count = 0;
const vatStats = new Map();
for (const row of rows) {
count += 1;
const vatID = row.key.split('.')[0];
const stat = vatStats.get(vatID) || newStat();
vatStats.set(vatID, stat);
stat.objectExportsReachable +=
object.test(row.key) && exportReach.test(row.value);

stat.objectExportsRecognizable +=
object.test(row.key) && exportRecog.test(row.value);

stat.objectImportsReachable +=
object.test(row.key) && importReach.test(row.value);

stat.objectImportsRecognizable +=
object.test(row.key) && importRecog.test(row.value);

stat.promises += vatPromise.test(row.key);
stat.vatStoreKeys += vatStoreKeys.test(row.key);
}
console.log(`Done transforming ${count} rows to stats}`);
return vatStats;
};
return Object.freeze({
rowsToStats,
});
};
33 changes: 33 additions & 0 deletions packages/SwingSet/misc-tools/store-stats/tests/test-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import test from 'ava';
import { makeStatsTransformer } from '../src/store-stats.js';

test('transformer', async t => {
const rows = [
{ key: 'v1.c.ko21', value: 'R o+0' },
{ key: 'v1.c.ko11', value: '_ o+0' },
{ key: 'v1.c.ko21', value: 'R o-0' },
{ key: 'v1.c.ko11', value: '_ o-0' },
{ key: 'v1.c.kp40', value: 'R p-60' },
{ key: 'v2.c.kp146', value: 'R p+12' },
{ key: 'v1.vs.vc.1', value: '3' },
{ key: 'v1.vs.vc.1', value: '3' },
{ key: 'v2.vs.vc.1', value: '3' },
{ key: 'v1.o.noImpact', value: 'R p+12' },
{ key: 'v1.p.noImpact', value: 'R p+12' },
];
const statsTransformer = makeStatsTransformer();
const stats = statsTransformer.rowsToStats(rows);
t.assert(stats.size === 2);
t.assert(stats.get('v1').objectExportsReachable === 1);
t.assert(stats.get('v1').objectExportsRecognizable === 1);
t.assert(stats.get('v1').objectImportsReachable === 1);
t.assert(stats.get('v1').objectImportsRecognizable === 1);
t.assert(stats.get('v1').promises === 1);
t.assert(stats.get('v1').vatStoreKeys === 2);
t.assert(stats.get('v2').objectExportsReachable === 0);
t.assert(stats.get('v2').objectExportsRecognizable === 0);
t.assert(stats.get('v2').objectImportsReachable === 0);
t.assert(stats.get('v2').objectImportsRecognizable === 0);
t.assert(stats.get('v2').promises === 1);
t.assert(stats.get('v2').vatStoreKeys === 1);
});
Loading

0 comments on commit 74f48af

Please sign in to comment.