lemans 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1cf8e2ef8780e6577e9810b782c4fd1fe37fbe5691589c3ef665c09068ef4121
4
- data.tar.gz: c697394a89a3056df20a174e55f83ad2ab81038cff1662a2bb91da784184c9d6
3
+ metadata.gz: 6ce349f204cd0dc573a18932210dd5298941fe097f24576df1e318b29ce7152a
4
+ data.tar.gz: c9113c9bc9dbc4cf1f92d422b3ff929b20420e769828f55585f2a3eead34aa93
5
5
  SHA512:
6
- metadata.gz: 223472ec514232b4ec45a5f735d5786a7932ccd929064cc2c92bc5ebe22a70470836a6054694598a2c761c8eeac21fc7d8dc608a8506d309f2f1bfa7ad0d53eb
7
- data.tar.gz: 3d05d9d1a0c1cd3432cf87c76f2a0afa6ec4b05298be9d6dbc8519268fb7805c6535246550c394a764de56c674ef22dba0a6c103cec807614b43d3548684ca77
6
+ metadata.gz: 89ecd01b291a2d75475f346e8e780d87e104139e0ca34f48dde192325ec2ed593920e0e63f8b41a26b9e13144052e6bfe841831bc5d40a4432a0cd98d3fc39fc
7
+ data.tar.gz: 84b2bceb8d5b7c60b0e6513bb05b801d38081f8f59c55e96338a2a47d660c0521a31d224c365e0b694581f4e600552e4e9e390f3ccd6130d7fc61c8a9897cce2
data/exe/lemans-remote CHANGED
@@ -24,8 +24,10 @@
24
24
  # Then watch, fetch, and clean up:
25
25
  #
26
26
  # exe/lemans-remote status [--history] [--running | --complete]
27
- # exe/lemans-remote pull-runs [RUN_ID ...] [--all]
27
+ # exe/lemans-remote pull-runs [RUN_ID ...] [--all] [--dry-run]
28
28
  # exe/lemans-remote clobber RUN_ID ... | --all
29
+ # exe/lemans-remote snapshots
30
+ # exe/lemans-remote deprovision [--name SNAPSHOT]
29
31
  #
30
32
  # Credentials come from the host ENV: DAYTONA_API_KEY (or DAYTONA_TOKEN),
31
33
  # OPENROUTER_API_KEY, LEMANS_PROVIDER_ORDER; forward extras with --env KEY.
@@ -48,7 +50,7 @@ require "timeout"
48
50
  require "tmpdir"
49
51
  require "openssl"
50
52
 
51
- module LemansRemote
53
+ module LemansRemote # :nodoc: all
52
54
  LABEL = "lemans-remote"
53
55
  RUN_ID_LABEL = "lemans-remote/run-id"
54
56
  STATUS_LABEL = "lemans-remote/status"
@@ -112,7 +114,7 @@ module LemansRemote
112
114
  name
113
115
  end
114
116
 
115
- def drop(target = name)
117
+ def drop(target = name) # rubocop:disable Naming/PredicateMethod
116
118
  existing = find(target)
117
119
  return false unless existing
118
120
 
@@ -349,7 +351,7 @@ module LemansRemote
349
351
  end
350
352
 
351
353
  def manifests(sandbox)
352
- code = 'require "json"; puts Dir.glob("/vault/*/manifest.json")' \
354
+ code = 'require "json"; puts Dir.glob("/vault/*/manifest.json", File::FNM_DOTMATCH)' \
353
355
  ".filter_map { |f| JSON.parse(File.read(f)) rescue nil }.to_json"
354
356
  response = sandbox.process.exec(command: "ruby -e #{Shellwords.escape(code)}", timeout: 120)
355
357
  raise "could not read the vault manifests: #{response.result}" unless response.exit_code.zero?
@@ -358,6 +360,14 @@ module LemansRemote
358
360
  body.empty? ? [] : JSON.parse(body)
359
361
  end
360
362
 
363
+ def archive_size(sandbox, run_id)
364
+ remote = "/vault/#{run_id}/runs.tar.gz"
365
+ response = sandbox.process.exec(command: "du -h #{Shellwords.escape(remote)}", timeout: 60)
366
+ return nil unless response.exit_code.zero?
367
+
368
+ response.result.to_s.split.first
369
+ end
370
+
361
371
  def download_runs(sandbox, run_id, runs_dir)
362
372
  remote = "/vault/#{run_id}/runs.tar.gz"
