diff --git a/Makefile b/Makefile index 04ee401942d0c043499bf978c5079aff1f7e2c81..70bbc2cfb047431035886c7714ba1cf60195e5ae 100755 --- a/Makefile +++ b/Makefile @@ -63,12 +63,14 @@ libinstall: mkdir -p $(DESTDIR)/etc/atuned/rules mkdir -p $(DESTDIR)/etc/atuned/training mkdir -p $(DESTDIR)/etc/atuned/classification + mkdir -p $(DESTDIR)/etc/atuned/webserver mkdir -p $(DESTDIR)$(PREFIX)/lib/atuned/modules mkdir -p $(DESTDIR)$(PREFIX)/lib/atuned/profiles mkdir -p $(DESTDIR)$(PREFIX)/lib/atuned/training mkdir -p $(DESTDIR)$(PREFIX)/share/atuned mkdir -p $(DESTDIR)$(PREFIX)/$(LIBEXEC)/atuned/scripts mkdir -p $(DESTDIR)$(PREFIX)/$(LIBEXEC)/atuned/analysis + mkdir -p $(DESTDIR)$(PREFIX)/$(LIBEXEC)/atuned/resources mkdir -p $(DESTDIR)/var/lib/atuned mkdir -p $(DESTDIR)/var/run/atuned mkdir -p $(DESTDIR)/var/atuned @@ -86,6 +88,8 @@ libinstall: chmod -R 750 $(DESTDIR)$(PREFIX)/$(LIBEXEC)/atuned/scripts/ \cp -rf analysis/* $(DESTDIR)$(PREFIX)/$(LIBEXEC)/atuned/analysis/ chmod -R 750 $(DESTDIR)$(PREFIX)/$(LIBEXEC)/atuned/analysis/ + \cp -rf resources/* $(DESTDIR)$(PREFIX)/$(LIBEXEC)/atuned/resources/ + chmod -R 750 $(DESTDIR)$(PREFIX)/$(LIBEXEC)/atuned/resources/ \cp -rf profiles/* $(DESTDIR)$(PREFIX)/lib/atuned/profiles/ chmod -R 640 $(DESTDIR)$(PREFIX)/lib/atuned/profiles/ @echo "END INSTALL A-Tune" diff --git a/analysis/engine/optimizer.py b/analysis/engine/optimizer.py index 46dedb8ec9d17c4488e04cb2aaddc893d5e86ffa..4be51f5381341595cfd9f7dc8506e18554a9e76e 100644 --- a/analysis/engine/optimizer.py +++ b/analysis/engine/optimizer.py @@ -61,7 +61,7 @@ class Optimizer(Resource): x_ref = args.get("x_ref") y_ref = args.get("y_ref") result = {} - engine = optimizer.Optimizer(task_id, args["knobs"], child_conn, engine=args.get("engine"), + engine = optimizer.Optimizer(task_id, args["knobs"], child_conn, args["prj_name"], engine=args.get("engine"), max_eval=args.get("max_eval"), n_random_starts=args.get("random_starts"), x0=x_ref, y0=y_ref, split_count=args.get("split_count"), diff --git a/analysis/engine/parser.py b/analysis/engine/parser.py index 6a21748bde78b1e71bd5850bc1812419090698f3..42d45ba9ba4e7488df8e05b90e87e022d8a36b50 100644 --- a/analysis/engine/parser.py +++ b/analysis/engine/parser.py @@ -40,6 +40,8 @@ OPTIMIZER_POST_PARSER.add_argument('sel_feature', type=bool, location='json', help="enable the feature selection or not") OPTIMIZER_POST_PARSER.add_argument('noise', type=float, location='json', help="the noise can not be null") +OPTIMIZER_POST_PARSER.add_argument('prj_name', type=str, location='json', + help="prj_name cannot be null") OPTIMIZER_PUT_PARSER = reqparse.RequestParser() OPTIMIZER_PUT_PARSER.add_argument('iterations', type=int, required=True, diff --git a/analysis/engine/utils/utils.py b/analysis/engine/utils/utils.py index 66cb761f4352373b184cdfca1a49078bd460f9b9..3b809525ef8aa2d84e65a38e2d3b508ebf96e270 100644 --- a/analysis/engine/utils/utils.py +++ b/analysis/engine/utils/utils.py @@ -42,3 +42,12 @@ def extract_file(file_path, target_path): tar.close() res_path = file_path.rpartition('-')[0] return res_path + + +def add_data_to_file(data, mode, filename): + """write tuning result to file""" + path = "/etc/atuned/webserver/" + filename + ".txt" + file_handle = open(path, mode) + file_handle.write(str(data)) + file_handle.write("\n") + file_handle.close() diff --git a/analysis/optimizer/optimizer.py b/analysis/optimizer/optimizer.py index b0af6c73c66d723a3a20be5ed5adee08b5eaea88..5b15719df569f5c4d19fa9f716ba255679e0981b 100644 --- a/analysis/optimizer/optimizer.py +++ b/analysis/optimizer/optimizer.py @@ -20,6 +20,7 @@ import numbers import multiprocessing import collections import numpy as np +import analysis.engine.utils.utils as utils from sklearn.linear_model import Lasso from sklearn.preprocessing import StandardScaler @@ -38,11 +39,12 @@ LOGGER = logging.getLogger(__name__) class Optimizer(multiprocessing.Process): """find optimal settings and generate optimized profile""" - def __init__(self, name, params, child_conn, engine="bayes", max_eval=50, sel_feature=False, + def __init__(self, name, params, child_conn, prj_name, engine="bayes", max_eval=50, sel_feature=False, x0=None, y0=None, n_random_starts=20, split_count=5, noise=0.00001 ** 2): super(Optimizer, self).__init__(name=name) self.knobs = params self.child_conn = child_conn + self.project_name = prj_name self.engine = engine self.noise = noise self.max_eval = int(max_eval) @@ -236,6 +238,12 @@ class Optimizer(multiprocessing.Process): performance = [] labels = [] estimator = None + + parameters = "" + for knob in self.knobs: + parameters += knob["name"] + "," + utils.add_data_to_file(parameters[:-1], "w", self.project_name) + try: if self.engine == 'random' or self.engine == 'forest' or \ self.engine == 'gbrt' or self.engine == 'bayes' or self.engine == 'extraTrees': @@ -304,6 +312,15 @@ class Optimizer(multiprocessing.Process): LOGGER.info("next_y: %s", next_y) ret = optimizer.tell(next_x, next_y) LOGGER.info("finish (ref_x, ref_y) tell") + + data = "" + for element in next_x: + data += str(element) + "," + data += str(abs(next_y)) + utils.add_data_to_file(data, "a", self.project_name) + + utils.add_data_to_file("END", "a", self.project_name) + elif self.engine == 'abtest': abtuning_manager = ABtestTuningManager(self.knobs, self.child_conn, self.split_count) diff --git a/common/models/optimizer.go b/common/models/optimizer.go index abc227075cbfcb7f040641c4f05da26bb9e08ec9..a8bb62fb0e1206049cc7650b58c7d4fe6f168f0a 100644 --- a/common/models/optimizer.go +++ b/common/models/optimizer.go @@ -33,6 +33,7 @@ type OptimizerPostBody struct { SplitCount int32 `json:"split_count"` Noise float64 `json:"noise"` SelFeature bool `json:"sel_feature"` + PrjName string `json:"prj_name"` } // Knob body store the tuning properties diff --git a/common/tuning/optimizer.go b/common/tuning/optimizer.go index 782d52bfaa1a4efd0459232121c57bb24492d6ed..43b1390e8568e334dc74bfc8f9e7edf06ce936a0 100644 --- a/common/tuning/optimizer.go +++ b/common/tuning/optimizer.go @@ -135,6 +135,7 @@ func (o *Optimizer) createOptimizerTask(ch chan *PB.TuningMessage, iters int32, optimizerBody.SplitCount = o.SplitCount optimizerBody.Noise = config.Noise optimizerBody.Knobs = make([]models.Knob, 0) + optimizerBody.PrjName = o.Prj.Project for _, item := range o.Prj.Object { if item.Info.Skip { continue diff --git a/resources/static/css/style.css b/resources/static/css/style.css new file mode 100644 index 0000000000000000000000000000000000000000..b37ea006010a1bac9c86fe1fc251c92546b7eab4 --- /dev/null +++ b/resources/static/css/style.css @@ -0,0 +1,219 @@ +/* + Copyright (c) 2019 Huawei Technologies Co., Ltd. + A-Tune is licensed under the Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + PURPOSE. + See the Mulan PSL v2 for more details. + Create: 2020-08-17 +*/ + + +/* left side bar */ +.sidebar-div { + position: relative; + top: 0; + left: 0; + height: 100vh; + width: 300px; + background: linear-gradient(white, midnightblue); +/* z-index: 200;*/ + box-shadow: 3px 0 5px 0 #000; +} + +/* logo */ +.logo-div { + position: relative; + padding: 3px 5px; +} + +.logo-div:before { + content: ""; + position: absolute; + left: 15px; + right: 15px; + top: auto; + bottom: 0; + height: 1px; + width: 270px; + background-color: grey; +} + +/* side bar wrapper */ +.sidebar-wrapper-div { + position: relative; + height: calc(100vh - 200px); + overflow: auto; + width: 300px; + z-index: 4; +} + +.nav { + list-style: none; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + margin-top: 20px; + display: block; +} + +.nav li { + height: 60px; + text-align: center; + font-family: sans-serif; + font-size: 17px; + position: relative; + overflow: hidden; + vertical-align: middle; +} + +.nav li:hover { + border-radius: 40px; + background: #f5f5dc; + width: 260px; + margin: auto; + border: none; + opacity: .5; +} + +.nav li.active { + border-radius: 40px; + background: #f0f8ff; + width: 260px; + margin: auto; + border: none; +} + +.nav a { + color: #000; + height: inherit; + text-decoration: none; + vertical-align: middle; +} + +.nav p { + margin: 8px; + height: 40px; + padding: 0; + vertical-align: middle; +} + +/* main part */ +.main-panel { + position: fixed; + top: 0; + left: 300px; + height: 100vh; + width: -webkit-fill-available; +} + +/* top bar */ +.nav-bar-div { + margin: 0 auto 60px; +} + +.nav-bar { + height: 60px; + width: calc(100% - 300px); + position: fixed; + background: #f0ffff; + z-index: 100; +/* box-shadow: 0 3px 5px 0 black;*/ +} + +.nav-bar-right { + justify-content: flex-end; + -webkit-box-pack: end; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; +} + +.nav-bar-right input { + font-size: 14px; + padding: 9px; + border-radius: 25px; + outline: none; +} + +.search-bar { + opacity: .5; + font-size: 14px; + -webkit-font-smoothing: antialiased; + padding: 9px; + width: 250px; +} + +.search-icon { + height: 22px; + vertical-align: middle; + cursor: pointer; +} + +.nav-bar-right ul { + list-style: none; + flex-direction: row; + display: flex; + margin: auto 0; + padding: 3px 0 0 5px; +} + +.nav-bar-right li { + padding: 0 20px 0 0; +} + +.nav-bar-right a { + color: #000; + text-decoration: none; +} + +.nav-bar-right span:hover { + opacity: .3; +} + +.nav-bar-right a:hover { + opacity: .3; +} + +.nav-dropdown:before { + display: inline-block; + position: absolute; + width: 0; + height: 0; + vertical-align: middle; + content: ""; + top: -5px; + left: auto; + right: 20px; + color: #FFF; + border-bottom: .4em solid; + border-right: .4em solid transparent; + border-left: .4em solid transparent; + border-radius: 5px; +} + +.dropdown-item { + margin: 5px; + padding: 2px 5px; +} + +.open > .dropdown-menu { + display: grid; +} + +/* main page */ +#tuning-chart { + overflow-y: scroll; + height: calc(100vh - 60px); +} + +.performance-chart { + width: 500px; + height: 400px; + float: left; + display: inline; +} diff --git a/resources/static/js/temp.js b/resources/static/js/temp.js new file mode 100644 index 0000000000000000000000000000000000000000..89311cd5b37d87198032c4c8114aa7dcc57eea65 --- /dev/null +++ b/resources/static/js/temp.js @@ -0,0 +1,169 @@ +/** + * @file help function for html + * + * Copyright (c) 2019 Huawei Technologies Co., Ltd. + * A-Tune is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + * Create: 2020-08-17 +*/ + + +var socket; +var line; +var projectName; +var intervalId; +var timestamp; + +function deleteOld() { + var container = document.getElementById('tuning-chart'); + while (container.hasChildNodes()) { + container.removeChild(container.firstChild); + } +} + +function initNdiv(list, name) { + list.push('performance'); + list.push('best performance'); + var container = window.document.getElementById('tuning-chart'); + list.forEach(function (item) { + var br = document.createElement('br'); + container.appendChild(br); + + var div = document.createElement('div'); + div.id = 'chart-' + item; + div.className = 'performance-chart'; + container.appendChild(div); + var chart = echarts.init(document.getElementById(div.id)); + var option = { + title: { + text: item + }, + tooltip: {}, + xAxis: { + name: 'times', + data: [] + }, + yAxis: { + name: 'value', + }, + series: [{ + name: item, + type: 'line', + data: [] + }] + }; + chart.setOption(option); + }); + projectName = name; +} + +function updateChart(name, times, value) { + console.log(name, '-', times, '-', value); + var id = 'chart-' + name; + var chart = echarts.init(document.getElementById(id)); + var oldData = chart.getOption().series[0].data; + var oldX = chart.getOption().xAxis[0].data; + for (var i in times) { + oldX.push(times[i]); + oldData.push(value[i]); + } + chart.setOption({ + xAxis: {data: oldX}, + series: [{data: oldData}] + }); +} + +function appendPrjList(list, timestamp) { + var container = document.getElementById('tuning-chart'); + console.log(list); + var h4 = document.createElement('h4'); + h4.appendChild(document.createTextNode('Project:')); + container.appendChild(h4); + if (list.length === 0) { + h4 = document.createElement('h4'); + h4.appendChild(document.createTextNode('No tuning project')); + container.appendChild(h4); + return; + } + var ul = document.createElement('ul'); + container.appendChild(ul); + for (var i in list) { + var li = document.createElement('li'); + li.id = 'project-name-' + list[i]; + li.appendChild(document.createTextNode(list[i])); + ul.appendChild(li); + li.style.cursor = 'pointer'; + li.onclick = function () { + socket.emit('inital_chart', {'prj_name': list[i]}, timestamp, namespace = '/tuning'); + }; + } +} + +function updateChartInit(nameList, lineNum, value) { + var lineList = []; + nameList.push('performance'); + console.log(value, value.length); + for (var i in value[0]) { + lineList[i] = parseInt(lineNum, 10) + parseInt(i, 10); + } + for (var i in value) { + updateChart(nameList[i], lineList, value[i]); + } +} + +function updateInterval() { + socket.emit('update_chart', projectName, line, timestamp, namespace = '/tuning'); +} + +function setSocket() { + $(document).ready(function () { + console.log((new Date()).valueOf()); + var namespace = '/tuning'; + socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace); + timestamp = (new Date()).valueOf(); + socket.emit('show_page', timestamp, namespace = '/tuning'); + + line = 0; + socket.on('get_all_tuning_list', function (res) { + if (res['timestamp'] === timestamp) { + deleteOld(); + appendPrjList(res['prj_list'], res['timestamp']); + } + }); + + socket.on('inital_chart', function (res) { + if (res['timestamp'] === timestamp) { + deleteOld(); + initNdiv(res['graph_list'], res['prj_name']); + intervalId = setInterval(updateInterval, 2000); + } + }); + + socket.on('update_chart', function (res) { + if (res['timestamp'] === timestamp) { + line = res['next_line']; + if (line === -1) { + window.clearInterval(intervalId); + } + updateChartInit(res['name'], res['num'] + 1, res['value']); + } + }); + + socket.on('file_removed', function (res) { + if (res['timestamp'] === timestamp) { + window.clearInterval(intervalId); + var container = document.getElementById('tuning-chart'); + container.appendChild(document.createElement('br')); + var h4 = document.createElement('h4'); + h4.appendChild(document.createTextNode('File ' + res['prj_name'] + ' has been delete')); + container.appendChild(h4); + } + }); + }); +} diff --git a/resources/templates/tuning.html b/resources/templates/tuning.html new file mode 100644 index 0000000000000000000000000000000000000000..de32b625f5da81a033361d6870e9d4ff5e9606bf --- /dev/null +++ b/resources/templates/tuning.html @@ -0,0 +1,138 @@ + + + + + + + Tuning + + + + + + + + + + + + + + + +
+ + + +
+ +
+ +
+
+ + diff --git a/resources/web.py b/resources/web.py new file mode 100644 index 0000000000000000000000000000000000000000..f808593cebe0ba90a1739176efa07a7f1cb7cb5d --- /dev/null +++ b/resources/web.py @@ -0,0 +1,104 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# Copyright (c) 2019 Huawei Technologies Co., Ltd. +# A-Tune is licensed under the Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# PURPOSE. +# See the Mulan PSL v2 for more details. +# Create: 2020-08-17 + +""" +Web UI initialization +""" + + +from flask import Flask, render_template +from flask_socketio import SocketIO +import os +import numpy +import logging + +APP = Flask(__name__, template_folder='./templates', static_folder='./static') +APP.config['TEMPLATES_AUTO_RELOAD'] = True +APP.jinja_env.auto_reload = True +socketio = SocketIO(APP, async_mode=None) + + +@APP.route('/tuning') +def index(): + """open tuning page""" + return render_template('tuning.html', async_mode=socketio.async_mode) + + +@socketio.on('connect', namespace='/tuning') +def init_connect(): + """inital connection""" + return + + +@socketio.on('show_page', namespace='/tuning') +def show_page(timestamp, _): + """list all tuning project name on page""" + path = '/etc/atuned/webserver' + filelist = os.listdir(path) + res = [] + for each in filelist: + res.append(each.split('.')[0]) + socketio.emit('get_all_tuning_list', {'prj_list': res, 'timestamp': timestamp}, namespace='/tuning') + return + + +@socketio.on('inital_chart', namespace='/tuning') +def inital_tuning_page(message, timestamp, _): + """inital chart graph for project""" + prj_name = message['prj_name'] + path = '/etc/atuned/webserver/' + prj_name + '.txt' + if not os.path.exists(path): + socketio.emit('file_removed', {'prj_name': prj_name, 'timestamp': timestamp}, namespace='/tuning') + return + graph_list = [] + with open(path, 'r') as tuning_file: + graph_list = tuning_file.readline()[:-1].split(',') + socketio.emit('inital_chart', + {'graph_list': graph_list, 'prj_name': prj_name, 'timestamp': timestamp}, + namespace='/tuning') + + +@socketio.on('update_chart', namespace='/tuning') +def update_tuning_page(prj_name, num, timestamp, _): + """get info for chart""" + path = '/etc/atuned/webserver/' + prj_name + '.txt' + if not os.path.exists(path): + socketio.emit('file_removed', {'prj_name': prj_name, 'timestamp': timestamp}, namespace='/tuning') + return + res = [] + count = 0 + first_line = '' + eof = False + with open(path, 'r') as tuning_file: + first_line = tuning_file.readline()[:-1].split(',') + lines = tuning_file.readlines() + while num + count < len(lines): + if lines[num + count][:-1] == 'END': + eof = True + break + line_list = lines[num + count][:-1].split(',') + res.append(line_list) + count += 1 + if count == 5: + break + res = numpy.array(res).T.tolist() + next_line = num + count + if eof: + next_line = -1 + socketio.emit('update_chart', + {'name': first_line, 'num': num, 'next_line': next_line, 'value': res, 'timestamp': timestamp}, + namespace='/tuning') + + +if __name__ == '__main__': + socketio.run(APP, host='9.41.51.85', port=10086)