diff options
| author | Xe Iaso <me@xeiaso.net> | 2024-10-25 14:06:42 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2024-10-25 14:06:42 -0400 |
| commit | afa4bc6c01297af78885bf0562e2dae7ff83605b (patch) | |
| tree | 97a1149d5646cf9b1c7aa2892d6e849c589219cc /web | |
| parent | 797eec6d94e193ae684db977179ea4a422b2499f (diff) | |
| download | x-afa4bc6c01297af78885bf0562e2dae7ff83605b.tar.xz x-afa4bc6c01297af78885bf0562e2dae7ff83605b.zip | |
cmd: add amano and stealthmountain
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'web')
| -rw-r--r-- | web/mistral/mistral.go | 14 | ||||
| -rw-r--r-- | web/vastai/error.go | 32 | ||||
| -rw-r--r-- | web/vastai/search.go | 101 | ||||
| -rw-r--r-- | web/vastai/templates.go | 67 | ||||
| -rw-r--r-- | web/vastai/users.go | 52 | ||||
| -rw-r--r-- | web/vastai/vastai.go | 119 |
6 files changed, 371 insertions, 14 deletions
diff --git a/web/mistral/mistral.go b/web/mistral/mistral.go index 75cf9d3..3f2d9e7 100644 --- a/web/mistral/mistral.go +++ b/web/mistral/mistral.go @@ -4,27 +4,13 @@ import ( "bytes" "context" "encoding/json" - "expvar" "fmt" "net/http" - "tailscale.com/metrics" "within.website/x/llm" "within.website/x/web" ) -var ( - promptTokens = metrics.LabelMap{Label: "model"} - completionTokens = metrics.LabelMap{Label: "model"} - totalTokens = metrics.LabelMap{Label: "model"} -) - -func init() { - expvar.Publish("gauge_x_web_mistral_prompt_tokens", &promptTokens) - expvar.Publish("gauge_x_web_mistral_completion_tokens", &completionTokens) - expvar.Publish("gauge_x_web_mistral_total_tokens", &totalTokens) -} - type Client struct { *http.Client apiKey string diff --git a/web/vastai/error.go b/web/vastai/error.go new file mode 100644 index 0000000..89487a7 --- /dev/null +++ b/web/vastai/error.go @@ -0,0 +1,32 @@ +package vastai + +import ( + "encoding/json" + "fmt" + "net/http" +) + +type Error struct { + Success bool `json:"success"` + ErrorKind string `json:"error"` + Msg string `json:"msg"` + StatusCode int `json:"status_code"` +} + +func (e Error) Error() string { + return fmt.Sprintf("%s (%d): %s", e.ErrorKind, e.StatusCode, e.Msg) +} + +func NewError(resp *http.Response) error { + var e Error + + dec := json.NewDecoder(resp.Body) + defer resp.Body.Close() + if err := dec.Decode(&e); err != nil { + return fmt.Errorf("vastai: can't decode json while handling error: %w", err) + } + + e.StatusCode = resp.StatusCode + + return e +} diff --git a/web/vastai/search.go b/web/vastai/search.go new file mode 100644 index 0000000..4ad301a --- /dev/null +++ b/web/vastai/search.go @@ -0,0 +1,101 @@ +package vastai + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +func (c *Client) SearchByTemplate(ctx context.Context, t Template) ([]Offer, error) { + filters := map[string]any{} + if err := json.Unmarshal([]byte(t.ExtraFilters), &filters); err != nil { + return nil, fmt.Errorf("can't unmarshal extra filters for template %s (%d): %w", t.Name, t.ID, err) + } + filters["order"] = []string{"dphtotal", "asc"} + + extraFilters, err := json.Marshal(filters) + if err != nil { + return nil, fmt.Errorf("can't remarshal extra filters for template %s (%d): %w", t.Name, t.ID, err) + } + + q := url.Values{} + q.Set("q", string(extraFilters)) + + resp, err := doJSON[struct { + Offers []Offer `json:"offers"` + }]( + ctx, c, http.MethodGet, + fmt.Sprintf("/v0/bundles/%s", q.Encode()), + http.StatusOK, + ) + if err != nil { + return nil, fmt.Errorf("can't find instances with template %s (%d): %w", t.Name, t.ID, err) + } + + return resp.Offers, nil +} + +type Offer struct { + IsBid bool `json:"is_bid"` + InetUpBilled any `json:"inet_up_billed"` + InetDownBilled any `json:"inet_down_billed"` + External bool `json:"external"` + Webpage any `json:"webpage"` + Logo string `json:"logo"` + Rentable bool `json:"rentable"` + ComputeCap int `json:"compute_cap"` + DriverVersion string `json:"driver_version"` + CudaMaxGood int `json:"cuda_max_good"` + MachineID int `json:"machine_id"` + HostingType any `json:"hosting_type"` + PublicIpaddr string `json:"public_ipaddr"` + Geolocation string `json:"geolocation"` + FlopsPerDphtotal float64 `json:"flops_per_dphtotal"` + DlperfPerDphtotal float64 `json:"dlperf_per_dphtotal"` + Reliability2 float64 `json:"reliability2"` + HostRunTime int `json:"host_run_time"` + HostID int `json:"host_id"` + ID int `json:"id"` + BundleID int `json:"bundle_id"` + NumGpus int `json:"num_gpus"` + TotalFlops float64 `json:"total_flops"` + MinBid float64 `json:"min_bid"` + DphBase float64 `json:"dph_base"` + DphTotal float64 `json:"dph_total"` + GpuName string `json:"gpu_name"` + GpuRAM int `json:"gpu_ram"` + GpuDisplayActive bool `json:"gpu_display_active"` + GpuMemBw float64 `json:"gpu_mem_bw"` + BwNvlink int `json:"bw_nvlink"` + DirectPortCount int `json:"direct_port_count"` + GpuLanes int `json:"gpu_lanes"` + PcieBw float64 `json:"pcie_bw"` + PciGen int `json:"pci_gen"` + Dlperf float64 `json:"dlperf"` + CPUName string `json:"cpu_name"` + MoboName string `json:"mobo_name"` + CPURAM int `json:"cpu_ram"` + CPUCores int `json:"cpu_cores"` + CPUCoresEffective int `json:"cpu_cores_effective"` + GpuFrac int `json:"gpu_frac"` + HasAvx int `json:"has_avx"` + DiskSpace float64 `json:"disk_space"` + DiskName string `json:"disk_name"` + DiskBw float64 `json:"disk_bw"` + InetUp float64 `json:"inet_up"` + InetDown float64 `json:"inet_down"` + StartDate float64 `json:"start_date"` + EndDate any `json:"end_date"` + Duration any `json:"duration"` + StorageCost float64 `json:"storage_cost"` + InetUpCost float64 `json:"inet_up_cost"` + InetDownCost float64 `json:"inet_down_cost"` + StorageTotalCost int `json:"storage_total_cost"` + Verification string `json:"verification"` + Score float64 `json:"score"` + Rented bool `json:"rented"` + BundledResults int `json:"bundled_results"` + PendingCount int `json:"pending_count"` +} diff --git a/web/vastai/templates.go b/web/vastai/templates.go new file mode 100644 index 0000000..2f5d31e --- /dev/null +++ b/web/vastai/templates.go @@ -0,0 +1,67 @@ +package vastai + +import ( + "context" + "fmt" + "net/http" +) + +func (c *Client) GetTemplatesForUser(ctx context.Context, userID int) ([]Template, error) { + resp, err := doJSON[struct { + Success bool `json:"success"` + Templates []Template `json:"template"` + }]( + ctx, c, http.MethodGet, + fmt.Sprintf("/v0/users/%d/created-templates/", userID), + http.StatusOK, + ) + if err != nil { + return nil, err + } + + return resp.Templates, nil +} + +type Template struct { + ID int `json:"id"` + CreatorID int `json:"creator_id"` + ArgsStr string `json:"args_str"` + Command any `json:"command"` + DefaultTag string `json:"default_tag"` + Desc string `json:"desc"` + DockerLoginRepo any `json:"docker_login_repo"` + Env string `json:"env"` + ExtraFilters string `json:"extra_filters"` + Href string `json:"href"` + Image string `json:"image"` + JupyterDir any `json:"jupyter_dir"` + JupDirect bool `json:"jup_direct"` + JupyterTested bool `json:"jupyter_tested"` + JupyterlabTested bool `json:"jupyterlab_tested"` + MaxCuda any `json:"max_cuda"` + MinCuda any `json:"min_cuda"` + Onstart string `json:"onstart"` + PythonUtf8 bool `json:"python_utf8"` + LangUtf8 bool `json:"lang_utf8"` + Repo string `json:"repo"` + Runtype string `json:"runtype"` + SSHDirect bool `json:"ssh_direct"` + Tag string `json:"tag"` + UseJupyterLab bool `json:"use_jupyter_lab"` + UseSSH bool `json:"use_ssh"` + HashID string `json:"hash_id"` + Name string `json:"name"` + CreatedFrom any `json:"created_from"` + CreatedFromID int `json:"created_from_id"` + CountCreated int `json:"count_created"` + DescCount int `json:"desc_count"` + RecentCreateDate float64 `json:"recent_create_date"` + CreatedAt float64 `json:"created_at"` + Private bool `json:"private"` + ReadmeVisible bool `json:"readme_visible"` + ReadmeHash any `json:"readme_hash"` + RecommendedDiskSpace float64 `json:"recommended_disk_space"` + Recommended bool `json:"recommended"` + Cached bool `json:"cached"` + Autoscaler bool `json:"autoscaler"` +} diff --git a/web/vastai/users.go b/web/vastai/users.go new file mode 100644 index 0000000..c043e60 --- /dev/null +++ b/web/vastai/users.go @@ -0,0 +1,52 @@ +package vastai + +import ( + "context" + "net/http" +) + +func (c *Client) Whoami(ctx context.Context) (*User, error) { + u, err := doJSON[User](ctx, c, http.MethodGet, "/v0/users/current/", http.StatusOK) + return &u, err +} + +type User struct { + CanPay bool `json:"can_pay"` + ID int `json:"id"` + APIKey string `json:"api_key"` + Username string `json:"username"` + SSHKey any `json:"ssh_key"` + PhoneNumber any `json:"phone_number"` + PaypalEmail any `json:"paypal_email"` + WiseEmail any `json:"wise_email"` + Fullname any `json:"fullname"` + BalanceThreshold float64 `json:"balance_threshold"` + BalanceThresholdEnabled bool `json:"balance_threshold_enabled"` + AutobillThreshold any `json:"autobill_threshold"` + AutobillAmount any `json:"autobill_amount"` + BilladdressLine1 any `json:"billaddress_line1"` + BilladdressLine2 any `json:"billaddress_line2"` + BilladdressCity any `json:"billaddress_city"` + BilladdressZip any `json:"billaddress_zip"` + BilladdressCountry any `json:"billaddress_country"` + BillingCreditonly int `json:"billing_creditonly"` + BilladdressTaxinfo any `json:"billaddress_taxinfo"` + PasswordResettable any `json:"password_resettable"` + Email string `json:"email"` + HasBilling bool `json:"has_billing"` + HasPayout bool `json:"has_payout"` + HostOnly bool `json:"host_only"` + HostAgreementAccepted bool `json:"host_agreement_accepted"` + EmailVerified bool `json:"email_verified"` + Last4 any `json:"last4"` + Balance int `json:"balance"` + Credit float64 `json:"credit"` + GotSignupCredit int `json:"got_signup_credit"` + User string `json:"user"` + PaidVerified float64 `json:"paid_verified"` + PaidExpected float64 `json:"paid_expected"` + BilledVerified float64 `json:"billed_verified"` + BilledExpected float64 `json:"billed_expected"` + Rights any `json:"rights"` + Sid string `json:"sid"` +} diff --git a/web/vastai/vastai.go b/web/vastai/vastai.go new file mode 100644 index 0000000..88463ea --- /dev/null +++ b/web/vastai/vastai.go @@ -0,0 +1,119 @@ +package vastai + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "runtime" +) + +type Client struct { + apiKey string + cli *http.Client + apiURL string +} + +func New(apiKey string) *Client { + return &Client{ + apiKey: apiKey, + cli: &http.Client{}, + apiURL: "https://cloud.vast.ai/api", + } +} + +func (c *Client) WithClient(cli *http.Client) *Client { + c.cli = cli + return c +} + +func (c *Client) WithAPIURL(apiURL string) *Client { + c.apiURL = apiURL + return c +} + +// Do performs a HTTP request with the appropriate authentication and user agent headers. +func (c *Client) Do(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("User-Agent", fmt.Sprintf("go/%s pkg:within.website/x/web/vastai", runtime.Version())) + + return c.cli.Do(req) +} + +func (c *Client) doRequestNoResponse(ctx context.Context, method, path string) error { + req, err := http.NewRequestWithContext(ctx, method, c.apiURL+path, nil) + if err != nil { + return err + } + + resp, err := c.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode/100 != 2 { + return NewError(resp) + } + + return nil +} + +// zilch returns the zero value of a given type. +func zilch[T any]() T { return *new(T) } + +func doJSON[Output any](ctx context.Context, c *Client, method, path string, wantStatusCode int) (Output, error) { + req, err := http.NewRequestWithContext(ctx, method, c.apiURL+path, nil) + if err != nil { + return zilch[Output](), err + } + + resp, err := c.Do(req) + if err != nil { + return zilch[Output](), err + } + defer resp.Body.Close() + + if resp.StatusCode != wantStatusCode { + return zilch[Output](), NewError(resp) + } + + var output Output + + if err := json.NewDecoder(resp.Body).Decode(&output); err != nil { + return zilch[Output](), err + } + + return output, nil +} + +func doJSONBody[Input any, Output any](ctx context.Context, c *Client, method, path string, input Input, wantStatusCode int) (Output, error) { + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(input); err != nil { + return zilch[Output](), err + } + + req, err := http.NewRequestWithContext(ctx, method, c.apiURL+path, buf) + if err != nil { + return zilch[Output](), err + } + + resp, err := c.Do(req) + if err != nil { + return zilch[Output](), err + } + defer resp.Body.Close() + + if resp.StatusCode != wantStatusCode { + return zilch[Output](), NewError(resp) + } + + var output Output + + if err := json.NewDecoder(resp.Body).Decode(&output); err != nil { + return zilch[Output](), err + } + + return output, nil +} |
