Class: Insika::Soak::Runner::Http

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/soak/runner.rb

Overview

stdlib HTTP transport, deliberately Net::HTTP — the same choice loadtest.rb made, and the runner measures the reactor from outside it.

Instance Method Summary collapse

Constructor Details

#initialize(token:) ⇒ Http

Returns a new instance of Http.



465
466
467
# File 'lib/insika/soak/runner.rb', line 465

def initialize(token:)
  @token = token
end

Instance Method Details

#get_vitals(base_url) ⇒ Object



469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/insika/soak/runner.rb', line 469

def get_vitals(base_url)
  uri = URI.join(base_url + "/", "v1/vitals")
  req = Net::HTTP::Get.new(uri)
  req["Authorization"] = "Bearer #{@token}"
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == "https"
  http.open_timeout = 10
  http.read_timeout = 30
  res = http.request(req)
  { status: res.code.to_i, body: safe_json(res.body) }
rescue StandardError => e
  { status: nil, body: nil, error: e.class.to_s }
end

#post_turn(base_url, agent, user:, message:, timeout:) ⇒ Object

One turn: streaming POST /v1/responses; measures TTFB/total and extracts the last usage + timing frames. No retry — a failed turn is evidence.



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
# File 'lib/insika/soak/runner.rb', line 486

def post_turn(base_url, agent, user:, message:, timeout:)
  uri = URI.join(base_url + "/", "v1/responses")
  req = Net::HTTP::Post.new(uri)
  req["Authorization"] = "Bearer #{@token}"
  req["Content-Type"] = "application/json"
  req["Accept"] = "text/event-stream"
  req.body = JSON.generate(model: "openclaw:#{agent}", user: user, stream: true, input: message)

  t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  ttfb = nil
  usage = nil
  timing = nil
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == "https"
  http.read_timeout = timeout
  http.open_timeout = 10

  status = nil
  http.start do
    http.request(req) do |res|
      status = res.code.to_i
      return { ok: false, status: status, error: "http #{status}" } unless status.between?(200, 299)

      buffer = +""
      res.read_body do |chunk|
        ttfb ||= Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
        buffer << chunk
        while (i = buffer.index("\n\n"))
          frame = buffer.slice!(0..i + 1)
          frame.each_line do |line|
            next unless line.start_with?("data:")

            payload = line.sub(/^data:\s*/, "").strip
            next if payload.empty? || payload == "[DONE]"

            begin
              obj = JSON.parse(payload)
              u = obj.dig("response", "usage") || obj["usage"]
              usage = u if u
              tm = obj.dig("response", "timing")
              timing = tm if tm
            rescue JSON::ParserError
              nil
            end
          end
        end
      end
    end
  end
  { ok: true, status: status, ttfb_ms: (ttfb || 0) * 1000.0,
    total_ms: (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000.0,
    usage: usage, timing: timing, error: nil }
rescue ::Timeout::Error
  { ok: false, status: status, error: "timeout" }
rescue StandardError => e
  { ok: false, status: status, error: e.class.to_s }
end