diff --git a/packages/local-sync/src/local-sync-dashboard/root.jsx b/packages/local-sync/src/local-sync-dashboard/root.jsx index 0106d1657..cad562aaf 100644 --- a/packages/local-sync/src/local-sync-dashboard/root.jsx +++ b/packages/local-sync/src/local-sync-dashboard/root.jsx @@ -84,7 +84,7 @@ class AccountCard extends React.Component { let firstSyncDuration = "Incomplete"; if (account.firstSyncCompletion) { - firstSyncDuration = (new Date(account.firstSyncCompletion) - new Date(account.createdAt)) / 1000; + firstSyncDuration = (new Date(account.firstSyncCompletion / 1) - new Date(account.createdAt)) / 1000; } const position = calcAcctPosition(this.props.count); diff --git a/packages/local-sync/src/local-sync-worker/sync-metrics-reporter.js b/packages/local-sync/src/local-sync-worker/sync-metrics-reporter.js new file mode 100644 index 000000000..f129fcc77 --- /dev/null +++ b/packages/local-sync/src/local-sync-worker/sync-metrics-reporter.js @@ -0,0 +1,58 @@ +const {N1CloudAPI, NylasAPIRequest, AccountStore} = require('nylas-exports'); +const os = require('os'); + +class SyncMetricsReporter { + constructor() { + this._logger = global.Logger.child(); + } + + async collectCPUUsage() { + return new Promise((resolve) => { + const startUsage = process.cpuUsage(); + const sampleDuration = 400; + setTimeout(() => { + const {user, system} = process.cpuUsage(startUsage); + const fractionToPrecent = 100.0; + resolve(Math.round((user + system) / (sampleDuration * 1000.0) * fractionToPrecent)); + }, sampleDuration); + }); + } + + async reportEvent(info) { + if (!info.emailAddress) { + throw new Error("You must include email_address"); + } + + const {workingSetSize, privateBytes, sharedBytes} = process.getProcessMemoryInfo(); + const percentCPU = await this.collectCPUUsage(); + + info.hostname = os.hostname(); + info.cpus = os.cpus().length; + info.arch = os.arch(); + info.platform = process.platform; + info.version = NylasEnv.getVersion(); + info.processWorkingSetSize = workingSetSize; + info.processPrivateBytes = privateBytes; + info.processSharedBytes = sharedBytes; + info.processPercentCPU = percentCPU; + + const req = new NylasAPIRequest({ + api: N1CloudAPI, + options: { + path: `/ingest-metrics`, + method: 'POST', + body: info, + error: () => { + this._logger.warn(info, "Metrics Collector: Submission Failed."); + }, + accountId: AccountStore.accountForEmail(info.emailAddress).id, + success: () => { + this._logger.info(info, "Metrics Collector: Submitted."); + }, + }, + }); + req.run(); + } +} + +module.exports = new SyncMetricsReporter(); diff --git a/packages/local-sync/src/local-sync-worker/sync-process-manager.js b/packages/local-sync/src/local-sync-worker/sync-process-manager.js index c9004ba9e..3fcd2ac20 100644 --- a/packages/local-sync/src/local-sync-worker/sync-process-manager.js +++ b/packages/local-sync/src/local-sync-worker/sync-process-manager.js @@ -66,7 +66,6 @@ class SyncProcessManager { this._workers[accountId] = null; } } - } module.exports = new SyncProcessManager(); diff --git a/packages/local-sync/src/local-sync-worker/sync-worker.js b/packages/local-sync/src/local-sync-worker/sync-worker.js index afc7f8bba..04500ca6d 100644 --- a/packages/local-sync/src/local-sync-worker/sync-worker.js +++ b/packages/local-sync/src/local-sync-worker/sync-worker.js @@ -11,6 +11,7 @@ const { const FetchFolderList = require('./imap/fetch-folder-list') const FetchMessagesInFolder = require('./imap/fetch-messages-in-folder') const SyncbackTaskFactory = require('./syncback-task-factory') +const SyncMetricsReporter = require('./sync-metrics-reporter'); class SyncWorker { @@ -27,6 +28,36 @@ class SyncWorker { this._syncTimer = setTimeout(() => { this.syncNow({reason: 'Initial'}); }, 0); + + // setup metrics collection. We do this in an isolated way by hooking onto + // the database, because otherwise things get /crazy/ messy and I don't like + // having counters and garbage everywhere. + if (!account.firstSyncCompletion) { + this._logger.info("This is initial sync. Setting up metrics collection!"); + + let seen = 0; + db.Thread.addHook('afterCreate', 'metricsCollection', () => { + if (seen === 0) { + SyncMetricsReporter.reportEvent({ + type: 'imap', + emailAddress: account.emailAddress, + msecToFirstThread: (Date.now() - new Date(account.createdAt).getTime()), + }) + } + if (seen === 500) { + SyncMetricsReporter.reportEvent({ + type: 'imap', + emailAddress: account.emailAddress, + msecToFirst500Threads: (Date.now() - new Date(account.createdAt).getTime()), + }) + } + + if (seen > 500) { + db.Thread.removeHook('afterCreate', 'metricsCollection') + } + seen += 1; + }); + } } cleanup() { diff --git a/packages/local-sync/src/new-message-processor/index.js b/packages/local-sync/src/new-message-processor/index.js index 5aed1b513..bc5d072b7 100644 --- a/packages/local-sync/src/new-message-processor/index.js +++ b/packages/local-sync/src/new-message-processor/index.js @@ -1,7 +1,7 @@ -const detectThread = require('./detect-thread') -const extractFiles = require('./extract-files') -const extractContacts = require('./extract-contacts') -const LocalDatabaseConnector = require('../shared/local-database-connector') +const detectThread = require('./detect-thread'); +const extractFiles = require('./extract-files'); +const extractContacts = require('./extract-contacts'); +const LocalDatabaseConnector = require('../shared/local-database-connector'); const Queue = require('promise-queue'); const queue = new Queue(1, Infinity);