363
373
  probe = sandbox.process.exec(command: "test -f #{Shellwords.escape(remote)}", timeout: 60)
@@ -385,7 +395,7 @@ module LemansRemote
385
395
  def mark_pulled(sandbox, run_id)
386
396
  code = 'require "json"; require "time"; ' \
387
397
  'path = File.join("/vault", ARGV[0], "manifest.json"); ' \
388
- 'data = JSON.parse(File.read(path)); ' \
398
+ "data = JSON.parse(File.read(path)); " \
389
399
  'data["pulled_at"] = Time.now.utc.iso8601; ' \
390
400
  "File.write(path, JSON.pretty_generate(data))"
391
401
  sandbox.process.exec(command: "ruby -e #{Shellwords.escape(code)} #{Shellwords.escape(run_id)}", timeout: 60)
@@ -538,7 +548,7 @@ module LemansRemote
538
548
  def run_metadata(sandbox)
539
549
  {
540
550
  "run_id" => @run_id,
541
- "bench" => @bench.root.basename.to_s,
551
+ "bench" => @bench.root.expand_path.basename.to_s,
542
552
  "tasks" => @tasks,
543
553
  "models" => @models,
544
554
  "args" => @extra_args,
@@ -734,9 +744,7 @@ module LemansRemote
734
744
  raise Thor::Error, "lemans-remote: no such task(s): #{unknown.join(", ")}" if unknown.any?
735
745
 
736
746
  provisioner = build_provisioner
737
- unless provisioner.provisioned?
738
- raise Thor::Error, "lemans-remote: snapshot #{provisioner.name} not found — run `lemans-remote provision` first"
739
- end
747
+ raise Thor::Error, "lemans-remote: snapshot #{provisioner.name} not found — run `lemans-remote provision` first" unless provisioner.provisioned?
740
748
 
741
749
  attempts = options[:attempts].to_i
742
750
  raise Thor::Error, "lemans-remote: --attempts must be at least 1" if attempts < 1
@@ -786,7 +794,7 @@ module LemansRemote
786
794
  if filtered
787
795
  say "no #{options[:running] ? "running" : "complete"} lemans-remote runs"
788
796
  else
789
- say "no lemans-remote runs#{options[:history] ? "" : " — try --history for completed ones"}"
797
+ say "no lemans-remote runs#{" — try --history for completed ones" unless options[:history]}"
790
798
  end
791
799
  return
792
800
  end
@@ -801,6 +809,7 @@ module LemansRemote
801
809
  map "pull-runs" => :pull_runs
802
810
  desc "pull-runs [RUN_IDS...]", "Download archived runs from the vault into the local runs directory"
803
811
  option :all, type: :boolean, default: false, desc: "Pull every completed run, even ones already pulled"
812
+ option :dry_run, type: :boolean, default: false, desc: "Preview what would be downloaded without pulling anything"
804
813
  option :runs_dir, default: "runs", desc: "Local directory to sync the results into"
805
814
  def pull_runs(*run_ids)
806
815
  pulled = []
@@ -821,6 +830,16 @@ module LemansRemote
821
830
  say_status :pull, "nothing new to pull (use --all to re-pull)", :yellow if targets.empty?
822
831
 
823
832
  targets.each do |run_id|
833
+ if options[:dry_run]
834
+ size = vault.archive_size(sandbox, run_id)
835
+ if size
836
+ say_status :would_pull, "#{run_id} (#{size})", :cyan
837
+ else
838
+ say_status :missing, "#{run_id} has no runs.tar.gz (still running?)", :yellow
839
+ end
840
+ next
841
+ end
842
+
824
843
  results = vault.download_runs(sandbox, run_id, options[:runs_dir])
825
844
  if results
826
845
  vault.mark_pulled(sandbox, run_id)
@@ -846,9 +865,7 @@ module LemansRemote
846
865
 
847
866
  live = Fleet.live
848
867
  if options[:all]
849
- unless live.empty?
850
- raise Thor::Error, "lemans-remote: #{live.size} run(s) still live (#{live.map(&:run_id).join(", ")}) — wait for them to finish"
851
- end
868
+ raise Thor::Error, "lemans-remote: #{live.size} run(s) still live (#{live.map(&:run_id).join(", ")}) — wait for them to finish" unless live.empty?
852
869
  return unless options[:force] || yes?("Delete the whole #{Vault::NAME} volume and every stopped run sandbox? [y/N]")
853
870
 
854
871
  Fleet.rows.each { safe_delete_sandbox(_1.sandbox) }
@@ -876,6 +893,28 @@ module LemansRemote
876
893
  raise Thor::Error, "lemans-remote: #{e.message}"
877
894
  end
878
895
 
896
+ desc "snapshots", "List lemans-remote orchestrator snapshots"
897
+ def snapshots
898
+ current = build_provisioner.name
899
+ rows = all_snapshots.select { _1.name.start_with?("#{LABEL}-") }.map do |snap|
900
+ shape = "#{snap.cpu.to_i}cpu/#{snap.mem.to_i}gb/#{snap.disk.to_i}gb"
901
+ [snap.name, snap.state, shape, snap.created_at.to_s[0, 19], snap.last_used_at.to_s[0, 19],
902
+ snap.name == current ? "current" : "-"]
903
+ end
904
+
905
+ if rows.empty?
906
+ say "no lemans-remote snapshots — run `lemans-remote provision` first"
907
+ return
908
+ end
909
+
910
+ rows.sort_by! { _1[3].to_s }
911
+ rows.reverse!
912
+ print_table([%w[snapshot state shape created last-used note]] + rows)
913
+ say "delete old ones with `lemans-remote deprovision --name SNAPSHOT`"
914
+ rescue Lemans::ConfigError, RuntimeError => e
915
+ raise Thor::Error, "lemans-remote: #{e.message}"
916
+ end
917
+
879
918
  desc "deprovision", "Delete the orchestrator snapshot"
880
919
  option :name, desc: "Delete a specific snapshot by name (default: the one for this shape and version)"
881
920
  def deprovision
@@ -895,14 +934,14 @@ module LemansRemote
895
934
  say Lemans::VERSION
896
935
  end
897
936
 
937
+ FANOUT_THREADS = 4
938
+
898
939
  private
899
940
 
900
941
  def build_provisioner
901
942
  Provisioner.new(cpus: options[:cpus].to_i, memory_gb: options[:memory].to_i, disk_gb: options[:disk].to_i)
902
943
  end
903
944
 
904
- FANOUT_THREADS = 4
905
-
906
945
  def plan_batches(bench, tasks)
907
946
  return [tasks] if options[:sync] || options[:run_in_band]
908
947
 
@@ -957,7 +996,7 @@ module LemansRemote
957
996
  end
958
997
 
959
998
  def build_run_id(bench, tasks, models, attempt = nil)
960
- parts = [bench.root.basename.to_s]
999
+ parts = [bench.root.expand_path.basename.to_s]
961
1000
  parts << (tasks.size == 1 ? tasks.first : "#{tasks.size}tasks") if tasks.any?
962
1001
  if models.any?
963
1002
  short = models.first.split("/").last
@@ -971,13 +1010,24 @@ module LemansRemote
971
1010
  parts.map { name_part(_1) }.reject(&:empty?).join("-")
972
1011
  end
973
1012
 
974
- def name_part(value) = value.to_s.downcase.gsub(/[^a-z0-9._+]+/, "-").gsub(/\A-+|-+\z/, "")
1013
+ def name_part(value) = value.to_s.downcase.gsub(/[^a-z0-9._+]+/, "-").gsub(/\A[-.]+|[-.]+\z/, "")
1014
+
1015
+ def all_snapshots
1016
+ items = []
1017
+ page = 1
1018
+ loop do
1019
+ result = LemansRemote.client.snapshot.list(page: page, limit: 200)
1020
+ items.concat(result.items)
1021
+ break if page >= result.total_pages.to_i
1022
+
1023
+ page += 1
1024
+ end
1025
+ items
1026
+ end
975
1027
 
976
1028
  def with_vault(&block)
977
1029
  provisioner = build_provisioner
978
- unless provisioner.provisioned?
979
- raise Thor::Error, "lemans-remote: snapshot #{provisioner.name} not found — run `lemans-remote provision` first"
980
- end
1030
+ raise Thor::Error, "lemans-remote: snapshot #{provisioner.name} not found — run `lemans-remote provision` first" unless provisioner.provisioned?
981
1031
 
982
1032
  vault = VaultClient.new(snapshot: provisioner.name)
983
1033
  vault.with_helper { |sandbox| block.call(vault, sandbox) }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lemans
4
- VERSION = "0.2.2"
4
+ VERSION = "0.2.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lemans
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov