Make skydb just a ctl-style helper (e.g. apachectl)

This makes it much easier to work with a device where we
don't expect a persistent commandline session.  Prompt no longer
actually makes a prompt but rather just runs an http server.

This is just a v1 patch.  It doesn't work with android yet
and it isn't smart enough to allow you to run more than one
copy of prompt.cc at the same time (since you can't control
the port it listens on).  If you have a second copy of
prompt runnning (or anything else bound to port 7777) it will
just crash.

We could make this a lot better, including splitting out the
pid-file tracking for sky_server into sky_server instead of
having skydb manage it.

R=ojan@chromium.org, abarth@chromium.org
BUG=

Review URL: https://codereview.chromium.org/840973002
This commit is contained in:
Eric Seidel 2015-01-08 13:01:15 -08:00
parent 6d658c8c95
commit 5f587b2878
4 changed files with 197 additions and 109 deletions

View File

@ -16,6 +16,8 @@ mojo_native_application("prompt") {
"//mojo/application",
"//mojo/public/cpp/bindings",
"//mojo/public/cpp/utility",
"//net",
"//net:http_server",
"//services/tracing:bindings",
"//sky/tools/debugger:bindings",
"//sky/viewer:bindings",

View File

@ -8,29 +8,17 @@
#include "mojo/public/c/system/main.h"
#include "mojo/public/cpp/application/application_delegate.h"
#include "mojo/public/cpp/application/application_impl.h"
#include "net/server/http_server.h"
#include "net/server/http_server_request_info.h"
#include "net/socket/tcp_server_socket.h"
#include "services/tracing/tracing.mojom.h"
#include "sky/tools/debugger/debugger.mojom.h"
#include <iostream>
namespace sky {
namespace debugger {
namespace {
std::string GetCommand() {
std::cout << "(skydb) ";
std::cout.flush();
std::string command;
std::getline(std::cin, command);
// Any errors (including eof) just quit the debugger:
if (!std::cin.good())
command = 'q';
return command;
}
}
class Prompt : public mojo::ApplicationDelegate {
class Prompt : public mojo::ApplicationDelegate, public net::HttpServer::Delegate {
public:
Prompt()
: is_tracing_(false),
@ -49,6 +37,12 @@ class Prompt : public mojo::ApplicationDelegate {
url_ = "https://raw.githubusercontent.com/domokit/mojo/master/sky/"
"examples/home.sky";
}
scoped_ptr<net::ServerSocket> server_socket(
new net::TCPServerSocket(NULL, net::NetLog::Source()));
// FIXME: This port needs to be configurable, as-is we can only run
// one copy of mojo_shell with sky at a time!
server_socket->ListenWithAddressAndPort("0.0.0.0", 7777, 1);
web_server_.reset(new net::HttpServer(server_socket.Pass(), this));
}
virtual bool ConfigureIncomingConnection(
@ -56,93 +50,86 @@ class Prompt : public mojo::ApplicationDelegate {
connection->ConnectToService(&debugger_);
std::cout << "Loading " << url_ << std::endl;
Reload();
#if !defined(OS_ANDROID)
// FIXME: To support device-centric development we need to re-write
// prompt.cc to just be a server and have all the command handling move
// to python (skydb). prompt.cc would just run until told to quit.
// If we don't comment this out then prompt.cc just quits when run headless
// as it immediately recieves EOF which it treats as quit.
ScheduleWaitForInput();
#endif
return true;
}
bool ExecuteCommand(const std::string& command) {
if (command == "help" || command == "h") {
PrintHelp();
return true;
}
if (command == "trace") {
ToggleTracing();
return true;
}
if (command == "reload" || command == "r") {
Reload();
return true;
}
if (command == "inspect") {
Inspect();
return true;
}
if (command == "quit" || command == "q") {
Quit();
return true;
}
if (command.size() == 1) {
std::cout << "Unknown command: " << command << std::endl;
return true;
}
return false;
// net::HttpServer::Delegate
void OnConnect(int connection_id) override {
}
void WaitForInput() {
std::string command = GetCommand();
void OnClose(int connection_id) override {
}
if (!ExecuteCommand(command)) {
if (command.size() > 0) {
url_ = command;
Reload();
}
void OnHttpRequest(
int connection_id, const net::HttpServerRequestInfo& info) override {
// FIXME: We should use use a fancier lookup system more like what
// services/http_server/http_server.cc does with AddHandler.
if (info.path == "/trace")
ToggleTracing(connection_id);
else if (info.path == "/reload")
Load(connection_id, url_);
else if (info.path == "/inspect")
Inspect(connection_id);
else if (info.path == "/quit")
Quit(connection_id);
else if (info.path == "/load")
Load(connection_id, info.data);
else {
Help(info.path, connection_id);
}
ScheduleWaitForInput();
}
void ScheduleWaitForInput() {
base::MessageLoop::current()->PostTask(FROM_HERE,
base::Bind(&Prompt::WaitForInput, weak_ptr_factory_.GetWeakPtr()));
void OnWebSocketRequest(
int connection_id, const net::HttpServerRequestInfo& info) override {
web_server_->Send500(connection_id, "http only");
}
void PrintHelp() {
std::cout
<< "Sky Debugger" << std::endl
<< "============" << std::endl
<< "Type a URL to load in the debugger, enter to reload." << std::endl
<< "Commands: help -- Help" << std::endl
<< " trace -- Capture a trace" << std::endl
<< " reload -- Reload the current page" << std::endl
<< " inspect -- Inspect the current page" << std::endl
<< " quit -- Quit" << std::endl;
void OnWebSocketMessage(
int connection_id, const std::string& data) override {
web_server_->Send500(connection_id, "http only");
}
void Respond(int connection_id, std::string response) {
web_server_->Send200(connection_id, response, "text/plain");
}
void Help(std::string path, int connection_id) {
std::string help = "Sky Debugger\n"
"Supported URLs:\n"
"/toggle_tracing -- Start/stop tracing\n"
"/reload -- Reload the current page\n"
"/inspect -- Start inspector server for current page\n"
"/quit -- Quit\n"
"/load -- Load a new URL, url in POST body.\n";
if (path != "/")
help = "Unknown path: " + path + "\n\n" + help;
Respond(connection_id, help);
}
void Load(int connection_id, std::string url) {
url_ = url;
Reload();
Respond(connection_id, "OK\n");
}
void Reload() {
debugger_->NavigateToURL(url_);
}
void Inspect() {
void Inspect(int connection_id) {
debugger_->InjectInspector();
std::cout
<< "Open the following URL in Chrome:" << std::endl
<< "chrome-devtools://devtools/bundled/devtools.html?ws=localhost:9898"
<< std::endl;
Respond(connection_id,
"Open the following URL in Chrome:\n"
"chrome-devtools://devtools/bundled/devtools.html?ws=localhost:9898\n");
}
void Quit() {
void Quit(int connection_id) {
std::cout << "quitting" << std::endl;
debugger_->Shutdown();
}
void ToggleTracing() {
void ToggleTracing(int connection_id) {
if (is_tracing_) {
std::cout << "Stopping trace (writing to sky_viewer.trace)" << std::endl;
tracing_->StopAndFlush();
@ -151,6 +138,7 @@ class Prompt : public mojo::ApplicationDelegate {
tracing_->Start(mojo::String("sky_viewer"), mojo::String("*"));
}
is_tracing_ = !is_tracing_;
Respond(connection_id, "OK\n");
}
bool is_tracing_;
@ -158,6 +146,7 @@ class Prompt : public mojo::ApplicationDelegate {
tracing::TraceCoordinatorPtr tracing_;
std::string url_;
base::WeakPtrFactory<Prompt> weak_ptr_factory_;
scoped_ptr<net::HttpServer> web_server_;
DISALLOW_COPY_AND_ASSIGN(Prompt);
};
@ -167,5 +156,6 @@ class Prompt : public mojo::ApplicationDelegate {
MojoResult MojoMain(MojoHandle shell_handle) {
mojo::ApplicationRunnerChromium runner(new sky::debugger::Prompt);
runner.set_message_loop_type(base::MessageLoop::TYPE_IO);
return runner.Run(shell_handle);
}

View File

@ -6,8 +6,11 @@
from skypy.paths import Paths
from skypy.skyserver import SkyServer
import argparse
import json
import logging
import os
import requests
import signal
import skypy.configuration as configuration
import subprocess
import urlparse
@ -19,13 +22,17 @@ SUPPORTED_MIME_TYPES = [
'text/plain',
]
HTTP_PORT = 9999
PID_FILE_PATH = "/tmp/skydb.pids"
class SkyDebugger(object):
def __init__(self):
self.paths = None
self.pids = {}
# FIXME: This is not android aware nor aware of the port
# skyserver is listening on.
self.base_url = 'http://localhost:7777'
def _server_root_and_url_from_path_arg(self, url_or_path):
# This is already a valid url we don't need a local server.
@ -46,19 +53,8 @@ class SkyDebugger(object):
def _in_chromoting(self):
return os.environ.get('CHROME_REMOTE_DESKTOP_SESSION', False)
def main(self):
logging.basicConfig(level=logging.INFO)
parser = argparse.ArgumentParser(description='Sky launcher/debugger')
parser.add_argument('--gdb', action='store_true')
parser.add_argument('--use-osmesa', action='store_true',
default=self._in_chromoting())
parser.add_argument('url_or_path', nargs='?', type=str)
parser.add_argument('--show-command', action='store_true',
help='Display the shell command and exit')
configuration.add_arguments(parser)
args = parser.parse_args()
def _build_mojo_shell_command(self, args):
sky_server = None
self.paths = Paths(os.path.join('out', args.configuration))
content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer')
@ -73,33 +69,126 @@ class SkyDebugger(object):
if args.use_osmesa:
shell_command.append('--args-for=mojo:native_viewport_service --use-osmesa')
server_root = None
if args.url_or_path:
# Check if we need a local server for the url/path arg:
server_root, url = \
self._server_root_and_url_from_path_arg(args.url_or_path)
sky_server = SkyServer(self.paths, HTTP_PORT, args.configuration,
server_root)
prompt_args = '--args-for=mojo:sky_debugger_prompt %s' % url
shell_command.append(prompt_args)
if args.gdb:
shell_command = ['gdb', '--args'] + shell_command
if server_root:
with SkyServer(self.paths, HTTP_PORT, args.configuration,
server_root):
subprocess.check_call(shell_command)
else:
subprocess.check_call(shell_command)
return shell_command, sky_server
def start_command(self, args):
shell_command, sky_server = self._build_mojo_shell_command(args)
if args.show_command:
print " ".join(shell_command)
else:
subprocess.check_call(shell_command)
return
def shutdown(self):
print "Quitting"
if self._sky_server:
self._sky_server.terminate()
self.stop_command([]) # Quit any existing process.
if sky_server:
self.pids['sky_server_pid'] = sky_server.start()
# self.pids['sky_server_port'] = sky_server.port
# self.pids['sky_server_root'] = sky_server.root
self.pids['mojo_shell_pid'] = subprocess.Popen(shell_command).pid
def _kill_if_exists(self, key, name):
pid = self.pids.pop(key, None)
if not pid:
logging.info('No pid for %s, nothing to do.' % name)
return
logging.info('Killing %s (%s).' % (name, pid))
try:
os.kill(pid, signal.SIGTERM)
except OSError:
logging.info('%s (%s) already gone.' % (name, pid))
def stop_command(self, args):
# FIXME: Send /quit to sky prompt instead of killing.
# self._send_command_to_sky('/quit')
self._kill_if_exists('mojo_shell_pid', 'mojo_shell')
self._kill_if_exists('sky_server_pid', 'sky_server')
def load_command(self, args):
# Should resolve paths to relative urls like start does.
# self.pids['sky_server_root'] and port should help.
self._send_command_to_sky('/load', args.url_or_path)
def _send_command_to_sky(self, command_path, payload=None):
url = self.base_url + command_path
if payload:
response = requests.post(url, payload)
else:
response = requests.get(url)
print response.text
# FIXME: These could be made into a context object with __enter__/__exit__.
def _load_pid_file(self, path):
try:
with open(path, 'r') as pid_file:
return json.load(pid_file)
except:
if os.path.exists(path):
logging.warn('Failed to read pid file: %s' % path)
return {}
def _write_pid_file(self, path, pids):
try:
with open(path, 'w') as pid_file:
json.dump(pids, pid_file)
except:
logging.warn('Failed to write pid file: %s' % path)
def _add_basic_command(self, subparsers, name, url_path, help_text):
parser = subparsers.add_parser(name, help=help_text)
command = lambda args: self._send_command_to_sky(url_path)
parser.set_defaults(func=command)
def main(self):
logging.basicConfig(level=logging.INFO)
self.pids = self._load_pid_file(PID_FILE_PATH)
parser = argparse.ArgumentParser(description='Sky launcher/debugger')
subparsers = parser.add_subparsers(help='sub-command help')
start_parser = subparsers.add_parser('start',
help='launch a new mojo_shell with sky')
configuration.add_arguments(start_parser)
start_parser.add_argument('--gdb', action='store_true')
start_parser.add_argument('--use-osmesa', action='store_true',
default=self._in_chromoting())
start_parser.add_argument('url_or_path', nargs='?', type=str)
start_parser.add_argument('--show-command', action='store_true',
help='Display the shell command and exit')
start_parser.set_defaults(func=self.start_command)
stop_parser = subparsers.add_parser('stop',
help=('stop sky (as listed in %s)' % PID_FILE_PATH))
stop_parser.set_defaults(func=self.stop_command)
self._add_basic_command(subparsers, 'trace', '/trace',
'toggle tracing')
self._add_basic_command(subparsers, 'reload', '/reload',
'reload the current page')
self._add_basic_command(subparsers, 'inspect', '/inspect',
'stop the running sky instance')
load_parser = subparsers.add_parser('load',
help='load a new page in the currently running sky')
load_parser.add_argument('url_or_path', type=str)
load_parser.set_defaults(func=self.load_command)
args = parser.parse_args()
args.func(args)
self._write_pid_file(PID_FILE_PATH, self.pids)
if __name__ == '__main__':

View File

@ -26,7 +26,7 @@ class SkyServer(object):
'download_sky_server'))
return os.path.join(paths.src_root, 'out', 'downloads', 'sky_server')
def __enter__(self):
def start(self):
if self._port_in_use(self.port):
logging.warn(
'Port %s already in use, assuming custom sky_server started.' %
@ -41,11 +41,18 @@ class SkyServer(object):
str(self.port),
]
self.server = subprocess.Popen(server_command)
return self.server.pid
def __exit__(self, exc_type, exc_value, traceback):
def stop(self):
if self.server:
self.server.terminate()
def __enter__(self):
self.start()
def __exit__(self, exc_type, exc_value, traceback):
self.stop()
def path_as_url(self, path):
return self.url_for_path(self.port, self.root, path)