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 +4 -4
- data/exe/lemans-remote +70 -20
- data/lib/lemans/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ce349f204cd0dc573a18932210dd5298941fe097f24576df1e318b29ce7152a
|
|
4
|
+
data.tar.gz: c9113c9bc9dbc4cf1f92d422b3ff929b20420e769828f55585f2a3eead34aa93
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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#{
|
|
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
|
|
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) }
|
data/lib/lemans/version.rb
CHANGED