FEATURE: Snapshots transporter (#463)

FEATURE: Snapshots transporter (#463)

  • FEATURE: Snapshots transporter

  • Protect buffer access

  • Add mertics

  • Remove unneeded dup

diff --git a/lib/html/includes.tmpl b/lib/html/includes.tmpl
index 1f1b69c..aa61388 100644
--- a/lib/html/includes.tmpl
+++ b/lib/html/includes.tmpl
@@ -17,7 +17,7 @@
         <span class="profiler-name">
           {{= it.name}} <span class="profiler-overall-duration">({{= MiniProfiler.formatDuration(it.duration_milliseconds)}} ms)</span>
         </span>
-        <span class="profiler-server-time">{{= it.machine_name}} on {{= MiniProfiler.renderDate(it.started)}}</span>
+        <span class="profiler-server-time">{{= it.machine_name}} on {{= MiniProfiler.renderDate(it.started_formatted)}}</span>
       </div>
       <div class="profiler-output">
         <table class="profiler-timings">
diff --git a/lib/mini_profiler/config.rb b/lib/mini_profiler/config.rb
index 362e886..16e4d67 100644
--- a/lib/mini_profiler/config.rb
+++ b/lib/mini_profiler/config.rb
@@ -53,6 +53,8 @@ module Rack
           @html_container       = 'body'
           @position             = "top-left"
           @snapshot_hidden_custom_fields = []
+          @snapshots_transport_destination_url = nil
+          @snapshots_transport_auth_key = nil
 
           self
         }
@@ -64,13 +66,17 @@ module Rack
         :flamegraph_sample_rate, :logger, :pre_authorize_cb, :skip_paths,
         :skip_schema_queries, :storage, :storage_failure, :storage_instance,
         :storage_options, :user_provider, :enable_advanced_debugging_tools,
-        :snapshot_every_n_requests, :snapshots_limit
-      attr_accessor :skip_sql_param_names, :suppress_encoding, :max_sql_param_length
+        :skip_sql_param_names, :suppress_encoding, :max_sql_param_length
 
       # ui accessors
       attr_accessor :collapse_results, :max_traces_to_show, :position,
         :show_children, :show_controls, :show_trivial, :show_total_sql_count,
-        :start_hidden, :toggle_shortcut, :html_container, :snapshot_hidden_custom_fields
+        :start_hidden, :toggle_shortcut, :html_container
+
+      # snapshot related config
+      attr_accessor :snapshot_every_n_requests, :snapshots_limit,
+        :snapshot_hidden_custom_fields, :snapshots_transport_destination_url,
+        :snapshots_transport_auth_key
 
       # Deprecated options
       attr_accessor :use_existing_jquery
diff --git a/lib/mini_profiler/profiler.rb b/lib/mini_profiler/profiler.rb
index 3024061..23f26b4 100644
--- a/lib/mini_profiler/profiler.rb
+++ b/lib/mini_profiler/profiler.rb
@@ -94,6 +94,11 @@ module Rack
           params
         end
       end
+
+      def snapshots_transporter?
+        !!config.snapshots_transport_destination_url &&
+        !!config.snapshots_transport_auth_key
+      end
     end
 
     #
@@ -809,10 +814,14 @@ Append the following to your query string:
         )
         custom_fields = MiniProfiler.get_snapshot_custom_fields
         page_struct[:custom_fields] = custom_fields if custom_fields
-        @storage.push_snapshot(
-          page_struct,
-          @config
-        )
+        if Rack::MiniProfiler.snapshots_transporter?
+          Rack::MiniProfiler::SnapshotsTransporter.transport(page_struct)
+        else
+          @storage.push_snapshot(
+            page_struct,
+            @config
+          )
+        end
       end
       self.current = nil
       results
diff --git a/lib/mini_profiler/snapshots_transporter.rb b/lib/mini_profiler/snapshots_transporter.rb
new file mode 100644
index 0000000..7e91149
--- /dev/null
+++ b/lib/mini_profiler/snapshots_transporter.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+class ::Rack::MiniProfiler::SnapshotsTransporter
+  @@transported_snapshots_count = 0
+  @@successful_http_requests_count = 0
+  @@failed_http_requests_count = 0
+
+  class << self
+    def transported_snapshots_count
+      @@transported_snapshots_count
+    end
+    def successful_http_requests_count
+      @@successful_http_requests_count
+    end
+    def failed_http_requests_count
+      @@failed_http_requests_count
+    end
+
+    def transport(snapshot)
+      @transporter ||= self.new(Rack::MiniProfiler.config)
+      @transporter.ship(snapshot)
+    end
+  end
+
+  attr_reader :buffer
+  attr_accessor :max_buffer_size
+
+  def initialize(config)
+    @uri = URI(config.snapshots_transport_destination_url)
+    @auth_key = config.snapshots_transport_auth_key
+    @thread = nil
+    @thread_mutex = Mutex.new
+    @buffer = []
+    @buffer_mutex = Mutex.new
+    @max_buffer_size = 100
+    @testing = false
+  end
+
+  def ship(snapshot)
+    @buffer_mutex.synchronize do
+      @buffer << snapshot
+      @buffer.shift if @buffer.size > @max_buffer_size
+    end
+    @thread_mutex.synchronize { start_thread }
+  end
+
+  def flush_buffer
+    buffer_content = @buffer_mutex.synchronize do
+      @buffer.dup if @buffer.size > 0
+    end
+    if buffer_content
+      request = Net::HTTP::Post.new(
+        @uri,
+        'Content-Type' => 'application/json',
+        'Mini-Profiler-Transport-Auth' => @auth_key
+      )
+      request.body = { snapshots: buffer_content }.to_json
+      http = Net::HTTP.new(@uri.hostname, @uri.port)
+      http.use_ssl = @uri.scheme == 'https'
+      res = http.request(request)
+      if res.code.to_i == 200
+        @@successful_http_requests_count += 1
+        @@transported_snapshots_count += buffer_content.size
+        @buffer_mutex.synchronize do
+          @buffer -= buffer_content
+        end
+      else
+        @@failed_http_requests_count += 1
+      end
+    end
+  end
+
+  private
+
+  def start_thread
+    return if @thread&.alive? || @testing
+    @thread = Thread.new do
+      while true
+        sleep 10
+        flush_buffer
+      end
+    end
+  end
+end
diff --git a/lib/mini_profiler/timer_struct/page.rb b/lib/mini_profiler/timer_struct/page.rb
index cf78aac..dbf13df 100644
--- a/lib/mini_profiler/timer_struct/page.rb
+++ b/lib/mini_profiler/timer_struct/page.rb
@@ -10,6 +10,53 @@ module Rack
       #     :has_many TimerStruct::Sql children
       #     :has_many TimerStruct::Custom children
       class Page < TimerStruct::Base
+        class << self
+          def from_hash(hash)
+            hash = symbolize_hash(hash)
+            if hash.key?(:custom_timing_names)
+              hash[:custom_timing_names] = []
+            end
+            hash.delete(:started_formatted)
+            if hash.key?(:duration_milliseconds)
+              hash[:duration_milliseconds] = 0
+            end
+            page = self.allocate
+            page.instance_variable_set(:@attributes, hash)
+            page
+          end
+
+          private
+
+          def symbolize_hash(hash)
+            new_hash = {}
+            hash.each do |k, v|
+              sym_k = String === k ? k.to_sym : k
+              if Hash === v
+                new_hash[sym_k] = symbolize_hash(v)
+              elsif Array === v
+                new_hash[sym_k] = symbolize_array(v)
+              else
+                new_hash[sym_k] = v
+              end
+            end
+            new_hash
+          end
+
+          def symbolize_array(array)
+            array.map do |item|
+              if Array === item
+                symbolize_array(item)
+              elsif Hash === item
+                symbolize_hash(item)
+              else
+                item
+              end
+            end
+          end
+        end
+
+        attr_reader :attributes
+
         def initialize(env)
           timer_id     = MiniProfiler.generate_id
           page_name    = env['PATH_INFO']
@@ -74,7 +121,7 @@ module Rack
 
         def extra_json
           {
-            started: '/Date(%d)/' % @attributes[:started_at],
+            started_formatted: '/Date(%d)/' % @attributes[:started_at],
             duration_milliseconds: @attributes[:root][:duration_milliseconds],
             custom_timing_names: @attributes[:custom_timing_stats].keys.sort
           }
diff --git a/lib/rack-mini-profiler.rb b/lib/rack-mini-profiler.rb
index 53ab6c4..9ee1443 100644

[... diff too long, it was truncated ...]

GitHub sha: 9041e21f

This commit appears in #463 which was approved by SamSaffron. It was merged by OsamaSayegh.