From 1096a9852ac84d0aca518830f8264e2182fa3265 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Wed, 27 Nov 2024 17:03:51 -0500 Subject: [PATCH 1/5] increase quote timeout --- src/lib/buy/index.tsx | 116 ++++++++++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 33 deletions(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 578b092..c078941 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -122,6 +122,29 @@ async function quoteAction(options: SfBuyOptions) { render(); } +function QuoteComponent( + props: { + options: SfBuyOptions; + }, +) { + const [quote, setQuote] = useState(null); + + useEffect(async () => { + const quote = await getQuoteFromParsedSfBuyOptions(props.options); + setQuote(quote); + }, []); + + return quote === null ? ( + + + + Getting quote... + + + ) : ; +} + + /* Flow is: 1. If --quote, get quote and exit @@ -142,32 +165,56 @@ async function buyOrderAction(options: SfBuyOptions) { ); } - // Grab the price per GPU hour, either - let pricePerGpuHour: number | null = parsePricePerGpuHour(options.price); - if (!pricePerGpuHour) { - const quote = await getQuoteFromParsedSfBuyOptions(options); - if (!quote) { - pricePerGpuHour = await getAggressivePricePerHour(options.type); - } else { - pricePerGpuHour = getPricePerGpuHourFromQuote(quote); + render(); +} + +function QuoteAndBuy( + props: { + options: SfBuyOptions; + }, +) { + const [orderProps, setOrderProps] = useState(null); + + // submit a quote request, handle loading state + useEffect(async () => { + const quote = await getQuoteFromParsedSfBuyOptions(props.options); + + // Grab the price per GPU hour, either + let pricePerGpuHour: number | null = parsePricePerGpuHour(props.options.price); + if (!pricePerGpuHour) { + const quote = await getQuoteFromParsedSfBuyOptions(props.options); + if (!quote) { + pricePerGpuHour = await getAggressivePricePerHour(props.options.type); + } else { + pricePerGpuHour = getPricePerGpuHourFromQuote(quote); + } } - } - const duration = parseDuration(options.duration); - const startDate = parseStartAsDate(options.start); - const endsAt = roundEndDate( - dayjs(startDate).add(duration, "seconds").toDate(), - ).toDate(); - - render( - , + const duration = parseDuration(props.options.duration); + const startDate = parseStartAsDate(props.options.start); + const endsAt = roundEndDate( + dayjs(startDate).add(duration, "seconds").toDate(), + ).toDate(); + + setOrderProps({ + type: props.options.type, + price: pricePerGpuHour, + size: parseAccelerators(props.options.accelerators), + startAt: startDate, + endsAt, + colocate: props.options.colocate, + }); + }, []); + + return orderProps === null ? ( + + + + Getting quote... + + + ) : ( + ); } @@ -264,16 +311,16 @@ function BuyOrderPreview( type Order = | Awaited> | Awaited>; - +type BuyOrderProps = { + price: number; + size: number; + startAt: Date | "NOW"; + endsAt: Date; + type: string; + colocate?: Array; +} function BuyOrder( - props: { - price: number; - size: number; - startAt: Date | "NOW"; - endsAt: Date; - type: string; - colocate?: Array; - }, + props: BuyOrderProps, ) { const [isLoading, setIsLoading] = useState(false); const [value, setValue] = useState(""); @@ -499,6 +546,7 @@ type QuoteOptions = { export async function getQuote(options: QuoteOptions) { const api = await apiClient(); + const { data, error, response } = await api.GET("/v0/quote", { params: { query: { @@ -514,6 +562,8 @@ export async function getQuote(options: QuoteOptions) { : options.startsAt.toISOString(), }, }, + // timeout after 600 seconds + signal: AbortSignal.timeout(600 * 1000), }); if (!response.ok) { From 871a1981cae0a1a7aed7418c7c2152412fad0691 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Wed, 27 Nov 2024 17:04:18 -0500 Subject: [PATCH 2/5] fmt --- package.json | 2 +- src/lib/buy/index.tsx | 38 ++++++++++++++------------- src/lib/clusters/kubeconfig.ts | 15 ++++++----- src/lib/contracts/ContractDisplay.tsx | 6 ++++- src/lib/sell.ts | 25 ++++++++++-------- 5 files changed, 48 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index bb8c290..c28193e 100644 --- a/package.json +++ b/package.json @@ -40,4 +40,4 @@ "typescript": "^5.6.2" }, "version": "0.1.2" -} \ No newline at end of file +} diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index c078941..8b62649 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -134,17 +134,18 @@ function QuoteComponent( setQuote(quote); }, []); - return quote === null ? ( - - + return quote === null + ? ( - Getting quote... + + + Getting quote... + - - ) : ; + ) + : ; } - /* Flow is: 1. If --quote, get quote and exit @@ -180,7 +181,9 @@ function QuoteAndBuy( const quote = await getQuoteFromParsedSfBuyOptions(props.options); // Grab the price per GPU hour, either - let pricePerGpuHour: number | null = parsePricePerGpuHour(props.options.price); + let pricePerGpuHour: number | null = parsePricePerGpuHour( + props.options.price, + ); if (!pricePerGpuHour) { const quote = await getQuoteFromParsedSfBuyOptions(props.options); if (!quote) { @@ -206,16 +209,16 @@ function QuoteAndBuy( }); }, []); - return orderProps === null ? ( - - + return orderProps === null + ? ( - Getting quote... + + + Getting quote... + - - ) : ( - - ); + ) + : ; } function roundEndDate(endDate: Date) { @@ -318,7 +321,7 @@ type BuyOrderProps = { endsAt: Date; type: string; colocate?: Array; -} +}; function BuyOrder( props: BuyOrderProps, ) { @@ -546,7 +549,6 @@ type QuoteOptions = { export async function getQuote(options: QuoteOptions) { const api = await apiClient(); - const { data, error, response } = await api.GET("/v0/quote", { params: { query: { diff --git a/src/lib/clusters/kubeconfig.ts b/src/lib/clusters/kubeconfig.ts index 2e742bf..811f27b 100644 --- a/src/lib/clusters/kubeconfig.ts +++ b/src/lib/clusters/kubeconfig.ts @@ -94,7 +94,8 @@ export function createKubeconfig(props: { // Set current context based on provided cluster and user names if (currentContext) { - const contextName = `${currentContext.clusterName}@${currentContext.userName}`; + const contextName = + `${currentContext.clusterName}@${currentContext.userName}`; kubeconfig["current-context"] = contextName; } else if (kubeconfig.contexts.length > 0) { kubeconfig["current-context"] = kubeconfig.contexts[0].name; @@ -105,7 +106,7 @@ export function createKubeconfig(props: { export function mergeNamedItems( items1: T[], - items2: T[] + items2: T[], ): T[] { const map = new Map(); for (const item of items1) { @@ -119,7 +120,7 @@ export function mergeNamedItems( export function mergeKubeconfigs( oldConfig: Kubeconfig, - newConfig?: Kubeconfig + newConfig?: Kubeconfig, ): Kubeconfig { if (!newConfig) { return oldConfig; @@ -129,15 +130,15 @@ export function mergeKubeconfigs( apiVersion: newConfig.apiVersion || oldConfig.apiVersion, clusters: mergeNamedItems( oldConfig.clusters || [], - newConfig.clusters || [] + newConfig.clusters || [], ), contexts: mergeNamedItems( oldConfig.contexts || [], - newConfig.contexts || [] + newConfig.contexts || [], ), users: mergeNamedItems(oldConfig.users || [], newConfig.users || []), - "current-context": - newConfig["current-context"] || oldConfig["current-context"], + "current-context": newConfig["current-context"] || + oldConfig["current-context"], kind: newConfig.kind || oldConfig.kind, preferences: { ...oldConfig.preferences, ...newConfig.preferences }, }; diff --git a/src/lib/contracts/ContractDisplay.tsx b/src/lib/contracts/ContractDisplay.tsx index 476ff36..638744c 100644 --- a/src/lib/contracts/ContractDisplay.tsx +++ b/src/lib/contracts/ContractDisplay.tsx @@ -51,7 +51,11 @@ export function ContractDisplay(props: { contract: Contract }) { return ( - {quantity * GPUS_PER_NODE} x {props.contract.instance_type} (gpus) + + {quantity * GPUS_PER_NODE} x {props.contract.instance_type} + {" "} + (gpus) + diff --git a/src/lib/sell.ts b/src/lib/sell.ts index b66e102..dcafd18 100644 --- a/src/lib/sell.ts +++ b/src/lib/sell.ts @@ -32,7 +32,7 @@ export function registerSell(program: Command) { .option( "-f, --flags ", "Specify additional flags as JSON", - JSON.parse + JSON.parse, ) .action(async (options) => { await placeSellOrder(options); @@ -54,7 +54,7 @@ function contractStartAndEnd(contract: { }) { const startDate = dayjs(contract.shape.intervals[0]).toDate(); const endDate = dayjs( - contract.shape.intervals[contract.shape.intervals.length - 1] + contract.shape.intervals[contract.shape.intervals.length - 1], ).toDate(); return { startDate, endDate }; @@ -84,14 +84,15 @@ async function placeSellOrder(options: { if (contract?.status === "pending") { return logAndQuit( - `Contract ${options.contractId} is currently pending. Please try again in a few seconds.` + `Contract ${options.contractId} is currently pending. Please try again in a few seconds.`, ); } if (options.accelerators % GPUS_PER_NODE !== 0) { - const exampleCommand = `sf sell -n ${GPUS_PER_NODE} -c ${options.contractId}`; + const exampleCommand = + `sf sell -n ${GPUS_PER_NODE} -c ${options.contractId}`; return logAndQuit( - `At the moment, only entire-nodes are available, so you must have a multiple of ${GPUS_PER_NODE} GPUs. Example command:\n\n${exampleCommand}` + `At the moment, only entire-nodes are available, so you must have a multiple of ${GPUS_PER_NODE} GPUs. Example command:\n\n${exampleCommand}`, ); } @@ -133,7 +134,7 @@ async function placeSellOrder(options: { priceCents, totalDurationSecs, nodes, - GPUS_PER_NODE + GPUS_PER_NODE, ); const params: PlaceSellOrderParameters = { @@ -154,11 +155,13 @@ async function placeSellOrder(options: { switch (response.status) { case 400: return logAndQuit( - `Bad Request: ${error?.message}: ${JSON.stringify( - error?.details, - null, - 2 - )}` + `Bad Request: ${error?.message}: ${ + JSON.stringify( + error?.details, + null, + 2, + ) + }`, ); // return logAndQuit(`Bad Request: ${error?.message}`); case 401: From af8fdd9a2128c9657ea8b829fdb857f6d1859153 Mon Sep 17 00:00:00 2001 From: Sladuca Date: Wed, 27 Nov 2024 17:12:48 -0500 Subject: [PATCH 3/5] fix promise thingy --- src/lib/buy/index.tsx | 66 +++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 8b62649..7731634 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -129,10 +129,12 @@ function QuoteComponent( ) { const [quote, setQuote] = useState(null); - useEffect(async () => { - const quote = await getQuoteFromParsedSfBuyOptions(props.options); - setQuote(quote); - }, []); + useEffect(() => { + (async () => { + const quote = await getQuoteFromParsedSfBuyOptions(props.options); + setQuote(quote); + })(); + }, [props.options]); return quote === null ? ( @@ -177,36 +179,38 @@ function QuoteAndBuy( const [orderProps, setOrderProps] = useState(null); // submit a quote request, handle loading state - useEffect(async () => { - const quote = await getQuoteFromParsedSfBuyOptions(props.options); - - // Grab the price per GPU hour, either - let pricePerGpuHour: number | null = parsePricePerGpuHour( - props.options.price, - ); - if (!pricePerGpuHour) { + useEffect(() => { + (async () => { const quote = await getQuoteFromParsedSfBuyOptions(props.options); - if (!quote) { - pricePerGpuHour = await getAggressivePricePerHour(props.options.type); - } else { - pricePerGpuHour = getPricePerGpuHourFromQuote(quote); + + // Grab the price per GPU hour, either + let pricePerGpuHour: number | null = parsePricePerGpuHour( + props.options.price, + ); + if (!pricePerGpuHour) { + const quote = await getQuoteFromParsedSfBuyOptions(props.options); + if (!quote) { + pricePerGpuHour = await getAggressivePricePerHour(props.options.type); + } else { + pricePerGpuHour = getPricePerGpuHourFromQuote(quote); + } } - } - const duration = parseDuration(props.options.duration); - const startDate = parseStartAsDate(props.options.start); - const endsAt = roundEndDate( - dayjs(startDate).add(duration, "seconds").toDate(), - ).toDate(); - - setOrderProps({ - type: props.options.type, - price: pricePerGpuHour, - size: parseAccelerators(props.options.accelerators), - startAt: startDate, - endsAt, - colocate: props.options.colocate, - }); + const duration = parseDuration(props.options.duration); + const startDate = parseStartAsDate(props.options.start); + const endsAt = roundEndDate( + dayjs(startDate).add(duration, "seconds").toDate(), + ).toDate(); + + setOrderProps({ + type: props.options.type, + price: pricePerGpuHour, + size: parseAccelerators(props.options.accelerators), + startAt: startDate, + endsAt, + colocate: props.options.colocate, + }); + })(); }, []); return orderProps === null From 5c78c00c647fe012c62ed4686b4ace00d0b627af Mon Sep 17 00:00:00 2001 From: Sladuca Date: Wed, 27 Nov 2024 18:11:38 -0500 Subject: [PATCH 4/5] fix quote component --- src/lib/buy/index.tsx | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 7731634..09add62 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -117,11 +117,6 @@ function parsePricePerGpuHour(price?: string) { return Number.parseFloat(priceWithoutDollar) * 100; } -async function quoteAction(options: SfBuyOptions) { - const quote = await getQuoteFromParsedSfBuyOptions(options); - render(); -} - function QuoteComponent( props: { options: SfBuyOptions; @@ -158,17 +153,17 @@ Flow is: */ async function buyOrderAction(options: SfBuyOptions) { if (options.quote) { - return quoteAction(options); - } + render(); + } else { + const nodes = parseAccelerators(options.accelerators); + if (!Number.isInteger(nodes)) { + return logAndQuit( + `You can only buy whole nodes, or 8 GPUs at a time. Got: ${options.accelerators}`, + ); + } - const nodes = parseAccelerators(options.accelerators); - if (!Number.isInteger(nodes)) { - return logAndQuit( - `You can only buy whole nodes, or 8 GPUs at a time. Got: ${options.accelerators}`, - ); + render(); } - - render(); } function QuoteAndBuy( From af00ff3a4ac5eb9d0e9728573e1cc6ab68b1779c Mon Sep 17 00:00:00 2001 From: Sladuca Date: Wed, 27 Nov 2024 19:53:30 -0500 Subject: [PATCH 5/5] fix --quote hang --- src/lib/buy/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 09add62..47bd458 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -123,15 +123,20 @@ function QuoteComponent( }, ) { const [quote, setQuote] = useState(null); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { (async () => { const quote = await getQuoteFromParsedSfBuyOptions(props.options); + setIsLoading(false); + if (!quote) { + return; + } setQuote(quote); })(); }, [props.options]); - return quote === null + return isLoading ? (