diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2020-10-30 00:17:01 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-10-30 00:17:01 +0100 |
| commit | 91b38f216f3107d4be2fa26e78c3e6df674bcbca (patch) | |
| tree | 1f2239bb3758ac3099542fe7b06d559d58bdecd5 | |
| parent | ea0dd07e8a5deb8084ebcbae780e57fdd90bccc2 (diff) | |
| parent | 4ec2ace2e1ca37294f6e55c2965f1fc6f98d622c (diff) | |
Merge pull request #56 from atlarge-research/bug/scenario
Fix workings of scenarios
26 files changed, 680 insertions, 733 deletions
diff --git a/api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py b/api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py index fc2cab16..2f042e06 100644 --- a/api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py +++ b/api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py @@ -1,5 +1,6 @@ from opendc.models.portfolio import Portfolio from opendc.models.scenario import Scenario +from opendc.models.topology import Topology from opendc.util.rest import Response @@ -32,8 +33,13 @@ def POST(request): scenario = Scenario(request.params_body['scenario']) + topology = Topology.from_id(scenario.obj['topology']['topologyId']) + topology.check_exists() + topology.check_user_access(request.google_id, True) + scenario.set_property('portfolioId', portfolio.get_id()) scenario.set_property('simulation', {'state': 'QUEUED'}) + scenario.set_property('topology.topologyId', topology.get_id()) scenario.insert() diff --git a/docker-compose.yml b/docker-compose.yml index 4d62b7cd..d49ac5dc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,15 +41,11 @@ services: - type: bind source: ./traces target: /app/traces - - type: volume - source: results-volume - target: /results environment: - OPENDC_DB - OPENDC_DB_USERNAME - OPENDC_DB_PASSWORD - OPENDC_DB_HOST=mongo - - OPENDC_OUTPUT=/results mongo: build: @@ -69,7 +65,6 @@ services: volumes: mongo-volume: - results-volume: networks: backend: {} diff --git a/frontend/package.json b/frontend/package.json index bb190954..16732995 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "author": "Georgios Andreadis <g.andreadis@atlarge-research.com> (https://gandreadis.com/)", "license": "MIT", "private": true, - "proxy": "http://localhost:8082", + "proxy": "http://localhost:8081", "dependencies": { "approximate-number": "~2.0.0", "bootstrap": "4.5.3", diff --git a/frontend/src/components/app/sidebars/topology/machine/UnitTabsComponent.js b/frontend/src/components/app/sidebars/topology/machine/UnitTabsComponent.js index 569166d8..6599fefd 100644 --- a/frontend/src/components/app/sidebars/topology/machine/UnitTabsComponent.js +++ b/frontend/src/components/app/sidebars/topology/machine/UnitTabsComponent.js @@ -14,7 +14,7 @@ const UnitTabsComponent = () => { <Nav tabs> <NavItem> <NavLink - className={activeTab === 'cpu-units' && 'active'} + className={activeTab === 'cpu-units' ? 'active' : ''} onClick={() => { toggle('cpu-units') }} @@ -24,7 +24,7 @@ const UnitTabsComponent = () => { </NavItem> <NavItem> <NavLink - className={activeTab === 'gpu-units' && 'active'} + className={activeTab === 'gpu-units' ? 'active' : ''} onClick={() => { toggle('gpu-units') }} @@ -34,7 +34,7 @@ const UnitTabsComponent = () => { </NavItem> <NavItem> <NavLink - className={activeTab === 'memory-units' && 'active'} + className={activeTab === 'memory-units' ? 'active' : ''} onClick={() => { toggle('memory-units') }} @@ -44,7 +44,7 @@ const UnitTabsComponent = () => { </NavItem> <NavItem> <NavLink - className={activeTab === 'storage-units' && 'active'} + className={activeTab === 'storage-units' ? 'active' : ''} onClick={() => { toggle('storage-units') }} diff --git a/frontend/src/components/modals/Modal.js b/frontend/src/components/modals/Modal.js index 36bb498e..b494d970 100644 --- a/frontend/src/components/modals/Modal.js +++ b/frontend/src/components/modals/Modal.js @@ -18,8 +18,8 @@ function Modal({ children, title, show, onSubmit, onCancel, submitButtonType, su } return ( - <RModal isOpen={modal} toggle={toggle}> - <ModalHeader toggle={toggle}>{title}</ModalHeader> + <RModal isOpen={modal} toggle={cancel}> + <ModalHeader toggle={cancel}>{title}</ModalHeader> <ModalBody>{children}</ModalBody> <ModalFooter> <Button color="secondary" onClick={cancel}> diff --git a/frontend/src/components/modals/custom-components/NewPortfolioModalComponent.js b/frontend/src/components/modals/custom-components/NewPortfolioModalComponent.js index 19049931..978ca11d 100644 --- a/frontend/src/components/modals/custom-components/NewPortfolioModalComponent.js +++ b/frontend/src/components/modals/custom-components/NewPortfolioModalComponent.js @@ -1,98 +1,69 @@ import PropTypes from 'prop-types' -import React from 'react' +import React, { useRef } from 'react' +import { Form, FormGroup, Input, Label } from 'reactstrap' import Modal from '../Modal' import { AVAILABLE_METRICS } from '../../../util/available-metrics' -class NewPortfolioModalComponent extends React.Component { - static propTypes = { - show: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, - } +const NewPortfolioModalComponent = ({ show, callback }) => { + const textInput = useRef(null) + const repeatsInput = useRef(null) + const metricCheckboxes = useRef({}) - constructor(props) { - super(props) - this.metricCheckboxes = {} - } - - componentDidMount() { - this.reset() - } - - reset() { - if (this.textInput) { - this.textInput.value = '' - AVAILABLE_METRICS.forEach((metric) => { - this.metricCheckboxes[metric].checked = true - }) - this.repeatsInput.value = 1 - } - } - - onSubmit() { - this.props.callback(this.textInput.value, { - enabledMetrics: AVAILABLE_METRICS.filter((metric) => this.metricCheckboxes[metric].checked), - repeatsPerScenario: parseInt(this.repeatsInput.value), + const onSubmit = () => + callback(textInput.current.value, { + enabledMetrics: AVAILABLE_METRICS.filter((metric) => metricCheckboxes.current[metric].checked), + repeatsPerScenario: parseInt(repeatsInput.current.value), }) - this.reset() - } + const onCancel = () => callback(undefined) - onCancel() { - this.props.callback(undefined) - this.reset() - } - - render() { - return ( - <Modal - title="New Portfolio" - show={this.props.show} - onSubmit={this.onSubmit.bind(this)} - onCancel={this.onCancel.bind(this)} + return ( + <Modal title="New Portfolio" show={show} onSubmit={onSubmit} onCancel={onCancel}> + <Form + onSubmit={(e) => { + e.preventDefault() + this.onSubmit() + }} > - <form - onSubmit={(e) => { - e.preventDefault() - this.onSubmit() - }} - > - <div className="form-group"> - <label className="form-control-label">Name</label> - <input - type="text" - className="form-control" - required - ref={(textInput) => (this.textInput = textInput)} - /> - </div> - <h4>Targets</h4> - <h5>Metrics</h5> - <div className="form-group"> - {AVAILABLE_METRICS.map((metric) => ( - <div className="form-check" key={metric}> - <label className="form-check-label"> - <input - type="checkbox" - className="form-check-input" - ref={(checkbox) => (this.metricCheckboxes[metric] = checkbox)} - /> - <code>{metric}</code> - </label> - </div> - ))} - </div> - <div className="form-group"> - <label className="form-control-label">Repeats per scenario</label> - <input - type="number" - className="form-control" - required - ref={(repeatsInput) => (this.repeatsInput = repeatsInput)} - /> - </div> - </form> - </Modal> - ) - } + <FormGroup> + <Label for="name">Name</Label> + <Input name="name" type="text" required innerRef={textInput} placeholder="My Portfolio" /> + </FormGroup> + <h4>Targets</h4> + <h5>Metrics</h5> + <FormGroup> + {AVAILABLE_METRICS.map((metric) => ( + <FormGroup check key={metric}> + <Label for={metric} check> + <Input + name={metric} + type="checkbox" + innerRef={(ref) => (metricCheckboxes.current[metric] = ref)} + /> + <code>{metric}</code> + </Label> + </FormGroup> + ))} + </FormGroup> + <FormGroup> + <Label for="repeats">Repeats per scenario</Label> + <Input + name="repeats" + type="number" + required + innerRef={repeatsInput} + defaultValue="1" + min="1" + step="1" + /> + </FormGroup> + </Form> + </Modal> + ) +} + +NewPortfolioModalComponent.propTypes = { + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, } export default NewPortfolioModalComponent diff --git a/frontend/src/components/modals/custom-components/NewScenarioModalComponent.js b/frontend/src/components/modals/custom-components/NewScenarioModalComponent.js index 5ba74b0f..631082a2 100644 --- a/frontend/src/components/modals/custom-components/NewScenarioModalComponent.js +++ b/frontend/src/components/modals/custom-components/NewScenarioModalComponent.js @@ -1,168 +1,138 @@ import PropTypes from 'prop-types' -import React from 'react' +import React, { useRef } from 'react' +import { Form, FormGroup, Input, Label } from 'reactstrap' import Shapes from '../../../shapes' import Modal from '../Modal' -class NewScenarioModalComponent extends React.Component { - static propTypes = { - show: PropTypes.bool.isRequired, - currentPortfolioId: PropTypes.string.isRequired, - currentPortfolioScenarioIds: PropTypes.arrayOf(PropTypes.string), - traces: PropTypes.arrayOf(Shapes.Trace), - topologies: PropTypes.arrayOf(Shapes.Topology), - schedulers: PropTypes.arrayOf(Shapes.Scheduler), - callback: PropTypes.func.isRequired, - } - - componentDidMount() { - this.reset() - } +const NewScenarioModalComponent = ({ + show, + callback, + currentPortfolioId, + currentPortfolioScenarioIds, + traces, + topologies, + schedulers, +}) => { + const textInput = useRef(null) + const traceSelect = useRef(null) + const traceLoadInput = useRef(null) + const topologySelect = useRef(null) + const failuresCheckbox = useRef(null) + const performanceInterferenceCheckbox = useRef(null) + const schedulerSelect = useRef(null) - componentDidUpdate() { - if (this.textInput) { - if (this.props.currentPortfolioScenarioIds.length === 0) { - this.textInput.value = 'Base scenario' - } else if (this.textInput.value === 'Base scenario') { - this.textInput.value = '' - } - } - } - - reset() { - if (this.textInput) { - this.textInput.value = this.props.currentPortfolioScenarioIds.length === 0 ? 'Base scenario' : '' - this.traceSelect.selectedIndex = 0 - this.traceLoadInput.value = 1.0 - this.topologySelect.selectedIndex = 0 - this.failuresCheckbox.checked = false - this.performanceInterferenceCheckbox.checked = false - this.schedulerSelect.selectedIndex = 0 - } - } - - onSubmit() { - this.props.callback( - this.textInput.value, - this.props.currentPortfolioId, + const onSubmit = () => { + callback( + textInput.current.value, + currentPortfolioId, { - traceId: this.traceSelect.value, - loadSamplingFraction: parseFloat(this.traceLoadInput.value), + traceId: traceSelect.current.value, + loadSamplingFraction: parseFloat(traceLoadInput.current.value), }, { - topologyId: this.topologySelect.value, + topologyId: topologySelect.current.value, }, { - failuresEnabled: this.failuresCheckbox.checked, - performanceInterferenceEnabled: this.performanceInterferenceCheckbox.checked, - schedulerName: this.schedulerSelect.value, + failuresEnabled: failuresCheckbox.current.checked, + performanceInterferenceEnabled: performanceInterferenceCheckbox.current.checked, + schedulerName: schedulerSelect.current.value, } ) - this.reset() } - - onCancel() { - this.props.callback(undefined) - this.reset() + const onCancel = () => { + callback(undefined) } - render() { - return ( - <Modal - title="New Scenario" - show={this.props.show} - onSubmit={this.onSubmit.bind(this)} - onCancel={this.onCancel.bind(this)} + return ( + <Modal title="New Scenario" show={show} onSubmit={onSubmit} onCancel={onCancel}> + <Form + onSubmit={(e) => { + e.preventDefault() + onSubmit() + }} > - <form - onSubmit={(e) => { - e.preventDefault() - this.onSubmit() - }} - > - <div className="form-group"> - <label className="form-control-label">Name</label> - <input - type="text" - className="form-control" - required - disabled={this.props.currentPortfolioScenarioIds.length === 0} - ref={(textInput) => (this.textInput = textInput)} - /> - </div> - <h4>Trace</h4> - <div className="form-group"> - <label className="form-control-label">Trace</label> - <select className="form-control" ref={(traceSelect) => (this.traceSelect = traceSelect)}> - {this.props.traces.map((trace) => ( - <option value={trace._id} key={trace._id}> - {trace.name} - </option> - ))} - </select> - </div> - <div className="form-group"> - <label className="form-control-label">Load sampling fraction</label> - <input - type="number" - className="form-control" - required - ref={(traceLoadInput) => (this.traceLoadInput = traceLoadInput)} - /> - </div> - <h4>Topology</h4> - <div className="form-group"> - <label className="form-control-label">Topology</label> - <select - className="form-control" - ref={(topologySelect) => (this.topologySelect = topologySelect)} - > - {this.props.topologies.map((topology) => ( - <option value={topology._id} key={topology._id}> - {topology.name} - </option> - ))} - </select> - </div> - <h4>Operational Phenomena</h4> - <div className="form-check"> - <label className="form-check-label"> - <input - type="checkbox" - className="form-check-input" - ref={(failuresCheckbox) => (this.failuresCheckbox = failuresCheckbox)} - /> - <span className="ml-2">Enable failures</span> - </label> - </div> - <div className="form-check"> - <label className="form-check-label"> - <input - type="checkbox" - className="form-check-input" - ref={(performanceInterferenceCheckbox) => - (this.performanceInterferenceCheckbox = performanceInterferenceCheckbox) - } - /> - <span className="ml-2">Enable performance interference</span> - </label> - </div> - <div className="form-group"> - <label className="form-control-label">Scheduler</label> - <select - className="form-control" - ref={(schedulerSelect) => (this.schedulerSelect = schedulerSelect)} - > - {this.props.schedulers.map((scheduler) => ( - <option value={scheduler.name} key={scheduler.name}> - {scheduler.name} - </option> - ))} - </select> - </div> - </form> - </Modal> - ) - } + <FormGroup> + <Label for="name">Name</Label> + <Input + name="name" + type="text" + required + disabled={currentPortfolioScenarioIds.length === 0} + defaultValue={currentPortfolioScenarioIds.length === 0 ? 'Base scenario' : ''} + innerRef={textInput} + /> + </FormGroup> + <h4>Trace</h4> + <FormGroup> + <Label for="trace">Trace</Label> + <Input name="trace" type="select" innerRef={traceSelect}> + {traces.map((trace) => ( + <option value={trace._id} key={trace._id}> + {trace.name} + </option> + ))} + </Input> + </FormGroup> + <FormGroup> + <Label for="trace-load">Load sampling fraction</Label> + <Input + name="trace-load" + type="number" + innerRef={traceLoadInput} + required + defaultValue="1" + min="0" + max="1" + step="0.1" + /> + </FormGroup> + <h4>Topology</h4> + <div className="form-group"> + <Label for="topology">Topology</Label> + <Input name="topology" type="select" innerRef={topologySelect}> + {topologies.map((topology) => ( + <option value={topology._id} key={topology._id}> + {topology.name} + </option> + ))} + </Input> + </div> + <h4>Operational Phenomena</h4> + <FormGroup check> + <Label check for="failures"> + <Input type="checkbox" name="failures" innerRef={failuresCheckbox} />{' '} + <span className="ml-2">Enable failures</span> + </Label> + </FormGroup> + <FormGroup check> + <Label check for="perf-interference"> + <Input type="checkbox" name="perf-interference" innerRef={performanceInterferenceCheckbox} />{' '} + <span className="ml-2">Enable performance interference</span> + </Label> + </FormGroup> + <FormGroup> + <Label for="scheduler">Scheduler</Label> + <Input name="scheduler" type="select" innerRef={schedulerSelect}> + {schedulers.map((scheduler) => ( + <option value={scheduler.name} key={scheduler.name}> + {scheduler.name} + </option> + ))} + </Input> + </FormGroup> + </Form> + </Modal> + ) +} + +NewScenarioModalComponent.propTypes = { + show: PropTypes.bool.isRequired, + currentPortfolioId: PropTypes.string.isRequired, + currentPortfolioScenarioIds: PropTypes.arrayOf(PropTypes.string), + traces: PropTypes.arrayOf(Shapes.Trace), + topologies: PropTypes.arrayOf(Shapes.Topology), + schedulers: PropTypes.arrayOf(Shapes.Scheduler), + callback: PropTypes.func.isRequired, } export default NewScenarioModalComponent diff --git a/frontend/src/components/modals/custom-components/NewTopologyModalComponent.js b/frontend/src/components/modals/custom-components/NewTopologyModalComponent.js index 8a625b13..b20ec13b 100644 --- a/frontend/src/components/modals/custom-components/NewTopologyModalComponent.js +++ b/frontend/src/components/modals/custom-components/NewTopologyModalComponent.js @@ -1,90 +1,65 @@ import PropTypes from 'prop-types' -import React from 'react' +import { Form, FormGroup, Input, Label } from 'reactstrap' +import React, { useRef } from 'react' import Shapes from '../../../shapes' import Modal from '../Modal' -class NewTopologyModalComponent extends React.Component { - static propTypes = { - show: PropTypes.bool.isRequired, - topologies: PropTypes.arrayOf(Shapes.Topology), - onCreateTopology: PropTypes.func.isRequired, - onDuplicateTopology: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, +const NewTopologyModalComponent = ({ show, onCreateTopology, onDuplicateTopology, onCancel, topologies }) => { + const textInput = useRef(null) + const originTopology = useRef(null) + + const onCreate = () => { + onCreateTopology(textInput.current.value) } - reset() { - if (this.textInput) { - this.textInput.value = '' - this.originTopology.selectedIndex = 0 - } + const onDuplicate = () => { + onDuplicateTopology(textInput.current.value, originTopology.current.value) } - onSubmit() { - if (this.originTopology.selectedIndex === 0) { - this.onCreate() + const onSubmit = () => { + if (originTopology.current.selectedIndex === 0) { + onCreate() } else { - this.onDuplicate() + onDuplicate() } } - onCreate() { - this.props.onCreateTopology(this.textInput.value) - this.reset() - } - - onDuplicate() { - this.props.onDuplicateTopology(this.textInput.value, this.originTopology.value) - this.reset() - } - - onCancel() { - this.props.onCancel() - this.reset() - } - - render() { - return ( - <Modal - title="New Topology" - show={this.props.show} - onSubmit={this.onSubmit.bind(this)} - onCancel={this.onCancel.bind(this)} + return ( + <Modal title="New Topology" show={show} onSubmit={onSubmit} onCancel={onCancel}> + <Form + onSubmit={(e) => { + e.preventDefault() + onSubmit() + }} > - <form - onSubmit={(e) => { - e.preventDefault() - this.onSubmit() - }} - > - <div className="form-group"> - <label className="form-control-label">Name</label> - <input - type="text" - className="form-control" - required - ref={(textInput) => (this.textInput = textInput)} - /> - </div> - <div className="form-group"> - <label className="form-control-label">Topology to duplicate</label> - <select - className="form-control" - ref={(originTopology) => (this.originTopology = originTopology)} - > - <option value={-1} key={-1}> - None - start from scratch + <FormGroup> + <Label for="name">Name</Label> + <Input name="name" type="text" required innerRef={textInput} /> + </FormGroup> + <FormGroup> + <Label for="origin">Topology to duplicate</Label> + <Input name="origin" type="select" innerRef={originTopology}> + <option value={-1} key={-1}> + None - start from scratch + </option> + {topologies.map((topology) => ( + <option value={topology._id} key={topology._id}> + {topology.name} </option> - {this.props.topologies.map((topology) => ( - <option value={topology._id} key={topology._id}> - {topology.name} - </option> - ))} - </select> - </div> - </form> - </Modal> - ) - } + ))} + </Input> + </FormGroup> + </Form> + </Modal> + ) +} + +NewTopologyModalComponent.propTypes = { + show: PropTypes.bool.isRequired, + topologies: PropTypes.arrayOf(Shapes.Topology), + onCreateTopology: PropTypes.func.isRequired, + onDuplicateTopology: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, } export default NewTopologyModalComponent diff --git a/frontend/src/components/navigation/AppNavbarComponent.js b/frontend/src/components/navigation/AppNavbarComponent.js index 7a1c2462..c5de3d0b 100644 --- a/frontend/src/components/navigation/AppNavbarComponent.js +++ b/frontend/src/components/navigation/AppNavbarComponent.js @@ -1,22 +1,23 @@ import React from 'react' import FontAwesome from 'react-fontawesome' import { Link } from 'react-router-dom' +import { NavLink } from 'reactstrap' import Navbar, { NavItem } from './Navbar' import './Navbar.sass' const AppNavbarComponent = ({ project, fullWidth }) => ( <Navbar fullWidth={fullWidth}> <NavItem route="/projects"> - <Link className="nav-link" title="My Projects" to="/projects"> + <NavLink tag={Link} title="My Projects" to="/projects"> <FontAwesome name="list" className="mr-2" /> My Projects - </Link> + </NavLink> </NavItem> {project ? ( <NavItem> - <Link className="nav-link" title="Current Project" to={`/projects/${project._id}`}> + <NavLink tag={Link} title="Current Project" to={`/projects/${project._id}`}> <span>{project.name}</span> - </Link> + </NavLink> </NavItem> ) : undefined} </Navbar> diff --git a/frontend/src/components/navigation/HomeNavbar.js b/frontend/src/components/navigation/HomeNavbar.js index e22933af..08d222ea 100644 --- a/frontend/src/components/navigation/HomeNavbar.js +++ b/frontend/src/components/navigation/HomeNavbar.js @@ -1,13 +1,12 @@ import React from 'react' +import { NavItem, NavLink } from 'reactstrap' import Navbar from './Navbar' import './Navbar.sass' const ScrollNavItem = ({ id, name }) => ( - <li className="nav-item"> - <a className="nav-link" href={id}> - {name} - </a> - </li> + <NavItem> + <NavLink href={id}>{name}</NavLink> + </NavItem> ) const HomeNavbar = () => ( diff --git a/frontend/src/components/navigation/LogoutButton.js b/frontend/src/components/navigation/LogoutButton.js index e3de2ec7..78b02b44 100644 --- a/frontend/src/components/navigation/LogoutButton.js +++ b/frontend/src/components/navigation/LogoutButton.js @@ -2,11 +2,12 @@ import PropTypes from 'prop-types' import React from 'react' import FontAwesome from 'react-fontawesome' import { Link } from 'react-router-dom' +import { NavLink } from 'reactstrap' const LogoutButton = ({ onLogout }) => ( - <Link className="logout nav-link" title="Sign out" to="#" onClick={onLogout}> + <NavLink tag={Link} className="logout" title="Sign out" to="#" onClick={onLogout}> <FontAwesome name="power-off" size="lg" /> - </Link> + </NavLink> ) LogoutButton.propTypes = { diff --git a/frontend/src/components/navigation/Navbar.js b/frontend/src/components/navigation/Navbar.js index 164a2309..55f98900 100644 --- a/frontend/src/components/navigation/Navbar.js +++ b/frontend/src/components/navigation/Navbar.js @@ -1,6 +1,15 @@ -import classNames from 'classnames' -import React from 'react' -import { Link, withRouter } from 'react-router-dom' +import React, { useState } from 'react' +import { Link, useLocation } from 'react-router-dom' +import { + Navbar as RNavbar, + NavItem as RNavItem, + NavLink, + NavbarBrand, + NavbarToggler, + Collapse, + Nav, + Container, +} from 'reactstrap' import { userIsLoggedIn } from '../../auth/index' import Login from '../../containers/auth/Login' import Logout from '../../containers/auth/Logout' @@ -9,9 +18,6 @@ import './Navbar.sass' export const NAVBAR_HEIGHT = 60 -export const NavItem = withRouter((props) => <NavItemWithoutRoute {...props} />) -export const LoggedInSection = withRouter((props) => <LoggedInSectionWithoutRoute {...props} />) - const GitHubLink = () => ( <a href="https://github.com/atlarge-research/opendc" @@ -22,64 +28,65 @@ const GitHubLink = () => ( </a> ) -const NavItemWithoutRoute = ({ route, location, children }) => ( - <li className={classNames('nav-item clickable', location.pathname === route ? 'active' : undefined)}>{children}</li> -) +export const NavItem = ({ route, children }) => { + const location = useLocation() + return <RNavItem active={location.pathname === route}>{children}</RNavItem> +} -const LoggedInSectionWithoutRoute = ({ location }) => ( - <ul className="navbar-nav auth-links"> - {userIsLoggedIn() ? ( - [ - location.pathname === '/' ? ( - <NavItem route="/projects" key="projects"> - <Link className="nav-link" title="My Projects" to="/projects"> - My Projects - </Link> - </NavItem> - ) : ( - <NavItem route="/profile" key="profile"> - <Link className="nav-link" title="My Profile" to="/profile"> - <ProfileName /> - </Link> - </NavItem> - ), - <NavItem route="logout" key="logout"> - <Logout /> - </NavItem>, - ] - ) : ( - <NavItem route="login"> - <GitHubLink /> - <Login visible={true} /> - </NavItem> - )} - </ul> -) +export const LoggedInSection = () => { + const location = useLocation() + return ( + <Nav navbar className="auth-links"> + {userIsLoggedIn() ? ( + [ + location.pathname === '/' ? ( + <NavItem route="/projects" key="projects"> + <NavLink tag={Link} title="My Projects" to="/projects"> + My Projects + </NavLink> + </NavItem> + ) : ( + <NavItem route="/profile" key="profile"> + <NavLink tag={Link} title="My Profile" to="/profile"> + <ProfileName /> + </NavLink> + </NavItem> + ), + <NavItem route="logout" key="logout"> + <Logout /> + </NavItem>, + ] + ) : ( + <NavItem route="login"> + <GitHubLink /> + <Login visible={true} /> + </NavItem> + )} + </Nav> + ) +} -const Navbar = ({ fullWidth, children }) => ( - <nav className="navbar fixed-top navbar-expand-lg navbar-light bg-faded" id="navbar"> - <div className={fullWidth ? 'container-fluid' : 'container'}> - <button - className="navbar-toggler navbar-toggler-right" - type="button" - data-toggle="collapse" - data-target="#navbarSupportedContent" - aria-controls="navbarSupportedContent" - aria-expanded="false" - aria-label="Toggle navigation" - > - <span className="navbar-toggler-icon" /> - </button> - <Link className="navbar-brand opendc-brand" to="/" title="OpenDC" onClick={() => window.scrollTo(0, 0)}> - <img src="/img/logo.png" alt="OpenDC" /> - </Link> +const Navbar = ({ fullWidth, children }) => { + const [isOpen, setIsOpen] = useState(false) + const toggle = () => setIsOpen(!isOpen) - <div className="collapse navbar-collapse" id="navbarSupportedContent"> - <ul className="navbar-nav mr-auto">{children}</ul> - <LoggedInSection /> - </div> - </div> - </nav> -) + return ( + <RNavbar fixed="top" color="light" light expand="lg" id="navbar"> + <Container fluid={fullWidth}> + <NavbarToggler onClick={toggle} /> + <NavbarBrand tag={Link} to="/" title="OpenDC" className="opendc-brand"> + <img src="/img/logo.png" alt="OpenDC" /> + </NavbarBrand> + + <Collapse isOpen={isOpen} navbar> + <Nav className="mr-auto" navbar> + {children} + </Nav> + <LoggedInSection /> + </Collapse> + </Container> + </RNavbar> + ) +} export default Navbar diff --git a/frontend/src/containers/modals/NewScenarioModal.js b/frontend/src/containers/modals/NewScenarioModal.js index 225ae321..7d774fa4 100644 --- a/frontend/src/containers/modals/NewScenarioModal.js +++ b/frontend/src/containers/modals/NewScenarioModal.js @@ -39,6 +39,7 @@ const mapDispatchToProps = (dispatch) => { }) ) } + dispatch(closeNewScenarioModal()) }, } diff --git a/frontend/src/sagas/objects.js b/frontend/src/sagas/objects.js index ebce1f0a..313d9976 100644 --- a/frontend/src/sagas/objects.js +++ b/frontend/src/sagas/objects.js @@ -36,8 +36,8 @@ function* fetchAndStoreObject(objectType, id, apiCall) { function* fetchAndStoreObjects(objectType, apiCall) { const objects = yield apiCall - for (let index in objects) { - yield put(addToStore(objectType, objects[index])) + for (let object of objects) { + yield put(addToStore(objectType, object)) } return objects } @@ -161,11 +161,11 @@ export const getAllRooms = function* (roomIds, keepIds) { let rooms = [] - for (let i in roomIds) { - let tiles = yield getAllRoomTiles(roomStore[roomIds[i]], keepIds) + for (let id of roomIds) { + let tiles = yield getAllRoomTiles(roomStore[id], keepIds) rooms.push({ - _id: keepIds ? i : undefined, - name: roomStore[roomIds[i]].name, + _id: keepIds ? id : undefined, + name: roomStore[id].name, tiles: tiles, }) } @@ -175,8 +175,8 @@ export const getAllRooms = function* (roomIds, keepIds) { export const getAllRoomTiles = function* (roomStore, keepIds) { let tiles = [] - for (let i in roomStore.tileIds) { - tiles.push(yield getTileById(roomStore.tileIds[i], keepIds)) + for (let id of roomStore.tileIds) { + tiles.push(yield getTileById(id, keepIds)) } return tiles } @@ -221,9 +221,9 @@ export const fetchAndStoreAllTraces = () => fetchAndStoreObjects('trace', call(g export const fetchAndStoreAllSchedulers = function* () { const objects = yield call(getAllSchedulers) - for (let index in objects) { - objects[index]._id = objects[index].name - yield put(addToStore('scheduler', objects[index])) + for (let object of objects) { + object._id = object.name + yield put(addToStore('scheduler', object)) } return objects } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index b77c38f6..7398bf38 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1069,14 +1069,14 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": version "7.10.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364" integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw== dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.2.0": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.2.0": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== @@ -5463,7 +5463,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -7428,13 +7428,12 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0: integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== mini-create-react-context@^0.3.0: - version "0.3.2" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" - integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== + version "0.3.3" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.3.tgz#b1b2bc6604d3a6c5d9752bad7692615410ebb38e" + integrity sha512-TtF6hZE59SGmS4U8529qB+jJFeW6asTLDIpPgvPLSCsooAwJS7QprHIFTqv9/Qh3NdLwQxFYgiHX5lqb6jqzPA== dependencies: - "@babel/runtime" "^7.4.0" - gud "^1.0.0" - tiny-warning "^1.0.2" + "@babel/runtime" "^7.12.1" + tiny-warning "^1.0.3" mini-css-extract-plugin@0.9.0: version "0.9.0" @@ -9463,7 +9462,7 @@ react-google-login@~5.1.14: "@types/react" "*" prop-types "^15.6.0" -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0: +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -9505,15 +9504,15 @@ react-reconciler@^0.25.1: scheduler "^0.19.1" react-redux@~7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" - integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA== + version "7.2.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736" + integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA== dependencies: - "@babel/runtime" "^7.5.5" - hoist-non-react-statics "^3.3.0" + "@babel/runtime" "^7.12.1" + hoist-non-react-statics "^3.3.2" loose-envify "^1.4.0" prop-types "^15.7.2" - react-is "^16.9.0" + react-is "^16.13.1" react-resize-detector@^2.3.0: version "2.3.0" @@ -9873,11 +9872,16 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: +regenerator-runtime@^0.13.3: version "0.13.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + regenerator-transform@^0.14.2: version "0.14.4" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" @@ -11251,7 +11255,7 @@ tiny-invariant@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== -tiny-warning@^1.0.0, tiny-warning@^1.0.2: +tiny-warning@^1.0.0, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== diff --git a/simulator/.dockerignore b/simulator/.dockerignore index 816d338c..8cbb9e5e 100644 --- a/simulator/.dockerignore +++ b/simulator/.dockerignore @@ -6,5 +6,5 @@ .idea_modules/ .gradle -**/build/ +**/build diff --git a/simulator/Dockerfile b/simulator/Dockerfile index 880af95d..34280a17 100644 --- a/simulator/Dockerfile +++ b/simulator/Dockerfile @@ -1,31 +1,17 @@ -FROM openjdk:14-slim AS staging +FROM openjdk:15-slim MAINTAINER OpenDC Maintainers <opendc@atlarge-research.com> -# Build staging artifacts for dependency caching -COPY ./ /app -WORKDIR /app -RUN mkdir /staging \ - && cp -r buildSrc/ /staging \ - && cp gradle.properties /staging 2>/dev/null | true \ - && find -name "*.gradle.kts" | xargs cp --parents -t /staging - -FROM openjdk:14-slim AS builder - # Obtain (cache) Gradle wrapper COPY gradlew /app/ COPY gradle /app/gradle WORKDIR /app RUN ./gradlew --version -# Install (cache) project dependencies only -COPY --from=staging /staging/ /app/ -RUN ./gradlew clean build --no-daemon > /dev/null 2>&1 || true - # Build project COPY ./ /app/ RUN ./gradlew --no-daemon :opendc-runner-web:installDist -FROM openjdk:14-slim -COPY --from=builder /app/opendc-runner-web/build/install /app +FROM openjdk:15-slim +COPY --from=0 /app/opendc-runner-web/build/install /app WORKDIR /app CMD opendc-runner-web/bin/opendc-runner-web diff --git a/simulator/buildSrc/src/main/kotlin/kotlin-library-convention.gradle.kts b/simulator/buildSrc/src/main/kotlin/kotlin-library-convention.gradle.kts index 452db573..bbecf346 100644 --- a/simulator/buildSrc/src/main/kotlin/kotlin-library-convention.gradle.kts +++ b/simulator/buildSrc/src/main/kotlin/kotlin-library-convention.gradle.kts @@ -44,6 +44,10 @@ kotlin { explicitApi() } +jacoco { + toolVersion = "0.8.6" +} + tasks.withType<KotlinCompile>().configureEach { kotlinOptions.jvmTarget = "1.8" kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" diff --git a/simulator/gradle/wrapper/gradle-wrapper.properties b/simulator/gradle/wrapper/gradle-wrapper.properties index 12d38de6..be52383e 100644 --- a/simulator/gradle/wrapper/gradle-wrapper.properties +++ b/simulator/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/simulator/opendc-runner-web/build.gradle.kts b/simulator/opendc-runner-web/build.gradle.kts index cf437843..a46e430f 100644 --- a/simulator/opendc-runner-web/build.gradle.kts +++ b/simulator/opendc-runner-web/build.gradle.kts @@ -42,12 +42,11 @@ dependencies { implementation("com.github.ajalt:clikt:2.8.0") implementation("io.github.microutils:kotlin-logging:1.7.10") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8") { + exclude("org.jetbrains.kotlin", module = "kotlin-reflect") + } implementation("org.mongodb:mongodb-driver-sync:4.0.5") - implementation("org.apache.spark:spark-sql_2.12:3.0.0") { - exclude(group = "org.slf4j", module = "slf4j-log4j12") - exclude(group = "log4j") - } runtimeOnly("org.apache.logging.log4j:log4j-slf4j-impl:2.13.1") runtimeOnly("org.apache.logging.log4j:log4j-1.2-api:2.13.1") diff --git a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/Main.kt b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/Main.kt index 26577ef2..80b3bb20 100644 --- a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/Main.kt +++ b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/Main.kt @@ -38,12 +38,12 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.test.TestCoroutineScope import mu.KotlinLogging import org.bson.Document +import org.bson.types.ObjectId import org.opendc.compute.simulator.allocation.* import org.opendc.experiments.sc20.experiment.attachMonitor import org.opendc.experiments.sc20.experiment.createFailureDomain import org.opendc.experiments.sc20.experiment.createProvisioner import org.opendc.experiments.sc20.experiment.model.Workload -import org.opendc.experiments.sc20.experiment.monitor.ParquetExperimentMonitor import org.opendc.experiments.sc20.experiment.processTrace import org.opendc.experiments.sc20.trace.Sc20ParquetTraceReader import org.opendc.experiments.sc20.trace.Sc20RawParquetTraceReader @@ -123,27 +123,6 @@ public class RunnerCli : CliktCommand(name = "runner") { .defaultLazy { File("traces/") } /** - * The path to the output directory. - */ - private val outputPath by option( - "--output", - help = "path to the results directory", - envvar = "OPENDC_OUTPUT" - ) - .file(canBeFile = false) - .defaultLazy { File("results/") } - - /** - * The Spark master to connect to. - */ - private val spark by option( - "--spark", - help = "Spark master to connect to", - envvar = "OPENDC_SPARK" - ) - .default("local[*]") - - /** * Connect to the user-specified database. */ private fun createDatabase(): MongoDatabase { @@ -164,8 +143,8 @@ public class RunnerCli : CliktCommand(name = "runner") { /** * Run a single scenario. */ - private suspend fun runScenario(portfolio: Document, scenario: Document, topologies: MongoCollection<Document>) { - val id = scenario.getString("_id") + private suspend fun runScenario(portfolio: Document, scenario: Document, topologies: MongoCollection<Document>): List<WebExperimentMonitor.Result> { + val id = scenario.getObjectId("_id") logger.info { "Constructing performance interference model" } @@ -188,12 +167,14 @@ public class RunnerCli : CliktCommand(name = "runner") { val targets = portfolio.get("targets", Document::class.java) - repeat(targets.getInteger("repeatsPerScenario")) { + val results = (0..targets.getInteger("repeatsPerScenario") - 1).map { logger.info { "Starting repeat $it" } runRepeat(scenario, it, topologies, traceReader, performanceInterferenceReader) } logger.info { "Finished simulation for scenario $id" } + + return results } /** @@ -205,8 +186,7 @@ public class RunnerCli : CliktCommand(name = "runner") { topologies: MongoCollection<Document>, traceReader: Sc20RawParquetTraceReader, performanceInterferenceReader: Sc20PerformanceInterferenceReader? - ) { - val id = scenario.getString("_id") + ): WebExperimentMonitor.Result { val seed = repeat val traceDocument = scenario.get("trace", Document::class.java) val workloadName = traceDocument.getString("traceId") @@ -240,13 +220,9 @@ public class RunnerCli : CliktCommand(name = "runner") { Workload(workloadName, workloadFraction), seed ) - val topologyId = scenario.getEmbedded(listOf("topology", "topologyId"), String::class.java) + val topologyId = scenario.getEmbedded(listOf("topology", "topologyId"), ObjectId::class.java) val environment = TopologyParser(topologies, topologyId) - val monitor = ParquetExperimentMonitor( - outputPath, - "scenario_id=$id/run_id=$repeat", - 4096 - ) + val monitor = WebExperimentMonitor() testScope.launch { val (bareMetalProvisioner, scheduler) = createProvisioner( @@ -292,9 +268,12 @@ public class RunnerCli : CliktCommand(name = "runner") { try { testScope.advanceUntilIdle() + testScope.uncaughtExceptions.forEach { it.printStackTrace() } } finally { monitor.close() } + + return monitor.getResult() } private val POLL_INTERVAL = 5000L // ms = 5 s @@ -308,9 +287,6 @@ public class RunnerCli : CliktCommand(name = "runner") { val portfolios = database.getCollection("portfolios") val topologies = database.getCollection("topologies") - logger.info { "Launching Spark" } - val resultProcessor = ResultProcessor(spark, outputPath) - logger.info { "Watching for queued scenarios" } while (true) { @@ -321,7 +297,7 @@ public class RunnerCli : CliktCommand(name = "runner") { continue } - val id = scenario.getString("_id") + val id = scenario.getObjectId("_id") logger.info { "Found queued scenario $id: attempting to claim" } @@ -340,13 +316,12 @@ public class RunnerCli : CliktCommand(name = "runner") { } try { - val portfolio = portfolios.find(Filters.eq("_id", scenario.getString("portfolioId"))).first()!! - runScenario(portfolio, scenario, topologies) + val portfolio = portfolios.find(Filters.eq("_id", scenario.getObjectId("portfolioId"))).first()!! + val results = runScenario(portfolio, scenario, topologies) - logger.info { "Starting result processing" } + logger.info { "Writing results to database" } - val result = resultProcessor.process(id) - manager.finish(id, result) + manager.finish(id, results) logger.info { "Successfully finished scenario $id" } } catch (e: Exception) { diff --git a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/ResultProcessor.kt b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/ResultProcessor.kt deleted file mode 100644 index f9a3cd2b..00000000 --- a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/ResultProcessor.kt +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.runner.web - -import org.apache.spark.sql.Column -import org.apache.spark.sql.Dataset -import org.apache.spark.sql.Row -import org.apache.spark.sql.SparkSession -import org.apache.spark.sql.functions.* -import java.io.File - -/** - * A helper class for processing the experiment results using Apache Spark. - */ -public class ResultProcessor(private val master: String, private val outputPath: File) { - /** - * Process the results of the scenario with the given [id]. - */ - public fun process(id: String): Result { - val spark = SparkSession.builder() - .master(master) - .appName("opendc-simulator-$id") - .config("spark.driver.bindAddress", "0.0.0.0") // Needed to allow the worker to connect to driver - .orCreate - - try { - val hostMetrics = spark.read().parquet(File(outputPath, "host-metrics/scenario_id=$id").path) - val provisionerMetrics = spark.read().parquet(File(outputPath, "provisioner-metrics/scenario_id=$id").path) - val res = aggregate(hostMetrics, provisionerMetrics).first() - - return Result( - res.getList<Long>(1), - res.getList<Long>(2), - res.getList<Long>(3), - res.getList<Long>(4), - res.getList<Double>(5), - res.getList<Double>(6), - res.getList<Double>(7), - res.getList<Int>(8), - res.getList<Long>(9), - res.getList<Long>(10), - res.getList<Long>(11), - res.getList<Int>(12), - res.getList<Int>(13), - res.getList<Int>(14), - res.getList<Int>(15) - ) - } finally { - spark.close() - } - } - - public data class Result( - public val totalRequestedBurst: List<Long>, - public val totalGrantedBurst: List<Long>, - public val totalOvercommittedBurst: List<Long>, - public val totalInterferedBurst: List<Long>, - public val meanCpuUsage: List<Double>, - public val meanCpuDemand: List<Double>, - public val meanNumDeployedImages: List<Double>, - public val maxNumDeployedImages: List<Int>, - public val totalPowerDraw: List<Long>, - public val totalFailureSlices: List<Long>, - public val totalFailureVmSlices: List<Long>, - public val totalVmsSubmitted: List<Int>, - public val totalVmsQueued: List<Int>, - public val totalVmsFinished: List<Int>, - public val totalVmsFailed: List<Int> - ) - - /** - * Perform aggregation of the experiment results. - */ - private fun aggregate(hostMetrics: Dataset<Row>, provisionerMetrics: Dataset<Row>): Dataset<Row> { - // Extrapolate the duration of the entries to span the entire trace - val hostMetricsExtra = hostMetrics - .withColumn("slice_counts", floor(col("duration") / lit(sliceLength))) - .withColumn("power_draw", col("power_draw") * col("slice_counts")) - .withColumn("state_int", states[col("state")]) - .withColumn("state_opposite_int", oppositeStates[col("state")]) - .withColumn("cpu_usage", col("cpu_usage") * col("slice_counts") * col("state_opposite_int")) - .withColumn("cpu_demand", col("cpu_demand") * col("slice_counts")) - .withColumn("failure_slice_count", col("slice_counts") * col("state_int")) - .withColumn("failure_vm_slice_count", col("slice_counts") * col("state_int") * col("vm_count")) - - // Process all data in a single run - val hostMetricsGrouped = hostMetricsExtra.groupBy("run_id") - - // Aggregate the summed total metrics - val systemMetrics = hostMetricsGrouped.agg( - sum("requested_burst").alias("total_requested_burst"), - sum("granted_burst").alias("total_granted_burst"), - sum("overcommissioned_burst").alias("total_overcommitted_burst"), - sum("interfered_burst").alias("total_interfered_burst"), - sum("power_draw").alias("total_power_draw"), - sum("failure_slice_count").alias("total_failure_slices"), - sum("failure_vm_slice_count").alias("total_failure_vm_slices") - ) - - // Aggregate metrics per host - val hvMetrics = hostMetrics - .groupBy("run_id", "host_id") - .agg( - sum("cpu_usage").alias("mean_cpu_usage"), - sum("cpu_demand").alias("mean_cpu_demand"), - avg("vm_count").alias("mean_num_deployed_images"), - count(lit(1)).alias("num_rows") - ) - .withColumn("mean_cpu_usage", col("mean_cpu_usage") / col("num_rows")) - .withColumn("mean_cpu_demand", col("mean_cpu_demand") / col("num_rows")) - .groupBy("run_id") - .agg( - avg("mean_cpu_usage").alias("mean_cpu_usage"), - avg("mean_cpu_demand").alias("mean_cpu_demand"), - avg("mean_num_deployed_images").alias("mean_num_deployed_images"), - max("mean_num_deployed_images").alias("max_num_deployed_images") - ) - - // Group the provisioner metrics per run - val provisionerMetricsGrouped = provisionerMetrics.groupBy("run_id") - - // Aggregate the provisioner metrics - val provisionerMetricsAggregated = provisionerMetricsGrouped.agg( - max("vm_total_count").alias("total_vms_submitted"), - max("vm_waiting_count").alias("total_vms_queued"), - max("vm_active_count").alias("total_vms_running"), - max("vm_inactive_count").alias("total_vms_finished"), - max("vm_failed_count").alias("total_vms_failed") - ) - - // Join the results into a single data frame - return systemMetrics - .join(hvMetrics, "run_id") - .join(provisionerMetricsAggregated, "run_id") - .select( - col("total_requested_burst"), - col("total_granted_burst"), - col("total_overcommitted_burst"), - col("total_interfered_burst"), - col("mean_cpu_usage"), - col("mean_cpu_demand"), - col("mean_num_deployed_images"), - col("max_num_deployed_images"), - col("total_power_draw"), - col("total_failure_slices"), - col("total_failure_vm_slices"), - col("total_vms_submitted"), - col("total_vms_queued"), - col("total_vms_finished"), - col("total_vms_failed") - ) - .groupBy(lit(1)) - .agg( - // TODO Check if order of values is correct - collect_list(col("total_requested_burst")).alias("total_requested_burst"), - collect_list(col("total_granted_burst")).alias("total_granted_burst"), - collect_list(col("total_overcommitted_burst")).alias("total_overcommitted_burst"), - collect_list(col("total_interfered_burst")).alias("total_interfered_burst"), - collect_list(col("mean_cpu_usage")).alias("mean_cpu_usage"), - collect_list(col("mean_cpu_demand")).alias("mean_cpu_demand"), - collect_list(col("mean_num_deployed_images")).alias("mean_num_deployed_images"), - collect_list(col("max_num_deployed_images")).alias("max_num_deployed_images"), - collect_list(col("total_power_draw")).alias("total_power_draw"), - collect_list(col("total_failure_slices")).alias("total_failure_slices"), - collect_list(col("total_failure_vm_slices")).alias("total_failure_vm_slices"), - collect_list(col("total_vms_submitted")).alias("total_vms_submitted"), - collect_list(col("total_vms_queued")).alias("total_vms_queued"), - collect_list(col("total_vms_finished")).alias("total_vms_finished"), - collect_list(col("total_vms_failed")).alias("total_vms_failed") - ) - } - - // Spark helper functions - private operator fun Column.times(other: Column): Column = `$times`(other) - private operator fun Column.div(other: Column): Column = `$div`(other) - private operator fun Column.get(other: Column): Column = this.apply(other) - - private val sliceLength = 5 * 60 * 1000 - private val states = map( - lit("ERROR"), - lit(1), - lit("ACTIVE"), - lit(0), - lit("SHUTOFF"), - lit(0) - ) - private val oppositeStates = map( - lit("ERROR"), - lit(0), - lit("ACTIVE"), - lit(1), - lit("SHUTOFF"), - lit(1) - ) -} diff --git a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/ScenarioManager.kt b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/ScenarioManager.kt index 504fccdc..a3907051 100644 --- a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/ScenarioManager.kt +++ b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/ScenarioManager.kt @@ -26,6 +26,7 @@ import com.mongodb.client.MongoCollection import com.mongodb.client.model.Filters import com.mongodb.client.model.Updates import org.bson.Document +import org.bson.types.ObjectId import java.time.Instant /** @@ -44,7 +45,7 @@ public class ScenarioManager(private val collection: MongoCollection<Document>) /** * Claim the scenario in the database with the specified id. */ - public fun claim(id: String): Boolean { + public fun claim(id: ObjectId): Boolean { val res = collection.findOneAndUpdate( Filters.and( Filters.eq("_id", id), @@ -61,7 +62,7 @@ public class ScenarioManager(private val collection: MongoCollection<Document>) /** * Update the heartbeat of the specified scenario. */ - public fun heartbeat(id: String) { + public fun heartbeat(id: ObjectId) { collection.findOneAndUpdate( Filters.and( Filters.eq("_id", id), @@ -74,7 +75,7 @@ public class ScenarioManager(private val collection: MongoCollection<Document>) /** * Mark the scenario as failed. */ - public fun fail(id: String) { + public fun fail(id: ObjectId) { collection.findOneAndUpdate( Filters.eq("_id", id), Updates.combine( @@ -87,28 +88,27 @@ public class ScenarioManager(private val collection: MongoCollection<Document>) /** * Persist the specified results. */ - public fun finish(id: String, result: ResultProcessor.Result) { + public fun finish(id: ObjectId, results: List<WebExperimentMonitor.Result>) { collection.findOneAndUpdate( Filters.eq("_id", id), Updates.combine( Updates.set("simulation.state", "FINISHED"), Updates.unset("simulation.time"), - Updates.set("results.total_requested_burst", result.totalRequestedBurst), - Updates.set("results.total_granted_burst", result.totalGrantedBurst), - Updates.set("results.total_overcommitted_burst", result.totalOvercommittedBurst), - Updates.set("results.total_interfered_burst", result.totalInterferedBurst), - Updates.set("results.mean_cpu_usage", result.meanCpuUsage), - Updates.set("results.mean_cpu_demand", result.meanCpuDemand), - Updates.set("results.mean_num_deployed_images", result.meanNumDeployedImages), - Updates.set("results.max_num_deployed_images", result.maxNumDeployedImages), - Updates.set("results.max_num_deployed_images", result.maxNumDeployedImages), - Updates.set("results.total_power_draw", result.totalPowerDraw), - Updates.set("results.total_failure_slices", result.totalFailureSlices), - Updates.set("results.total_failure_vm_slices", result.totalFailureVmSlices), - Updates.set("results.total_vms_submitted", result.totalVmsSubmitted), - Updates.set("results.total_vms_queued", result.totalVmsQueued), - Updates.set("results.total_vms_finished", result.totalVmsFinished), - Updates.set("results.total_vms_failed", result.totalVmsFailed) + Updates.set("results.total_requested_burst", results.map { it.totalRequestedBurst }), + Updates.set("results.total_granted_burst", results.map { it.totalGrantedBurst }), + Updates.set("results.total_overcommitted_burst", results.map { it.totalOvercommittedBurst }), + Updates.set("results.total_interfered_burst", results.map { it.totalInterferedBurst }), + Updates.set("results.mean_cpu_usage", results.map { it.meanCpuUsage }), + Updates.set("results.mean_cpu_demand", results.map { it.meanCpuDemand }), + Updates.set("results.mean_num_deployed_images", results.map { it.meanNumDeployedImages }), + Updates.set("results.max_num_deployed_images", results.map { it.maxNumDeployedImages }), + Updates.set("results.total_power_draw", results.map { it.totalPowerDraw }), + Updates.set("results.total_failure_slices", results.map { it.totalFailureSlices }), + Updates.set("results.total_failure_vm_slices", results.map { it.totalFailureVmSlices }), + Updates.set("results.total_vms_submitted", results.map { it.totalVmsSubmitted }), + Updates.set("results.total_vms_queued", results.map { it.totalVmsQueued }), + Updates.set("results.total_vms_finished", results.map { it.totalVmsFinished }), + Updates.set("results.total_vms_failed", results.map { it.totalVmsFailed }) ) ) } diff --git a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt index 5e483271..80bd20f7 100644 --- a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt +++ b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt @@ -31,6 +31,7 @@ import com.mongodb.client.model.Projections import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.bson.Document +import org.bson.types.ObjectId import org.opendc.compute.core.metal.NODE_CLUSTER import org.opendc.compute.core.metal.service.ProvisioningService import org.opendc.compute.core.metal.service.SimpleProvisioningService @@ -51,7 +52,7 @@ import java.util.* /** * A helper class that converts the MongoDB topology into an OpenDC environment. */ -public class TopologyParser(private val collection: MongoCollection<Document>, private val id: String) : EnvironmentReader { +public class TopologyParser(private val collection: MongoCollection<Document>, private val id: ObjectId) : EnvironmentReader { /** * Parse the topology with the specified [id]. */ @@ -60,7 +61,7 @@ public class TopologyParser(private val collection: MongoCollection<Document>, p val random = Random(0) for (machine in fetchMachines(id)) { - val clusterId = machine.getString("rack_id") + val clusterId = machine.get("rack_id").toString() val position = machine.getInteger("position") val processors = machine.getList("cpus", Document::class.java).flatMap { cpu -> @@ -121,7 +122,7 @@ public class TopologyParser(private val collection: MongoCollection<Document>, p /** * Fetch the metadata of the topology. */ - private fun fetchName(id: String): String { + private fun fetchName(id: ObjectId): String { return collection.aggregate( listOf( Aggregates.match(Filters.eq("_id", id)), @@ -135,7 +136,7 @@ public class TopologyParser(private val collection: MongoCollection<Document>, p /** * Fetch a topology from the database with the specified [id]. */ - private fun fetchMachines(id: String): AggregateIterable<Document> { + private fun fetchMachines(id: ObjectId): AggregateIterable<Document> { return collection.aggregate( listOf( Aggregates.match(Filters.eq("_id", id)), diff --git a/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/WebExperimentMonitor.kt b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/WebExperimentMonitor.kt new file mode 100644 index 00000000..7ef25552 --- /dev/null +++ b/simulator/opendc-runner-web/src/main/kotlin/org/opendc/runner/web/WebExperimentMonitor.kt @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2020 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.runner.web + +import mu.KotlinLogging +import org.opendc.compute.core.Server +import org.opendc.compute.core.ServerState +import org.opendc.compute.core.virt.driver.VirtDriver +import org.opendc.compute.core.virt.service.VirtProvisioningEvent +import org.opendc.experiments.sc20.experiment.monitor.ExperimentMonitor +import org.opendc.experiments.sc20.telemetry.HostEvent +import kotlin.math.max + +/** + * An [ExperimentMonitor] that tracks the aggregate metrics for each repeat. + */ +public class WebExperimentMonitor : ExperimentMonitor { + private val logger = KotlinLogging.logger {} + private val currentHostEvent = mutableMapOf<Server, HostEvent>() + private var startTime = -1L + + override fun reportVmStateChange(time: Long, server: Server) { + if (startTime < 0) { + startTime = time + + // Update timestamp of initial event + currentHostEvent.replaceAll { _, v -> v.copy(timestamp = startTime) } + } + } + + override fun reportHostStateChange( + time: Long, + driver: VirtDriver, + server: Server + ) { + logger.debug { "Host ${server.uid} changed state ${server.state} [$time]" } + + val previousEvent = currentHostEvent[server] + + val roundedTime = previousEvent?.let { + val duration = time - it.timestamp + val k = 5 * 60 * 1000L // 5 min in ms + val rem = duration % k + + if (rem == 0L) { + time + } else { + it.timestamp + duration + k - rem + } + } ?: time + + reportHostSlice( + roundedTime, + 0, + 0, + 0, + 0, + 0.0, + 0.0, + 0, + server + ) + } + + private val lastPowerConsumption = mutableMapOf<Server, Double>() + + override fun reportPowerConsumption(host: Server, draw: Double) { + lastPowerConsumption[host] = draw + } + + override fun reportHostSlice( + time: Long, + requestedBurst: Long, + grantedBurst: Long, + overcommissionedBurst: Long, + interferedBurst: Long, + cpuUsage: Double, + cpuDemand: Double, + numberOfDeployedImages: Int, + hostServer: Server, + duration: Long + ) { + val previousEvent = currentHostEvent[hostServer] + when { + previousEvent == null -> { + val event = HostEvent( + time, + 5 * 60 * 1000L, + hostServer, + numberOfDeployedImages, + requestedBurst, + grantedBurst, + overcommissionedBurst, + interferedBurst, + cpuUsage, + cpuDemand, + lastPowerConsumption[hostServer] ?: 200.0, + hostServer.flavor.cpuCount + ) + + currentHostEvent[hostServer] = event + } + previousEvent.timestamp == time -> { + val event = HostEvent( + time, + previousEvent.duration, + hostServer, + numberOfDeployedImages, + requestedBurst, + grantedBurst, + overcommissionedBurst, + interferedBurst, + cpuUsage, + cpuDemand, + lastPowerConsumption[hostServer] ?: 200.0, + hostServer.flavor.cpuCount + ) + + currentHostEvent[hostServer] = event + } + else -> { + processHostEvent(previousEvent) + + val event = HostEvent( + time, + time - previousEvent.timestamp, + hostServer, + numberOfDeployedImages, + requestedBurst, + grantedBurst, + overcommissionedBurst, + interferedBurst, + cpuUsage, + cpuDemand, + lastPowerConsumption[hostServer] ?: 200.0, + hostServer.flavor.cpuCount + ) + + currentHostEvent[hostServer] = event + } + } + } + + private var hostAggregateMetrics: AggregateHostMetrics = AggregateHostMetrics() + private val hostMetrics: MutableMap<Server, HostMetrics> = mutableMapOf() + + private fun processHostEvent(event: HostEvent) { + val slices = event.duration / SLICE_LENGTH + + hostAggregateMetrics = AggregateHostMetrics( + hostAggregateMetrics.totalRequestedBurst + event.requestedBurst, + hostAggregateMetrics.totalGrantedBurst + event.grantedBurst, + hostAggregateMetrics.totalOvercommittedBurst + event.overcommissionedBurst, + hostAggregateMetrics.totalInterferedBurst + event.interferedBurst, + hostAggregateMetrics.totalPowerDraw + (slices * (event.powerDraw / 12)), + hostAggregateMetrics.totalFailureSlices + if (event.host.state != ServerState.ACTIVE) slices.toLong() else 0, + hostAggregateMetrics.totalFailureVmSlices + if (event.host.state != ServerState.ACTIVE) event.vmCount * slices.toLong() else 0 + ) + + hostMetrics.compute(event.host) { key, prev -> + HostMetrics( + (event.cpuUsage.takeIf { event.host.state == ServerState.ACTIVE } ?: 0.0) + (prev?.cpuUsage ?: 0.0), + (event.cpuDemand.takeIf { event.host.state == ServerState.ACTIVE } ?: 0.0) + (prev?.cpuDemand ?: 0.0), + event.vmCount + (prev?.vmCount ?: 0), + 1 + (prev?.count ?: 0) + ) + } + } + + private val SLICE_LENGTH: Long = 5 * 60 * 1000 + + public data class AggregateHostMetrics( + val totalRequestedBurst: Long = 0, + val totalGrantedBurst: Long = 0, + val totalOvercommittedBurst: Long = 0, + val totalInterferedBurst: Long = 0, + val totalPowerDraw: Double = 0.0, + val totalFailureSlices: Long = 0, + val totalFailureVmSlices: Long = 0, + ) + + public data class HostMetrics( + val cpuUsage: Double, + val cpuDemand: Double, + val vmCount: Long, + val count: Long + ) + + private var provisionerMetrics: AggregateProvisionerMetrics = AggregateProvisionerMetrics() + + override fun reportProvisionerMetrics(time: Long, event: VirtProvisioningEvent.MetricsAvailable) { + provisionerMetrics = AggregateProvisionerMetrics( + max(event.totalVmCount, provisionerMetrics.vmTotalCount), + max(event.waitingVmCount, provisionerMetrics.vmWaitingCount), + max(event.activeVmCount, provisionerMetrics.vmActiveCount), + max(event.inactiveVmCount, provisionerMetrics.vmInactiveCount), + max(event.failedVmCount, provisionerMetrics.vmFailedCount), + ) + } + + public data class AggregateProvisionerMetrics( + val vmTotalCount: Int = 0, + val vmWaitingCount: Int = 0, + val vmActiveCount: Int = 0, + val vmInactiveCount: Int = 0, + val vmFailedCount: Int = 0 + ) + + override fun close() { + for ((_, event) in currentHostEvent) { + processHostEvent(event) + } + currentHostEvent.clear() + } + + public fun getResult(): Result { + return Result( + hostAggregateMetrics.totalRequestedBurst, + hostAggregateMetrics.totalGrantedBurst, + hostAggregateMetrics.totalOvercommittedBurst, + hostAggregateMetrics.totalInterferedBurst, + hostMetrics.map { it.value.cpuUsage / it.value.count }.average(), + hostMetrics.map { it.value.cpuDemand / it.value.count }.average(), + hostMetrics.map { it.value.vmCount.toDouble() / it.value.count }.average(), + hostMetrics.map { it.value.vmCount.toDouble() / it.value.count }.maxOrNull() ?: 0.0, + hostAggregateMetrics.totalPowerDraw, + hostAggregateMetrics.totalFailureSlices, + hostAggregateMetrics.totalFailureVmSlices, + provisionerMetrics.vmTotalCount, + provisionerMetrics.vmWaitingCount, + provisionerMetrics.vmInactiveCount, + provisionerMetrics.vmFailedCount, + ) + } + + public data class Result( + public val totalRequestedBurst: Long, + public val totalGrantedBurst: Long, + public val totalOvercommittedBurst: Long, + public val totalInterferedBurst: Long, + public val meanCpuUsage: Double, + public val meanCpuDemand: Double, + public val meanNumDeployedImages: Double, + public val maxNumDeployedImages: Double, + public val totalPowerDraw: Double, + public val totalFailureSlices: Long, + public val totalFailureVmSlices: Long, + public val totalVmsSubmitted: Int, + public val totalVmsQueued: Int, + public val totalVmsFinished: Int, + public val totalVmsFailed: Int + ) +} diff --git a/simulator/opendc-runner-web/src/main/resources/log4j2.xml b/simulator/opendc-runner-web/src/main/resources/log4j2.xml index 16cedf34..87179332 100644 --- a/simulator/opendc-runner-web/src/main/resources/log4j2.xml +++ b/simulator/opendc-runner-web/src/main/resources/log4j2.xml @@ -36,12 +36,6 @@ <Logger name="org.opendc.runner" level="info" additivity="false"> <AppenderRef ref="Console"/> </Logger> - <Logger name="org.apache.hadoop" level="warn" additivity="false"> - <AppenderRef ref="Console"/> - </Logger> - <Logger name="org.apache.spark" level="info" additivity="false"> - <AppenderRef ref="Console"/> - </Logger> <Root level="error"> <AppenderRef ref="Console"/> </Root> |
