Revert "[client-app] Measure and report archiving times"

This reverts commit a8fbcb0c93.

# Conflicts:
#	packages/client-app/internal_packages/thread-list/lib/thread-list-quick-actions.cjsx
#	packages/client-app/internal_packages/thread-list/lib/thread-list.cjsx
#	packages/client-app/src/flux/actions.es6
#	packages/client-app/src/flux/stores/thread-list-actions-store.es6
#	packages/client-app/src/global/nylas-exports.es6
#	packages/isomorphic-core/src/metrics-reporter.es6
This commit is contained in:
Ben Gotow 2017-06-23 16:09:29 -07:00
parent 0f6fdb4256
commit ef9a1b1164
12 changed files with 59 additions and 313 deletions

View file

@ -26,10 +26,10 @@ class ThreadArchiveButton extends React.Component
_onArchive: (e) =>
return unless DOMUtils.nodeIsVisible(e.currentTarget)
Actions.archiveThreads({
threads: [@props.thread],
source: 'Toolbar Button: Message List',
})
tasks = TaskFactory.tasksForArchiving
threads: [@props.thread]
source: "Toolbar Button: Message List"
Actions.queueTasks(tasks)
Actions.popSheet()
e.stopPropagation()

View file

@ -20,10 +20,11 @@ export function sendActions() {
Actions.queueTask(new SendDraftTask(draft.id))
return DatabaseStore.modelify(Thread, [draft.threadId])
.then((threads) => {
Actions.archiveThreads({
const tasks = TaskFactory.tasksForArchiving({
source: "Send and Archive",
threads: threads,
})
Actions.queueTasks(tasks)
})
},
}]

View file

