diff --git a/lib/components/Memory.js b/lib/components/Memory.js index e0aa7c1..2c167b7 100644 --- a/lib/components/Memory.js +++ b/lib/components/Memory.js @@ -1,12 +1,11 @@ 'use babel'; import React from 'react'; +import {connect} from 'react-redux'; import Subheader from './Subheader'; import BarSlider from './BarSlider'; import NumericDisplay from './NumericDisplay'; -import BatchSizeStore from '../stores/batchsize_store'; -import INNPVStore from '../stores/innpv_store'; import PerfVisState from '../models/PerfVisState'; export default class Memory extends React.Component { @@ -17,10 +16,6 @@ export default class Memory extends React.Component { _handleResize(deltaPct, basePct) { // TODO: Add in memory predictions again - return; - - BatchSizeStore.updateMemoryUsage(deltaPct, basePct); - INNPVStore.setPerfVisState(PerfVisState.SHOWING_PREDICTIONS); } render() { @@ -56,3 +51,9 @@ export default class Memory extends React.Component { } } +const mapStateToProps = (state, ownProps) => ({ + model: state.memoryUsage, + ...ownProps, +}); + +export default connect(mapStateToProps)(Memory); diff --git a/lib/components/MemoryBreakdown.js b/lib/components/MemoryBreakdown.js index 87b454b..813b200 100644 --- a/lib/components/MemoryBreakdown.js +++ b/lib/components/MemoryBreakdown.js @@ -1,9 +1,8 @@ 'use babel'; import React from 'react'; +import {connect} from 'react-redux'; -import AnalysisStore from '../stores/analysis_store'; -import ProjectStore from '../stores/project_store'; import PerfBarContainer from './generic/PerfBarContainer'; import MemoryEntryLabel from '../models/MemoryEntryLabel'; import PerfVisState from '../models/PerfVisState'; @@ -38,77 +37,18 @@ const COLORS_BY_LABEL = { ], } -export default class MemoryPerfBarContainer extends React.Component { +class MemoryBreakdown extends React.Component { constructor(props) { super(props); - const memoryBreakdown = AnalysisStore.getMemoryBreakdown(); - this.state = { - memoryBreakdown, - textEditorMap: this._getTextEditorMap(memoryBreakdown), - expanded: null, - }; - - this._onLabelClick = this._onLabelClick.bind(this); - this._onAnalysisStoreUpdate = this._onAnalysisStoreUpdate.bind(this); - this._onProjectStoreUpdate = this._onProjectStoreUpdate.bind(this); - } - - componentDidMount() { - AnalysisStore.addListener(this._onAnalysisStoreUpdate); - ProjectStore.addListener(this._onProjectStoreUpdate); - } - - componentWillUnmount() { - AnalysisStore.removeListener(this._onAnalysisStoreUpdate); - ProjectStore.removeListener(this._onProjectStoreUpdate); - } - - _onAnalysisStoreUpdate() { - const memoryBreakdown = AnalysisStore.getMemoryBreakdown(); - this.setState({ - memoryBreakdown, - textEditorMap: this._getTextEditorMap(memoryBreakdown), - }); - } - - _onProjectStoreUpdate() { - this.setState({ - textEditorMap: this._getTextEditorMap(this.state.memoryBreakdown), - }); - } - - _getTextEditorMap(memoryBreakdown) { - const editorMap = new Map(); - if (memoryBreakdown == null) { - return editorMap; - } - - [MemoryEntryLabel.Weights, MemoryEntryLabel.Activations].forEach(label => { - memoryBreakdown.getEntriesByLabel(label).forEach(entry => { - if (entry.filePath == null || editorMap.has(entry.filePath)) { - return; - } - editorMap.set(entry.filePath, ProjectStore.getTextEditorsFor(entry.filePath)); - }); - }); - - return editorMap; + this._renderPerfBars = this._renderPerfBars.bind(this); } _getLabels() { - const {memoryBreakdown, expanded} = this.state; + const {memoryBreakdown} = this.props; if (memoryBreakdown == null) { return DEFAULT_MEMORY_LABELS; } - if (expanded != null) { - return DEFAULT_MEMORY_LABELS.map(({label, ...rest}) => ({ - ...rest, - label, - percentage: expanded === label ? 100 : 0.001, - })); - } - const peakUsageBytes = memoryBreakdown.getPeakUsageBytes(); return DEFAULT_MEMORY_LABELS.map(({label, ...rest}) => ({ ...rest, @@ -117,24 +57,13 @@ export default class MemoryPerfBarContainer extends React.Component { })); } - _onLabelClick(label) { - const {expanded} = this.state; - if (expanded == null && label !== MemoryEntryLabel.Untracked) { - this.setState({expanded: label}); - } else if (expanded === label) { - this.setState({expanded: null}); - } - } - _entryKey(entry, index) { - if (entry.filePath != null && entry.lineNumber != null) { - return `${entry.name}-${entry.filePath}-${entry.lineNumber}`; - } + // TODO: Use a stable identifier (presumably an id from the report database) return `${entry.name}-idx${index}`; } - _renderPerfBars() { - const {memoryBreakdown, expanded, textEditorMap} = this.state; + _renderPerfBars(expanded) { + const {editorsByPath, projectRoot, memoryBreakdown} = this.props; if (memoryBreakdown == null) { return null; } @@ -158,12 +87,13 @@ export default class MemoryPerfBarContainer extends React.Component { displayPct = toPercentage(entry.sizeBytes, totalSizeBytes); } - const editors = entry.filePath != null ? textEditorMap.get(entry.filePath) : []; + const editors = entry.filePath != null ? editorsByPath.get(entry.filePath) : []; results.push( - {this._renderPerfBars()} - + renderPerfBars={this._renderPerfBars} + /> ); } } + +const mapStateToProps = (state, ownProps) => ({ + editorsByPath: state.editorsByPath, + memoryBreakdown: state.memoryBreakdown, + ...ownProps, +}); + +export default connect(mapStateToProps)(MemoryBreakdown); diff --git a/lib/components/MemoryPerfBar.js b/lib/components/MemoryPerfBar.js index 999fae6..8380432 100644 --- a/lib/components/MemoryPerfBar.js +++ b/lib/components/MemoryPerfBar.js @@ -5,9 +5,11 @@ import React from 'react'; import PerfBar from './generic/PerfBar'; import UsageHighlight from './UsageHighlight'; -import ProjectStore from '../stores/project_store'; import {toReadableByteSize} from '../utils'; +import Events from '../telemetry/events'; +import TelemetryClientContext from '../telemetry/react_context'; + class MemoryPerfBar extends React.Component { constructor(props) { super(props); @@ -36,14 +38,18 @@ class MemoryPerfBar extends React.Component { } _onClick() { - const {memoryEntry} = this.props; - if (memoryEntry.filePath == null) { + const {memoryEntry, projectRoot} = this.props; + if (memoryEntry.filePath == null || projectRoot == null) { return; } // Atom uses 0-based line numbers, but INNPV uses 1-based line numbers - const absoluteFilePath = path.join(ProjectStore.getProjectRoot(), memoryEntry.filePath); + const absoluteFilePath = path.join(projectRoot, memoryEntry.filePath); atom.workspace.open(absoluteFilePath, {initialLine: memoryEntry.lineNumber - 1}); + this.context.record( + Events.Interaction.CLICKED_MEMORY_ENTRY, + {label: memoryEntry.name}, + ); } render() { @@ -64,4 +70,6 @@ MemoryPerfBar.defaultProps = { editors: [], }; +MemoryPerfBar.contextType = TelemetryClientContext; + export default MemoryPerfBar; diff --git a/lib/components/PerfHint.js b/lib/components/PerfHint.js index 4afcc64..aab3c2f 100644 --- a/lib/components/PerfHint.js +++ b/lib/components/PerfHint.js @@ -4,7 +4,6 @@ import React from 'react' import ReactDOM from 'react-dom'; import SourceMarker from '../editor/marker'; -import INNPVStore from '../stores/innpv_store'; import PerfHintState from '../models/PerfHintState'; function Increase() { @@ -26,7 +25,7 @@ function Decrease() { class PerfHint extends React.Component { constructor(props) { super(props); - this._marker = new SourceMarker(INNPVStore.getEditor()); + this._marker = new SourceMarker(/* TODO: Reference to a TextEditor */); this._el = document.createElement('div'); } diff --git a/lib/components/PerfVis.js b/lib/components/PerfVis.js index b59fae2..eec36d9 100644 --- a/lib/components/PerfVis.js +++ b/lib/components/PerfVis.js @@ -1,57 +1,39 @@ 'use babel'; import React from 'react'; +import {connect} from 'react-redux'; +import AppState from '../models/AppState'; import GetStarted from './GetStarted'; import PerfVisMainView from './PerfVisMainView'; -import AppState from '../models/AppState'; -import INNPVStore from '../stores/innpv_store'; - -export default class PerfVis extends React.Component { - constructor(props) { - super(props); - this.state = { - appState: INNPVStore.getAppState(), - perfVisState: INNPVStore.getPerfVisState(), - errorMessage: INNPVStore.getErrorMessage(), - }; - this._onStoreUpdate = this._onStoreUpdate.bind(this); - } - - componentDidMount() { - INNPVStore.addListener(this._onStoreUpdate); - } - - componentWillUnmount() { - INNPVStore.removeListener(this._onStoreUpdate); - } - - _onStoreUpdate() { - this.setState({ - appState: INNPVStore.getAppState(), - perfVisState: INNPVStore.getPerfVisState(), - errorMessage: INNPVStore.getErrorMessage(), - }); - } - +class PerfVis extends React.Component { _renderContents() { - switch (this.state.appState) { + const { + appState, + perfVisState, + errorMessage, + projectRoot, + handleGetStartedClick, + } = this.props; + + switch (appState) { case AppState.OPENED: case AppState.CONNECTING: return ( ); case AppState.CONNECTED: return ( ); @@ -64,3 +46,13 @@ export default class PerfVis extends React.Component { return
{this._renderContents()}
; } } + +const mapStateToProps = (state, ownProps) => ({ + appState: state.appState, + perfVisState: state.perfVisState, + errorMessage: state.errorMessage, + projectRoot: state.projectRoot, + ...ownProps, +}); + +export default connect(mapStateToProps)(PerfVis); diff --git a/lib/components/PerfVisMainView.js b/lib/components/PerfVisMainView.js index 8aa6475..1beaf57 100644 --- a/lib/components/PerfVisMainView.js +++ b/lib/components/PerfVisMainView.js @@ -6,10 +6,9 @@ import ErrorMessage from './ErrorMessage'; import Memory from './Memory'; import MemoryBreakdown from './MemoryBreakdown'; import PerfVisStatusBar from './PerfVisStatusBar'; +import RunTimeBreakdown from './RunTimeBreakdown'; import Throughput from './Throughput'; import PerfVisState from '../models/PerfVisState'; -import AnalysisStore from '../stores/analysis_store'; -import INNPVStore from '../stores/innpv_store'; import SourceMarker from '../editor/marker'; function PerfVisHeader() { @@ -23,31 +22,11 @@ function PerfVisHeader() { export default class PerfVisMainView extends React.Component { constructor(props) { super(props); - this.state = { - overallMemoryUsage: AnalysisStore.getOverallMemoryUsage(), - throughput: AnalysisStore.getThroughput(), - }; - this._onStoreUpdate = this._onStoreUpdate.bind(this); this._handleStatusBarClick = this._handleStatusBarClick.bind(this); this._handleSliderHoverEnter = this._handleSliderHoverEnter.bind(this); this._handleSliderHoverExit = this._handleSliderHoverExit.bind(this); } - componentDidMount() { - AnalysisStore.addListener(this._onStoreUpdate); - } - - componentWillUnmount() { - AnalysisStore.removeListener(this._onStoreUpdate); - } - - _onStoreUpdate() { - this.setState({ - overallMemoryUsage: AnalysisStore.getOverallMemoryUsage(), - throughput: AnalysisStore.getThroughput(), - }); - } - _handleStatusBarClick() { // TODO: Handle status bar clicks (currently only to undo predictions) } @@ -61,34 +40,31 @@ export default class PerfVisMainView extends React.Component { _subrowClasses() { const {perfVisState} = this.props; - const {throughput, memory} = this.state; const mainClass = 'innpv-contents-subrows'; if (perfVisState === PerfVisState.MODIFIED || - (perfVisState === PerfVisState.ANALYZING && - (throughput == null || memory == null))) { + perfVisState === PerfVisState.ANALYZING) { return mainClass + ' innpv-no-events'; } return mainClass; } _renderBody() { - const {perfVisState} = this.props; + const {perfVisState, projectRoot, errorMessage} = this.props; if (this.props.errorMessage !== '') { - return ; + return ; } else { return (
- + +
diff --git a/lib/components/RunTimeBreakdown.js b/lib/components/RunTimeBreakdown.js index f0a9b18..3999eff 100644 --- a/lib/components/RunTimeBreakdown.js +++ b/lib/components/RunTimeBreakdown.js @@ -1,78 +1,131 @@ 'use babel'; import React from 'react'; +import {connect} from 'react-redux'; import RunTimePerfBar from './RunTimePerfBar'; +import RunTimeEntryLabel from '../models/RunTimeEntryLabel'; import PerfBarContainer from './generic/PerfBarContainer'; -import OperationInfoStore from '../stores/operationinfo_store'; import PerfVisState from '../models/PerfVisState'; +import {toPercentage} from '../utils'; -const COLOR_CLASSES = [ - 'innpv-blue-color-1', - 'innpv-blue-color-2', - 'innpv-blue-color-3', - 'innpv-blue-color-4', - 'innpv-blue-color-5', +const DEFAULT_LABELS = [ + {label: RunTimeEntryLabel.Forward, percentage: 30, clickable: true}, + {label: RunTimeEntryLabel.Backward, percentage: 60, clickable: true}, + {label: RunTimeEntryLabel.Other, percentage: 10, clickable: false}, ]; -export default class RunTimeBreakdown extends React.Component { +const LABEL_ORDER = DEFAULT_LABELS.map(({label}) => label); + +const COLORS_BY_LABEL = { + [RunTimeEntryLabel.Forward]: [ + 'innpv-green-color-1', + 'innpv-green-color-2', + 'innpv-green-color-3', + 'innpv-green-color-4', + 'innpv-green-color-5', + ], + [RunTimeEntryLabel.Backward]: [ + 'innpv-blue-color-1', + 'innpv-blue-color-2', + 'innpv-blue-color-3', + 'innpv-blue-color-4', + 'innpv-blue-color-5', + ], + [RunTimeEntryLabel.Other]: [ + 'innpv-untracked-color', + ], +} + +class RunTimeBreakdown extends React.Component { constructor(props) { super(props); + this._renderPerfBars = this._renderPerfBars.bind(this); + } - const operationInfos = OperationInfoStore.getOperationInfos(); - this.state = { - operationInfos, - totalTimeUs: this._getTotalTimeUs(operationInfos), - marginTop: 0, - }; + _getLabels() { + const {runTimeBreakdown} = this.props; + if (runTimeBreakdown == null) { + return DEFAULT_LABELS; + } - this._onStoreChange = this._onStoreChange.bind(this); - this._perfBarGenerator = this._perfBarGenerator.bind(this); - this._updateMarginTop = this._updateMarginTop.bind(this); + const iterationRunTimeMs = runTimeBreakdown.getIterationRunTimeMs(); + return DEFAULT_LABELS.map(({label, ...rest}) => ({ + ...rest, + label, + percentage: toPercentage(runTimeBreakdown.getTotalTimeMsByLabel(label), iterationRunTimeMs), + })); } - componentDidMount() { - OperationInfoStore.addListener(this._onStoreChange); + _entryKey(entry, index) { + // TODO: Use a stable identifier (presumably an id from the report database) + return `${entry.name}-idx${index}`; } - componentWillUnmount() { - OperationInfoStore.removeListener(this._onStoreChange); - } + _renderPerfBars(expanded) { + const {editorsByPath, projectRoot, runTimeBreakdown} = this.props; + if (runTimeBreakdown == null) { + return null; + } - _onStoreChange() { - const operationInfos = OperationInfoStore.getOperationInfos(); - this.setState({ - operationInfos, - totalTimeUs: this._getTotalTimeUs(operationInfos), - }); - } + const results = []; + const iterationRunTimeMs = runTimeBreakdown.getIterationRunTimeMs(); - _getTotalTimeUs(operationInfos) { - return operationInfos.reduce((acc, info) => acc + info.getRuntimeUs(), 0); - } + // [].flatMap() is a nicer way to do this, but it is not yet available. + LABEL_ORDER.forEach(label => { + const totalTimeMs = runTimeBreakdown.getTotalTimeMsByLabel(label); + const colors = COLORS_BY_LABEL[label]; + + runTimeBreakdown.getEntriesByLabel(label).forEach((entry, index) => { + const overallPct = toPercentage(entry.runTimeMs, iterationRunTimeMs); + + // This helps account for "expanded" labels + let displayPct = 0.001; + if (expanded == null) { + displayPct = overallPct; + } else if (label === expanded) { + displayPct = toPercentage(entry.runTimeMs, totalTimeMs); + } - _updateMarginTop(marginTop) { - this.setState({marginTop}); + const editors = entry.filePath != null ? editorsByPath.get(entry.filePath) : []; + + results.push( + + ); + }) + }); + + return results; } render() { const {perfVisState} = this.props; - const {operationInfos, marginTop} = this.state; const disabled = perfVisState === PerfVisState.MODIFIED || - (perfVisState === PerfVisState.ANALYZING && operationInfos.length == 0); + perfVisState === PerfVisState.ANALYZING; return ( - - {operationInfos.map((opInfo, index) => ( - - ))} - + ); } } + +const mapStateToProps = (state, ownProps) => ({ + editorsByPath: state.editorsByPath, + runTimeBreakdown: state.runTimeBreakdown, + ...ownProps, +}); + +export default connect(mapStateToProps)(RunTimeBreakdown); diff --git a/lib/components/RunTimePerfBar.js b/lib/components/RunTimePerfBar.js index 6c01e67..41ab997 100644 --- a/lib/components/RunTimePerfBar.js +++ b/lib/components/RunTimePerfBar.js @@ -1,52 +1,74 @@ 'use babel'; +import path from 'path'; import React from 'react'; import PerfBar from './generic/PerfBar'; -import PerfHint from './PerfHint'; import UsageHighlight from './UsageHighlight'; -import SourceMarker from '../editor/marker'; -import PerfHintState from '../models/PerfHintState'; -import INNPVStore from '../stores/innpv_store'; -export default class RunTimePerfBar extends React.Component { +import Events from '../telemetry/events'; +import TelemetryClientContext from '../telemetry/react_context'; + +class RunTimePerfBar extends React.Component { constructor(props) { super(props); - + this._onClick = this._onClick.bind(this); this._renderPerfHints = this._renderPerfHints.bind(this); } _generateTooltipHTML() { - const {operationInfo, percentage} = this.props; - return `${operationInfo.getOpName()} (${operationInfo.getBoundName()})
` + - `Run Time: ${operationInfo.getRuntimeUs().toFixed(2)} us
` + - `Weight: ${percentage.toFixed(2)}%`; + const {runTimeEntry, overallPct} = this.props; + return `${runTimeEntry.name}
` + + `${runTimeEntry.runTimeMs.toFixed(2)} ms
` + + `${overallPct.toFixed(2)}%`; } _renderPerfHints(isActive, perfHintState) { - const {operationInfo} = this.props; - - return [ - ...operationInfo.getHintsList().map(perfHint => - - ), - ...operationInfo.getUsagesList().map(location => - - ), - ]; + const {editors, runTimeEntry} = this.props; + + return editors.map(editor => ( + + )); } - render() { - const {operationInfo, ...rest} = this.props; - const resizable = operationInfo.getHintsList().length !== 0 + _onClick() { + const {runTimeEntry, projectRoot} = this.props; + if (runTimeEntry.filePath == null || projectRoot == null) { + return; + } + + // Atom uses 0-based line numbers, but INNPV uses 1-based line numbers + const absoluteFilePath = path.join(projectRoot, runTimeEntry.filePath); + atom.workspace.open(absoluteFilePath, {initialLine: runTimeEntry.lineNumber - 1}); + this.context.record( + Events.Interaction.CLICKED_RUN_TIME_ENTRY, + {label: runTimeEntry.name}, + ); + } + render() { + const {runTimeEntry, editors, ...rest} = this.props; return ( ); } } + +RunTimePerfBar.defaultProps = { + editors: [], +}; + +RunTimePerfBar.contextType = TelemetryClientContext; + +export default RunTimePerfBar; diff --git a/lib/components/Throughput.js b/lib/components/Throughput.js index feaed78..89bac45 100644 --- a/lib/components/Throughput.js +++ b/lib/components/Throughput.js @@ -1,16 +1,15 @@ 'use babel'; import React from 'react'; +import {connect} from 'react-redux'; import Subheader from './Subheader'; import BarSlider from './BarSlider'; import NumericDisplay from './NumericDisplay'; -import BatchSizeStore from '../stores/batchsize_store'; -import INNPVStore from '../stores/innpv_store'; import PerfVisState from '../models/PerfVisState'; import {toPercentage} from '../utils'; -export default class Throughput extends React.Component { +class Throughput extends React.Component { constructor(props) { super(props); this._handleResize = this._handleResize.bind(this); @@ -18,10 +17,6 @@ export default class Throughput extends React.Component { _handleResize(deltaPct, basePct) { // TODO: Add in throughput predictions again - return; - - BatchSizeStore.updateThroughput(deltaPct, basePct); - INNPVStore.setPerfVisState(PerfVisState.SHOWING_PREDICTIONS); } _getPercentage() { @@ -76,3 +71,10 @@ export default class Throughput extends React.Component { ); } } + +const mapStateToProps = (state, ownProps) => ({ + model: state.throughput, + ...ownProps, +}); + +export default connect(mapStateToProps)(Throughput); diff --git a/lib/components/UsageHighlight.js b/lib/components/UsageHighlight.js index 06d32d4..b94411e 100644 --- a/lib/components/UsageHighlight.js +++ b/lib/components/UsageHighlight.js @@ -3,7 +3,6 @@ import React from 'react' import SourceMarker from '../editor/marker'; -import INNPVStore from '../stores/innpv_store'; class UsageHighlight extends React.Component { componentDidMount() { diff --git a/lib/components/generic/PerfBarContainer.js b/lib/components/generic/PerfBarContainer.js index 9b4b166..8d5b747 100644 --- a/lib/components/generic/PerfBarContainer.js +++ b/lib/components/generic/PerfBarContainer.js @@ -3,6 +3,26 @@ import React from 'react'; class PerfBarContainer extends React.Component { + constructor(props) { + super(props); + this.state = { + expanded: null, + }; + this._onLabelClick = this._onLabelClick.bind(this); + } + + _onLabelClick(label) { + const {expanded} = this.state; + const {labels} = this.props; + + if (expanded == null && + labels.some((labelInfo) => labelInfo.clickable && labelInfo.label === label)) { + this.setState({expanded: label}); + } else if (expanded === label) { + this.setState({expanded: null}); + } + } + _classes() { const mainClass = 'innpv-perfbarcontainer-wrap'; if (this.props.disabled) { @@ -12,7 +32,8 @@ class PerfBarContainer extends React.Component { } render() { - const {children, marginTop, labels, onLabelClick} = this.props; + const {renderPerfBars, marginTop, labels} = this.props; + const {expanded} = this.state; return (
@@ -20,10 +41,14 @@ class PerfBarContainer extends React.Component { className="innpv-perfbarcontainer-inner" style={{marginTop: `-${marginTop}px`}} > - {children} + {renderPerfBars(expanded)}
- +
); } @@ -33,7 +58,7 @@ PerfBarContainer.defaultProps = { disabled: false, labels: [], marginTop: 0, - onLabelClick: () => {}, + renderPerfBars: (expanded) => null, }; function LabelContainer(props) { @@ -43,18 +68,28 @@ function LabelContainer(props) { return (
- {props.labels.filter(({percentage}) => percentage > 0).map(({label, percentage, clickable}) => ( -
percentage > 0).map(({label, percentage, clickable}) => { + let displayPct = percentage; + if (props.expanded != null) { + if (label === props.expanded) { + displayPct = 100; + } else { + displayPct = 0.001; } - key={label} - style={{height: `${percentage}%`}} - onClick={() => props.onLabelClick(label)} - > -
{percentage >= 5 ? label : null}
-
- ))} + } + return ( +
props.onLabelClick(label)} + > +
{displayPct >= 5 ? label : null}
+
+ ); + })}
); } diff --git a/lib/config/manager.js b/lib/config/manager.js new file mode 100644 index 0000000..f1bbe74 --- /dev/null +++ b/lib/config/manager.js @@ -0,0 +1,44 @@ +'use babel'; + +import {CompositeDisposable} from 'atom'; +import configSchema from './schema'; +import Logger from '../logger'; + +import ConfigActions from '../redux/actions/config'; + +// The purpose of this class is to subscribe to our configuration schemas +// through Atom's configuration API and to update our store with the latest +// values. This helps reduce the coupling between our components and the +// Atom APIs. +export default class ConfigManager { + constructor(store) { + this._store = store; + this._subscriptions = this._subscribeToConfigs(); + } + + dispose() { + this._subscriptions.dispose(); + this._store = null; + } + + _subscribeToConfigs() { + const subscriptions = new CompositeDisposable(); + for (const key in configSchema) { + if (!Object.prototype.hasOwnProperty.call(configSchema, key)) { + continue; + } + subscriptions.add(atom.config.observe( + `skyline.${key}`, this._handleConfigChange.bind(this, key))); + } + return subscriptions; + } + + _handleConfigChange(key, value) { + if (this._store == null) { + return; + } + this._store.dispatch(ConfigActions.configChanged({ + [key]: value, + })); + } +}; diff --git a/lib/config/schema.js b/lib/config/schema.js new file mode 100644 index 0000000..606ddd5 --- /dev/null +++ b/lib/config/schema.js @@ -0,0 +1,16 @@ +'use babel'; + +// Configuration schemas need to follow the specifications in the +// Atom Config documentation. A default value is **required** +// because we use it to populate our initial configuration state. +// +// https://flight-manual.atom.io/api/v1.43.0/Config/ + +export default { + enableTelemetry: { + title: 'Enable Usage Statistics Reporting', + description: 'Allow usage statistics to be sent to the Skyline team to help improve Skyline.', + type: 'boolean', + default: true, + }, +}; diff --git a/lib/editor/file_tracker.js b/lib/editor/file_tracker.js index 0109f7e..c9fbc53 100644 --- a/lib/editor/file_tracker.js +++ b/lib/editor/file_tracker.js @@ -85,6 +85,10 @@ export default class FileTracker { return this._modifiedEditors.size > 0; } + editorsByFilePath() { + return new Map(this._editorsByFilePath); + } + _onNewEditor(editor) { // Subscribe to relevant events on the TextEditor const subs = new CompositeDisposable(); diff --git a/lib/editor/innpv_file_tracker.js b/lib/editor/innpv_file_tracker.js index 2ef5be5..08e9c51 100644 --- a/lib/editor/innpv_file_tracker.js +++ b/lib/editor/innpv_file_tracker.js @@ -2,83 +2,52 @@ import FileTracker from './file_tracker'; -import INNPVStore from '../stores/innpv_store'; -import AnalysisStore from '../stores/analysis_store'; -import ProjectStore from '../stores/project_store'; import PerfVisState from '../models/PerfVisState'; import Logger from '../logger'; +import AnalysisActions from '../redux/actions/analysis'; +import ProjectActions from '../redux/actions/project'; -// The purpose of the INNPVFileTracker is to bind to the stores -// that we use in INNPV. This allows the FileTracker to remain -// generic (i.e. no dependence on INNPV). +// The purpose of the INNPVFileTracker is to bind to the store +// that we use in Skyline. This allows the FileTracker to remain +// generic (i.e. no dependence on Skyline). export default class INNPVFileTracker { - constructor(projectRoot, messageSender) { + constructor(projectRoot, messageSender, store) { this._messageSender = messageSender; + this._store = store; this._tracker = new FileTracker({ projectRoot, onOpenFilesChange: this._onOpenFilesChange.bind(this), onProjectFileSave: this._onProjectFileSave.bind(this), onProjectModifiedChange: this._onProjectModifiedChange.bind(this), }); - - this._projectConfig = { - getTextEditorsFor: this._tracker.getTextEditorsFor.bind(this._tracker), - getProjectRoot: () => projectRoot, - }; - ProjectStore.receivedProjectConfig(this._projectConfig); } dispose() { this._tracker.dispose(); this._tracker = null; this._messageSender = null; - this._projectConfig = null; + this._store = null; } _onOpenFilesChange() { - ProjectStore.receivedProjectConfig(this._projectConfig); + this._store.dispatch(ProjectActions.editorsChange({ + editorsByPath: this._tracker.editorsByFilePath(), + })); } _onProjectFileSave() { if (this._tracker.isProjectModified() || - INNPVStore.getPerfVisState() === PerfVisState.ANALYZING) { + this._store.getState().perfVisState === PerfVisState.ANALYZING) { return; } - INNPVStore.setPerfVisState(PerfVisState.ANALYZING); + this._store.dispatch(AnalysisActions.request()); this._messageSender.sendAnalysisRequest(); } _onProjectModifiedChange() { - const modified = this._tracker.isProjectModified(); - const perfVisState = INNPVStore.getPerfVisState(); - - if (modified) { - // Project went from unmodified -> modified - switch (perfVisState) { - case PerfVisState.ANALYZING: - case PerfVisState.READY: - case PerfVisState.ERROR: - INNPVStore.setPerfVisState(PerfVisState.MODIFIED); - break; - - case PerfVisState.MODIFIED: - Logger.warn('Warning: PerfVisState.MODIFIED mismatch (unmodified -> modified).'); - break; - - default: - Logger.warn(`Modified change unhandled state: ${perfVisState}`); - break; - } - - } else { - // Project went from modified -> unmodified - if (perfVisState === PerfVisState.MODIFIED) { - INNPVStore.setPerfVisState(PerfVisState.READY); - - } else { - Logger.warn('Warning: PerfVisState.MODIFIED mismatch (modified -> unmodified).'); - } - } + this._store.dispatch(ProjectActions.modifiedChange({ + modified: this._tracker.isProjectModified() + })); } } diff --git a/lib/env.json b/lib/env.json new file mode 100644 index 0000000..0f381a0 --- /dev/null +++ b/lib/env.json @@ -0,0 +1,3 @@ +{ + "development": true +} diff --git a/lib/io/connection_state.js b/lib/io/connection_state.js deleted file mode 100644 index 89e05b1..0000000 --- a/lib/io/connection_state.js +++ /dev/null @@ -1,54 +0,0 @@ -'use babel'; - -export default class ConnectionState { - constructor() { - this._sequenceNumber = 0; - // The connection only has two states: uninitialized and "ready" (initialized) - // Therefore for simplicity, we use a boolean to represent these states - this._initialized = false; - this._fileTracker = null; - this._initializationTimeout = null; - } - - dispose() { - if (this._fileTracker != null) { - this._fileTracker.dispose(); - this._fileTracker = null; - } - if (this._initializationTimeout != null) { - clearTimeout(this._initializationTimeout); - this._initializationTimeout = null; - } - } - - get initialized() { - return this._initialized; - } - - setInitializationTimeout(fn, timeoutMs = 5000) { - this._initializationTimeout = setTimeout(fn, timeoutMs); - } - - markInitialized(fileTracker) { - this._initialized = true; - this._fileTracker = fileTracker; - - if (this._initializationTimeout != null) { - clearTimeout(this._initializationTimeout); - this._initializationTimeout = null; - } - } - - nextSequenceNumber() { - const number = this._sequenceNumber; - this._sequenceNumber += 1; - return number; - } - - isResponseCurrent(responseSequenceNumber) { - // Since we always increase the sequence number by one, a "current" - // response is one with a sequence number exactly one less than the next - // sequence number to be assigned (this._sequenceNumber). - return responseSequenceNumber === this._sequenceNumber - 1; - } -} diff --git a/lib/io/message_handler.js b/lib/io/message_handler.js index 59dad0f..f6619ae 100644 --- a/lib/io/message_handler.js +++ b/lib/io/message_handler.js @@ -1,21 +1,31 @@ 'use babel'; import pm from '../protocol_gen/innpv_pb'; -import AppState from '../models/AppState'; -import PerfVisState from '../models/PerfVisState'; -import INNPVStore from '../stores/innpv_store'; -import AnalysisStore from '../stores/analysis_store'; + import INNPVFileTracker from '../editor/innpv_file_tracker'; +import AnalysisActions from '../redux/actions/analysis'; +import ConnectionActions from '../redux/actions/connection'; import Logger from '../logger'; +import Events from '../telemetry/events'; export default class MessageHandler { - constructor(messageSender, connectionState) { + constructor({messageSender, connectionStateView, store, disposables, telemetryClient}) { this._messageSender = messageSender; - this._connectionState = connectionState; + this._connectionStateView = connectionStateView; + this._store = store; + this._disposables = disposables; + this._telemetryClient = telemetryClient; + + this._handleInitializeResponse = this._handleInitializeResponse.bind(this); + this._handleProtocolError = this._handleProtocolError.bind(this); + this._handleMemoryUsageResponse = this._handleMemoryUsageResponse.bind(this); + this._handleAnalysisError = this._handleAnalysisError.bind(this); + this._handleThroughputResponse = this._handleThroughputResponse.bind(this); + this._handleRunTimeResponse = this._handleRunTimeResponse.bind(this); } _handleInitializeResponse(message) { - if (this._connectionState.initialized) { + if (this._connectionStateView.isInitialized()) { Logger.warn('Connection already initialized, but received an initialize response.'); return; } @@ -23,51 +33,64 @@ export default class MessageHandler { // TODO: Validate the project root and entry point paths. // We don't (yet) support remote work, so "trusting" the server is fine // for now because we validate the paths on the server. - INNPVStore.clearErrorMessage(); - INNPVStore.setAppState(AppState.CONNECTED); - this._connectionState.markInitialized( - new INNPVFileTracker(message.getServerProjectRoot(), this._messageSender), - ); + const projectRoot = message.getServerProjectRoot(); + + this._store.dispatch(ConnectionActions.initialized({projectRoot})); + this._disposables.add(new INNPVFileTracker(projectRoot, this._messageSender, this._store)); + this._telemetryClient.record(Events.Skyline.CONNECTED); Logger.info('Connected!'); Logger.info('Sending analysis request...'); - INNPVStore.setPerfVisState(PerfVisState.ANALYZING); + this._store.dispatch(AnalysisActions.request()); this._messageSender.sendAnalysisRequest(); } _handleProtocolError(message) { const errorCode = message.getErrorCode(); if (errorCode === pm.ProtocolError.ErrorCode.UNSUPPORTED_PROTOCOL_VERSION) { - Logger.error('The plugin that you are using is out of date. Please update it before retrying.'); + Logger.error( + 'The plugin and/or server that you are using are out of date. ' + + 'Please update them before retrying.', + ); return; } Logger.error( `Received a protocol error with code: ${errorCode}. Please file a bug report.`); + this._telemetryClient.record(Events.Error.PROTOCOL_ERROR); } _handleMemoryUsageResponse(message) { Logger.info('Received memory usage message.'); Logger.info(`Peak usage: ${message.getPeakUsageBytes()} bytes.`); - AnalysisStore.receivedMemoryUsage(message); - INNPVStore.clearErrorMessage(); + this._store.dispatch(AnalysisActions.receivedMemoryAnalysis({ + memoryUsageResponse: message, + })); } _handleAnalysisError(message) { - INNPVStore.setErrorMessage(message.getErrorMessage()); - INNPVStore.setPerfVisState(PerfVisState.ERROR); + this._store.dispatch(AnalysisActions.error({ + errorMessage: message.getErrorMessage(), + })); + this._telemetryClient.record(Events.Error.ANALYSIS_ERROR); } _handleThroughputResponse(message) { Logger.info(`Received throughput message: ${message.getSamplesPerSecond()} samples/s.`); - AnalysisStore.receivedThroughput(message); - INNPVStore.clearErrorMessage(); - if (INNPVStore.getPerfVisState() !== PerfVisState.MODIFIED) { - INNPVStore.setPerfVisState(PerfVisState.READY); - } + this._store.dispatch(AnalysisActions.receivedThroughputAnalysis({ + throughputResponse: message, + })); + this._telemetryClient.record(Events.Skyline.RECEIVED_ANALYSIS); + } + + _handleRunTimeResponse(message) { + Logger.info('Received run time message.'); + this._store.dispatch(AnalysisActions.receivedRunTimeAnalysis({ + runTimeResponse: message, + })); } _handleAfterInitializationMessage(handler, message) { - if (!this._connectionState.initialized) { + if (!this._connectionStateView.isInitialized()) { Logger.warn('Connection not initialized, but received a regular protocol message.'); return; } @@ -78,7 +101,7 @@ export default class MessageHandler { const enclosingMessage = pm.FromServer.deserializeBinary(byteArray); const payloadCase = enclosingMessage.getPayloadCase(); - if (!this._connectionState.isResponseCurrent(enclosingMessage.getSequenceNumber())) { + if (!this._connectionStateView.isResponseCurrent(enclosingMessage.getSequenceNumber())) { // Ignore old responses (e.g., if we make a new analysis request before // the previous one completes). Logger.info('Ignoring stale response with sequence number:', enclosingMessage.getSequenceNumber()); @@ -118,6 +141,13 @@ export default class MessageHandler { enclosingMessage.getThroughput(), ); break; + + case pm.FromServer.PayloadCase.RUN_TIME: + this._handleAfterInitializationMessage( + this._handleRunTimeResponse, + enclosingMessage.getRunTime(), + ); + break; } } } diff --git a/lib/io/message_sender.js b/lib/io/message_sender.js index 9c922a0..eb66c4a 100644 --- a/lib/io/message_sender.js +++ b/lib/io/message_sender.js @@ -1,30 +1,33 @@ 'use babel'; import pm from '../protocol_gen/innpv_pb'; +import Events from '../telemetry/events'; export default class MessageSender { - constructor(connection, connectionState) { + constructor({connection, connectionStateView, telemetryClient}) { this._connection = connection; - this._connectionState = connectionState; + this._connectionStateView = connectionStateView; + this._telemetryClient = telemetryClient; } sendInitializeRequest() { const message = new pm.InitializeRequest(); - // For now, we only have one version of the protocol - message.setProtocolVersion(1); + // Version 1 - v0.1.x + message.setProtocolVersion(2); this._sendMessage(message, 'Initialize'); } sendAnalysisRequest() { const message = new pm.AnalysisRequest(); this._sendMessage(message, 'Analysis'); + this._telemetryClient.record(Events.Skyline.REQUESTED_ANALYSIS); } _sendMessage(message, payloadName) { const enclosingMessage = new pm.FromClient(); enclosingMessage['set' + payloadName](message); enclosingMessage.setSequenceNumber( - this._connectionState.nextSequenceNumber(), + this._connectionStateView.nextSequenceNumber(), ); this._connection.sendBytes(enclosingMessage.serializeBinary()); } diff --git a/lib/logger.js b/lib/logger.js index 45f9269..1cd0bab 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,7 +1,14 @@ 'use babel'; import {Logger, LogLevel} from './logger_impl'; +import env from './env.json'; -const logger = new Logger(LogLevel.WARN); +let logger; + +if (env.development) { + logger = new Logger(LogLevel.DEBUG); +} else { + logger = new Logger(LogLevel.WARN); +} export default logger; diff --git a/lib/models/LegacyThroughput.js b/lib/models/LegacyThroughput.js deleted file mode 100644 index 08cc684..0000000 --- a/lib/models/LegacyThroughput.js +++ /dev/null @@ -1,45 +0,0 @@ -'use babel'; - -export default class Throughput { - constructor(throughput, maxThroughput, throughputLimit) { - this._maxThroughput = maxThroughput; - this._throughput = throughput; - this._throughputLimit = throughputLimit; - } - - get throughput() { - return this._throughput; - } - - get maxThroughput() { - return this._maxThroughput; - } - - get displayPct() { - return this._throughput / this._maxThroughput * 100; - } - - get limitPct() { - return this._throughputLimit / this._maxThroughput * 100; - } - - static fromInfo(infoProtobuf, limitsProtobuf) { - return new Throughput( - infoProtobuf.getThroughput(), - infoProtobuf.getMaxThroughput(), - limitsProtobuf.getThroughputLimit(), - ); - } - - static fromPrediction(infoProtobuf, limitsProtobuf, batchSize) { - const runtimeModel = infoProtobuf.getRuntimeModelMs(); - const predRuntime = runtimeModel.getCoefficient() * batchSize + runtimeModel.getBias(); - // Runtime is in milliseconds, so multiply throughput by 1000 to get units in seconds - const predThroughput = batchSize / predRuntime * 1000; - return new Throughput( - predThroughput, - infoProtobuf.getMaxThroughput(), - limitsProtobuf.getThroughputLimit(), - ); - } -} diff --git a/lib/models/MemoryBreakdown.js b/lib/models/MemoryBreakdown.js index 6ab8519..8b98f23 100644 --- a/lib/models/MemoryBreakdown.js +++ b/lib/models/MemoryBreakdown.js @@ -1,6 +1,5 @@ 'use babel'; -import path from 'path'; import MemoryEntryLabel from './MemoryEntryLabel'; import {processFileReference, toPercentage} from '../utils'; diff --git a/lib/models/RunTimeBreakdown.js b/lib/models/RunTimeBreakdown.js new file mode 100644 index 0000000..719a73b --- /dev/null +++ b/lib/models/RunTimeBreakdown.js @@ -0,0 +1,114 @@ +'use babel'; + +import RunTimeEntryLabel from './RunTimeEntryLabel'; +import {processFileReference, toPercentage} from '../utils'; +import Logger from '../logger'; + +class RunTimeEntry { + constructor({name, runTimeMs, filePath, lineNumber}) { + this._name = name; + this._runTimeMs = runTimeMs; + this._filePath = filePath; + this._lineNumber = lineNumber; + } + + get name() { + return this._name; + } + + get runTimeMs() { + return this._runTimeMs; + } + + get filePath() { + return this._filePath; + } + + get lineNumber() { + return this._lineNumber; + } +} + +class RunTimeBreakdown { + constructor(iterationRunTimeMs, entryMap) { + this._iterationRunTimeMs = iterationRunTimeMs; + this._entryMap = entryMap; + } + + getIterationRunTimeMs() { + return this._iterationRunTimeMs; + } + + getEntriesByLabel(label) { + return this._entryMap[label].entries; + } + + getTotalTimeMsByLabel(label) { + return this._entryMap[label].totalTimeMs; + } + + static fromRunTimeResponse(runTimeResponse) { + const iterationRunTimeMs = runTimeResponse.getIterationRunTimeMs(); + + const forwardEntries = []; + const backwardEntries = []; + + for (const entry of runTimeResponse.getRunTimeEntriesList()) { + forwardEntries.push(new RunTimeEntry({ + name: entry.getOperationName(), + runTimeMs: entry.getForwardMs(), + ...processFileReference(entry.getContext()), + })); + + if (isNaN(entry.getBackwardMs())) { + continue; + } + + backwardEntries.push(new RunTimeEntry({ + name: entry.getOperationName(), + runTimeMs: entry.getBackwardMs(), + ...processFileReference(entry.getContext()), + })); + } + + const sumEntryTimes = (acc, entry) => entry.runTimeMs + acc; + const totalForwardMs = forwardEntries.reduce(sumEntryTimes, 0); + const totalBackwardMs = backwardEntries.reduce(sumEntryTimes, 0); + + const entryMap = { + [RunTimeEntryLabel.Forward]: { + entries: forwardEntries, + totalTimeMs: totalForwardMs, + }, + [RunTimeEntryLabel.Backward]: { + entries: backwardEntries, + totalTimeMs: totalBackwardMs, + }, + }; + + const remainingTimeMs = iterationRunTimeMs - totalForwardMs - totalBackwardMs; + let totalIterationMs = iterationRunTimeMs; + if (remainingTimeMs > 0) { + entryMap[RunTimeEntryLabel.Other] = { + entries: [new RunTimeEntry({ + name: 'Other', + runTimeMs: remainingTimeMs, + filePath: null, + lineNumber: null, + })], + totalTimeMs: remainingTimeMs, + }; + } else { + Logger.info('The total measured iteration run time is less than the sum of the run time entries.'); + totalIterationMs = totalForwardMs + totalBackwardMs; + entryMap[RunTimeEntryLabel.Other] = { + entries: [], + totalTimeMs: 0., + }; + } + + return new RunTimeBreakdown(totalIterationMs, entryMap); + } +} + +export default RunTimeBreakdown; diff --git a/lib/models/RunTimeEntryLabel.js b/lib/models/RunTimeEntryLabel.js new file mode 100644 index 0000000..cd50fd9 --- /dev/null +++ b/lib/models/RunTimeEntryLabel.js @@ -0,0 +1,7 @@ +'use babel'; + +export default { + Forward: 'Forward', + Backward: 'Backward', + Other: 'Other', +}; diff --git a/lib/protocol_gen/innpv_pb.js b/lib/protocol_gen/innpv_pb.js index 32dba5b..f989f0d 100644 --- a/lib/protocol_gen/innpv_pb.js +++ b/lib/protocol_gen/innpv_pb.js @@ -21,6 +21,8 @@ goog.exportSymbol('proto.innpv.protocol.MemoryUsageResponse', null, global); goog.exportSymbol('proto.innpv.protocol.Path', null, global); goog.exportSymbol('proto.innpv.protocol.ProtocolError', null, global); goog.exportSymbol('proto.innpv.protocol.ProtocolError.ErrorCode', null, global); +goog.exportSymbol('proto.innpv.protocol.RunTimeEntry', null, global); +goog.exportSymbol('proto.innpv.protocol.RunTimeResponse', null, global); goog.exportSymbol('proto.innpv.protocol.ThroughputResponse', null, global); goog.exportSymbol('proto.innpv.protocol.WeightEntry', null, global); @@ -642,7 +644,7 @@ if (goog.DEBUG && !COMPILED) { * @private {!Array>} * @const */ -proto.innpv.protocol.FromServer.oneofGroups_ = [[2,3,4,5,6]]; +proto.innpv.protocol.FromServer.oneofGroups_ = [[2,3,4,5,6,7]]; /** * @enum {number} @@ -653,7 +655,8 @@ proto.innpv.protocol.FromServer.PayloadCase = { INITIALIZE: 3, MEMORY_USAGE: 4, ANALYSIS_ERROR: 5, - THROUGHPUT: 6 + THROUGHPUT: 6, + RUN_TIME: 7 }; /** @@ -696,7 +699,8 @@ proto.innpv.protocol.FromServer.toObject = function(includeInstance, msg) { initialize: (f = msg.getInitialize()) && proto.innpv.protocol.InitializeResponse.toObject(includeInstance, f), memoryUsage: (f = msg.getMemoryUsage()) && proto.innpv.protocol.MemoryUsageResponse.toObject(includeInstance, f), analysisError: (f = msg.getAnalysisError()) && proto.innpv.protocol.AnalysisError.toObject(includeInstance, f), - throughput: (f = msg.getThroughput()) && proto.innpv.protocol.ThroughputResponse.toObject(includeInstance, f) + throughput: (f = msg.getThroughput()) && proto.innpv.protocol.ThroughputResponse.toObject(includeInstance, f), + runTime: (f = msg.getRunTime()) && proto.innpv.protocol.RunTimeResponse.toObject(includeInstance, f) }; if (includeInstance) { @@ -762,6 +766,11 @@ proto.innpv.protocol.FromServer.deserializeBinaryFromReader = function(msg, read reader.readMessage(value,proto.innpv.protocol.ThroughputResponse.deserializeBinaryFromReader); msg.setThroughput(value); break; + case 7: + var value = new proto.innpv.protocol.RunTimeResponse; + reader.readMessage(value,proto.innpv.protocol.RunTimeResponse.deserializeBinaryFromReader); + msg.setRunTime(value); + break; default: reader.skipField(); break; @@ -847,6 +856,14 @@ proto.innpv.protocol.FromServer.prototype.serializeBinaryToWriter = function (wr proto.innpv.protocol.ThroughputResponse.serializeBinaryToWriter ); } + f = this.getRunTime(); + if (f != null) { + writer.writeMessage( + 7, + f, + proto.innpv.protocol.RunTimeResponse.serializeBinaryToWriter + ); + } }; @@ -1024,6 +1041,36 @@ proto.innpv.protocol.FromServer.prototype.hasThroughput = function() { }; +/** + * optional RunTimeResponse run_time = 7; + * @return {proto.innpv.protocol.RunTimeResponse} + */ +proto.innpv.protocol.FromServer.prototype.getRunTime = function() { + return /** @type{proto.innpv.protocol.RunTimeResponse} */ ( + jspb.Message.getWrapperField(this, proto.innpv.protocol.RunTimeResponse, 7)); +}; + + +/** @param {proto.innpv.protocol.RunTimeResponse|undefined} value */ +proto.innpv.protocol.FromServer.prototype.setRunTime = function(value) { + jspb.Message.setOneofWrapperField(this, 7, proto.innpv.protocol.FromServer.oneofGroups_[0], value); +}; + + +proto.innpv.protocol.FromServer.prototype.clearRunTime = function() { + this.setRunTime(undefined); +}; + + +/** + * Returns whether this field is set. + * @return{!boolean} + */ +proto.innpv.protocol.FromServer.prototype.hasRunTime = function() { + return jspb.Message.getField(this, 7) != null; +}; + + /** * Generated by JsPbCodeGenerator. @@ -1499,6 +1546,211 @@ proto.innpv.protocol.MemoryUsageResponse.prototype.clearActivationEntriesList = +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.innpv.protocol.RunTimeResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.innpv.protocol.RunTimeResponse.repeatedFields_, null); +}; +goog.inherits(proto.innpv.protocol.RunTimeResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.innpv.protocol.RunTimeResponse.displayName = 'proto.innpv.protocol.RunTimeResponse'; +} +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.innpv.protocol.RunTimeResponse.repeatedFields_ = [2]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.innpv.protocol.RunTimeResponse.prototype.toObject = function(opt_includeInstance) { + return proto.innpv.protocol.RunTimeResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.innpv.protocol.RunTimeResponse} msg The msg instance to transform. + * @return {!Object} + */ +proto.innpv.protocol.RunTimeResponse.toObject = function(includeInstance, msg) { + var f, obj = { + iterationRunTimeMs: msg.getIterationRunTimeMs(), + runTimeEntriesList: jspb.Message.toObjectList(msg.getRunTimeEntriesList(), + proto.innpv.protocol.RunTimeEntry.toObject, includeInstance) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.innpv.protocol.RunTimeResponse} + */ +proto.innpv.protocol.RunTimeResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.innpv.protocol.RunTimeResponse; + return proto.innpv.protocol.RunTimeResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.innpv.protocol.RunTimeResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.innpv.protocol.RunTimeResponse} + */ +proto.innpv.protocol.RunTimeResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readFloat()); + msg.setIterationRunTimeMs(value); + break; + case 2: + var value = new proto.innpv.protocol.RunTimeEntry; + reader.readMessage(value,proto.innpv.protocol.RunTimeEntry.deserializeBinaryFromReader); + msg.getRunTimeEntriesList().push(value); + msg.setRunTimeEntriesList(msg.getRunTimeEntriesList()); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Class method variant: serializes the given message to binary data + * (in protobuf wire format), writing to the given BinaryWriter. + * @param {!proto.innpv.protocol.RunTimeResponse} message + * @param {!jspb.BinaryWriter} writer + */ +proto.innpv.protocol.RunTimeResponse.serializeBinaryToWriter = function(message, writer) { + message.serializeBinaryToWriter(writer); +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.innpv.protocol.RunTimeResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + this.serializeBinaryToWriter(writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the message to binary data (in protobuf wire format), + * writing to the given BinaryWriter. + * @param {!jspb.BinaryWriter} writer + */ +proto.innpv.protocol.RunTimeResponse.prototype.serializeBinaryToWriter = function (writer) { + var f = undefined; + f = this.getIterationRunTimeMs(); + if (f !== 0.0) { + writer.writeFloat( + 1, + f + ); + } + f = this.getRunTimeEntriesList(); + if (f.length > 0) { + writer.writeRepeatedMessage( + 2, + f, + proto.innpv.protocol.RunTimeEntry.serializeBinaryToWriter + ); + } +}; + + +/** + * Creates a deep clone of this proto. No data is shared with the original. + * @return {!proto.innpv.protocol.RunTimeResponse} The clone. + */ +proto.innpv.protocol.RunTimeResponse.prototype.cloneMessage = function() { + return /** @type {!proto.innpv.protocol.RunTimeResponse} */ (jspb.Message.cloneMessage(this)); +}; + + +/** + * optional float iteration_run_time_ms = 1; + * @return {number} + */ +proto.innpv.protocol.RunTimeResponse.prototype.getIterationRunTimeMs = function() { + return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0)); +}; + + +/** @param {number} value */ +proto.innpv.protocol.RunTimeResponse.prototype.setIterationRunTimeMs = function(value) { + jspb.Message.setField(this, 1, value); +}; + + +/** + * repeated RunTimeEntry run_time_entries = 2; + * If you change this array by adding, removing or replacing elements, or if you + * replace the array itself, then you must call the setter to update it. + * @return {!Array.} + */ +proto.innpv.protocol.RunTimeResponse.prototype.getRunTimeEntriesList = function() { + return /** @type{!Array.} */ ( + jspb.Message.getRepeatedWrapperField(this, proto.innpv.protocol.RunTimeEntry, 2)); +}; + + +/** @param {Array.} value */ +proto.innpv.protocol.RunTimeResponse.prototype.setRunTimeEntriesList = function(value) { + jspb.Message.setRepeatedWrapperField(this, 2, value); +}; + + +proto.innpv.protocol.RunTimeResponse.prototype.clearRunTimeEntriesList = function() { + this.setRunTimeEntriesList([]); +}; + + + /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -2876,4 +3128,261 @@ proto.innpv.protocol.WeightEntry.prototype.hasContext = function() { }; + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.innpv.protocol.RunTimeEntry = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.innpv.protocol.RunTimeEntry, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.innpv.protocol.RunTimeEntry.displayName = 'proto.innpv.protocol.RunTimeEntry'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.innpv.protocol.RunTimeEntry.prototype.toObject = function(opt_includeInstance) { + return proto.innpv.protocol.RunTimeEntry.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.innpv.protocol.RunTimeEntry} msg The msg instance to transform. + * @return {!Object} + */ +proto.innpv.protocol.RunTimeEntry.toObject = function(includeInstance, msg) { + var f, obj = { + operationName: msg.getOperationName(), + forwardMs: msg.getForwardMs(), + backwardMs: msg.getBackwardMs(), + context: (f = msg.getContext()) && proto.innpv.protocol.FileReference.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.innpv.protocol.RunTimeEntry} + */ +proto.innpv.protocol.RunTimeEntry.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.innpv.protocol.RunTimeEntry; + return proto.innpv.protocol.RunTimeEntry.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.innpv.protocol.RunTimeEntry} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.innpv.protocol.RunTimeEntry} + */ +proto.innpv.protocol.RunTimeEntry.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setOperationName(value); + break; + case 2: + var value = /** @type {number} */ (reader.readFloat()); + msg.setForwardMs(value); + break; + case 3: + var value = /** @type {number} */ (reader.readFloat()); + msg.setBackwardMs(value); + break; + case 4: + var value = new proto.innpv.protocol.FileReference; + reader.readMessage(value,proto.innpv.protocol.FileReference.deserializeBinaryFromReader); + msg.setContext(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Class method variant: serializes the given message to binary data + * (in protobuf wire format), writing to the given BinaryWriter. + * @param {!proto.innpv.protocol.RunTimeEntry} message + * @param {!jspb.BinaryWriter} writer + */ +proto.innpv.protocol.RunTimeEntry.serializeBinaryToWriter = function(message, writer) { + message.serializeBinaryToWriter(writer); +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.innpv.protocol.RunTimeEntry.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + this.serializeBinaryToWriter(writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the message to binary data (in protobuf wire format), + * writing to the given BinaryWriter. + * @param {!jspb.BinaryWriter} writer + */ +proto.innpv.protocol.RunTimeEntry.prototype.serializeBinaryToWriter = function (writer) { + var f = undefined; + f = this.getOperationName(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = this.getForwardMs(); + if (f !== 0.0) { + writer.writeFloat( + 2, + f + ); + } + f = this.getBackwardMs(); + if (f !== 0.0) { + writer.writeFloat( + 3, + f + ); + } + f = this.getContext(); + if (f != null) { + writer.writeMessage( + 4, + f, + proto.innpv.protocol.FileReference.serializeBinaryToWriter + ); + } +}; + + +/** + * Creates a deep clone of this proto. No data is shared with the original. + * @return {!proto.innpv.protocol.RunTimeEntry} The clone. + */ +proto.innpv.protocol.RunTimeEntry.prototype.cloneMessage = function() { + return /** @type {!proto.innpv.protocol.RunTimeEntry} */ (jspb.Message.cloneMessage(this)); +}; + + +/** + * optional string operation_name = 1; + * @return {string} + */ +proto.innpv.protocol.RunTimeEntry.prototype.getOperationName = function() { + return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, "")); +}; + + +/** @param {string} value */ +proto.innpv.protocol.RunTimeEntry.prototype.setOperationName = function(value) { + jspb.Message.setField(this, 1, value); +}; + + +/** + * optional float forward_ms = 2; + * @return {number} + */ +proto.innpv.protocol.RunTimeEntry.prototype.getForwardMs = function() { + return /** @type {number} */ (jspb.Message.getFieldProto3(this, 2, 0)); +}; + + +/** @param {number} value */ +proto.innpv.protocol.RunTimeEntry.prototype.setForwardMs = function(value) { + jspb.Message.setField(this, 2, value); +}; + + +/** + * optional float backward_ms = 3; + * @return {number} + */ +proto.innpv.protocol.RunTimeEntry.prototype.getBackwardMs = function() { + return /** @type {number} */ (jspb.Message.getFieldProto3(this, 3, 0)); +}; + + +/** @param {number} value */ +proto.innpv.protocol.RunTimeEntry.prototype.setBackwardMs = function(value) { + jspb.Message.setField(this, 3, value); +}; + + +/** + * optional FileReference context = 4; + * @return {proto.innpv.protocol.FileReference} + */ +proto.innpv.protocol.RunTimeEntry.prototype.getContext = function() { + return /** @type{proto.innpv.protocol.FileReference} */ ( + jspb.Message.getWrapperField(this, proto.innpv.protocol.FileReference, 4)); +}; + + +/** @param {proto.innpv.protocol.FileReference|undefined} value */ +proto.innpv.protocol.RunTimeEntry.prototype.setContext = function(value) { + jspb.Message.setWrapperField(this, 4, value); +}; + + +proto.innpv.protocol.RunTimeEntry.prototype.clearContext = function() { + this.setContext(undefined); +}; + + +/** + * Returns whether this field is set. + * @return{!boolean} + */ +proto.innpv.protocol.RunTimeEntry.prototype.hasContext = function() { + return jspb.Message.getField(this, 4) != null; +}; + + goog.object.extend(exports, proto.innpv.protocol); diff --git a/lib/redux/actions/analysis.js b/lib/redux/actions/analysis.js new file mode 100644 index 0000000..b80f8b9 --- /dev/null +++ b/lib/redux/actions/analysis.js @@ -0,0 +1,28 @@ +'use babel'; + +import {emptyFor, fromPayloadCreator} from './utils'; +import { + ANALYSIS_REQ, + ANALYSIS_REC_RUN, + ANALYSIS_REC_MEM, + ANALYSIS_REC_THPT, + ANALYSIS_ERROR, +} from './types'; + +export default { + request: emptyFor(ANALYSIS_REQ), + receivedRunTimeAnalysis: fromPayloadCreator( + ANALYSIS_REC_RUN, + ({runTimeResponse}) => ({runTimeResponse}), + ), + receivedMemoryAnalysis: fromPayloadCreator( + ANALYSIS_REC_MEM, + ({memoryUsageResponse}) => ({memoryUsageResponse}), + ), + receivedThroughputAnalysis: fromPayloadCreator( + ANALYSIS_REC_THPT, + ({throughputResponse}) => ({throughputResponse}), + ), + error: fromPayloadCreator(ANALYSIS_ERROR, ({errorMessage}) => ({errorMessage})), +}; + diff --git a/lib/redux/actions/app.js b/lib/redux/actions/app.js new file mode 100644 index 0000000..7cd8e08 --- /dev/null +++ b/lib/redux/actions/app.js @@ -0,0 +1,13 @@ +'use babel'; + +import {emptyFor} from './utils'; +import { + APP_OPENED, + APP_CLOSED, +} from './types'; + +export default { + appOpened: emptyFor(APP_OPENED), + appClosed: emptyFor(APP_CLOSED), +}; + diff --git a/lib/redux/actions/config.js b/lib/redux/actions/config.js new file mode 100644 index 0000000..9db5d30 --- /dev/null +++ b/lib/redux/actions/config.js @@ -0,0 +1,13 @@ +'use babel'; + +import {fromPayloadCreator} from './utils'; +import { + CONFIG_CHANGED, +} from './types'; + +export default { + // The payload contains the new value of the configuration setting + // (the key refers to the configuration key). + configChanged: fromPayloadCreator(CONFIG_CHANGED, (payload) => payload), +}; + diff --git a/lib/redux/actions/connection.js b/lib/redux/actions/connection.js new file mode 100644 index 0000000..b89dc23 --- /dev/null +++ b/lib/redux/actions/connection.js @@ -0,0 +1,20 @@ +'use babel'; + +import {emptyFor, fromPayloadCreator} from './utils'; +import { + CONN_CONNECTING, + CONN_INITIALIZING, + CONN_INITIALIZED, + CONN_ERROR, + CONN_LOST, + CONN_INCR_SEQ, +} from './types'; + +export default { + connecting: emptyFor(CONN_CONNECTING), + initializing: fromPayloadCreator(CONN_INITIALIZING, ({onTimeout}) => ({onTimeout})), + initialized: fromPayloadCreator(CONN_INITIALIZED, ({projectRoot}) => ({projectRoot})), + error: fromPayloadCreator(CONN_ERROR, ({errorMessage}) => ({errorMessage})), + lost: fromPayloadCreator(CONN_LOST, ({errorMessage}) => ({errorMessage})), + incrementSequence: emptyFor(CONN_INCR_SEQ), +}; diff --git a/lib/redux/actions/project.js b/lib/redux/actions/project.js new file mode 100644 index 0000000..fd25ab3 --- /dev/null +++ b/lib/redux/actions/project.js @@ -0,0 +1,13 @@ +'use babel'; + +import {fromPayloadCreator} from './utils'; +import { + PROJECT_MODIFIED_CHANGE, + PROJECT_EDITORS_CHANGE, +} from './types'; + +export default { + modifiedChange: fromPayloadCreator(PROJECT_MODIFIED_CHANGE, ({modified}) => ({modified})), + editorsChange: fromPayloadCreator(PROJECT_EDITORS_CHANGE, ({editorsByPath}) => ({editorsByPath})), +}; + diff --git a/lib/redux/actions/types.js b/lib/redux/actions/types.js new file mode 100644 index 0000000..a9dc8b3 --- /dev/null +++ b/lib/redux/actions/types.js @@ -0,0 +1,82 @@ +'use babel'; + +function generateActionType(namespace, action) { + return namespace + ':' + action; +} + +function generateActionNamespaceChecker(namespace) { + return function(candidateNamespace) { + return candidateNamespace === namespace; + }; +} + +export function getActionNamespace(action) { + return action.type.split(':')[0]; +} + +// ============================================================================ + +// App-related Actions +const APP_NAMESPACE = 'app'; +export const isAppAction = generateActionNamespaceChecker(APP_NAMESPACE); + +// The user "opened" Skyline (i.e. the Skyline sidebar becomes visible) +export const APP_OPENED = generateActionType(APP_NAMESPACE, 'opened'); +// The user "closed" Skyline (i.e. the user closed the Skyline sidebar) +export const APP_CLOSED = generateActionType(APP_NAMESPACE, 'closed'); + +// ============================================================================ + +// Connection-related Actions +const CONN_NAMESPACE = 'conn'; +export const isConnectionAction = generateActionNamespaceChecker(CONN_NAMESPACE); + +// We initiated a connection and we have not heard back from the server yet +export const CONN_CONNECTING = generateActionType(CONN_NAMESPACE, 'connecting'); +// The connection to the server has been established and we are now waiting on initialization +export const CONN_INITIALIZING = generateActionType(CONN_NAMESPACE, 'initializing'); +// The connection to the server has been established and initialized +export const CONN_INITIALIZED = generateActionType(CONN_NAMESPACE, 'initialized'); +// We received an error while connecting to or intializing a connection with the server +export const CONN_ERROR = generateActionType(CONN_NAMESPACE, 'error'); +// We lost our connection to the server (possibly because the server was shut down) +export const CONN_LOST = generateActionType(CONN_NAMESPACE, 'lost'); +// Increment the sequence number tracker in the connection state +export const CONN_INCR_SEQ = generateActionType(CONN_NAMESPACE, 'incr_seq'); + +// ============================================================================ + +// Analysis-related Actions +const ANALYSIS_NAMESPACE = 'analysis'; +export const isAnalysisAction = generateActionNamespaceChecker(ANALYSIS_NAMESPACE); + +// We issued an analysis request +export const ANALYSIS_REQ = generateActionType(ANALYSIS_NAMESPACE, 'req'); +// We received the run time analysis (iteration run time breakdown) +export const ANALYSIS_REC_RUN = generateActionType(ANALYSIS_NAMESPACE, 'rec_run_time'); +// We received the memory analysis +export const ANALYSIS_REC_MEM = generateActionType(ANALYSIS_NAMESPACE, 'rec_memory'); +// We received throughput information +export const ANALYSIS_REC_THPT = generateActionType(ANALYSIS_NAMESPACE, 'rec_thpt'); +// An error occurred during the analysis +export const ANALYSIS_ERROR = generateActionType(ANALYSIS_NAMESPACE, 'error'); + +// ============================================================================ + +// Project-related Actions +const PROJECT_NAMESPACE = 'proj'; +export const isProjectAction = generateActionNamespaceChecker(PROJECT_NAMESPACE); + +// The project's modified status has changed (e.g., unmodified -> modified) +export const PROJECT_MODIFIED_CHANGE = generateActionType(PROJECT_NAMESPACE, 'modified_change'); +// The Atom TextEditors associated with the relevant project files have changed +export const PROJECT_EDITORS_CHANGE = generateActionType(PROJECT_NAMESPACE, 'editors_change'); + +// ============================================================================ + +// Config-related Actions +const CONFIG_NAMESPACE = 'config'; +export const isConfigAction = generateActionNamespaceChecker(CONFIG_NAMESPACE); + +// The user changed a setting +export const CONFIG_CHANGED = generateActionType(CONFIG_NAMESPACE, 'changed'); diff --git a/lib/redux/actions/utils.js b/lib/redux/actions/utils.js new file mode 100644 index 0000000..7fcb68b --- /dev/null +++ b/lib/redux/actions/utils.js @@ -0,0 +1,19 @@ +'use babel'; + +export function emptyFor(actionType) { + return function() { + return { + type: actionType, + payload: {}, + }; + }; +} + +export function fromPayloadCreator(actionType, payloadCreator) { + return function(...args) { + return { + type: actionType, + payload: payloadCreator(...args), + }; + }; +} diff --git a/lib/redux/reducers/analysis.js b/lib/redux/reducers/analysis.js new file mode 100644 index 0000000..586c4d1 --- /dev/null +++ b/lib/redux/reducers/analysis.js @@ -0,0 +1,69 @@ +'use babel'; + +import { + ANALYSIS_REQ, + ANALYSIS_REC_RUN, + ANALYSIS_REC_MEM, + ANALYSIS_REC_THPT, + ANALYSIS_ERROR, +} from '../actions/types'; +import PerfVisState from '../../models/PerfVisState'; +import MemoryBreakdown from '../../models/MemoryBreakdown'; +import MemoryUsage from '../../models/MemoryUsage'; +import Throughput from '../../models/Throughput'; +import RunTimeBreakdown from '../../models/RunTimeBreakdown'; + +export default function(state, action) { + switch (action.type) { + case ANALYSIS_REQ: + return { + ...state, + perfVisState: PerfVisState.ANALYZING, + }; + + case ANALYSIS_REC_RUN: { + const {runTimeResponse} = action.payload; + return { + ...state, + runTimeBreakdown: RunTimeBreakdown.fromRunTimeResponse(runTimeResponse), + errorMessage: '', + }; + } + + case ANALYSIS_REC_MEM: { + const {memoryUsageResponse} = action.payload; + return { + ...state, + memoryBreakdown: + MemoryBreakdown.fromMemoryUsageResponse(memoryUsageResponse), + memoryUsage: + MemoryUsage.fromMemoryUsageResponse(memoryUsageResponse), + errorMessage: '', + }; + } + + case ANALYSIS_REC_THPT: { + const {throughputResponse} = action.payload; + return { + ...state, + throughput: + Throughput.fromThroughputResponse(throughputResponse), + errorMessage: '', + perfVisState: + state.perfVisState !== PerfVisState.MODIFIED + ? PerfVisState.READY + : PerfVisState.MODIFIED, + }; + } + + case ANALYSIS_ERROR: + return { + ...state, + errorMessage: action.payload.errorMessage, + perfVisState: PerfVisState.ERROR, + }; + + default: + return state; + } +}; diff --git a/lib/redux/reducers/app.js b/lib/redux/reducers/app.js new file mode 100644 index 0000000..0c2d69c --- /dev/null +++ b/lib/redux/reducers/app.js @@ -0,0 +1,21 @@ +'use babel'; + +import {APP_OPENED, APP_CLOSED} from '../actions/types'; +import AppState from '../../models/AppState'; +import initialState from './initial_state'; + +export default function(state, action) { + switch (action.type) { + case APP_OPENED: + return { + ...state, + appState: AppState.OPENED, + }; + + case APP_CLOSED: + return initialState; + + default: + return state; + } +}; diff --git a/lib/redux/reducers/config.js b/lib/redux/reducers/config.js new file mode 100644 index 0000000..a575f8f --- /dev/null +++ b/lib/redux/reducers/config.js @@ -0,0 +1,29 @@ +'use babel'; + +import { + getActionNamespace, + isConfigAction, +} from '../actions/types'; + +import configSchema from '../../config/schema'; + +const defaultConfig = (() => { + const config = {}; + for (const key in configSchema) { + if (!Object.prototype.hasOwnProperty.call(configSchema, key)) { + continue; + } + config[key] = configSchema[key].default; + } + return config; +})(); + +export default function(config = defaultConfig, action) { + if (!isConfigAction(getActionNamespace(action))) { + return config; + } + return { + ...config, + ...action.payload, + }; +} diff --git a/lib/redux/reducers/connection.js b/lib/redux/reducers/connection.js new file mode 100644 index 0000000..48d7b32 --- /dev/null +++ b/lib/redux/reducers/connection.js @@ -0,0 +1,66 @@ +'use babel'; + +import AppState from '../../models/AppState'; +import PerfVisState from '../../models/PerfVisState'; +import { + CONN_CONNECTING, + CONN_INITIALIZING, + CONN_INITIALIZED, + CONN_ERROR, + CONN_LOST, + CONN_INCR_SEQ, +} from '../actions/types'; +import initialState from './initial_state'; + +export default function(state, action) { + switch (action.type) { + case CONN_CONNECTING: + return { + ...state, + appState: AppState.CONNECTING, + }; + + case CONN_INITIALIZING: + return { + ...state, + connection: { + ...state.connection, + onTimeout: action.payload.onTimeout, + }, + }; + + case CONN_INITIALIZED: + return { + ...state, + appState: AppState.CONNECTED, + perfVisState: PerfVisState.READY, + errorMessage: '', + connection: { + ...state.connection, + initialized: true, + onTimeout: null, + }, + projectRoot: action.payload.projectRoot, + }; + + case CONN_ERROR: + case CONN_LOST: + return { + ...initialState, + appState: AppState.OPENED, + errorMessage: action.payload.errorMessage, + }; + + case CONN_INCR_SEQ: + return { + ...state, + connection: { + ...state.connection, + sequenceNumber: state.connection.sequenceNumber + 1, + }, + }; + + default: + return state; + } +}; diff --git a/lib/redux/reducers/initial_state.js b/lib/redux/reducers/initial_state.js new file mode 100644 index 0000000..d07aca9 --- /dev/null +++ b/lib/redux/reducers/initial_state.js @@ -0,0 +1,28 @@ +'use babel'; + +import AppState from '../../models/AppState'; +import PerfVisState from '../../models/PerfVisState'; + +export default { + // Global application states + appState: AppState.ACTIVATED, + perfVisState: PerfVisState.READY, + errorMessage: '', + + // Server connection state + connection: { + initialized: false, + sequenceNumber: 0, + onTimeout: null, + }, + + // Project state + projectRoot: null, + editorsByPath: new Map(), + + // Analysis received + memoryBreakdown: null, + memoryUsage: null, + throughput: null, + runTimeBreakdown: null, +}; diff --git a/lib/redux/reducers/project.js b/lib/redux/reducers/project.js new file mode 100644 index 0000000..e773293 --- /dev/null +++ b/lib/redux/reducers/project.js @@ -0,0 +1,46 @@ +'use babel'; + +import { + PROJECT_MODIFIED_CHANGE, + PROJECT_EDITORS_CHANGE, +} from '../actions/types'; +import PerfVisState from '../../models/PerfVisState'; +import Logger from '../../logger'; + +export default function(state, action) { + switch (action.type) { + case PROJECT_MODIFIED_CHANGE: { + const {perfVisState} = state; + const {modified} = action.payload; + if (modified && perfVisState !== PerfVisState.MODIFIED) { + return { + ...state, + perfVisState: PerfVisState.MODIFIED, + }; + + } else if (!modified && perfVisState === PerfVisState.MODIFIED) { + return { + ...state, + perfVisState: PerfVisState.READY, + }; + } else { + Logger.warn( + 'Modified change unexpected case. Modified:', + modified, + 'PerfVisState:', + perfVisState, + ); + return state; + } + } + + case PROJECT_EDITORS_CHANGE: + return { + ...state, + editorsByPath: action.payload.editorsByPath, + }; + + default: + return state; + } +}; diff --git a/lib/redux/reducers/skyline.js b/lib/redux/reducers/skyline.js new file mode 100644 index 0000000..e10eecd --- /dev/null +++ b/lib/redux/reducers/skyline.js @@ -0,0 +1,56 @@ +'use babel'; + +import { + getActionNamespace, + isAppAction, + isConnectionAction, + isAnalysisAction, + isProjectAction, +} from '../actions/types'; + +import analysisReducer from './analysis'; +import appReducer from './app'; +import configReducer from './config'; +import connectionReducer from './connection'; +import projectReducer from './project'; +import initialState from './initial_state'; + +import Logger from '../../logger'; + +function skylineReducer(state = initialState, action) { + const actionNamespace = getActionNamespace(action); + + if (isAppAction(actionNamespace)) { + return appReducer(state, action); + + } else if (isConnectionAction(actionNamespace)) { + return connectionReducer(state, action); + + } else if (isAnalysisAction(actionNamespace)) { + return analysisReducer(state, action); + + } else if (isProjectAction(actionNamespace)) { + return projectReducer(state, action); + + } else { + return state; + } +}; + +export default function(state, action) { + // We don't use combineReducers() here to avoid placing all the Skyline state + // under another object. This way the config remains a "top level" state + // object alongside other Skyline state properties. + Logger.debug('Reducer applying action:', action); + if (state === undefined) { + // Initial state + const newState = skylineReducer(undefined, action); + newState.config = configReducer(undefined, action); + return newState; + } + + const {config, ...rest} = state; + const newState = skylineReducer(rest, action); + newState.config = configReducer(config, action); + return newState; +}; diff --git a/lib/redux/store.js b/lib/redux/store.js new file mode 100644 index 0000000..e14579e --- /dev/null +++ b/lib/redux/store.js @@ -0,0 +1,10 @@ +'use babel'; + +import {createStore} from 'redux'; +import rootReducer from './reducers/skyline'; + +// We want to avoid using the singleton pattern. Therefore +// we export a factory function instead. +export default function() { + return createStore(rootReducer); +} diff --git a/lib/redux/views/connection_state.js b/lib/redux/views/connection_state.js new file mode 100644 index 0000000..a19650d --- /dev/null +++ b/lib/redux/views/connection_state.js @@ -0,0 +1,30 @@ +'use babel'; + +import ConnectionActions from '../actions/connection'; + +export default class ConnectionStateView { + constructor(store) { + this._store = store; + } + + get _connectionState() { + return this._store.getState().connection; + } + + isInitialized() { + return this._connectionState.initialized; + } + + nextSequenceNumber() { + const nextSequenceNumber = this._connectionState.sequenceNumber; + this._store.dispatch(ConnectionActions.incrementSequence()); + return nextSequenceNumber; + } + + isResponseCurrent(responseSequenceNumber) { + // Since we always increase the sequence number by one, a "current" + // response is one with a sequence number exactly one less than the next + // sequence number to be assigned (this._sequenceNumber). + return responseSequenceNumber === this._connectionState.sequenceNumber - 1; + } +} diff --git a/lib/skyline.js b/lib/skyline.js index 5aee06b..995d891 100644 --- a/lib/skyline.js +++ b/lib/skyline.js @@ -1,26 +1,41 @@ 'use babel'; -import { CompositeDisposable } from 'atom'; +import {CompositeDisposable, Disposable} from 'atom'; import SkylinePlugin from './skyline_plugin'; +import configSchema from './config/schema'; export default { _plugin: null, _subscriptions: null, activate() { - this._plugin = new SkylinePlugin(); - - this._subscriptions = new CompositeDisposable(); - this._subscriptions.add(atom.commands.add('atom-workspace', { - 'skyline:open': () => this._plugin.open(), - })); - this._subscriptions.add(atom.commands.add('atom-workspace', { - 'skyline:close': () => this._plugin.close(), - })); + this._subscriptions = new CompositeDisposable( + atom.commands.add('atom-workspace', { + 'skyline:toggle': () => this.toggle(), + }), + new Disposable(this._disposePlugin.bind(this)), + ); }, deactivate() { - this._plugin.close(); this._subscriptions.dispose(); }, + + config: configSchema, + + toggle() { + if (this._plugin == null) { + this._plugin = new SkylinePlugin(); + } else { + this._disposePlugin(); + } + }, + + _disposePlugin() { + if (this._plugin == null) { + return; + } + this._plugin.dispose(); + this._plugin = null; + }, }; diff --git a/lib/skyline_plugin.js b/lib/skyline_plugin.js index 4629284..28d4155 100644 --- a/lib/skyline_plugin.js +++ b/lib/skyline_plugin.js @@ -2,139 +2,128 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import {Provider} from 'react-redux'; import PerfVis from './components/PerfVis'; -import Connection from './io/connection'; -import MessageHandler from './io/message_handler'; -import MessageSender from './io/message_sender'; -import ConnectionState from './io/connection_state'; import AppState from './models/AppState'; -import PerfVisState from './models/PerfVisState'; -import INNPVStore from './stores/innpv_store'; -import BatchSizeStore from './stores/batchsize_store'; -import OperationInfoStore from './stores/operationinfo_store'; -import AnalysisStore from './stores/analysis_store'; -import ProjectStore from './stores/project_store'; +import SkylineSession from './skyline_session'; import Logger from './logger'; +import env from './env.json'; +import ConfigManager from './config/manager'; -// Clear the views if an analysis request is pending for more than -// this many milliseconds. -const CLEAR_VIEW_AFTER_MS = 200; +import Events from './telemetry/events'; +import TelemetryClientContext from './telemetry/react_context'; +import TelemetryClient from './telemetry/client'; + +import AppActions from './redux/actions/app'; +import ConnectionActions from './redux/actions/connection'; +import storeCreator from './redux/store'; +import ConnectionStateView from './redux/views/connection_state'; export default class SkylinePlugin { constructor() { - this._handleMessage = this._handleMessage.bind(this); + this._store = storeCreator(); + this._session = null; + this._telemetryClient = TelemetryClient.from(env.uaId, this._store); + this._configManager = new ConfigManager(this._store); + this._getStartedClicked = this._getStartedClicked.bind(this); this._handleServerClosure = this._handleServerClosure.bind(this); + this._handleInitializationTimeout = this._handleInitializationTimeout.bind(this); + + this._activate(); } - open() { - if (INNPVStore.getAppState() !== AppState.ACTIVATED) { + _activate() { + if (this._store.getState().appState !== AppState.ACTIVATED) { return; } - INNPVStore.setAppState(AppState.OPENED); + this._store.dispatch(AppActions.appOpened()); + this._telemetryClient.record(Events.Skyline.OPENED); this._panel = atom.workspace.addRightPanel({item: document.createElement('div')}); ReactDOM.render( - , + + + + + , this._panel.getItem(), ); } - close() { - if (INNPVStore.getAppState() === AppState.ACTIVATED) { + dispose() { + if (this._store.getState().appState === AppState.ACTIVATED) { return; } - INNPVStore.setAppState(AppState.ACTIVATED); - this._disconnectFromServer(); + this._store.dispatch(AppActions.appClosed()); + this._disposeSession(); ReactDOM.unmountComponentAtNode(this._panel.getItem()); this._panel.destroy(); this._panel = null; - INNPVStore.reset(); - BatchSizeStore.reset(); - OperationInfoStore.reset(); - AnalysisStore.reset(); - ProjectStore.reset(); - } + this._configManager.dispose(); + this._configManager = null; + this._telemetryClient = null; - _connectToServer(host, port) { - this._connection = new Connection(this._handleMessage, this._handleServerClosure); - return this._connection.connect(host, port) - .then(() => { - this._connectionState = new ConnectionState(); - this._messageSender = new MessageSender(this._connection, this._connectionState); - this._messageHandler = new MessageHandler( - this._messageSender, this._connectionState); - }); + // We purposely do not set the store to null since we use + // it to check the application state in this method. } - _disconnectFromServer() { - // 1. Shutdown the connection socket - if (this._connection != null) { - this._connection.close(); - this._connection = null; - } - - // 2. Discard any unneeded connection state - this._messageHandler = null; - this._messageSender = null; - if (this._connectionState != null) { - this._connectionState.dispose(); - this._connectionState = null; + _disposeSession() { + if (this._session == null) { + return; } - + this._session.dispose(); + this._session = null; Logger.info('Disconnected from the server.'); } _getStartedClicked({host, port}) { - if (INNPVStore.getAppState() !== AppState.OPENED) { + if (this._store.getState().appState !== AppState.OPENED) { return; } - INNPVStore.setAppState(AppState.CONNECTING); - this._connectToServer(host, port) - .then(() => { - this._messageSender.sendInitializeRequest(); - this._connectionState.setInitializationTimeout(() => { - this._disconnectFromServer(); - INNPVStore.setErrorMessage( - 'Skyline timed out when establishing a connection with the Skyline server. ' + - 'Please check that the server is running.' - ); - INNPVStore.setAppState(AppState.OPENED); - }); - }) + this._store.dispatch(ConnectionActions.connecting()); + this._session = new SkylineSession({ + store: this._store, + telemetryClient: this._telemetryClient, + handleServerClosure: this._handleServerClosure, + handleInitializationTimeout: this._handleInitializationTimeout, + }); + this._session.connect(host, port) .catch((err) => { - this._connection = null; + let errorMessage = null; if (err.hasOwnProperty('errno') && err.errno === 'ECONNREFUSED') { - INNPVStore.setErrorMessage( + errorMessage = 'Skyline could not connect to the Skyline server. Please check that the server ' + - 'is running and that the connection options are correct.' - ); + 'is running and that the connection options are correct.'; } else { - INNPVStore.setErrorMessage('Unknown error occurred. Please file a bug report!'); + errorMessage = 'Unknown error occurred. Please file a bug report!'; Logger.error(err); } - INNPVStore.setAppState(AppState.OPENED); + + this._store.dispatch(ConnectionActions.error({errorMessage})); + this._telemetryClient.record(Events.Error.CONNECTION_ERROR); + this._disposeSession(); }); } _handleServerClosure() { - this._disconnectFromServer(); - INNPVStore.setAppState(AppState.OPENED); - INNPVStore.setErrorMessage( - 'Skyline has lost its connection to the server. Please check that ' + - 'the server is running before reconnecting.' - ); - BatchSizeStore.reset(); - OperationInfoStore.reset(); - AnalysisStore.reset(); - ProjectStore.reset(); + this._store.dispatch(ConnectionActions.lost({ + errorMessage: 'Skyline has lost its connection to the server. ' + + 'Please check that the server is running before reconnecting.', + })); + this._disposeSession(); } - _handleMessage(message) { - this._messageHandler.handleMessage(message); + _handleInitializationTimeout() { + this._store.dispatch(ConnectionActions.error({ + errorMessage: 'Skyline timed out when establishing a connection ' + + 'with the Skyline server. Please check that the server is running.', + })); + this._telemetryClient.record(Events.Error.CONNECTION_TIMEOUT); + this._disposeSession(); } } diff --git a/lib/skyline_session.js b/lib/skyline_session.js new file mode 100644 index 0000000..d38926e --- /dev/null +++ b/lib/skyline_session.js @@ -0,0 +1,80 @@ +'use babel'; + +import {CompositeDisposable} from 'atom'; + +import Connection from './io/connection'; +import MessageHandler from './io/message_handler'; +import MessageSender from './io/message_sender'; +import ConnectionActions from './redux/actions/connection'; +import ConnectionStateView from './redux/views/connection_state'; + +export default class SkylineSession { + constructor({store, telemetryClient, handleServerClosure, handleInitializationTimeout}) { + this._handleMessage = this._handleMessage.bind(this); + this._invokeTimeout = this._invokeTimeout.bind(this); + this._handleInitializationTimeout = handleInitializationTimeout; + + this._disposed = false; + this._disposables = new CompositeDisposable(); + + this._store = store; + const connectionStateView = new ConnectionStateView(this._store); + this._telemetryClient = telemetryClient; + + this._connection = new Connection(this._handleMessage, handleServerClosure); + this._messageSender = new MessageSender({ + connection: this._connection, + connectionStateView, + telemetryClient: this._telemetryClient, + }); + this._messageHandler = new MessageHandler({ + messageSender: this._messageSender, + connectionStateView, + store: this._store, + disposables: this._disposables, + telemetryClient: this._telemetryClient, + }); + } + + connect(host, port, timeoutMs = 5000) { + return this._connection.connect(host, port) + .then(() => { + this._store.dispatch(ConnectionActions.initializing({ + onTimeout: this._handleInitializationTimeout, + })); + this._messageSender.sendInitializeRequest(); + setTimeout(this._invokeTimeout, timeoutMs); + }); + } + + dispose() { + if (this._disposed) { + return; + } + this._disposables.dispose(); + this._disposables = null; + this._connection.close(); + this._connection = null; + this._store = null; + this._messageSender = null; + this._messageHandler = null; + this._disposed = true; + } + + _handleMessage(message) { + this._messageHandler.handleMessage(message); + } + + _invokeTimeout() { + if (this._store == null) { + return; + } + + const {onTimeout} = this._store.getState().connection; + if (onTimeout == null) { + return; + } + + onTimeout(); + } +} diff --git a/lib/stores/analysis_store.js b/lib/stores/analysis_store.js deleted file mode 100644 index 7966eb2..0000000 --- a/lib/stores/analysis_store.js +++ /dev/null @@ -1,49 +0,0 @@ -'use babel'; - -import BaseStore from './base_store'; - -import MemoryBreakdown from '../models/MemoryBreakdown'; -import MemoryUsage from '../models/MemoryUsage'; -import Throughput from '../models/Throughput'; - -class AnalysisStore extends BaseStore { - constructor() { - super(); - } - - reset() { - this._memoryBreakdown = null; - this._overallMemoryUsage = null; - this._throughput = null; - } - - receivedMemoryUsage(memoryUsageResponse) { - this._memoryBreakdown = MemoryBreakdown.fromMemoryUsageResponse(memoryUsageResponse); - this._overallMemoryUsage = MemoryUsage.fromMemoryUsageResponse(memoryUsageResponse); - this.notifyChanged(); - } - - receivedThroughput(throughputResponse) { - this._throughput = Throughput.fromThroughputResponse(throughputResponse); - this.notifyChanged(); - } - - getMemoryBreakdown() { - // This data is used by the MemoryBreakdown component - return this._memoryBreakdown; - } - - getOverallMemoryUsage() { - // This data is used by the Memory component - return this._overallMemoryUsage; - } - - getThroughput() { - // This data is used by the Throughput component - return this._throughput; - } -} - -const storeInstance = new AnalysisStore(); - -export default storeInstance; diff --git a/lib/stores/base_store.js b/lib/stores/base_store.js deleted file mode 100644 index 2dcef4f..0000000 --- a/lib/stores/base_store.js +++ /dev/null @@ -1,28 +0,0 @@ -'use babel'; - -import EventEmitter from 'events'; - -const UPDATE_EVENT = 'updated'; - -export default class BaseStore { - constructor() { - this._emitter = new EventEmitter(); - this.reset(); - } - - reset() { - // Implemented by child classes. Used to initialize/reset the store's state. - } - - addListener(callback) { - this._emitter.on(UPDATE_EVENT, callback); - } - - removeListener(callback) { - this._emitter.removeListener(UPDATE_EVENT, callback); - } - - notifyChanged() { - this._emitter.emit(UPDATE_EVENT); - } -} diff --git a/lib/stores/batchsize_store.js b/lib/stores/batchsize_store.js deleted file mode 100644 index 3f84d9d..0000000 --- a/lib/stores/batchsize_store.js +++ /dev/null @@ -1,175 +0,0 @@ -'use babel'; - -import BaseStore from './base_store'; -import Throughput from '../models/LegacyThroughput'; -import Memory from '../models/Memory'; -import { - evaluateLinearModel, - getBatchSizeFromUsage, - getBatchSizeFromThroughput, -} from '../utils'; -import INNPVStore from './innpv_store'; -import {Range} from 'atom'; - -class BatchSizeStore extends BaseStore { - constructor() { - super(); - } - - reset() { - this._throughputInfo = null; - this._memoryInfo = null; - this._inputInfo = null; - this._perfLimits = null; - - this._predictedBatchSize = null; - this._maxBatchSize = null; - - this._currentAnnotationRange = null; - - this._clearViewDebounce = null; - } - - receivedAnalysis(throughputInfo, memoryInfo, inputInfo, perfLimits) { - this._cancelClearView(); - this._throughputInfo = throughputInfo; - this._memoryInfo = memoryInfo; - this._inputInfo = inputInfo; - this._perfLimits = perfLimits; - - this._updateComputedState(); - this.notifyChanged(); - } - - receivedMemoryResponse(memoryInfo, inputInfo) { - this._cancelClearView(); - this._memoryInfo = memoryInfo; - this._inputInfo = inputInfo; - this.notifyChanged(); - } - - receivedThroughputResponse(throughputInfo, perfLimits) { - this._cancelClearView(); - this._throughputInfo = throughputInfo; - this._perfLimits = perfLimits; - this._updateComputedState(); - this.notifyChanged(); - } - - _updateComputedState() { - this._predictedBatchSize = null; - this._maxBatchSize = this._perfLimits.getMaxBatchSize(); - - const startPoint = this._inputInfo.getAnnotationStart(); - const endPoint = this._inputInfo.getAnnotationEnd(); - this._currentAnnotationRange = new Range( - [startPoint.getLine(), startPoint.getColumn()], - [endPoint.getLine(), endPoint.getColumn()], - ); - } - - updateMemoryUsage(deltaPct, basePct) { - // Map the delta to a usage value - // NOTE: We clamp the values (upper bound for usage, lower bound for batch size) - const updatedPct = basePct + deltaPct; - const updatedUsage = Math.min( - updatedPct / 100 * this._memoryInfo.getMaxCapacityMb(), - this._memoryInfo.getMaxCapacityMb(), - ); - this._predictedBatchSize = Math.min(Math.max( - getBatchSizeFromUsage(this._memoryInfo.getUsageModelMb(), updatedUsage), - 1, - ), this._maxBatchSize); - - this._updateAnnotationInBuffer(); - this.notifyChanged(); - } - - updateThroughput(deltaPct, basePct) { - // Map the delta to a throughput value - // NOTE: We clamp the values (upper bound for throughput, lower bound for batch size) - const updatedPct = basePct + deltaPct; - const updatedThroughput = Math.max(Math.min( - updatedPct / 100 * this._throughputInfo.getMaxThroughput(), - this._perfLimits.getThroughputLimit(), - ), 0); - const throughputBatchSize = getBatchSizeFromThroughput( - this._throughputInfo.getRuntimeModelMs(), - updatedThroughput, - ); - - if (throughputBatchSize < 0) { - // NOTE: The throughput batch size may be so large that it overflows - this._predictedBatchSize = this._maxBatchSize; - } else { - this._predictedBatchSize = Math.max(Math.min(throughputBatchSize, this._maxBatchSize), 1); - } - - this._updateAnnotationInBuffer(); - this.notifyChanged(); - } - - _updateAnnotationInBuffer() { - // TODO: Re-implement this function so that it handles - // cases where there are multiple text editors. - } - - _cancelClearView() { - if (this._clearViewDebounce == null) { - return; - } - clearTimeout(this._clearViewDebounce); - this._clearViewDebounce = null; - } - - setClearViewDebounce(clearViewDebounce) { - this._clearViewDebounce = clearViewDebounce; - } - - _getAnnotationString() { - const inputSizeTuple = this._inputInfo.getInputSize().getValuesList(); - const inputSizeCopy = inputSizeTuple.map(x => x); - if (this._predictedBatchSize != null) { - inputSizeCopy[0] = Math.round(this._predictedBatchSize); - } - return `@innpv size (${inputSizeCopy.join(', ')})`; - } - - clearPredictions() { - this._predictedBatchSize = null; - this._updateAnnotationInBuffer(); - this.notifyChanged(); - } - - getThroughputModel() { - if (this._throughputInfo == null || this._perfLimits == null) { - return null; - } - - if (this._predictedBatchSize == null) { - return Throughput.fromInfo(this._throughputInfo, this._perfLimits); - } else { - return Throughput.fromPrediction(this._throughputInfo, this._perfLimits, this._predictedBatchSize); - } - } - - getMemoryModel() { - if (this._memoryInfo == null) { - return null; - } - - if (this._predictedBatchSize == null) { - return Memory.fromInfo(this._memoryInfo); - } else { - return Memory.fromPrediction(this._memoryInfo, this._predictedBatchSize); - } - } - - getInputInfo() { - return this._inputInfo; - } -} - -const storeInstance = new BatchSizeStore(); - -export default storeInstance; diff --git a/lib/stores/innpv_store.js b/lib/stores/innpv_store.js deleted file mode 100644 index f0d6911..0000000 --- a/lib/stores/innpv_store.js +++ /dev/null @@ -1,58 +0,0 @@ -'use babel'; - -import BaseStore from './base_store'; -import AppState from '../models/AppState'; -import PerfVisState from '../models/PerfVisState'; - -class INNPVStore extends BaseStore { - constructor() { - super(); - } - - reset() { - this._appState = AppState.ACTIVATED; - this._perfVisState = PerfVisState.READY; - this._errorMessage = ''; - } - - getAppState() { - return this._appState; - } - - setAppState(state) { - this._appState = state; - this.notifyChanged(); - } - - getPerfVisState() { - return this._perfVisState; - } - - setPerfVisState(state) { - if (this._perfVisState === state) { - return; - } - this._perfVisState = state; - this.notifyChanged(); - } - - getErrorMessage() { - return this._errorMessage; - } - - setErrorMessage(message) { - if (this._errorMessage === message) { - return; - } - this._errorMessage = message; - this.notifyChanged(); - } - - clearErrorMessage() { - this.setErrorMessage(''); - } -} - -const storeInstance = new INNPVStore(); - -export default storeInstance; diff --git a/lib/stores/operationinfo_store.js b/lib/stores/operationinfo_store.js deleted file mode 100644 index 1f3915e..0000000 --- a/lib/stores/operationinfo_store.js +++ /dev/null @@ -1,38 +0,0 @@ -'use babel'; - -import BaseStore from './base_store'; - -class OperationInfoStore extends BaseStore { - constructor() { - super(); - } - - reset() { - this._operationInfos = []; - this._clearViewDebounce = null; - } - - getOperationInfos() { - return this._operationInfos; - } - - setOperationInfos(operationInfos) { - // Prevent the view from being cleared if we receive - // results really quickly. - if (this._clearViewDebounce != null) { - clearTimeout(this._clearViewDebounce); - this._clearViewDebounce = null; - } - - this._operationInfos = operationInfos; - this.notifyChanged(); - } - - setClearViewDebounce(clearViewDebounce) { - this._clearViewDebounce = clearViewDebounce; - } -} - -const storeInstance = new OperationInfoStore(); - -export default storeInstance; diff --git a/lib/stores/project_store.js b/lib/stores/project_store.js deleted file mode 100644 index a3cf6da..0000000 --- a/lib/stores/project_store.js +++ /dev/null @@ -1,34 +0,0 @@ -'use babel'; - -import BaseStore from './base_store'; -import FileTracker from '../editor/file_tracker'; - -class ProjectStore extends BaseStore { - constructor() { - super(); - } - - reset() { - this._projectConfig = null; - } - - receivedProjectConfig(projectConfig) { - this._projectConfig = projectConfig; - this.notifyChanged(); - } - - getProjectRoot() { - return this._projectConfig && this._projectConfig.getProjectRoot(); - } - - getTextEditorsFor(filePath) { - if (this._projectConfig == null) { - return []; - } - return this._projectConfig.getTextEditorsFor(filePath); - } -} - -const storeInstance = new ProjectStore(); - -export default storeInstance; diff --git a/lib/telemetry/EventType.js b/lib/telemetry/EventType.js new file mode 100644 index 0000000..df62c9f --- /dev/null +++ b/lib/telemetry/EventType.js @@ -0,0 +1,24 @@ +'use babel'; + +export default class EventType { + constructor({category, action}) { + this._category = category; + this._action = action; + } + + static of(category, action) { + return new EventType({category, action}); + } + + get category() { + return this._category; + } + + get action() { + return this._action; + } + + get name() { + return `${this.category} / ${this.action}`; + } +}; diff --git a/lib/telemetry/client.js b/lib/telemetry/client.js new file mode 100644 index 0000000..c572c48 --- /dev/null +++ b/lib/telemetry/client.js @@ -0,0 +1,54 @@ +'use babel'; + +import ua from 'universal-analytics'; +import uuidv4 from 'uuid/v4'; + +import env from '../env'; +import Logger from '../logger'; + +const CLIENT_ID_KEY = 'skyline:telemetry:clientId'; + +export default class TelemetryClient { + constructor({uaId, clientId, store}) { + if (uaId == null) { + // If a universal analytics ID is not provided, or if + // the plugin is in development mode, this client will + // just log the events to the console. + this._visitor = null; + } else { + this._visitor = ua(uaId, clientId); + } + this._store = store; + } + + static from(uaId, store) { + let clientId = localStorage.getItem(CLIENT_ID_KEY); + if (clientId == null) { + clientId = uuidv4(); + localStorage.setItem(CLIENT_ID_KEY, clientId); + } + return new TelemetryClient({uaId, clientId, store}); + } + + record(eventType, {label, value} = {}) { + if (eventType == null) { + Logger.warn('Attempted to record an undefined event type.'); + return; + } + + if (!(this._store.getState().config.enableTelemetry)) { + // We do not send events if the user has disabled them. + return; + } + + if (env.development || this._visitor == null) { + // During development, or if a universal analytics ID was not supplied, we + // will log the event instead of sending it. + Logger.debug('Event:', eventType.name, '| Label:', label, '| Value:', value); + return; + } + + this._visitor.event( + eventType.category, eventType.action, label, value).send(); + } +}; diff --git a/lib/telemetry/events.js b/lib/telemetry/events.js new file mode 100644 index 0000000..6e15cab --- /dev/null +++ b/lib/telemetry/events.js @@ -0,0 +1,55 @@ +'use babel'; + +import EventType from './EventType'; + +// This object is the "source of truth" for all event definitions. +// We generate event classes from the data here. +const events = new Map([ + // Application-wide events + ['Skyline', [ + 'Opened', + 'Connected', + 'Requested Analysis', + 'Received Analysis', + ]], + + // Events specific to interactions + ['Interaction', [ + 'Clicked Run Time Entry', + 'Clicked Memory Entry', + ]], + + // Application-wide errors. We use a separate category to + // aggregate them separately from regular Skyline events. + ['Error', [ + 'Connection Error', + 'Connection Timeout', + 'Analysis Error', + 'Protocol Error', + ]], +]); + +// Create EventType instances for each defined event +// under the chosen categories +const exportedEvents = {}; +for (const [category, events] of events) { + const eventInstances = {}; + events.forEach((eventAction) => { + const key = eventAction.toUpperCase().split(' ').join('_'); + eventInstances[key] = EventType.of(category, eventAction); + }); + exportedEvents[category] = eventInstances; +} + +// Events are accessible by category and action name in this exported object. +// The action name is capitalized and spaces have been replaced with underscores. +// +// For example, the event with category "Skyline" and action "Requested Analysis" +// will be available as: +// +// import Events from './telemetry/events'; +// const event = Events.Skyline.REQUESTED_ANALYSIS; +// +// To record an event, this event type instance is passed to the TelemetryClient's +// record method. +export default exportedEvents; diff --git a/lib/telemetry/react_context.js b/lib/telemetry/react_context.js new file mode 100644 index 0000000..389cb32 --- /dev/null +++ b/lib/telemetry/react_context.js @@ -0,0 +1,7 @@ +'use babel'; + +import React from 'react'; + +const TelemetryClientContext = React.createContext(null); + +export default TelemetryClientContext; diff --git a/menus/skyline.json b/menus/skyline.json new file mode 100644 index 0000000..0289831 --- /dev/null +++ b/menus/skyline.json @@ -0,0 +1,18 @@ +{ + "menu": [ + { + "label": "Packages", + "submenu": [ + { + "label": "Skyline", + "submenu": [ + { + "label": "Show/Hide Skyline", + "command": "skyline:toggle" + } + ] + } + ] + } + ] +} diff --git a/package-lock.json b/package-lock.json index fc13561..ac1817a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,246 @@ { "name": "skyline", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/runtime": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", + "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "ajv": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "google-protobuf": { "version": "3.7.0-rc.2", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.7.0-rc.2.tgz", "integrity": "sha512-mHl4c23RGm7PakQ7OODtWa0H3tpm78we9JE23GkcrTL8rbD/7jpX6p5977quPdr9bLa0yJSst4tDDyXl8wwW3Q==" }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw==", + "requires": { + "react-is": "^16.7.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -22,11 +249,39 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "prop-types": { "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", @@ -37,6 +292,21 @@ "react-is": "^16.8.1" } }, + "psl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, "react": { "version": "16.8.3", "resolved": "https://registry.npmjs.org/react/-/react-16.8.3.tgz", @@ -64,6 +334,77 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==" }, + "react-redux": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.3.tgz", + "integrity": "sha512-uI1wca+ECG9RoVkWQFF4jDMqmaw0/qnvaSvOoL/GA4dNxf6LoV8sUAcNDvE5NWKs4hFpn0t6wswNQnY3f7HT3w==", + "requires": { + "@babel/runtime": "^7.5.5", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^16.9.0" + }, + "dependencies": { + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" + } + } + }, + "redux": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", + "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "scheduler": { "version": "0.13.3", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", @@ -72,6 +413,89 @@ "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "universal-analytics": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/universal-analytics/-/universal-analytics-0.4.20.tgz", + "integrity": "sha512-gE91dtMvNkjO+kWsPstHRtSwHXz0l2axqptGYp5ceg4MsuurloM0PU3pdOfpb5zBXUvyjT4PwhWK2m39uczZuw==", + "requires": { + "debug": "^3.0.0", + "request": "^2.88.0", + "uuid": "^3.0.0" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } } } } diff --git a/package.json b/package.json index 09caf36..ca99fd3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skyline", "main": "./lib/skyline", - "version": "0.1.0", + "version": "0.2.0", "description": "Interactive in-editor performance profiling, visualization, and debugging for PyTorch neural networks.", "keywords": [ "neural networks", @@ -14,8 +14,7 @@ ], "activationCommands": { "atom-workspace": [ - "skyline:open", - "skyline:close" + "skyline:toggle" ] }, "repository": "https://github.com/geoffxy/skyline-atom", @@ -26,7 +25,11 @@ "dependencies": { "google-protobuf": "^3.7.0-rc.2", "react": "^16.8.3", - "react-dom": "^16.8.3" + "react-dom": "^16.8.3", + "react-redux": "^7.1.3", + "redux": "^4.0.5", + "universal-analytics": "^0.4.20", + "uuid": "^3.4.0" }, "devDependencies": {} } diff --git a/styles/GetStarted.less b/styles/GetStarted.less index d23b5ba..14200b8 100644 --- a/styles/GetStarted.less +++ b/styles/GetStarted.less @@ -20,6 +20,10 @@ font-style: italic; text-align: center; } + + p { + text-align: center; + } } .innpv-get-started-buttons { diff --git a/styles/PerfBarContainer.less b/styles/PerfBarContainer.less index 23558ee..dd69627 100644 --- a/styles/PerfBarContainer.less +++ b/styles/PerfBarContainer.less @@ -22,7 +22,7 @@ .innpv-perfbarcontainer-labelcontainer { height: 100%; - width: 10px; + width: 15px; margin-left: 5px; } @@ -46,7 +46,7 @@ .innpv-perfbarcontainer-label { transform: rotate(270deg); color: @text-color-subtle; - font-size: 0.8em; + font-size: 1em; transition-property: color; transition-duration: @perfbar-label-duration; diff --git a/styles/PerfVis.less b/styles/PerfVis.less index 1a858e2..2b3c2f4 100644 --- a/styles/PerfVis.less +++ b/styles/PerfVis.less @@ -1,4 +1,4 @@ .innpv-wrap { height: 100%; - width: 300px; + width: 350px; } diff --git a/styles/PerfVisMainView.less b/styles/PerfVisMainView.less index 7912430..c8f7cec 100644 --- a/styles/PerfVisMainView.less +++ b/styles/PerfVisMainView.less @@ -76,8 +76,13 @@ } .innpv-perfbar-contents { + display: flex; padding: @component-padding; border-right: 1px solid @inset-panel-border-color; transition-property: opacity; transition-duration: @noevents-duration; + + & > *:first-child { + margin-right: 15px; + } }