Skip to content

Commit 731f187

Browse files
committed
Google Cloud Autoscaler demo
This application is a very simple web server, which responds to get requests by consuming some CPU cycles. It is part of a demo of the Google Compute Engine Autoscaler. Link to the Autoscaler tutorial video which refers to this application will follow.
1 parent 53fe64c commit 731f187

File tree

6 files changed

+177
-0
lines changed

6 files changed

+177
-0
lines changed

compute/__init__.py

Whitespace-only changes.

compute/autoscaler/__init__.py

Whitespace-only changes.

compute/autoscaler/demo/__init__.py

Whitespace-only changes.

compute/autoscaler/demo/frontend.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Copyright 2015, Google, Inc.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
#
14+
"""A simple web server which responds to HTTP GET requests by consuming CPU.
15+
This binary runs in a GCE VM. It serves HTTP requests on port 80. Every request
16+
with path '/service' consumes 1 core-second of CPU time, with the timeout of
17+
5 (walltime) seconds. The purpose of this application is to demonstrate how
18+
Google Compute Engine Autoscaler can scale a web frontend server based on CPU
19+
utilization.
20+
The original version of this file is available here:
21+
https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/compute/
22+
autoscaler/demo/tests/test_frontend.py
23+
"""
24+
25+
import BaseHTTPServer
26+
from multiprocessing import Process
27+
import os
28+
import SocketServer
29+
import sys
30+
import time
31+
32+
REQUEST_CPUTIME_SEC = 1.0
33+
REQUEST_TIMEOUT_SEC = 5.0
34+
35+
36+
class CpuBurner(object):
37+
def get_walltime(self):
38+
return time.time()
39+
40+
def get_user_cputime(self):
41+
return os.times()[0]
42+
43+
def busy_wait(self):
44+
for _ in xrange(100000):
45+
pass
46+
47+
def burn_cpu(self):
48+
"""Consume REQUEST_CPUTIME_SEC core seconds.
49+
This method consumes REQUEST_CPUTIME_SEC core seconds. If unable to
50+
complete within REQUEST_TIMEOUT_SEC walltime seconds, it times out and
51+
terminates the process.
52+
"""
53+
start_walltime_sec = self.get_walltime()
54+
start_cputime_sec = self.get_user_cputime()
55+
while (self.get_user_cputime() <
56+
start_cputime_sec + REQUEST_CPUTIME_SEC):
57+
self.busy_wait()
58+
if (self.get_walltime() >
59+
start_walltime_sec + REQUEST_TIMEOUT_SEC):
60+
sys.exit(1)
61+
62+
def handle_http_request(self):
63+
"""Process a request to consume CPU and produce an HTTP response."""
64+
start_time = self.get_walltime()
65+
p = Process(target=self.burn_cpu) # Run in a separate process.
66+
p.start()
67+
# Force kill after timeout + 1 sec.
68+
p.join(timeout=REQUEST_TIMEOUT_SEC + 1)
69+
if p.is_alive():
70+
p.terminate()
71+
if p.exitcode != 0:
72+
return (500, "Request failed\n")
73+
else:
74+
end_time = self.get_walltime()
75+
response = "Request took %.2f walltime seconds\n" % (
76+
end_time - start_time)
77+
return (200, response)
78+
79+
80+
class DemoRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
81+
"""Request handler for Demo http server."""
82+
83+
def do_GET(self):
84+
"""Handle an HTTP GET request."""
85+
mapping = {
86+
"/": lambda: (200, "OK"), # Return HTTP 200 response.
87+
"/service": CpuBurner().handle_http_request,
88+
}
89+
if self.path not in mapping:
90+
self.send_response(404)
91+
self.end_headers()
92+
return
93+
(code, response) = mapping[self.path]()
94+
self.send_response(code)
95+
self.end_headers()
96+
self.wfile.write(response)
97+
self.wfile.close()
98+
99+
100+
class DemoHttpServer(SocketServer.ThreadingMixIn,
101+
BaseHTTPServer.HTTPServer):
102+
pass
103+
104+
105+
if __name__ == "__main__":
106+
httpd = DemoHttpServer(("", 80), DemoRequestHandler)
107+
httpd.serve_forever()

compute/autoscaler/demo/tests/__init__.py

Whitespace-only changes.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright 2015, Google, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
16+
import unittest
17+
18+
from compute.autoscaler.demo import frontend
19+
20+
21+
class FakeTime(object):
22+
"""Fake implementations of GetUserCpuTime, GetUserCpuTime and BusyWait.
23+
Each call to BusyWait advances both the cpu and the wall clocks by fixed
24+
intervals (cpu_time_step and wall_time_step, respectively). This can be
25+
used to simulate arbitrary fraction of CPU time available to the process.
26+
"""
27+
def __init__(self, cpu_time_step=1.0, wall_time_step=1.0):
28+
self.cpu_time = 0.0
29+
self.wall_time = 0.0
30+
self.cpu_time_step = cpu_time_step
31+
self.wall_time_step = wall_time_step
32+
33+
def get_walltime(self):
34+
return self.wall_time
35+
36+
def get_user_cputime(self):
37+
return self.cpu_time
38+
39+
def busy_wait(self):
40+
self.wall_time += self.wall_time_step
41+
self.cpu_time += self.cpu_time_step
42+
43+
44+
class TestHandlers(unittest.TestCase):
45+
def setUp(self):
46+
self.fake_time = FakeTime()
47+
self.cpu_burner = frontend.CpuBurner()
48+
self.cpu_burner.get_user_cputime = self.fake_time.get_user_cputime
49+
self.cpu_burner.get_walltime = self.fake_time.get_walltime
50+
self.cpu_burner.busy_wait = self.fake_time.busy_wait
51+
52+
# In this test scenario CPU time advances at 25% of the wall time speed.
53+
# Given the request requires 1 CPU core second, we expect it to finish
54+
# within the timeout (5 seconds) and return success.
55+
def test_ok_response(self):
56+
self.fake_time.cpu_time_step = 0.25
57+
(code, _) = self.cpu_burner.handle_http_request()
58+
self.assertEqual(200, code)
59+
60+
# In this test scenario CPU time advances at 15% of the wall time speed.
61+
# Given the request requires 1 CPU core second, we expect it to timeout
62+
# after 5 simulated wall time seconds and return error 500.
63+
def test_timeout(self):
64+
self.fake_time.cpu_time_step = 0.15
65+
(code, _) = self.cpu_burner.handle_http_request()
66+
self.assertEqual(500, code)
67+
68+
69+
if __name__ == '__main__':
70+
unittest.main()

0 commit comments

Comments
 (0)