aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2024-11-08 15:51:27 -0500
committerXe Iaso <me@xeiaso.net>2024-11-08 15:51:27 -0500
commit3756e97b6aeee2e9f8c9e4c1fd6f65ebba6f54aa (patch)
tree7d54f3e533b271deaa4e2c1685a787ac0c710590 /web
parenta731950f5c80c6b19c8b92f85600d38f6dc9ae63 (diff)
downloadx-3756e97b6aeee2e9f8c9e4c1fd6f65ebba6f54aa.tar.xz
x-3756e97b6aeee2e9f8c9e4c1fd6f65ebba6f54aa.zip
cmd/orodyagzou: initial implementation
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'web')
-rw-r--r--web/vastai/vastaicli/.gitignore1
-rw-r--r--web/vastai/vastaicli/instances.go198
-rw-r--r--web/vastai/vastaicli/search.go164
-rw-r--r--web/vastai/vastaicli/search_test.go19
4 files changed, 382 insertions, 0 deletions
diff --git a/web/vastai/vastaicli/.gitignore b/web/vastai/vastaicli/.gitignore
new file mode 100644
index 0000000..1957ae2
--- /dev/null
+++ b/web/vastai/vastaicli/.gitignore
@@ -0,0 +1 @@
+gpu_names_cache.json \ No newline at end of file
diff --git a/web/vastai/vastaicli/instances.go b/web/vastai/vastaicli/instances.go
new file mode 100644
index 0000000..26c9d14
--- /dev/null
+++ b/web/vastai/vastaicli/instances.go
@@ -0,0 +1,198 @@
+package vastaicli
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "strings"
+
+ "al.essio.dev/pkg/shellescape"
+)
+
+type InstanceConfig struct {
+ DiskSizeGB int `json:"diskSizeGB"`
+ DockerImage string `json:"dockerImage"`
+ Environment map[string]string `json:"env"`
+ OnStartCMD string `json:"onStartCmd"`
+ Ports []int `json:"ports"`
+}
+
+func (ic InstanceConfig) EnvString() string {
+ var sb strings.Builder
+
+ for _, port := range ic.Ports {
+ fmt.Fprintf(&sb, "-p %d:%d ", port, port)
+ }
+
+ // -e FOO=bar
+ for k, v := range ic.Environment {
+ fmt.Fprintf(&sb, "-e %s=%s ", shellescape.Quote(k), shellescape.Quote(v))
+ }
+
+ return sb.String()
+}
+
+type NewInstance struct {
+ Success bool `json:"success"`
+ NewContract int `json:"new_contract"`
+}
+
+func Mint(ctx context.Context, askContractID int, ic InstanceConfig) (*NewInstance, error) {
+ result, err := runJSON[NewInstance](
+ ctx,
+ "vastai", "create", "instance",
+ askContractID,
+ "--image", ic.DockerImage,
+ "--env", ic.EnvString(),
+ "--disk", ic.DiskSizeGB,
+ "--onstart-cmd", ic.OnStartCMD,
+ "--raw",
+ )
+
+ if err != nil {
+ return nil, fmt.Errorf("can't create instance for contract ID %d: %v", askContractID, err)
+ }
+
+ return &result, nil
+}
+
+func Slay(ctx context.Context, instanceID int) error {
+ type slayResp struct {
+ Success bool `json:"success"`
+ }
+
+ if _, err := runJSON[slayResp](ctx, "vastai", "destroy", "instance", instanceID, "--raw"); err != nil {
+ return fmt.Errorf("can't slay instance %d: %w", instanceID, err)
+ }
+
+ return nil
+}
+
+func GetInstance(ctx context.Context, instanceID int) (*Instance, error) {
+ result, err := runJSON[Instance](ctx, "vastai", "show", "instance", instanceID, "--raw")
+ if err != nil {
+ return nil, fmt.Errorf("can't get instance %d: %w", instanceID, err)
+ }
+
+ return &result, nil
+}
+
+type Instance struct {
+ ActualStatus string `json:"actual_status"`
+ BundleID int `json:"bundle_id"`
+ BwNvlink float64 `json:"bw_nvlink"`
+ ClientRunTime float64 `json:"client_run_time"`
+ ComputeCap int `json:"compute_cap"`
+ CPUArch string `json:"cpu_arch"`
+ CPUCores int `json:"cpu_cores"`
+ CPUCoresEffective float64 `json:"cpu_cores_effective"`
+ CPUName string `json:"cpu_name"`
+ CPURAM int `json:"cpu_ram"`
+ CPUUtil float64 `json:"cpu_util"`
+ CreditBalance any `json:"credit_balance"`
+ CreditDiscount any `json:"credit_discount"`
+ CreditDiscountMax float64 `json:"credit_discount_max"`
+ CudaMaxGood float64 `json:"cuda_max_good"`
+ CurState string `json:"cur_state"`
+ DirectPortCount int `json:"direct_port_count"`
+ DirectPortEnd int `json:"direct_port_end"`
+ DirectPortStart int `json:"direct_port_start"`
+ DiskBw float64 `json:"disk_bw"`
+ DiskName string `json:"disk_name"`
+ DiskSpace float64 `json:"disk_space"`
+ DiskUsage float64 `json:"disk_usage"`
+ DiskUtil float64 `json:"disk_util"`
+ Dlperf float64 `json:"dlperf"`
+ DlperfPerDphtotal float64 `json:"dlperf_per_dphtotal"`
+ DphBase float64 `json:"dph_base"`
+ DphTotal float64 `json:"dph_total"`
+ DriverVersion string `json:"driver_version"`
+ Duration float64 `json:"duration"`
+ EndDate float64 `json:"end_date"`
+ External bool `json:"external"`
+ ExtraEnv any `json:"extra_env"`
+ FlopsPerDphtotal float64 `json:"flops_per_dphtotal"`
+ Geolocation string `json:"geolocation"`
+ GpuDisplayActive bool `json:"gpu_display_active"`
+ GpuFrac float64 `json:"gpu_frac"`
+ GpuLanes int `json:"gpu_lanes"`
+ GpuMemBw float64 `json:"gpu_mem_bw"`
+ GpuName string `json:"gpu_name"`
+ GpuRAM int `json:"gpu_ram"`
+ GpuTemp any `json:"gpu_temp"`
+ GpuTotalram int `json:"gpu_totalram"`
+ GpuUtil any `json:"gpu_util"`
+ HasAvx int `json:"has_avx"`
+ HostID int `json:"host_id"`
+ HostRunTime float64 `json:"host_run_time"`
+ HostingType any `json:"hosting_type"`
+ ID int `json:"id"`
+ ImageArgs []any `json:"image_args"`
+ ImageRuntype string `json:"image_runtype"`
+ ImageUUID string `json:"image_uuid"`
+ InetDown float64 `json:"inet_down"`
+ InetDownBilled any `json:"inet_down_billed"`
+ InetDownCost float64 `json:"inet_down_cost"`
+ InetUp float64 `json:"inet_up"`
+ InetUpBilled any `json:"inet_up_billed"`
+ InetUpCost float64 `json:"inet_up_cost"`
+ Instance InstancePricingDetails `json:"instance"`
+ IntendedStatus string `json:"intended_status"`
+ InternetDownCostPerTb float64 `json:"internet_down_cost_per_tb"`
+ InternetUpCostPerTb float64 `json:"internet_up_cost_per_tb"`
+ IsBid bool `json:"is_bid"`
+ JupyterToken string `json:"jupyter_token"`
+ Label any `json:"label"`
+ LocalIpaddrs string `json:"local_ipaddrs"`
+ Logo string `json:"logo"`
+ MachineDirSSHPort int `json:"machine_dir_ssh_port"`
+ MachineID int `json:"machine_id"`
+ MemLimit any `json:"mem_limit"`
+ MemUsage any `json:"mem_usage"`
+ MinBid float64 `json:"min_bid"`
+ MoboName string `json:"mobo_name"`
+ NextState string `json:"next_state"`
+ NumGpus int `json:"num_gpus"`
+ Onstart any `json:"onstart"`
+ OsVersion string `json:"os_version"`
+ PciGen float64 `json:"pci_gen"`
+ PcieBw float64 `json:"pcie_bw"`
+ PublicIpaddr string `json:"public_ipaddr"`
+ Reliability2 float64 `json:"reliability2"`
+ Rentable bool `json:"rentable"`
+ Score float64 `json:"score"`
+ Search SearchPricing `json:"search"`
+ SSHHost string `json:"ssh_host"`
+ SSHIdx string `json:"ssh_idx"`
+ SSHPort int `json:"ssh_port"`
+ StartDate float64 `json:"start_date"`
+ StaticIP bool `json:"static_ip"`
+ StatusMsg string `json:"status_msg"`
+ StorageCost float64 `json:"storage_cost"`
+ StorageTotalCost float64 `json:"storage_total_cost"`
+ TemplateHashID any `json:"template_hash_id"`
+ TimeRemaining string `json:"time_remaining"`
+ TimeRemainingIsbid string `json:"time_remaining_isbid"`
+ TotalFlops float64 `json:"total_flops"`
+ UptimeMins any `json:"uptime_mins"`
+ Verification string `json:"verification"`
+ VmemUsage any `json:"vmem_usage"`
+ VramCostperhour float64 `json:"vram_costperhour"`
+ Webpage any `json:"webpage"`
+
+ Ports map[string][]PortData
+}
+
+func (i Instance) AddrFor(port int) (string, bool) {
+ data, ok := i.Ports[fmt.Sprintf("%d/tcp", port)]
+ if !ok {
+ return "", false
+ }
+
+ return net.JoinHostPort(i.PublicIpaddr, data[0].HostPort), true
+}
+
+type PortData struct {
+ HostIp string `json:"HostIp"`
+ HostPort string `json:"HostPort"`
+}
diff --git a/web/vastai/vastaicli/search.go b/web/vastai/vastaicli/search.go
new file mode 100644
index 0000000..2a143c0
--- /dev/null
+++ b/web/vastai/vastaicli/search.go
@@ -0,0 +1,164 @@
+package vastaicli
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "os"
+ "os/exec"
+)
+
+// zilch returns the zero value of a given type.
+func zilch[T any]() T { return *new(T) }
+
+func runJSON[T any](ctx context.Context, program string, args ...any) (T, error) {
+ exePath, err := exec.LookPath(program)
+ if err != nil {
+ return zilch[T](), fmt.Errorf("can't find %s: %w", program, err)
+ }
+
+ var argStr []string
+
+ for _, arg := range args {
+ argStr = append(argStr, fmt.Sprint(arg))
+ }
+
+ var stdout bytes.Buffer
+ var stderr bytes.Buffer
+
+ cmd := exec.CommandContext(ctx, exePath, argStr...)
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+
+ if err := cmd.Run(); err != nil {
+ os.Stderr.Write(stderr.Bytes())
+ return zilch[T](), fmt.Errorf("can't run %s: %w", program, err)
+ }
+
+ var result T
+ if err := json.NewDecoder(&stdout).Decode(&result); err != nil {
+ return zilch[T](), fmt.Errorf("can't decode json: %w", err)
+ }
+
+ return result, nil
+}
+
+func Search(ctx context.Context, filters, orderBy string) ([]SearchItem, error) {
+ result, err := runJSON[[]SearchItem](ctx, "vastai", "search", "offers", filters, "-o", orderBy, "--raw")
+ if err != nil {
+ return nil, fmt.Errorf("while searching for %s: %w", filters, err)
+ }
+
+ return result, nil
+}
+
+type SearchItem struct {
+ AskContractID int `json:"ask_contract_id"`
+ BundleID int `json:"bundle_id"`
+ BundledResults any `json:"bundled_results"`
+ BwNvlink float64 `json:"bw_nvlink"`
+ ComputeCap int `json:"compute_cap"`
+ CPUArch string `json:"cpu_arch"`
+ CPUCores int `json:"cpu_cores"`
+ CPUCoresEffective float64 `json:"cpu_cores_effective"`
+ CPUGhz float64 `json:"cpu_ghz"`
+ CPUName string `json:"cpu_name"`
+ CPURAM int `json:"cpu_ram"`
+ CreditDiscountMax float64 `json:"credit_discount_max"`
+ CudaMaxGood float64 `json:"cuda_max_good"`
+ DirectPortCount int `json:"direct_port_count"`
+ DiscountRate any `json:"discount_rate"`
+ DiscountedDphTotal float64 `json:"discounted_dph_total"`
+ DiscountedHourly float64 `json:"discounted_hourly"`
+ DiskBw float64 `json:"disk_bw"`
+ DiskName string `json:"disk_name"`
+ DiskSpace float64 `json:"disk_space"`
+ Dlperf float64 `json:"dlperf"`
+ DlperfPerDphtotal float64 `json:"dlperf_per_dphtotal"`
+ DphBase float64 `json:"dph_base"`
+ DphTotal float64 `json:"dph_total"`
+ DphTotalAdj float64 `json:"dph_total_adj"`
+ DriverVers int `json:"driver_vers"`
+ DriverVersion string `json:"driver_version"`
+ Duration float64 `json:"duration"`
+ EndDate float64 `json:"end_date"`
+ External any `json:"external"`
+ FlopsPerDphtotal float64 `json:"flops_per_dphtotal"`
+ Geolocation string `json:"geolocation"`
+ Geolocode float64 `json:"geolocode"`
+ GpuArch string `json:"gpu_arch"`
+ GpuDisplayActive bool `json:"gpu_display_active"`
+ GpuFrac float64 `json:"gpu_frac"`
+ GpuIds []int `json:"gpu_ids"`
+ GpuLanes int `json:"gpu_lanes"`
+ GpuMaxPower float64 `json:"gpu_max_power"`
+ GpuMaxTemp float64 `json:"gpu_max_temp"`
+ GpuMemBw float64 `json:"gpu_mem_bw"`
+ GpuName string `json:"gpu_name"`
+ GpuRAM float64 `json:"gpu_ram"`
+ GpuTotalRAM float64 `json:"gpu_total_ram"`
+ HasAvx int `json:"has_avx"`
+ HostID int `json:"host_id"`
+ HostingType int `json:"hosting_type"`
+ Hostname any `json:"hostname"`
+ ID int `json:"id"`
+ InetDown float64 `json:"inet_down"`
+ InetDownCost float64 `json:"inet_down_cost"`
+ InetUp float64 `json:"inet_up"`
+ InetUpCost float64 `json:"inet_up_cost"`
+ Instance InstancePricingDetails `json:"instance"`
+ InternetDownCostPerTb float64 `json:"internet_down_cost_per_tb"`
+ InternetUpCostPerTb float64 `json:"internet_up_cost_per_tb"`
+ IsBid bool `json:"is_bid"`
+ Logo string `json:"logo"`
+ MachineID int `json:"machine_id"`
+ MinBid float64 `json:"min_bid"`
+ MoboName string `json:"mobo_name"`
+ NumGpus int `json:"num_gpus"`
+ OsVersion string `json:"os_version"`
+ PciGen float64 `json:"pci_gen"`
+ PcieBw float64 `json:"pcie_bw"`
+ PublicIpaddr string `json:"public_ipaddr"`
+ Reliability float64 `json:"reliability"`
+ Reliability2 float64 `json:"reliability2"`
+ ReliabilityMult float64 `json:"reliability_mult"`
+ Rentable bool `json:"rentable"`
+ Rented bool `json:"rented"`
+ Rn int `json:"rn"`
+ Score float64 `json:"score"`
+ Search SearchPricing `json:"search"`
+ StartDate float64 `json:"start_date"`
+ StaticIP bool `json:"static_ip"`
+ StorageCost float64 `json:"storage_cost"`
+ StorageTotalCost float64 `json:"storage_total_cost"`
+ TimeRemaining string `json:"time_remaining"`
+ TimeRemainingIsbid string `json:"time_remaining_isbid"`
+ TotalFlops float64 `json:"total_flops"`
+ Vericode int `json:"vericode"`
+ Verification string `json:"verification"`
+ VmsEnabled bool `json:"vms_enabled"`
+ VramCostperhour float64 `json:"vram_costperhour"`
+ Webpage any `json:"webpage"`
+}
+
+type InstancePricingDetails struct {
+ DiscountTotalHour float64 `json:"discountTotalHour"`
+ DiscountedTotalPerHour float64 `json:"discountedTotalPerHour"`
+ DiskHour float64 `json:"diskHour"`
+ GpuCostPerHour float64 `json:"gpuCostPerHour"`
+ TotalHour float64 `json:"totalHour"`
+}
+
+type SearchPricing struct {
+ DiscountTotalHour float64 `json:"discountTotalHour"`
+ DiscountedTotalPerHour float64 `json:"discountedTotalPerHour"`
+ DiskHour float64 `json:"diskHour"`
+ GpuCostPerHour float64 `json:"gpuCostPerHour"`
+ TotalHour float64 `json:"totalHour"`
+}
+
+type Foo struct {
+ SomeValue string `json:"someValue"`
+ AnotherValue string "json:\"anotherValue\""
+}
diff --git a/web/vastai/vastaicli/search_test.go b/web/vastai/vastaicli/search_test.go
new file mode 100644
index 0000000..10960fa
--- /dev/null
+++ b/web/vastai/vastaicli/search_test.go
@@ -0,0 +1,19 @@
+package vastaicli
+
+import (
+ "context"
+ "testing"
+ "time"
+)
+
+func TestSearch(t *testing.T) {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
+ defer cancel()
+
+ instances, err := Search(ctx, "verified=False cuda_max_good>=12.1 gpu_ram>=12 num_gpus=1 inet_down>=850", "dph+")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Logf("found %d candidates", len(instances))
+}