##// END OF EJS Templates
update parallel magics...
MinRK -
Show More
@@ -32,6 +32,7 b' import zmq'
32
32
33 from IPython.config.configurable import MultipleInstanceError
33 from IPython.config.configurable import MultipleInstanceError
34 from IPython.core.application import BaseIPythonApplication
34 from IPython.core.application import BaseIPythonApplication
35 from IPython.core.profiledir import ProfileDir, ProfileDirError
35
36
36 from IPython.utils.coloransi import TermColors
37 from IPython.utils.coloransi import TermColors
37 from IPython.utils.jsonutil import rekey
38 from IPython.utils.jsonutil import rekey
@@ -50,7 +51,6 b' from IPython.parallel import util'
50 from IPython.zmq.session import Session, Message
51 from IPython.zmq.session import Session, Message
51
52
52 from .asyncresult import AsyncResult, AsyncHubResult
53 from .asyncresult import AsyncResult, AsyncHubResult
53 from IPython.core.profiledir import ProfileDir, ProfileDirError
54 from .view import DirectView, LoadBalancedView
54 from .view import DirectView, LoadBalancedView
55
55
56 if sys.version_info[0] >= 3:
56 if sys.version_info[0] >= 3:
@@ -480,6 +480,18 b' class Client(HasTraits):'
480 self._queue_handlers = {'execute_reply' : self._handle_execute_reply,
480 self._queue_handlers = {'execute_reply' : self._handle_execute_reply,
481 'apply_reply' : self._handle_apply_reply}
481 'apply_reply' : self._handle_apply_reply}
482 self._connect(sshserver, ssh_kwargs, timeout)
482 self._connect(sshserver, ssh_kwargs, timeout)
483
484 # last step: setup magics, if we are in IPython:
485
486 try:
487 ip = get_ipython()
488 except NameError:
489 return
490 else:
491 if 'px' not in ip.magics_manager.magics:
492 # in IPython but we are the first Client.
493 # activate a default view for parallel magics.
494 self.activate()
483
495
484 def __del__(self):
496 def __del__(self):
485 """cleanup sockets, but _not_ context."""
497 """cleanup sockets, but _not_ context."""
@@ -905,6 +917,29 b' class Client(HasTraits):'
905 # always copy:
917 # always copy:
906 return list(self._ids)
918 return list(self._ids)
907
919
920 def activate(self, targets='all', suffix=''):
921 """Create a DirectView and register it with IPython magics
922
923 Defines the magics `%px, %autopx, %pxresult, %%px`
924
925 Parameters
926 ----------
927
928 targets: int, list of ints, or 'all'
929 The engines on which the view's magics will run
930 suffix: str [default: '']
931 The suffix, if any, for the magics. This allows you to have
932 multiple views associated with parallel magics at the same time.
933
934 e.g. ``rc.activate(targets=0, suffix='0')`` will give you
935 the magics ``%px0``, ``%pxresult0``, etc. for running magics just
936 on engine 0.
937 """
938 view = self.direct_view(targets)
939 view.block = True
940 view.activate(suffix)
941 return view
942
908 def close(self):
943 def close(self):
909 if self._closed:
944 if self._closed:
910 return
945 return
@@ -17,10 +17,14 b' Usage'
17
17
18 {PX_DOC}
18 {PX_DOC}
19
19
20 ``%result``
20 ``%pxresult``
21
21
22 {RESULT_DOC}
22 {RESULT_DOC}
23
23
24 ``%pxconfig``
25
26 {CONFIG_DOC}
27
24 """
28 """
25
29
26 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
@@ -38,7 +42,8 b' import ast'
38 import re
42 import re
39
43
40 from IPython.core.error import UsageError
44 from IPython.core.error import UsageError
41 from IPython.core.magic import Magics, magics_class, line_magic, cell_magic
45 from IPython.core.magic import Magics
46 from IPython.core import magic_arguments
42 from IPython.testing.skipdoctest import skip_doctest
47 from IPython.testing.skipdoctest import skip_doctest
43
48
44 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
@@ -46,95 +51,164 b' from IPython.testing.skipdoctest import skip_doctest'
46 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
47
52
48
53
49 NO_ACTIVE_VIEW = "Use activate() on a DirectView object to use it with magics."
54 NO_LAST_RESULT = "%pxresult recalls last %px result, which has not yet been used."
50 NO_LAST_RESULT = "%result recalls last %px result, which has not yet been used."
51
55
56 def exec_args(f):
57 """decorator for adding block/targets args for execution
58
59 applied to %pxconfig and %%px
60 """
61 args = [
62 magic_arguments.argument('-b', '--block', action="store_const",
63 const=True, dest='block',
64 help="use blocking (sync) execution"
65 ),
66 magic_arguments.argument('-a', '--noblock', action="store_const",
67 const=False, dest='block',
68 help="use non-blocking (async) execution"
69 ),
70 magic_arguments.argument('-t', '--targets', type=str,
71 help="specify the targets on which to execute"
72 ),
73 ]
74 for a in args:
75 f = a(f)
76 return f
77
78 def output_args(f):
79 """decorator for output-formatting args
80
81 applied to %pxresult and %%px
82 """
83 args = [
84 magic_arguments.argument('-r', action="store_const", dest='groupby',
85 const='order',
86 help="collate outputs in order (same as group-outputs=order)"
87 ),
88 magic_arguments.argument('-e', action="store_const", dest='groupby',
89 const='engine',
90 help="group outputs by engine (same as group-outputs=engine)"
91 ),
92 magic_arguments.argument('--group-outputs', dest='groupby', type=str,
93 choices=['engine', 'order', 'type'], default='type',
94 help="""Group the outputs in a particular way.
95
96 Choices are:
97
98 type: group outputs of all engines by type (stdout, stderr, displaypub, etc.).
99
100 engine: display all output for each engine together.
101
102 order: like type, but individual displaypub output from each engine is collated.
103 For example, if multiple plots are generated by each engine, the first
104 figure of each engine will be displayed, then the second of each, etc.
105 """
106 ),
107 magic_arguments.argument('-o', '--out', dest='save_name', type=str,
108 help="""store the AsyncResult object for this computation
109 in the global namespace under this name.
110 """
111 ),
112 ]
113 for a in args:
114 f = a(f)
115 return f
52
116
53 @magics_class
54 class ParallelMagics(Magics):
117 class ParallelMagics(Magics):
55 """A set of magics useful when controlling a parallel IPython cluster.
118 """A set of magics useful when controlling a parallel IPython cluster.
56 """
119 """
57
120
121 # magic-related
122 magics = None
123 registered = True
124
125 # suffix for magics
126 suffix = ''
58 # A flag showing if autopx is activated or not
127 # A flag showing if autopx is activated or not
59 _autopx = False
128 _autopx = False
60 # the current view used by the magics:
129 # the current view used by the magics:
61 active_view = None
130 view = None
62 # last result cache for %result
131 # last result cache for %pxresult
63 last_result = None
132 last_result = None
64
133
65 @skip_doctest
134 def __init__(self, shell, view, suffix=''):
66 @line_magic
135 self.view = view
67 def result(self, line=''):
136 self.suffix = suffix
68 """Print the result of the last asynchronous %px command.
69
70 Usage:
71
137
72 %result [-o] [-e] [--group-options=type|engine|order]
138 # register magics
139 self.magics = dict(cell={},line={})
140 line_magics = self.magics['line']
73
141
74 Options:
142 px = 'px' + suffix
143 if not suffix:
144 # keep %result for legacy compatibility
145 line_magics['result'] = self.result
75
146
76 -o: collate outputs in order (same as group-outputs=order)
147 line_magics['pxresult' + suffix] = self.result
148 line_magics[px] = self.px
149 line_magics['pxconfig' + suffix] = self.pxconfig
150 line_magics['auto' + px] = self.autopx
77
151
78 -e: group outputs by engine (same as group-outputs=engine)
152 self.magics['cell'][px] = self.cell_px
79
153
80 --group-outputs=type [default behavior]:
154 super(ParallelMagics, self).__init__(shell=shell)
81 each output type (stdout, stderr, displaypub) for all engines
155
82 displayed together.
156 def _eval_target_str(self, ts):
83
157 if ':' in ts:
84 --group-outputs=order:
158 targets = eval("self.view.client.ids[%s]" % ts)
85 The same as 'type', but individual displaypub outputs (e.g. plots)
159 elif 'all' in ts:
86 will be interleaved, so it will display all of the first plots,
160 targets = 'all'
87 then all of the second plots, etc.
161 else:
88
162 targets = eval(ts)
89 --group-outputs=engine:
163 return targets
90 All of an engine's output is displayed before moving on to the next.
164
91
165 @magic_arguments.magic_arguments()
92 To use this a :class:`DirectView` instance must be created
166 @exec_args
93 and then activated by calling its :meth:`activate` method.
167 def pxconfig(self, line):
168 """configure default targets/blocking for %px magics"""
169 args = magic_arguments.parse_argstring(self.pxconfig, line)
170 if args.targets:
171 self.view.targets = self._eval_target_str(args.targets)
172 if args.block is not None:
173 self.view.block = args.block
174
175 @magic_arguments.magic_arguments()
176 @output_args
177 @skip_doctest
178 def result(self, line=''):
179 """Print the result of the last asynchronous %px command.
94
180
95 This lets you recall the results of %px computations after
181 This lets you recall the results of %px computations after
96 asynchronous submission (view.block=False).
182 asynchronous submission (block=False).
97
183
98 Then you can do the following::
184 Examples
185 --------
186 ::
99
187
100 In [23]: %px os.getpid()
188 In [23]: %px os.getpid()
101 Async parallel execution on engine(s): all
189 Async parallel execution on engine(s): all
102
190
103 In [24]: %result
191 In [24]: %pxresult
104 [ 8] Out[10]: 60920
192 [ 8] Out[10]: 60920
105 [ 9] Out[10]: 60921
193 [ 9] Out[10]: 60921
106 [10] Out[10]: 60922
194 [10] Out[10]: 60922
107 [11] Out[10]: 60923
195 [11] Out[10]: 60923
108 """
196 """
109 opts, _ = self.parse_options(line, 'oe', 'group-outputs=')
197 args = magic_arguments.parse_argstring(self.result, line)
110
111 if 'group-outputs' in opts:
112 groupby = opts['group-outputs']
113 elif 'o' in opts:
114 groupby = 'order'
115 elif 'e' in opts:
116 groupby = 'engine'
117 else:
118 groupby = 'type'
119
120 if self.active_view is None:
121 raise UsageError(NO_ACTIVE_VIEW)
122
198
123 if self.last_result is None:
199 if self.last_result is None:
124 raise UsageError(NO_LAST_RESULT)
200 raise UsageError(NO_LAST_RESULT)
125
201
126 self.last_result.get()
202 self.last_result.get()
127 self.last_result.display_outputs(groupby=groupby)
203 self.last_result.display_outputs(groupby=args.groupby)
128
204
129 @skip_doctest
205 @skip_doctest
130 @line_magic
206 def px(self, line=''):
131 def px(self, parameter_s=''):
132 """Executes the given python command in parallel.
207 """Executes the given python command in parallel.
133
208
134 To use this a :class:`DirectView` instance must be created
209 Examples
135 and then activated by calling its :meth:`activate` method.
210 --------
136
211 ::
137 Then you can do the following::
138
212
139 In [24]: %px a = os.getpid()
213 In [24]: %px a = os.getpid()
140 Parallel execution on engine(s): all
214 Parallel execution on engine(s): all
@@ -145,27 +219,24 b' class ParallelMagics(Magics):'
145 [stdout:2] 1236
219 [stdout:2] 1236
146 [stdout:3] 1237
220 [stdout:3] 1237
147 """
221 """
148 return self.parallel_execute(parameter_s)
222 return self.parallel_execute(line)
149
223
150 def parallel_execute(self, cell, block=None, groupby='type', save_name=None):
224 def parallel_execute(self, cell, block=None, groupby='type', save_name=None):
151 """implementation used by %px and %%parallel"""
225 """implementation used by %px and %%parallel"""
152
226
153 if self.active_view is None:
154 raise UsageError(NO_ACTIVE_VIEW)
155
156 # defaults:
227 # defaults:
157 block = self.active_view.block if block is None else block
228 block = self.view.block if block is None else block
158
229
159 base = "Parallel" if block else "Async parallel"
230 base = "Parallel" if block else "Async parallel"
160
231
161 targets = self.active_view.targets
232 targets = self.view.targets
162 if isinstance(targets, list) and len(targets) > 10:
233 if isinstance(targets, list) and len(targets) > 10:
163 str_targets = str(targets[:4])[:-1] + ', ..., ' + str(targets[-4:])[1:]
234 str_targets = str(targets[:4])[:-1] + ', ..., ' + str(targets[-4:])[1:]
164 else:
235 else:
165 str_targets = str(targets)
236 str_targets = str(targets)
166 print base + " execution on engine(s): %s" % str_targets
237 print base + " execution on engine(s): %s" % str_targets
167
238
168 result = self.active_view.execute(cell, silent=False, block=False)
239 result = self.view.execute(cell, silent=False, block=False)
169 self.last_result = result
240 self.last_result = result
170
241
171 if save_name:
242 if save_name:
@@ -178,45 +249,16 b' class ParallelMagics(Magics):'
178 # return AsyncResult only on non-blocking submission
249 # return AsyncResult only on non-blocking submission
179 return result
250 return result
180
251
252 @magic_arguments.magic_arguments()
253 @exec_args
254 @output_args
181 @skip_doctest
255 @skip_doctest
182 @cell_magic('px')
183 def cell_px(self, line='', cell=None):
256 def cell_px(self, line='', cell=None):
184 """Executes the given python command in parallel.
257 """Executes the cell in parallel.
185
186 Cell magic usage:
187
188 %%px [-o] [-e] [--group-options=type|engine|order] [--[no]block] [--out name]
189
190 Options:
191
192 --out <name>: store the AsyncResult object for this computation
193 in the global namespace.
194
195 -o: collate outputs in order (same as group-outputs=order)
196
197 -e: group outputs by engine (same as group-outputs=engine)
198
199 --group-outputs=type [default behavior]:
200 each output type (stdout, stderr, displaypub) for all engines
201 displayed together.
202
203 --group-outputs=order:
204 The same as 'type', but individual displaypub outputs (e.g. plots)
205 will be interleaved, so it will display all of the first plots,
206 then all of the second plots, etc.
207
208 --group-outputs=engine:
209 All of an engine's output is displayed before moving on to the next.
210
211 --[no]block:
212 Whether or not to block for the execution to complete
213 (and display the results). If unspecified, the active view's
214
258
215
259 Examples
216 To use this a :class:`DirectView` instance must be created
260 --------
217 and then activated by calling its :meth:`activate` method.
261 ::
218
219 Then you can do the following::
220
262
221 In [24]: %%px --noblock
263 In [24]: %%px --noblock
222 ....: a = os.getpid()
264 ....: a = os.getpid()
@@ -230,38 +272,29 b' class ParallelMagics(Magics):'
230 [stdout:3] 1237
272 [stdout:3] 1237
231 """
273 """
232
274
233 block = None
275 args = magic_arguments.parse_argstring(self.cell_px, line)
234 groupby = 'type'
276
235 # as a cell magic, we accept args
277 if args.targets:
236 opts, _ = self.parse_options(line, 'oe', 'group-outputs=', 'out=', 'block', 'noblock')
278 save_targets = self.view.targets
237
279 self.view.targets = self._eval_target_str(args.targets)
238 if 'group-outputs' in opts:
280 try:
239 groupby = opts['group-outputs']
281 return self.parallel_execute(cell, block=args.block,
240 elif 'o' in opts:
282 groupby=args.groupby,
241 groupby = 'order'
283 save_name=args.save_name,
242 elif 'e' in opts:
284 )
243 groupby = 'engine'
285 finally:
244
286 if args.targets:
245 if 'block' in opts:
287 self.view.targets = save_targets
246 block = True
288
247 elif 'noblock' in opts:
248 block = False
249
250 save_name = opts.get('out')
251
252 return self.parallel_execute(cell, block=block, groupby=groupby, save_name=save_name)
253
254 @skip_doctest
289 @skip_doctest
255 @line_magic
290 def autopx(self, line=''):
256 def autopx(self, parameter_s=''):
257 """Toggles auto parallel mode.
291 """Toggles auto parallel mode.
258
292
259 To use this a :class:`DirectView` instance must be created
293 Once this is called, all commands typed at the command line are send to
260 and then activated by calling its :meth:`activate` method. Once this
294 the engines to be executed in parallel. To control which engine are
261 is called, all commands typed at the command line are send to
295 used, the ``targets`` attribute of the view before
262 the engines to be executed in parallel. To control which engine
296 entering ``%autopx`` mode.
263 are used, set the ``targets`` attributed of the multiengine client
297
264 before entering ``%autopx`` mode.
265
298
266 Then you can do the following::
299 Then you can do the following::
267
300
@@ -290,9 +323,6 b' class ParallelMagics(Magics):'
290 """Enable %autopx mode by saving the original run_cell and installing
323 """Enable %autopx mode by saving the original run_cell and installing
291 pxrun_cell.
324 pxrun_cell.
292 """
325 """
293 if self.active_view is None:
294 raise UsageError(NO_ACTIVE_VIEW)
295
296 # override run_cell
326 # override run_cell
297 self._original_run_cell = self.shell.run_cell
327 self._original_run_cell = self.shell.run_cell
298 self.shell.run_cell = self.pxrun_cell
328 self.shell.run_cell = self.pxrun_cell
@@ -356,12 +386,12 b' class ParallelMagics(Magics):'
356 return False
386 return False
357 else:
387 else:
358 try:
388 try:
359 result = self.active_view.execute(cell, silent=False, block=False)
389 result = self.view.execute(cell, silent=False, block=False)
360 except:
390 except:
361 ipself.showtraceback()
391 ipself.showtraceback()
362 return True
392 return True
363 else:
393 else:
364 if self.active_view.block:
394 if self.view.block:
365 try:
395 try:
366 result.get()
396 result.get()
367 except:
397 except:
@@ -376,15 +406,6 b' class ParallelMagics(Magics):'
376 __doc__ = __doc__.format(
406 __doc__ = __doc__.format(
377 AUTOPX_DOC = ' '*8 + ParallelMagics.autopx.__doc__,
407 AUTOPX_DOC = ' '*8 + ParallelMagics.autopx.__doc__,
378 PX_DOC = ' '*8 + ParallelMagics.px.__doc__,
408 PX_DOC = ' '*8 + ParallelMagics.px.__doc__,
379 RESULT_DOC = ' '*8 + ParallelMagics.result.__doc__
409 RESULT_DOC = ' '*8 + ParallelMagics.result.__doc__,
410 CONFIG_DOC = ' '*8 + ParallelMagics.pxconfig.__doc__,
380 )
411 )
381
382 _loaded = False
383
384
385 def load_ipython_extension(ip):
386 """Load the extension in IPython."""
387 global _loaded
388 if not _loaded:
389 ip.register_magics(ParallelMagics)
390 _loaded = True
@@ -792,33 +792,37 b' class DirectView(View):'
792 return self.client.kill(targets=targets, block=block)
792 return self.client.kill(targets=targets, block=block)
793
793
794 #----------------------------------------
794 #----------------------------------------
795 # activate for %px,%autopx magics
795 # activate for %px, %autopx, etc. magics
796 #----------------------------------------
796 #----------------------------------------
797 def activate(self):
798 """Make this `View` active for parallel magic commands.
799
797
800 IPython has a magic command syntax to work with `MultiEngineClient` objects.
798 def activate(self, suffix=''):
801 In a given IPython session there is a single active one. While
799 """Activate IPython magics associated with this View
802 there can be many `Views` created and used by the user,
800
803 there is only one active one. The active `View` is used whenever
801 Defines the magics `%px, %autopx, %pxresult, %%px, %pxconfig`
804 the magic commands %px and %autopx are used.
802
805
803 Parameters
806 The activate() method is called on a given `View` to make it
804 ----------
807 active. Once this has been done, the magic commands can be used.
805
806 suffix: str [default: '']
807 The suffix, if any, for the magics. This allows you to have
808 multiple views associated with parallel magics at the same time.
809
810 e.g. ``rc[::2].activate(suffix='_even')`` will give you
811 the magics ``%px_even``, ``%pxresult_even``, etc. for running magics
812 on the even engines.
808 """
813 """
809
814
815 from IPython.parallel.client.magics import ParallelMagics
816
810 try:
817 try:
811 # This is injected into __builtins__.
818 # This is injected into __builtins__.
812 ip = get_ipython()
819 ip = get_ipython()
813 except NameError:
820 except NameError:
814 print "The IPython parallel magics (%result, %px, %autopx) only work within IPython."
821 print "The IPython parallel magics (%px, etc.) only work within IPython."
815 else:
822 return
816 pmagic = ip.magics_manager.registry.get('ParallelMagics')
823
817 if pmagic is None:
824 M = ParallelMagics(ip, self, suffix)
818 ip.magic('load_ext parallelmagic')
825 ip.magics_manager.register(M)
819 pmagic = ip.magics_manager.registry.get('ParallelMagics')
820
821 pmagic.active_view = self
822
826
823
827
824 @skip_doctest
828 @skip_doctest
@@ -24,6 +24,7 b' from tempfile import mktemp'
24
24
25 import zmq
25 import zmq
26
26
27 from IPython import parallel
27 from IPython.parallel.client import client as clientmod
28 from IPython.parallel.client import client as clientmod
28 from IPython.parallel import error
29 from IPython.parallel import error
29 from IPython.parallel import AsyncResult, AsyncHubResult
30 from IPython.parallel import AsyncResult, AsyncHubResult
@@ -420,3 +421,16 b' class TestClient(ClusterTestCase):'
420 "Shouldn't be spinning, but got wall_time=%f" % ar.wall_time
421 "Shouldn't be spinning, but got wall_time=%f" % ar.wall_time
421 )
422 )
422
423
424 def test_activate(self):
425 ip = get_ipython()
426 magics = ip.magics_manager.magics
427 self.assertTrue('px' in magics['line'])
428 self.assertTrue('px' in magics['cell'])
429 v0 = self.client.activate(-1, '0')
430 self.assertTrue('px0' in magics['line'])
431 self.assertTrue('px0' in magics['cell'])
432 self.assertEquals(v0.targets, self.client.ids[-1])
433 v0 = self.client.activate('all', 'all')
434 self.assertTrue('pxall' in magics['line'])
435 self.assertTrue('pxall' in magics['cell'])
436 self.assertEquals(v0.targets, 'all')
@@ -301,20 +301,13 b' class TestParallelMagics(ClusterTestCase, ParametricTestCase):'
301 data = dict(a=111,b=222)
301 data = dict(a=111,b=222)
302 v.push(data, block=True)
302 v.push(data, block=True)
303
303
304 ip.magic('px a')
304 for name in ('a', 'b'):
305 ip.magic('px b')
305 ip.magic('px ' + name)
306 for idx, name in [
307 ('', 'b'),
308 ('-1', 'b'),
309 ('2', 'b'),
310 ('1', 'a'),
311 ('-2', 'a'),
312 ]:
313 with capture_output() as io:
306 with capture_output() as io:
314 ip.magic('result ' + idx)
307 ip.magic('pxresult')
315 output = io.stdout
308 output = io.stdout
316 msg = "expected %s output to include %s, but got: %s" % \
309 msg = "expected %s output to include %s, but got: %s" % \
317 ('%result '+idx, str(data[name]), output)
310 ('%pxresult', str(data[name]), output)
318 self.assertTrue(str(data[name]) in output, msg)
311 self.assertTrue(str(data[name]) in output, msg)
319
312
320 @dec.skipif_not_matplotlib
313 @dec.skipif_not_matplotlib
@@ -336,5 +329,17 b' class TestParallelMagics(ClusterTestCase, ParametricTestCase):'
336
329
337 self.assertTrue('Out[' in io.stdout, io.stdout)
330 self.assertTrue('Out[' in io.stdout, io.stdout)
338 self.assertTrue('matplotlib.lines' in io.stdout, io.stdout)
331 self.assertTrue('matplotlib.lines' in io.stdout, io.stdout)
332
333 def test_pxconfig(self):
334 ip = get_ipython()
335 rc = self.client
336 v = rc.activate(-1, '_tst')
337 self.assertEquals(v.targets, rc.ids[-1])
338 ip.magic("%pxconfig_tst -t :")
339 self.assertEquals(v.targets, rc.ids)
340 ip.magic("%pxconfig_tst --block")
341 self.assertEquals(v.block, True)
342 ip.magic("%pxconfig_tst --noblock")
343 self.assertEquals(v.block, False)
339
344
340
345
General Comments 0
You need to be logged in to leave comments. Login now