3434subparsers = parser .add_subparsers ()
3535
3636start_parser = subparsers .add_parser ('start' , help = "Start daemon" )
37+ start_parser .add_argument ('--log-file' , metavar = 'FILE' , type = str ,
38+ help = "Direct daemon stdout/stderr to FILE" )
3739start_parser .add_argument ('flags' , metavar = 'FLAG' , nargs = '*' , type = str ,
3840 help = "Regular mypy flags (precede with --)" )
3941
4547
4648restart_parser = subparsers .add_parser ('restart' ,
4749 help = "Restart daemon (stop or kill followed by start)" )
50+ restart_parser .add_argument ('--log-file' , metavar = 'FILE' , type = str ,
51+ help = "Direct daemon stdout/stderr to FILE" )
4852restart_parser .add_argument ('flags' , metavar = 'FLAG' , nargs = '*' , type = str ,
4953 help = "Regular mypy flags (precede with --)" )
5054
@@ -103,7 +107,7 @@ def do_start(args: argparse.Namespace) -> None:
103107 try :
104108 pid , sockname = get_status ()
105109 except SystemExit as err :
106- if daemonize (Server (args .flags ).serve ):
110+ if daemonize (Server (args .flags ).serve , args . log_file ):
107111 sys .exit (1 )
108112 wait_for_server ()
109113 else :
@@ -169,7 +173,7 @@ def do_restart(args: argparse.Namespace) -> None:
169173 sys .exit ("Status: %s" % str (response ))
170174 else :
171175 print ("Daemon stopped" )
172- if daemonize (Server (args .flags ).serve ):
176+ if daemonize (Server (args .flags ).serve , args . log_file ):
173177 sys .exit (1 )
174178 wait_for_server ()
175179
@@ -333,7 +337,7 @@ def read_status() -> Dict[str, object]:
333337 return data
334338
335339
336- def daemonize (func : Callable [[], None ]) -> int :
340+ def daemonize (func : Callable [[], None ], log_file : Optional [ str ] = None ) -> int :
337341 """Arrange to call func() in a grandchild of the current process.
338342
339343 Return 0 for success, exit status for failure, negative if
@@ -368,6 +372,11 @@ def daemonize(func: Callable[[], None]) -> int:
368372 # Child is done, exit to parent.
369373 os ._exit (0 )
370374 # Grandchild: run the server.
375+ if log_file :
376+ sys .stdout = sys .stderr = open (log_file , 'a' , buffering = 1 )
377+ fd = sys .stdout .fileno ()
378+ os .dup2 (fd , 2 )
379+ os .dup2 (fd , 1 )
371380 func ()
372381 finally :
373382 # Make sure we never get back into the caller.
@@ -490,43 +499,28 @@ def cmd_recheck(self) -> Dict[str, object]:
490499 return {'error' : "Command 'recheck' is only valid after a 'check' command" }
491500 return self .check (self .last_sources )
492501
493- last_mananager = None # type: Optional[mypy.build.BuildManager]
502+ # Needed by tests.
503+ last_manager = None # type: Optional[mypy.build.BuildManager]
494504
495505 def check (self , sources : List [mypy .build .BuildSource ],
496506 alt_lib_path : Optional [str ] = None ) -> Dict [str , Any ]:
497- # TODO: Move stats handling code to make the logic here less cluttered.
498- bound_gc_callback = self .gc_callback
499- self .gc_start_time = None # type: Optional[float]
500- self .gc_time = 0.0
501- self .gc_calls = 0
502- self .gc_collected = 0
503- self .gc_uncollectable = 0
504- t0 = time .time ()
505- try :
506- gc .callbacks .append (bound_gc_callback )
507- # saved_cache is mutated in place.
508- res = mypy .build .build (sources , self .options ,
509- saved_cache = self .saved_cache ,
510- alt_lib_path = alt_lib_path )
511- msgs = res .errors
512- self .last_manager = res .manager # type: Optional[mypy.build.BuildManager]
513- except mypy .errors .CompileError as err :
514- msgs = err .messages
515- self .last_manager = None
516- finally :
517- while bound_gc_callback in gc .callbacks :
518- gc .callbacks .remove (bound_gc_callback )
519- t1 = time .time ()
507+ self .last_manager = None
508+ with GcLogger () as gc_result :
509+ try :
510+ # saved_cache is mutated in place.
511+ res = mypy .build .build (sources , self .options ,
512+ saved_cache = self .saved_cache ,
513+ alt_lib_path = alt_lib_path )
514+ msgs = res .errors
515+ self .last_manager = res .manager # type: Optional[mypy.build.BuildManager]
516+ except mypy .errors .CompileError as err :
517+ msgs = err .messages
520518 if msgs :
521519 msgs .append ("" )
522520 response = {'out' : "\n " .join (msgs ), 'err' : "" , 'status' : 1 }
523521 else :
524522 response = {'out' : "" , 'err' : "" , 'status' : 0 }
525- response ['build_time' ] = t1 - t0
526- response ['gc_time' ] = self .gc_time
527- response ['gc_calls' ] = self .gc_calls
528- response ['gc_collected' ] = self .gc_collected
529- response ['gc_uncollectable' ] = self .gc_uncollectable
523+ response .update (gc_result .get_stats ())
530524 response .update (get_meminfo ())
531525 if self .last_manager is not None :
532526 response .update (self .last_manager .stats_summary ())
@@ -537,20 +531,6 @@ def cmd_hang(self) -> Dict[str, object]:
537531 time .sleep (100 )
538532 return {}
539533
540- def gc_callback (self , phase : str , info : Mapping [str , int ]) -> None :
541- if phase == 'start' :
542- assert self .gc_start_time is None , "Start phase out of sequence"
543- self .gc_start_time = time .time ()
544- elif phase == 'stop' :
545- assert self .gc_start_time is not None , "Stop phase out of sequence"
546- self .gc_calls += 1
547- self .gc_time += time .time () - self .gc_start_time
548- self .gc_start_time = None
549- self .gc_collected += info ['collected' ]
550- self .gc_uncollectable += info ['uncollectable' ]
551- else :
552- assert False , "Unrecognized gc phase (%r)" % (phase ,)
553-
554534
555535# Misc utilities.
556536
@@ -570,6 +550,48 @@ def receive(sock: socket.socket) -> Any:
570550 return data
571551
572552
553+ class GcLogger :
554+ """Context manager to log GC stats and overall time."""
555+
556+ def __enter__ (self ) -> 'GcLogger' :
557+ self .gc_start_time = None # type: Optional[float]
558+ self .gc_time = 0.0
559+ self .gc_calls = 0
560+ self .gc_collected = 0
561+ self .gc_uncollectable = 0
562+ gc .callbacks .append (self .gc_callback )
563+ self .start_time = time .time ()
564+ return self
565+
566+ def gc_callback (self , phase : str , info : Mapping [str , int ]) -> None :
567+ if phase == 'start' :
568+ assert self .gc_start_time is None , "Start phase out of sequence"
569+ self .gc_start_time = time .time ()
570+ elif phase == 'stop' :
571+ assert self .gc_start_time is not None , "Stop phase out of sequence"
572+ self .gc_calls += 1
573+ self .gc_time += time .time () - self .gc_start_time
574+ self .gc_start_time = None
575+ self .gc_collected += info ['collected' ]
576+ self .gc_uncollectable += info ['uncollectable' ]
577+ else :
578+ assert False , "Unrecognized gc phase (%r)" % (phase ,)
579+
580+ def __exit__ (self , * args : object ) -> None :
581+ while self .gc_callback in gc .callbacks :
582+ gc .callbacks .remove (self .gc_callback )
583+
584+ def get_stats (self ) -> Dict [str , float ]:
585+ end_time = time .time ()
586+ result = {}
587+ result ['gc_time' ] = self .gc_time
588+ result ['gc_calls' ] = self .gc_calls
589+ result ['gc_collected' ] = self .gc_collected
590+ result ['gc_uncollectable' ] = self .gc_uncollectable
591+ result ['build_time' ] = end_time - self .start_time
592+ return result
593+
594+
573595MiB = 2 ** 20
574596
575597
0 commit comments