Skip to content

Commit 9272e9b

Browse files
committed
API response logging, OrderedDict supported in stats, scheduler fixed - per-app and all-app tasks work now, sessions management fixed and less lock-demanding
1 parent 732be13 commit 9272e9b

File tree

12 files changed

+156
-87
lines changed

12 files changed

+156
-87
lines changed

src/api.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,27 +52,29 @@ def check_session():
5252

5353
@staticmethod
5454
def run_handler():
55-
hruntime.response.headers['Content-Type'] = 'application/json'
55+
res = hruntime.response
56+
57+
res.headers['Content-Type'] = 'application/json'
5658

5759
try:
5860
hlib.input.validate()
5961

60-
r = hruntime.request.handler(**hruntime.request.params)
62+
res.api_response = hruntime.request.handler(**hruntime.request.params)
6163

62-
if r == None:
63-
r = Reply(200)
64+
if res.api_response == None:
65+
res.api_response = Reply(200)
6466

65-
if hasattr(r, 'dump'):
66-
return r.dump()
67+
if hasattr(res.api_response, 'dump'):
68+
return res.api_response.dump()
6769

68-
if hasattr(r, 'get_serializer'):
69-
return r.get_serializer().serialize(r)
70+
if hasattr(res.api_response, 'get_serializer'):
71+
return res.api_response.get_serializer().serialize(res.api_response)
7072

71-
if type(r) == types.DictType:
72-
return Raw(r).dump()
73+
if type(res.api_response) == types.DictType:
74+
return Raw(res.api_response).dump()
7375

74-
if type(r) in types.StringTypes:
75-
return r
76+
if type(res.api_response) in types.StringTypes:
77+
return res.api_response
7678

7779
raise hlib.error.InvalidOutputError()
7880

@@ -86,8 +88,10 @@ def run_handler():
8688
hlib.log.log_error(e)
8789

8890
kwargs = e.args_for_reply()
91+
res.api_response = Reply(e.reply_status, error = Error(e), **kwargs)
92+
8993
# pylint: disable-msg=W0142
90-
return Reply(e.reply_status, error = Error(e), **kwargs).dump()
94+
return res.api_response.dump()
9195

9296
@staticmethod
9397
def redirect(url):

src/auth.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def start_session(user = None, tainted = False):
4646
elif hasattr(hruntime.session, 'tainted'):
4747
hruntime.session.tainted = False
4848

49+
hruntime.app.sessions.invalidate_onlines()
50+
4951
refresh_session()
5052

5153
def check_session(redirect_to_login = True):
@@ -57,15 +59,15 @@ def check_session(redirect_to_login = True):
5759
"""
5860

5961
if not hruntime.request.is_authenticated:
62+
hruntime.app.sessions.invalidate_onlines()
63+
6064
if redirect_to_login == True:
6165
raise hlib.http.Redirect('/login/')
6266

6367
return
6468

6569
refresh_session()
6670

67-
hruntime.app.sessions.purge()
68-
6971
def logout(trigger_event = True):
7072
"""
7173
Mark session as not authenticated and redirect to login page.
@@ -77,4 +79,6 @@ def logout(trigger_event = True):
7779
hruntime.session.destroy()
7880
hruntime.session = None
7981

82+
hruntime.app.sessions.invalidate_onlines()
83+
8084
raise hlib.http.Redirect('/login/')

src/cache.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import sys
88
import threading
99

10+
from collections import OrderedDict
11+
1012
import hlib.locks
1113

1214
from hlib.stats import stats as STATS
@@ -23,16 +25,15 @@ def __init__(self, name, app):
2325

2426
self.stats_name = 'Cache (%s - %s)' % (self.app.name, self.name)
2527

26-
with STATS:
27-
STATS.set(self.stats_name, {
28-
'Total objects': lambda s: sum([len(chain) for chain in self.objects.values()]),
29-
'Total chains': lambda s: len(self.objects),
30-
'Total size': lambda s: sum([sum([sys.getsizeof(v) for v in chain.values()]) for chain in self.objects.values()]),
31-
'Hits': 0,
32-
'Misses': 0,
33-
'Inserts': 0,
34-
'Chains': lambda s: self.to_stats()
35-
})
28+
STATS.set(self.stats_name, OrderedDict([
29+
('Total objects', lambda s: sum([len(chain) for chain in self.objects.values()])),
30+
('Total chains', lambda s: len(self.objects)),
31+
('Total size', lambda s: sum([sum([sys.getsizeof(v) for v in chain.values()]) for chain in self.objects.values()])),
32+
('Hits', 0),
33+
('Misses', 0),
34+
('Inserts', 0),
35+
('Chains', lambda s: self.to_stats())
36+
]))
3637

3738
def stats_inc(self, key):
3839
with STATS:

src/database.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import persistent
1414
import threading
1515

