-
Notifications
You must be signed in to change notification settings - Fork 6.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #69 from kgrygiel/kgrygiel-autoscaler-demo-1
Compute Autoscaler demo
- Loading branch information
Showing
6 changed files
with
177 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# Copyright 2015, Google, Inc. | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
"""A simple web server which responds to HTTP GET requests by consuming CPU. | ||
This binary runs in a GCE VM. It serves HTTP requests on port 80. Every request | ||
with path '/service' consumes 1 core-second of CPU time, with the timeout of | ||
5 (walltime) seconds. The purpose of this application is to demonstrate how | ||
Google Compute Engine Autoscaler can scale a web frontend server based on CPU | ||
utilization. | ||
The original version of this file is available here: | ||
https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/compute/ | ||
autoscaler/demo/tests/test_frontend.py | ||
""" | ||
|
||
import BaseHTTPServer | ||
from multiprocessing import Process | ||
import os | ||
import SocketServer | ||
import sys | ||
import time | ||
|
||
REQUEST_CPUTIME_SEC = 1.0 | ||
REQUEST_TIMEOUT_SEC = 5.0 | ||
|
||
|
||
class CpuBurner(object): | ||
def get_walltime(self): | ||
return time.time() | ||
|
||
def get_user_cputime(self): | ||
return os.times()[0] | ||
|
||
def busy_wait(self): | ||
for _ in xrange(100000): | ||
pass | ||
|
||
def burn_cpu(self): | ||
"""Consume REQUEST_CPUTIME_SEC core seconds. | ||
This method consumes REQUEST_CPUTIME_SEC core seconds. If unable to | ||
complete within REQUEST_TIMEOUT_SEC walltime seconds, it times out and | ||
terminates the process. | ||
""" | ||
start_walltime_sec = self.get_walltime() | ||
start_cputime_sec = self.get_user_cputime() | ||
while (self.get_user_cputime() < | ||
start_cputime_sec + REQUEST_CPUTIME_SEC): | ||
self.busy_wait() | ||
if (self.get_walltime() > | ||
start_walltime_sec + REQUEST_TIMEOUT_SEC): | ||
sys.exit(1) | ||
|
||
def handle_http_request(self): | ||
"""Process a request to consume CPU and produce an HTTP response.""" | ||
start_time = self.get_walltime() | ||
p = Process(target=self.burn_cpu) # Run in a separate process. | ||
p.start() | ||
# Force kill after timeout + 1 sec. | ||
p.join(timeout=REQUEST_TIMEOUT_SEC + 1) | ||
if p.is_alive(): | ||
p.terminate() | ||
if p.exitcode != 0: | ||
return (500, "Request failed\n") | ||
else: | ||
end_time = self.get_walltime() | ||
response = "Request took %.2f walltime seconds\n" % ( | ||
end_time - start_time) | ||
return (200, response) | ||
|
||
|
||
class DemoRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): | ||
"""Request handler for Demo http server.""" | ||
|
||
def do_GET(self): | ||
"""Handle an HTTP GET request.""" | ||
mapping = { | ||
"/": lambda: (200, "OK"), # Return HTTP 200 response. | ||
"/service": CpuBurner().handle_http_request, | ||
} | ||
if self.path not in mapping: | ||
self.send_response(404) | ||
self.end_headers() | ||
return | ||
(code, response) = mapping[self.path]() | ||
self.send_response(code) | ||
self.end_headers() | ||
self.wfile.write(response) | ||
self.wfile.close() | ||
|
||
|
||
class DemoHttpServer(SocketServer.ThreadingMixIn, | ||
BaseHTTPServer.HTTPServer): | ||
pass | ||
|
||
|
||
if __name__ == "__main__": | ||
httpd = DemoHttpServer(("", 80), DemoRequestHandler) | ||
httpd.serve_forever() |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# Copyright 2015, Google, Inc. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
|
||
import unittest | ||
|
||
from compute.autoscaler.demo import frontend | ||
|
||
|
||
class FakeTime(object): | ||
"""Fake implementations of GetUserCpuTime, GetUserCpuTime and BusyWait. | ||
Each call to BusyWait advances both the cpu and the wall clocks by fixed | ||
intervals (cpu_time_step and wall_time_step, respectively). This can be | ||
used to simulate arbitrary fraction of CPU time available to the process. | ||
""" | ||
def __init__(self, cpu_time_step=1.0, wall_time_step=1.0): | ||
self.cpu_time = 0.0 | ||
self.wall_time = 0.0 | ||
self.cpu_time_step = cpu_time_step | ||
self.wall_time_step = wall_time_step | ||
|
||
def get_walltime(self): | ||
return self.wall_time | ||
|
||
def get_user_cputime(self): | ||
return self.cpu_time | ||
|
||
def busy_wait(self): | ||
self.wall_time += self.wall_time_step | ||
self.cpu_time += self.cpu_time_step | ||
|
||
|
||
class TestHandlers(unittest.TestCase): | ||
def setUp(self): | ||
self.fake_time = FakeTime() | ||
self.cpu_burner = frontend.CpuBurner() | ||
self.cpu_burner.get_user_cputime = self.fake_time.get_user_cputime | ||
self.cpu_burner.get_walltime = self.fake_time.get_walltime | ||
self.cpu_burner.busy_wait = self.fake_time.busy_wait | ||
|
||
# In this test scenario CPU time advances at 25% of the wall time speed. | ||
# Given the request requires 1 CPU core second, we expect it to finish | ||
# within the timeout (5 seconds) and return success. | ||
def test_ok_response(self): | ||
self.fake_time.cpu_time_step = 0.25 | ||
(code, _) = self.cpu_burner.handle_http_request() | ||
self.assertEqual(200, code) | ||
|
||
# In this test scenario CPU time advances at 15% of the wall time speed. | ||
# Given the request requires 1 CPU core second, we expect it to timeout | ||
# after 5 simulated wall time seconds and return error 500. | ||
def test_timeout(self): | ||
self.fake_time.cpu_time_step = 0.15 | ||
(code, _) = self.cpu_burner.handle_http_request() | ||
self.assertEqual(500, code) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |