testing, pre-wails

This commit is contained in:
2024-12-13 17:57:37 -06:00
parent 81a9d1f315
commit 34c1319f44
5 changed files with 134 additions and 96 deletions

View File

@@ -1,27 +1,37 @@
package api package api
import "net/http" import (
"bytes"
"encoding/json"
"io"
"net/http"
"go.uber.org/zap"
)
// Client handles API communication with the Vast.ai API // Client handles API communication with the Vast.ai API
type Client struct { type Client struct {
apiKey string apiKey string
baseURL string baseURL string
httpClient *http.Client httpClient *http.Client
logger *zap.SugaredLogger
} }
// Error represents an API error response // Error represents an API error response
type APIError struct { type APIError struct {
Success bool `json:"success"` Success bool `json:"success"`
Error string `json:"error"`
Msg string `json:"msg"`
} }
// NewClient creates a new Vast.ai API client // NewClient creates a new Vast.ai API client
func NewClient(apiKey string) *Client { func NewClient(apiKey string) *Client {
logger, _ := zap.NewDevelopment()
sugar := logger.Sugar()
return &Client{ return &Client{
apiKey: apiKey, apiKey: apiKey,
baseURL: "https://console.vast.ai/api/v0", baseURL: "https://console.vast.ai/api/v0",
httpClient: &http.Client{}, httpClient: &http.Client{},
logger: sugar,
} }
} }
@@ -32,11 +42,68 @@ func (c *Client) Apply(req *http.Request) {
// makeRequest creates and sends an HTTP request with the provided method, path and body // makeRequest creates and sends an HTTP request with the provided method, path and body
func (c *Client) makeRequest(method, path string, body interface{}) (*http.Response, error) { func (c *Client) makeRequest(method, path string, body interface{}) (*http.Response, error) {
req, err := http.NewRequest(method, c.baseURL+path, nil) c.logger.Debugw("making request",
"method", method,
"path", path,
"body", body,
)
var bodyReader io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
bodyReader = bytes.NewBuffer(jsonBody)
}
req, err := http.NewRequest(method, c.baseURL+path, bodyReader)
if err != nil {
c.logger.Errorw("failed to create request",
"error", err,
"method", method,
"path", path,
)
return nil, err
}
c.Apply(req) c.Apply(req)
return c.httpClient.Do(req) res, err := c.httpClient.Do(req)
if err != nil {
c.logger.Errorw("failed to send request",
"error", err,
"method", method,
"path", path,
)
return nil, err
}
// debug print response
res_body, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
var apiError APIError
if err := json.NewDecoder(bytes.NewBuffer(res_body)).Decode(&apiError); err != nil {
c.logger.Errorw("failed to decode error response",
"error", err,
)
} else {
c.logger.Errorw("api error",
"error", apiError,
)
}
} else {
c.logger.Debugw("response",
"status", res.Status,
"status_code", res.StatusCode,
)
}
res.Body = io.NopCloser(bytes.NewBuffer(res_body))
return res, nil
} }

View File

@@ -5,22 +5,6 @@ import (
"net/http" "net/http"
) )
type ComparableInteger struct {
eq *int `json:"eq,omitempty"`
lt *int `json:"lt,omitempty"`
le *int `json:"le,omitempty"`
gt *int `json:"gt,omitempty"`
ge *int `json:"ge,omitempty"`
}
type ComparableFloat struct {
eq *float64 `json:"eq,omitempty"`
lt *float64 `json:"lt,omitempty"`
le *float64 `json:"le,omitempty"`
gt *float64 `json:"gt,omitempty"`
ge *float64 `json:"ge,omitempty"`
}
type AdvancedSearch struct { type AdvancedSearch struct {
Verified *bool `json:"verified,omitempty"` Verified *bool `json:"verified,omitempty"`
ComputeCap *ComparableInteger `json:"compute_cap,omitempty"` ComputeCap *ComparableInteger `json:"compute_cap,omitempty"`
@@ -69,75 +53,12 @@ func NewSearch() *AdvancedSearch {
} }
} }
type Offer struct {
IsBid bool `json:"is_bid"`
InetUpBilled *float64 `json:"inet_up_billed"`
InetDownBilled *float64 `json:"inet_down_billed"`
External bool `json:"external"`
Webpage *string `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 *string `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 float64 `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 *float64 `json:"end_date"`
Duration *float64 `json:"duration"`
StorageCost float64 `json:"storage_cost"`
InetUpCost float64 `json:"inet_up_cost"`
InetDownCost float64 `json:"inet_down_cost"`
StorageTotalCost float64 `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"`
}
type SearchResponse struct { type SearchResponse struct {
Offers []Offer `json:"offers"` Offers []Offer `json:"offers"`
} }
func (c *Client) Search(search *AdvancedSearch) (*SearchResponse, error) { func (c *Client) Search(search *AdvancedSearch) (*SearchResponse, error) {
resp, err := c.makeRequest(http.MethodPost, "/bundles/", nil) resp, err := c.makeRequest(http.MethodPost, "/bundles/", search)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1,5 +1,7 @@
package api package api
import "fmt"
type PortMapping struct { type PortMapping struct {
HostIp string `json:"HostIp"` HostIp string `json:"HostIp"`
HostPort string `json:"HostPort"` HostPort string `json:"HostPort"`
@@ -42,7 +44,7 @@ type Instance struct {
GPURam int `json:"gpu_ram"` GPURam int `json:"gpu_ram"`
GPUDisplayActive bool `json:"gpu_display_active"` GPUDisplayActive bool `json:"gpu_display_active"`
GPUMemBW float64 `json:"gpu_mem_bw"` GPUMemBW float64 `json:"gpu_mem_bw"`
BWNVLink int `json:"bw_nvlink"` BWNVLink float64 `json:"bw_nvlink"`
DirectPortCount int `json:"direct_port_count"` DirectPortCount int `json:"direct_port_count"`
GPULanes int `json:"gpu_lanes"` GPULanes int `json:"gpu_lanes"`
PCIeBW float64 `json:"pcie_bw"` PCIeBW float64 `json:"pcie_bw"`
@@ -108,11 +110,12 @@ type Offer struct {
Rentable bool `json:"rentable"` Rentable bool `json:"rentable"`
ComputeCap int `json:"compute_cap"` ComputeCap int `json:"compute_cap"`
DriverVersion string `json:"driver_version"` DriverVersion string `json:"driver_version"`
CudaMaxGood int `json:"cuda_max_good"` CudaMaxGood float64 `json:"cuda_max_good"`
MachineID int `json:"machine_id"` MachineID int `json:"machine_id"`
HostingType *string `json:"hosting_type"` HostingType *float64 `json:"hosting_type"`
PublicIPAddr string `json:"public_ipaddr"` PublicIPAddr string `json:"public_ipaddr"`
Geolocation string `json:"geolocation"` Geolocation string `json:"geolocation"`
Geocode *int64 `json:"geolocode"`
FlopsPerDPHTotal float64 `json:"flops_per_dphtotal"` FlopsPerDPHTotal float64 `json:"flops_per_dphtotal"`
DLPerfPerDPHTotal float64 `json:"dlperf_per_dphtotal"` DLPerfPerDPHTotal float64 `json:"dlperf_per_dphtotal"`
Reliability2 float64 `json:"reliability2"` Reliability2 float64 `json:"reliability2"`
@@ -129,17 +132,17 @@ type Offer struct {
GPURam int `json:"gpu_ram"` GPURam int `json:"gpu_ram"`
GPUDisplayActive bool `json:"gpu_display_active"` GPUDisplayActive bool `json:"gpu_display_active"`
GPUMemBw float64 `json:"gpu_mem_bw"` GPUMemBw float64 `json:"gpu_mem_bw"`
BwNVLink int `json:"bw_nvlink"` BwNVLink float64 `json:"bw_nvlink"`
DirectPortCount int `json:"direct_port_count"` DirectPortCount int `json:"direct_port_count"`
GPULanes int `json:"gpu_lanes"` GPULanes int `json:"gpu_lanes"`
PCIeBw float64 `json:"pcie_bw"` PCIeBw float64 `json:"pcie_bw"`
PCIGen int `json:"pci_gen"` PCIGen float64 `json:"pci_gen"`
DLPerf float64 `json:"dlperf"` DLPerf float64 `json:"dlperf"`
CPUName string `json:"cpu_name"` CPUName string `json:"cpu_name"`
MoboName string `json:"mobo_name"` MoboName string `json:"mobo_name"`
CPURam int `json:"cpu_ram"` CPURam int `json:"cpu_ram"`
CPUCores int `json:"cpu_cores"` CPUCores float64 `json:"cpu_cores"`
CPUCoresEffective int `json:"cpu_cores_effective"` CPUCoresEffective float64 `json:"cpu_cores_effective"`
GPUFrac float64 `json:"gpu_frac"` GPUFrac float64 `json:"gpu_frac"`
HasAVX int `json:"has_avx"` HasAVX int `json:"has_avx"`
DiskSpace float64 `json:"disk_space"` DiskSpace float64 `json:"disk_space"`
@@ -160,3 +163,11 @@ type Offer struct {
BundledResults int `json:"bundled_results"` BundledResults int `json:"bundled_results"`
PendingCount int `json:"pending_count"` PendingCount int `json:"pending_count"`
} }
func (o *Offer) String() string {
geocode := 0
if o.Geocode != nil {
geocode = int(*o.Geocode)
}
return fmt.Sprintf("[%s] %s %s %d", o.GPUName, o.PublicIPAddr, o.Geolocation, geocode)
}

View File

@@ -3,3 +3,19 @@ package api
func Pointer[T any](d T) *T { func Pointer[T any](d T) *T {
return &d return &d
} }
type Comparable[T comparable] struct {
Eq *T `json:"eq,omitempty"`
Lt *T `json:"lt,omitempty"`
Le *T `json:"le,omitempty"`
Gt *T `json:"gt,omitempty"`
Ge *T `json:"ge,omitempty"`
}
// Then you can use these type aliases for convenience
type ComparableInteger = Comparable[int]
type ComparableFloat = Comparable[float64]
func Ge[T comparable](d T) *Comparable[T] {
return &Comparable[T]{Ge: Pointer(d)}
}

View File

@@ -1,26 +1,49 @@
package main package main
import ( import (
"log"
"os" "os"
"go.uber.org/zap"
"xevion.dev/quickvast/api" "xevion.dev/quickvast/api"
"github.com/joho/godotenv" "github.com/joho/godotenv"
) )
func main() { func main() {
logger, _ := zap.NewDevelopment()
defer logger.Sync()
sugar := logger.Sugar()
// Load .env file // Load .env file
if err := godotenv.Load(); err != nil { if err := godotenv.Load(); err != nil {
log.Fatal("Error loading .env file") sugar.Fatal(err)
} }
// Get API key from environment // Get API key from environment
apiKey := os.Getenv("VASTAI_API_KEY") apiKey := os.Getenv("VASTAI_API_KEY")
if apiKey == "" { if apiKey == "" {
log.Fatal("VASTAI_API_KEY not found in environment") sugar.Fatal("VASTAI_API_KEY not found in environment")
} }
// Create client // Create client
client := api.NewClient(apiKey) client := api.NewClient(apiKey)
// Create search
search := api.NewSearch()
// search.CPUCores = api.Ge(8)
// Perform search
sugar.Infow("Searching", "search", search)
resp, err := client.Search(search)
if err != nil {
sugar.Fatal(err)
}
// Print offers
sugar.Infof("Offers: %d", len(resp.Offers))
for _, offer := range resp.Offers {
sugar.Info(offer.String())
}
sugar.Info("Done")
} }