16+
from collections import OrderedDict
17+
1618
import hlib.console
1719
import hlib.error
1820
import hlib.locks
@@ -143,16 +145,15 @@ def __init__(self, name, address, **kwargs):
143145
self.stats_name = 'Database (%s)' % self.name
144146

145147
# pylint: disable-msg=W0621
146-
with STATS:
147-
STATS.set(self.stats_name, {
148-
'Loads': 0,
149-
'Stores': 0,
150-
'Commits': 0,
151-
'Rollbacks': 0,
152-
'Failed commits': 0,
153-
'Connections': {},
154-
'Caches': {}
155-
})
148+
STATS.set(self.stats_name, OrderedDict([
149+
('Loads', 0),
150+
('Stores', 0),
151+
('Commits', 0),
152+
('Rollbacks', 0),
153+
('Failed commits', 0),
154+
('Connections', {}),
155+
('Caches', {})
156+
]))
156157

157158
import events
158159
events.Hook('engine.ThreadFinished', self.on_thread_finished)

src/engine.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def log_access(self):
169169
self.__log(self.config['log.access.format'].format(**params), self.channels.access)
170170

171171
def log_event(self, event):
172-
self.__log('%s: %s' % (event.__class__.ename(), event.to_api()))
172+
self.__log('%s: %s' % (event.__class__.ename(), event.to_api()), self.channels.events)
173173

174174
class HeaderMap(UserDict.UserDict):
175175
"""
@@ -419,6 +419,8 @@ def __init__(self):
419419
self.location = None
420420
self.time = hruntime.time
421421

422+
self.api_response = None
423+
422424
self._output = None
423425
self.output_length = None
424426

@@ -506,23 +508,27 @@ def dumps(self):
506508

507509
class DataBaseGCTask(hlib.scheduler.Task):
508510
def __init__(self):
509-
super(DataBaseGCTask, self).__init__('database-gc', self.database_gc, min = [5, 25, 45])
511+
super(DataBaseGCTask, self).__init__('database-gc', self.database_gc, min = range(5, 60, 20))
510512

511513
def database_gc(self, engine = None, app = None):
512-
for app in engine.apps.values():
513-
if app.db != None and app.db.is_opened:
514-
app.db.globalGC()
514+
if app.db != None and app.db.is_opened:
515+
app.db.globalGC()
515516

516517
class SaveSessionsTask(hlib.scheduler.Task):
517518
def __init__(self):
518-
super(SaveSessionsTask, self).__init__('save-sessions', self.save_sessions, min = [10, 30, 50])
519+
super(SaveSessionsTask, self).__init__('save-sessions', self.save_sessions, min = range(10, 60, 20))
519520

520521
def save_sessions(self, engine = None, app = None):
521-
print 'Save sessions task triggered'
522+
if app.sessions != None:
523+
app.sessions.save_sessions()
524+
525+
class PurgeSessionsTask(hlib.scheduler.Task):
526+
def __init__(self):
527+
super(PurgeSessionsTask, self).__init__('purge-sessions', self.purge_sessions, min = range(0, 60, 5))
522528

523-
for app in engine.apps.values():
524-
if app.sessions != None:
525-
app.sessions.save_sessions()
529+
def purge_sessions(self, engine = None, app = None):
530+
if app.sessions != None:
531+
app.sessions.purge()
526532

527533
class Engine(object):
528534
"""
@@ -545,6 +551,7 @@ def __init__(self, name, server_configs):
545551
self.scheduler.start()
546552

547553
self.scheduler.add_task(DataBaseGCTask(), None)
554+
self.scheduler.add_task(PurgeSessionsTask(), None)
548555
self.scheduler.add_task(SaveSessionsTask(), None)
549556

550557
i = 0
@@ -661,14 +668,14 @@ def on_request_connected(self, _):
661668
with STATS:
662669
STATS.inc(self.stats_name, 'Total requests')
663670

664-
STATS.set(self.stats_name, 'Requests', hruntime.tid, {
665-
'Bytes read': 0,
666-
'Bytes written': 0,
667-
'Client': hlib.server.ips_to_str(hruntime.request.ips),
668-
'Start time': hruntime.time,
669-
'Requested line': None,
670-
'User': None
671-
})
671+
STATS.set(self.stats_name, 'Requests', hruntime.tid, OrderedDict([
672+
('Bytes read', 0),
673+
('Bytes written', 0),
674+
('Client', hlib.server.ips_to_str(hruntime.request.ips)),
675+
('Start time', hruntime.time),
676+
('Requested line', None),
677+
('User', None)
678+
]))
672679

673680
def on_request_accepted(self, _):
674681
STATS.set(self.stats_name, 'Requests', hruntime.tid, 'Client', hlib.server.ips_to_str(hruntime.request.ips))

src/error.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@
1313
import traceback
1414
import types
1515

