<commit_before>(in-package :sample-profiler)

(defstruct profiler
  (process nil)
  (file nil)
  (last-result nil)
  output-file)

(defvar *profiler* (make-profiler))

(defun start-profiling (&key (seconds T) (verbose nil) (output-file "profile.html") (profiler *profiler*))
  (when (eq seconds T) (setq seconds (* 60 60 24)))
  (check-type seconds (integer (0)))
  (when (profiler-process profiler)
    (when (eq :running (ccl:external-process-status (profiler-process profiler)))
      (cerror "Abort existing profiler" "Profiling currently in effect: ~s." profiler))
    (end-profiling :abort t))
  (ccl::purify)
  (let* ((file (ccl:temp-pathname))
         (proc (ccl:run-program "/usr/bin/sample"
                                (list (princ-to-string (ccl::getpid))
                                      (princ-to-string seconds)
                                      "-file"
                                      (ccl:native-translated-namestring file))
                                :input nil
                                :wait nil
                                :output :stream)))
    (setf (profiler-output-file profiler) output-file)
    (setf (profiler-last-result profiler) nil)
    (setf (profiler-file profiler) file)
    (setf (profiler-process profiler) proc)
    ;; wait for some output to appear then copy it to standard output.
    (let ((stream (ccl:external-process-output-stream proc)))
      (when (peek-char nil stream nil)
        (when verbose (fresh-line))
        (loop as ch = (read-char-no-hang stream nil) while ch
           do (when verbose (write-char ch))))))
  (values))

(defun stop-profiler (&key abort (verbose nil) (profiler *profiler*))
  (let ((p (profiler-process profiler)))
    (setf (profiler-process profiler) nil)
    (ccl:signal-external-process p (if abort #$SIGKILL #$SIGINT) :error-if-exited nil)
    (when (and (ccl:external-process-id p) (eq (ccl:external-process-status p) :running))
      ;; TODO: if abort is true, should time this out in fairly short order.  Maybe don't even
      ;; bother with the semaphore, just sleep a second and then proceed no matter what.
      (ccl:with-interrupts-enabled
        (ccl:wait-on-semaphore (ccl::external-process-completed p) nil "end-profiling")))
    (when verbose
      (let ((stream (ccl:external-process-output-stream p)))
        (when (listen stream)
          (fresh-line)
          (loop as ch = (read-char-no-hang stream nil) while ch do (write-char ch))))))
  (setf (profiler-last-result profiler)
        (let ((file (profiler-file profiler)))
          (when (probe-file file)
            (when verbose (format t "~&Parsing ~s" file))
            (read-sample-file file))))
  (ccl::impurify)
  (profiler-last-result profiler))

(defun output-profiling (&key (profiler *profiler*) (file (profiler-output-file profiler)))
  (assert (profiler-last-result profiler) ()
          "No profiling results available in ~s" profiler)
  (setf (profiler-output-file profiler) file)
  (output-sample-html (profiler-last-result profiler) file))

(defun end-profiling (&key abort (verbose nil) output-file (profiler *profiler*))
  (stop-profiler :abort abort :verbose verbose :profiler profiler)
  (unless abort
    (when output-file<commit_msg>Turn off impurifying after profile, in case that's bit-rotted somehow<commit_after>(in-package :sample-profiler)

(defstruct profiler
  (process nil)
  (file nil)
  (last-result nil)
  output-file)

(defvar *profiler* (make-profiler))

(defvar *impurify-after-profile* nil)

(defun start-profiling (&key (seconds T) (verbose nil) (output-file "profile.html") (profiler *profiler*))
  (when (eq seconds T) (setq seconds (* 60 60 24)))
  (check-type seconds (integer (0)))
  (when (profiler-process profiler)
    (when (eq :running (ccl:external-process-status (profiler-process profiler)))
      (cerror "Abort existing profiler" "Profiling currently in effect: ~s." profiler))
    (end-profiling :abort t))
  (ccl::purify)
  (let* ((file (ccl:temp-pathname))
         (proc (ccl:run-program "/usr/bin/sample"
                                (list (princ-to-string (ccl::getpid))
                                      (princ-to-string seconds)
                                      "-file"
                                      (ccl:native-translated-namestring file))
                                :input nil
                                :wait nil
                                :output :stream)))
    (setf (profiler-output-file profiler) output-file)
    (setf (profiler-last-result profiler) nil)
    (setf (profiler-file profiler) file)
    (setf (profiler-process profiler) proc)
    ;; wait for some output to appear then copy it to standard output.
    (let ((stream (ccl:external-process-output-stream proc)))
      (when (peek-char nil stream nil)
        (when verbose (fresh-line))
        (loop as ch = (read-char-no-hang stream nil) while ch
           do (when verbose (write-char ch))))))
  (values))

(defun stop-profiler (&key abort (verbose nil) (profiler *profiler*))
  (let ((p (profiler-process profiler)))
    (setf (profiler-process profiler) nil)
    (ccl:signal-external-process p (if abort #$SIGKILL #$SIGINT) :error-if-exited nil)
    (when (and (ccl:external-process-id p) (eq (ccl:external-process-status p) :running))
      ;; TODO: if abort is true, should time this out in fairly short order.  Maybe don't even
      ;; bother with the semaphore, just sleep a second and then proceed no matter what.
      (ccl:with-interrupts-enabled
        (ccl:wait-on-semaphore (ccl::external-process-completed p) nil "end-profiling")))
    (when verbose
      (let ((stream (ccl:external-process-output-stream p)))
        (when (listen stream)
          (fresh-line)
          (loop as ch = (read-char-no-hang stream nil) while ch do (write-char ch))))))
  (setf (profiler-last-result profiler)
        (let ((file (profiler-file profiler)))
          (when (probe-file file)
            (when verbose (format t "~&Parsing ~s" file))
            (read-sample-file file))))
  (when *impurify-after-profile*  (ccl::impurify))
  (profiler-last-result profiler))

(defun output-profiling (&key (profiler *profiler*) (file (profiler-output-file profiler)))
  (assert (profiler-last-result profiler) ()
          "No profiling results available in ~s" profiler)
  (setf (profiler-output-file profiler) file)
  (output-sample-html (profiler-last-result profiler) file))

(defun end-profiling (&key abort (verbose nil) output-file (profiler *profiler*))
  (stop-profiler :abort abort :verbose verbose :profiler profiler)
  (unless abort
    (when output-file
{'fraction_non_alphanumeric': 0.13141219070609536, 'fraction_numerical': 0.002112251056125528, 'mean_word_length': 2.6705426356589146, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '597355', 'n_tokens_mistral': 2118, 'n_tokens_neox': 2105, 'n_words': 597}