App Engine Python SDK version 1.8.8
[gae.git] / python / google / appengine / tools / devappserver2 / admin / memcache_viewer.py
blob347255ee9f042e6e825c1814a809fad9204784d6
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """A memcache viewer and editor UI.
19 Memcache associates a key with a value and an integer flag. The Go API maps
20 keys to strings and lets the user control the flag. Java, PHP and Python
21 map keys to an arbitrary type and uses the flag to indicate the type
22 information. Java, PHP and Python map types in inconsistent ways, see:
23 - google/appengine/api/memcache/__init__.py
24 - google/appengine/api/memcache/MemcacheSerialization.java
25 - google/appengine/runtime/MemcacheUtils.php
26 """
29 import datetime
30 import logging
31 import urllib
33 from google.appengine.api import apiproxy_stub_map
34 from google.appengine.api import memcache
35 from google.appengine.api.memcache import memcache_service_pb
36 from google.appengine.tools.devappserver2.admin import admin_request_handler
39 class StringValueConverter(object):
40 memcache_type = memcache.TYPE_STR
41 placeholder = 'hello world!'
42 can_edit = True
43 friendly_type_name = 'String'
45 @staticmethod
46 def to_display(cache_value):
47 return cache_value
49 @staticmethod
50 def to_cache(display_value):
51 return display_value
54 class UnicodeValueConverter(object):
55 memcache_type = memcache.TYPE_UNICODE
56 # Hello world in Japanese.
57 placeholder = u'\u3053\u3093\u306b\u3061\u306f\u4e16\u754c'
58 can_edit = True
59 friendly_type_name = 'Unicode String'
61 @staticmethod
62 def to_display(cache_value):
63 return cache_value.decode('utf-8')
65 @staticmethod
66 def to_cache(display_value):
67 return display_value.encode('utf-8')
70 class BooleanValueConverter(object):
71 memcache_type = memcache.TYPE_BOOL
72 placeholder = 'true'
73 can_edit = True
74 friendly_type_name = 'Boolean'
76 @staticmethod
77 def to_display(cache_value):
78 if cache_value == '0':
79 return 'false'
80 elif cache_value == '1':
81 return 'true'
82 else:
83 raise ValueError('unexpected boolean %r' % cache_value)
85 @staticmethod
86 def to_cache(display_value):
87 if display_value.lower() in ('false', 'no', 'off', '0'):
88 return '0'
89 elif display_value.lower() in ('true', 'yes', 'on', '1'):
90 return '1'
92 raise ValueError(
93 'invalid literal for boolean: %s (must be "true" or "false")' %
94 display_value)
97 class IntValueConverter(object):
98 memcache_type = memcache.TYPE_INT
99 placeholder = '42'
100 can_edit = True
101 friendly_type_name = 'Integer'
103 @staticmethod
104 def to_display(cache_value):
105 return str(cache_value)
107 @staticmethod
108 def to_cache(display_value):
109 return str(int(display_value))
112 class OtherValueConverter(object):
113 memcache_type = None
114 placeholder = None
115 can_edit = False
116 friendly_type_name = 'Unknown Type'
118 @staticmethod
119 def to_display(cache_value):
120 return repr(cache_value)[1:-1]
122 @staticmethod
123 def to_cache(display_value):
124 raise NotImplementedError('cannot to a memcache value of unknown type')
127 class MemcacheViewerRequestHandler(admin_request_handler.AdminRequestHandler):
128 CONVERTERS = [StringValueConverter, UnicodeValueConverter,
129 BooleanValueConverter, IntValueConverter,
130 OtherValueConverter]
131 MEMCACHE_TYPE_TO_CONVERTER = {c.memcache_type: c for c in CONVERTERS
132 if c.memcache_type is not None}
133 FRIENDLY_TYPE_NAME_TO_CONVERTER = {c.friendly_type_name: c
134 for c in CONVERTERS}
136 def _get_memcache_value_and_flags(self, key):
137 """Return a 2-tuple containing a memcache value and its flags."""
138 request = memcache_service_pb.MemcacheGetRequest()
139 response = memcache_service_pb.MemcacheGetResponse()
141 request.add_key(key)
142 apiproxy_stub_map.MakeSyncCall('memcache', 'Get', request, response)
143 assert response.item_size() < 2
144 if response.item_size() == 0:
145 return None, None
146 else:
147 return response.item(0).value(), response.item(0).flags()
149 def _set_memcache_value(self, key, value, flags):
150 """Store a value in memcache."""
151 request = memcache_service_pb.MemcacheSetRequest()
152 response = memcache_service_pb.MemcacheSetResponse()
154 item = request.add_item()
155 item.set_key(key)
156 item.set_value(value)
157 item.set_flags(flags)
159 apiproxy_stub_map.MakeSyncCall('memcache', 'Set', request, response)
160 return (response.set_status(0) ==
161 memcache_service_pb.MemcacheSetResponse.STORED)
163 def get(self):
164 """Show template and prepare stats and/or key+value to display/edit."""
165 values = {'request': self.request,
166 'message': self.request.get('message')}
168 edit = self.request.get('edit')
169 key = self.request.get('key')
170 if edit:
171 # Show the form to edit/create the value.
172 key = edit
173 values['show_stats'] = False
174 values['show_value'] = False
175 values['show_valueform'] = True
176 values['types'] = [type_value.friendly_type_name
177 for type_value in self.CONVERTERS
178 if type_value.can_edit]
179 elif key:
180 # A key was given, show it's value on the stats page.
181 values['show_stats'] = True
182 values['show_value'] = True
183 values['show_valueform'] = False
184 else:
185 # Plain stats display + key lookup form.
186 values['show_stats'] = True
187 values['show_valueform'] = False
188 values['show_value'] = False
190 if key:
191 values['key'] = key
192 memcache_value, memcache_flags = self._get_memcache_value_and_flags(key)
193 if memcache_value is not None:
194 converter = self.MEMCACHE_TYPE_TO_CONVERTER.get(memcache_flags,
195 OtherValueConverter)
196 try:
197 values['value'] = converter.to_display(memcache_value)
198 except ValueError:
199 # This exception is possible in the case where the value was set by
200 # Go, which allows for arbitrary user-assigned flag values.
201 logging.exception('Could not convert %s value %s',
202 converter.friendly_type_name, memcache_value)
203 converter = OtherValueConverter
204 values['value'] = converter.to_display(memcache_value)
206 values['type'] = converter.friendly_type_name
207 values['writable'] = converter.can_edit
208 values['key_exists'] = True
209 values['value_placeholder'] = converter.placeholder
210 else:
211 values['writable'] = True
212 values['key_exists'] = False
214 if values['show_stats']:
215 memcache_stats = memcache.get_stats()
216 if not memcache_stats:
217 # No stats means no memcache usage.
218 memcache_stats = {'hits': 0, 'misses': 0, 'byte_hits': 0, 'items': 0,
219 'bytes': 0, 'oldest_item_age': 0}
220 values['stats'] = memcache_stats
221 try:
222 hitratio = memcache_stats['hits'] * 100 / (memcache_stats['hits']
223 + memcache_stats['misses'])
224 except ZeroDivisionError:
225 hitratio = 0
226 values['hitratio'] = hitratio
227 # TODO: oldest_item_age should be formatted in a more useful
228 # way.
229 delta_t = datetime.timedelta(seconds=memcache_stats['oldest_item_age'])
230 values['oldest_item_age'] = datetime.datetime.now() - delta_t
232 self.response.write(self.render('memcache_viewer.html', values))
234 def _urlencode(self, query):
235 """Encode a dictionary into a URL query string.
237 In contrast to urllib this encodes unicode characters as UTF8.
239 Args:
240 query: Dictionary of key/value pairs.
242 Returns:
243 String.
245 return '&'.join('%s=%s' % (urllib.quote_plus(k.encode('utf8')),
246 urllib.quote_plus(v.encode('utf8')))
247 for k, v in query.iteritems())
249 def post(self):
250 """Handle modifying actions and/or redirect to GET page."""
251 next_param = {}
253 if self.request.get('action:flush'):
254 if memcache.flush_all():
255 next_param['message'] = 'Cache flushed, all keys dropped.'
256 else:
257 next_param['message'] = 'Flushing the cache failed. Please try again.'
259 elif self.request.get('action:display'):
260 next_param['key'] = self.request.get('key')
262 elif self.request.get('action:edit'):
263 next_param['edit'] = self.request.get('key')
265 elif self.request.get('action:delete'):
266 key = self.request.get('key')
267 result = memcache.delete(key)
268 if result == memcache.DELETE_NETWORK_FAILURE:
269 next_param['message'] = ('ERROR: Network failure, key "%s" not deleted.'
270 % key)
271 elif result == memcache.DELETE_ITEM_MISSING:
272 next_param['message'] = 'Key "%s" not in cache.' % key
273 elif result == memcache.DELETE_SUCCESSFUL:
274 next_param['message'] = 'Key "%s" deleted.' % key
275 else:
276 next_param['message'] = ('Unknown return value. Key "%s" might still '
277 'exist.' % key)
279 elif self.request.get('action:save'):
280 key = self.request.get('key')
281 value = self.request.get('value')
282 type_ = self.request.get('type')
283 next_param['key'] = key
285 converter = self.FRIENDLY_TYPE_NAME_TO_CONVERTER[type_]
286 try:
287 memcache_value = converter.to_cache(value)
288 except ValueError as e:
289 next_param['message'] = 'ERROR: Failed to save key "%s": %s.' % (key, e)
290 else:
291 if self._set_memcache_value(key,
292 memcache_value,
293 converter.memcache_type):
294 next_param['message'] = 'Key "%s" saved.' % key
295 else:
296 next_param['message'] = 'ERROR: Failed to save key "%s".' % key
297 elif self.request.get('action:cancel'):
298 next_param['key'] = self.request.get('key')
299 else:
300 next_param['message'] = 'Unknown action.'
302 next = self.request.path_url
303 if next_param:
304 next = '%s?%s' % (next, self._urlencode(next_param))
305 self.redirect(next)