@ -103,10 +103,11 @@ export default class ThreadListContextMenu {
return {
label: "Archive",
click: () => {
Actions.archiveThreads({
const tasks = TaskFactory.tasksForArchiving({
source: "Context Menu: Thread List",
threads: this.threads,
})
Actions.queueTasks(tasks)
},
}
}

View file

@ -25,12 +25,10 @@ class ThreadArchiveQuickAction extends React.Component
newProps.thread.id isnt @props?.thread.id
_onArchive: (event) =>
# Don't trigger the thread row click
event.stopPropagation()
Actions.archiveThreads({
source: "Quick Actions: Thread List",
threads: [@props.thread],
})
tasks = TaskFactory.tasksForArchiving
source: "Quick Actions: Thread List"
threads: [@props.thread]
Actions.queueTasks(tasks)
class ThreadTrashQuickAction extends React.Component
@displayName: 'ThreadTrashQuickAction'

View file

@ -342,9 +342,11 @@ class ThreadList extends React.Component
_onArchiveItem: =>
threads = @_threadsForKeyboardAction()
if not threads
return
Actions.archiveThreads({threads, source: "Keyboard Shortcut"})
if threads
tasks = TaskFactory.tasksForArchiving
source: "Keyboard Shortcut"
threads: threads
Actions.queueTasks(tasks)
Actions.popSheet()
_onDeleteItem: =>

View file

@ -22,10 +22,11 @@ export class ArchiveButton extends React.Component {
}
_onArchive = (event) => {
Actions.archiveThreads({
const tasks = TaskFactory.tasksForArchiving({
threads: this.props.items,
source: "Toolbar Button: Thread List",
})
Actions.queueTasks(tasks);
Actions.popSheet();
event.stopPropagation();
return;

View file

@ -11,9 +11,9 @@ export default class GlobalTimer {
this._pendingRuns = {}
}
start(key, now = Date.now()) {
start(key) {
if (!this._pendingRuns[key]) {
this._pendingRuns[key] = [now]
this._pendingRuns[key] = [Date.now()]
}
}
@ -30,20 +30,15 @@ export default class GlobalTimer {
}
}
stop(key, now = Date.now()) {
stop(key) {
if (!this._pendingRuns[key]) { return 0 }
if (!this._doneRuns[key]) {
this._doneRuns[key] = []
}
this._pendingRuns[key].push(now);
if (!this._doneRuns[key]) { this._doneRuns[key] = [] }
this._pendingRuns[key].push(Date.now());
const total = this.calcTotal(this._pendingRuns[key])
this._doneRuns[key].push(this._pendingRuns[key])
if (this._doneRuns[key].length > BUFFER_SIZE) {
this._doneRuns[key].shift()
}
delete this._pendingRuns[key]
return total
}

View file

@ -556,19 +556,6 @@ class Actions {
static resetEmailCache = ActionScopeGlobal;
static debugSync = ActionScopeGlobal;
// Thread list actions
static archiveThreads = ActionScopeWindow;
static trashThreads = ActionScopeWindow;
static markAsSpamThreads = ActionScopeWindow;
static toggleStarredThreads = ActionScopeWindow;
static toggleUnreadThreads = ActionScopeWindow;
static setUnreadThreads = ActionScopeWindow;
static removeThreadsFromView = ActionScopeWindow;
static moveThreadsToPerspective = ActionScopeWindow;
static applyCategoryToThreads = ActionScopeWindow;
static removeCategoryFromThreads = ActionScopeWindow;
static threadListDidUpdate = ActionScopeWindow;
}

View file

@ -86,11 +86,6 @@ export default class ObservableListDataSource extends ListTabular.DataSource {
return this._resultSet.offsetOfId(id);
}
itemsCurrentlyInView() {
if (!this._resultSet) { return [] }
return this._resultSet.models()
}
itemsCurrentlyInViewMatching(matchFn) {
if (!this._resultSet) {
return [];

View file

@ -1,214 +0,0 @@
import NylasStore from 'nylas-store'
import Actions from '../actions'
import Utils from '../models/utils'
import TaskFactory from '../tasks/task-factory'
import AccountStore from '../stores/account-store'
import FocusedPerspectiveStore from '../stores/focused-perspective-store'
class ThreadListActionsStore extends NylasStore {
constructor() {
super()
this._timers = new Map()
}
activate() {
if (!NylasEnv.isMainWindow()) { return }
this.listenTo(Actions.archiveThreads, this._onArchiveThreads)
this.listenTo(Actions.trashThreads, this._onTrashThreads)
this.listenTo(Actions.markAsSpamThreads, this._onMarkAsSpamThreads)
this.listenTo(Actions.toggleStarredThreads, this._onToggleStarredThreads)
this.listenTo(Actions.toggleUnreadThreads, this._onToggleUnreadThreads)
this.listenTo(Actions.setUnreadThreads, this._onSetUnreadThreads)
this.listenTo(Actions.removeThreadsFromView, this._onRemoveThreadsFromView)
this.listenTo(Actions.moveThreadsToPerspective, this._onMoveThreadsToPerspective)
this.listenTo(Actions.removeCategoryFromThreads, this._onRemoveCategoryFromThreads)
this.listenTo(Actions.applyCategoryToThreads, this._onApplyCategoryToThreads)
this.listenTo(Actions.threadListDidUpdate, this._onThreadListDidUpdate)
}
deactivate() {
this.stopListeningToAll()
}
_onThreadListDidUpdate = (threads) => {
const updatedAt = Date.now()
const threadIdsInList = new Set(threads.map(t => t.id))
for (const [timerId, timerData] of this._timers.entries()) {
const {threadIds, provider, source, action, targetCategory} = timerData
const threadsHaveBeenRemoved = threadIds.every(id => !threadIdsInList.has(id))
if (threadsHaveBeenRemoved) {
const actionTimeMs = NylasEnv.timer.stop(timerId, updatedAt)
Actions.recordPerfMetric({
action,
source,
provider,
actionTimeMs,
targetCategory,
threadCount: threadIds.length,
sample: 0.9,
})
this._timers.delete(timerId)
}
}
}
_setNewTimer({threads, threadIds, accountIds, source, action, targetCategory = 'unknown'} = {}) {
if (!threads && !threadIds) {
return
}
if (threadIds && !accountIds) {
throw new Error('ThreadListActionStore._setNewTimer: Must pass accountIds along with threadIds')
}
const tIds = threadIds || threads.map(t => t.id);
const timerId = Utils.generateTempId()
let accounts
if (!threads) {
accounts = accountIds
.map(id => AccountStore.accountForId(id))
.filter(Boolean)
} else {
accounts = AccountStore.accountsForItems(threads)
}
const firstProvider = accounts[0].provider
const haveSameProvider = accounts
.reduce((provider, acct) => (acct.provider === provider ? provider : false), firstProvider)
const provider = haveSameProvider ? firstProvider : 'mixed'
const timerData = {
source,
action,
provider,
targetCategory,
threadIds: tIds,
}
this._timers.set(timerId, timerData)
NylasEnv.timer.start(timerId)
}
_onArchiveThreads = ({threads, source} = {}) => {
if (!threads) { return }
if (threads.length === 0) { return }
this._setNewTimer({threads, source, action: 'remove-threads-from-list', targetCategory: 'archive'})
const tasks = TaskFactory.tasksForArchiving({threads, source})
Actions.queueTasks(tasks)
}
_onTrashThreads = ({threads, source} = {}) => {
if (!threads) { return }
if (threads.length === 0) { return }
this._setNewTimer({threads, source, action: 'remove-threads-from-list', targetCategory: 'trash'})
const tasks = TaskFactory.tasksForMovingToTrash({threads, source})
Actions.queueTasks(tasks)
}
_onMarkAsSpamThreads = ({threads, source} = {}) => {
if (!threads) { return }
if (threads.length === 0) { return }
this._setNewTimer({threads, source, action: 'remove-threads-from-list', targetCategory: 'spam'})
const tasks = TaskFactory.tasksForMarkingAsSpam({threads, source})
Actions.queueTasks(tasks)
}
_onToggleStarredThreads = ({threads, source} = {}) => {
if (!threads) { return }
if (threads.length === 0) { return }
const task = TaskFactory.taskForInvertingStarred({threads, source})
Actions.queueTask(task)
}
_onToggleUnreadThreads = ({threads, canBeUndone, source} = {}) => {
if (!threads) { return }
if (threads.length === 0) { return }
const task = TaskFactory.taskForInvertingUnread({threads, source, canBeUndone})
Actions.queueTask(task)
}
_onSetUnreadThreads = ({threads, unread, canBeUndone, source} = {}) => {
if (!threads) { return }
if (threads.length === 0) { return }
const task = TaskFactory.taskForSettingUnread({threads, unread, source, canBeUndone})
Actions.queueTask(task)
}
_onRemoveThreadsFromView = ({threads, ruleset, source} = {}) => {
if (!threads) { return }
if (threads.length === 0) { return }
const currentPerspective = FocusedPerspectiveStore.current()
const tasks = currentPerspective.tasksForRemovingItems(threads, ruleset, source)
// This action can encompass many different actions, e.g.:
// - unstarring in starred view
// - changing unread in unread view
// - Moving to inbox from trash
// - archiving a search result (which won't actually remove it from the thread-list)
// For now, we are only interested in timing actions that remove threads
// from the inbox
if (currentPerspective.isInbox()) {
// TODO figure out the `targetCategory`
this._setNewTimer({threads, source, action: 'remove-threads-from-list'})
}
Actions.queueTasks(tasks)
}
_onMoveThreadsToPerspective = ({targetPerspective, threadIds, accountIds} = {}) => {
if (!threadIds) { return }
if (threadIds.length === 0) { return }
const currentPerspective = FocusedPerspectiveStore.current()
// For now, we are only interested in timing actions that remove threads
// from the inbox
const targetCategories = targetPerspective.categories()
const targetCategoryIsFolder = (
targetCategories && targetCategories.length > 0 &&
targetCategories.every(c => c.object === 'folder')
)
const isRemovingFromInbox = currentPerspective.isInbox() && targetCategoryIsFolder
if (isRemovingFromInbox) {
const targetCategory = targetPerspective.isArchive() ? 'archive' : targetPerspective.categoriesSharedName();
this._setNewTimer({
threadIds,
accountIds,
targetCategory,
source: "Dragged to Sidebar",
action: 'remove-threads-from-list',
})
}
targetPerspective.receiveThreads(threadIds)
}
_onApplyCategoryToThreads = ({threads, source, categoryToApply} = {}) => {
if (!threads) { return }
if (threads.length === 0) { return }
const task = TaskFactory.taskForApplyingCategory({
threads,
source,
category: categoryToApply,
})
Actions.queueTask(task)
}
_onRemoveCategoryFromThreads = ({threads, source, categoryToRemove} = {}) => {
if (!threads) { return }
if (threads.length === 0) { return }
// For now, we are only interested in timing actions that remove threads
// from the inbox
if (categoryToRemove.isInbox()) {
this._setNewTimer({
source,
threads,
targetCategory: 'archive',
action: 'remove-threads-from-list',
})
}
const task = TaskFactory.taskForRemovingCategory({
threads,
source,
category: categoryToRemove,
})
Actions.queueTask(task)
}
}
export default new ThreadListActionsStore()

View file

@ -164,7 +164,7 @@ lazyLoadAndRegisterStore(`MessageBodyProcessor`, 'message-body-processor');
lazyLoadAndRegisterStore(`FocusedContactsStore`, 'focused-contacts-store');
lazyLoadAndRegisterStore(`DeltaConnectionStore`, 'delta-connection-store');
lazyLoadAndRegisterStore(`FolderSyncProgressStore`, 'folder-sync-progress-store');
lazyLoadAndRegisterStore(`ThreadListActionsStore`, 'thread-list-actions-store');
lazyLoadAndRegisterStore(`TaskQueueStatusStore`, 'task-queue-status-store');
lazyLoadAndRegisterStore(`FocusedPerspectiveStore`, 'focused-perspective-store');
lazyLoadAndRegisterStore(`SearchableComponentStore`, 'searchable-component-store');
lazyLoad(`CustomContenteditableComponents`, 'components/overlaid-components/custom-contenteditable-components');

View file

@ -1,31 +1,13 @@
import os from 'os'
import {isClientEnv, isCloudEnv} from './env-helpers'
/**
* NOTE: This is the Honeycomb performance metrics reporting for the Nylas
* Mail Client. It is NOT the logging data for cloud plugins. This is
* accessed via the /ingest-metrics endpoint of the cloud api. this can
* also be used from the cloud environment to report metrics to honeycomb,
* which is different from sending the logs to honeycomb
*
* Each AWS box automatically sends all log data to Honeycomb via
* honeytail. You can find the config by ssh-ing to a production cloud box
* and looking at /etc/sv/honeytail/run
*/
class MetricsReporter {
constructor() {
this._honey = null
this._baseReportingData = {
hostname: os.hostname(),
cpus: os.cpus().length,
arch: os.arch(),
platform: process.platform,
version: isClientEnv() ? NylasEnv.getVersion() : undefined,
}
if (isCloudEnv()) {
const LibHoney = require('libhoney').default // eslint-disable-line
const LibHoney = require('libhoney') // eslint-disable-line
this._honey = new LibHoney({
writeKey: process.env.HONEY_WRITE_KEY,
@ -46,55 +28,53 @@ class MetricsReporter {
});
}
sendToHoneycomb(data) {
sendToHoneycomb(info) {
if (!this._honey) {
throw new Error('Metrics Reporter: Honeycomb is not available in this environment')
}
this._honey.sendNow(data);
this._honey.sendNow(info);
}
async reportEvent(data) {
if (!data.nylasId) {
async reportEvent(info) {
if (!info.nylasId) {
throw new Error("Metrics Reporter: You must include an nylasId");
}
const {accountId: id, emailAddress} = data
const logger = global.Logger ? global.Logger.forAccount({id, emailAddress}) : console;
const logger = global.Logger.child({accountEmail: info.emailAddress})
const {workingSetSize, privateBytes, sharedBytes} = process.getProcessMemoryInfo();
const dataToReport = Object.assign({}, this._baseReportingData, data, {
processWorkingSetSize: workingSetSize,
processPrivateBytes: privateBytes,
processSharedBytes: sharedBytes,
})
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;
try {
if (!isClientEnv()) {
this.sendToHoneycomb(dataToReport)
return
}
if (NylasEnv.inDevMode()) { return }
if (isClientEnv()) {
if (NylasEnv.inDevMode()) { return }
if (!info.accountId) {
throw new Error("Metrics Reporter: You must include an accountId");
}
const {IdentityStore, N1CloudAPI, NylasAPIRequest} = require('nylas-exports') // eslint-disable-line
if (!IdentityStore.identity()) {
throw new Error("Metrics Reporter: Identity must be available");
const {N1CloudAPI, NylasAPIRequest} = require('nylas-exports') // eslint-disable-line
const req = new NylasAPIRequest({
api: N1CloudAPI,
options: {
path: `/ingest-metrics`,
method: 'POST',
body: info,
accountId: info.accountId,
},
});
await req.run()
} else {
this.sendToHoneycomb(info)
}
if (!dataToReport.accountId) {
throw new Error("Metrics Reporter: You must include an accountId");
}
const req = new NylasAPIRequest({
api: N1CloudAPI,
options: {
path: `/ingest-metrics`,
method: 'POST',
body: dataToReport,
accountId: dataToReport.accountId,
},
});
await req.run()
logger.log("Metrics Reporter: Submitted.", dataToReport);
logger.log(info, "Metrics Reporter: Submitted.", info);
} catch (err) {
logger.warn("Metrics Reporter: Submission Failed.", {error: err, ...dataToReport});
logger.warn("Metrics Reporter: Submission Failed.", {error: err, ...info});
}
}
}