16+
def get_caller(back = 0):
17+
stack = traceback.extract_stack()
18+
19+
i = -3 - back
20+
if abs(i) > len(stack):
21+
i = -len(stack)
22+
23+
return stack[i]
24+
25+
def fmt_caller(back = 0):
26+
caller = get_caller(back = back + 1)
27+
return 'Called by "%s" on %s:%i ("%s")' % (caller[2], caller[0], caller[1], caller[3])
28+
1629
class BaseError(Exception):
1730
def __init__(self, msg = None, params = None, exception = None, exc_info = None, http_status = 500, reply_status = 500, dont_log = False, **kwargs):
1831
super(BaseError, self).__init__()
@@ -118,7 +131,7 @@ def __init__(self, obj = None):
118131
@param obj: Unimplemented function wrapper that called this function.
119132
"""
120133

121-
super(UnimplementedError, self).__init__('Unimplemented abstract method: %(method)s', params = {'method': UnimplementedError.function_name(obj, 2)})
134+
super(UnimplementedError, self).__init__('Unimplemented abstract method: %(method)s', params = {'method': UnimplementedError.function_name(obj, 1)})
122135

123136
class UnknownError(BaseError):
124137
pass

src/http/session.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import random
99
import string
1010
import threading
11-
import UserDict
11+
12+
from UserDict import UserDict
13+
from collections import OrderedDict
1214

1315
import hlib.locks
1416
import hlib.server
@@ -23,9 +25,9 @@ def gen_rand_string(l):
2325
chars = string.letters + string.digits
2426
return reduce(lambda s, i: s + random.choice(chars), range(l), '')
2527

26-
class Storage(UserDict.UserDict):
28+
class Storage(UserDict):
2729
def __init__(self, app, *args, **kwargs):
28-
UserDict.UserDict.__init__(self, *args, **kwargs)
30+
UserDict.__init__(self, *args, **kwargs)
2931

3032
self.app = app
3133

@@ -38,27 +40,39 @@ def __init__(self, app, *args, **kwargs):
3840

3941
self.stats_name = 'Sessions (%s)' % self.app.name
4042

41-
STATS.set(self.stats_name, {
42-
'Active': lambda s: ', '.join(self.online_users)
43-
})
43+
STATS.set(self.stats_name, OrderedDict([
44+
('Active', lambda s: ', '.join(self.online_users))
45+
]))
4446

4547
@property
4648
def online_users(self):
49+
# Some actions can be done without holding lock
50+
51+
__online_ref = self.__online
52+
if __online_ref != None:
53+
return __online_ref
54+
55+
if not self.sessions:
56+
return []
57+
4758
with self.lock:
48-
if self.sessions == None:
49-
return []
59+
if self.__online != None:
60+
return self.__online
5061

51-
if hruntime.time - self.__online_ctime > 60 or self.__online == None:
52-
self.__online = []
62+
self.__online = []
5363

54-
for session in self.sessions.values():
55-
if session.age < 300 and hasattr(session, 'authenticated') and hasattr(session, 'name') and session.name:
56-
self.__online.append(session.name)
64+
for session in self.sessions.values():
65+
if session.age < 300 and hasattr(session, 'authenticated') and hasattr(session, 'name') and session.name:
66+
self.__online.append(session.name)
5767

58-
self.__online_ctime = hruntime.time
68+
self.__online_ctime = hruntime.time
5969

6070
return list(self.__online)
6171

72+
def invalidate_onlines(self):
73+
with self.lock:
74+
self.__online = None
75+
6276
def purge(self):
6377
with self.lock:
6478
rm = []

src/log/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ def log_msg(msg, channels, flush = False):
4040
def log_params():
4141
import hlib.server
4242

43+
def __response_api_status():
44+
res = hruntime.response
45+
46+
if not res.api_response:
47+
return '-'
48+
if not hasattr(res.api_response, 'status'):
49+
return '/'
50+
51+
return res.api_response.status
52+
4353
return {
4454
'tid': hruntime.tid,
4555
'stamp': hruntime.localtime,
@@ -51,6 +61,7 @@ def log_params():
5161
'request_agent': hruntime.request.headers.get('User-Agent', '-'),
5262
'response_status': hruntime.response.status,
5363
'response_length': hruntime.response.output_length != None and hruntime.response.output_length or 0,
64+
'response_api_status': __response_api_status(),
5465
'session_id': hruntime.session.sid if hruntime.session != None else None
5566
}
5667

src/log/channels/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ def __init__(self, stream, *args, **kwargs):
3838
self.stream = stream
3939

4040
def do_log_message(self, msg):
41-
print >> self.stream, msg.encode('ascii', 'replace')
41+
msg = msg.decode('utf-8', 'replace').encode('ascii', 'replace')
42+
self.stream.write(msg + '\n')
4243
self.stream.flush()
4344

4445
def log_error(self, error):

0 commit comments

Comments
 (0)