Skip to content

Commit

Permalink
increase quote timeout (#42)
Browse files Browse the repository at this point in the history
* increase quote timeout

* fmt

* fix promise thingy

* fix quote component

* fix --quote hang
  • Loading branch information
Sladuca authored Nov 28, 2024
1 parent 4851dcd commit 382a4bc
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 65 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@
"typescript": "^5.6.2"
},
"version": "0.1.3"
}
}
146 changes: 101 additions & 45 deletions src/lib/buy/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,35 @@ function parsePricePerGpuHour(price?: string) {
return Number.parseFloat(priceWithoutDollar) * 100;
}

async function quoteAction(options: SfBuyOptions) {
const quote = await getQuoteFromParsedSfBuyOptions(options);
render(<QuoteDisplay quote={quote} />);
function QuoteComponent(
props: {
options: SfBuyOptions;
},
) {
const [quote, setQuote] = useState<Quote | null>(null);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
(async () => {
const quote = await getQuoteFromParsedSfBuyOptions(props.options);
setIsLoading(false);
if (!quote) {
return;
}
setQuote(quote);
})();
}, [props.options]);

return isLoading
? (
<Box gap={1}>
<Spinner type="dots" />
<Box gap={1}>
<Text>Getting quote...</Text>
</Box>
</Box>
)
: <QuoteDisplay quote={quote} />;
}

/*
Expand All @@ -132,43 +158,71 @@ Flow is:
*/
async function buyOrderAction(options: SfBuyOptions) {
if (options.quote) {
return quoteAction(options);
}
render(<QuoteComponent options={options} />);
} 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(<QuoteAndBuy options={options} />);
}
}

// 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);
}
}
function QuoteAndBuy(
props: {
options: SfBuyOptions;
},
) {
const [orderProps, setOrderProps] = useState<BuyOrderProps | null>(null);

const duration = parseDuration(options.duration);
const startDate = parseStartAsDate(options.start);
const endsAt = roundEndDate(
dayjs(startDate).add(duration, "seconds").toDate(),
).toDate();

render(
<BuyOrder
price={pricePerGpuHour}
size={parseAccelerators(options.accelerators)}
startAt={startDate}
type={options.type}
endsAt={endsAt}
colocate={options.colocate}
/>,
);
// 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(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
? (
<Box gap={1}>
<Spinner type="dots" />
<Box gap={1}>
<Text>Getting quote...</Text>
</Box>
</Box>
)
: <BuyOrder {...orderProps} />;
}

function roundEndDate(endDate: Date) {
Expand Down Expand Up @@ -264,16 +318,16 @@ function BuyOrderPreview(
type Order =
| Awaited<ReturnType<typeof getOrder>>
| Awaited<ReturnType<typeof placeBuyOrder>>;

type BuyOrderProps = {
price: number;
size: number;
startAt: Date | "NOW";
endsAt: Date;
type: string;
colocate?: Array<string>;
};
function BuyOrder(
props: {
price: number;
size: number;
startAt: Date | "NOW";
endsAt: Date;
type: string;
colocate?: Array<string>;
},
props: BuyOrderProps,
) {
const [isLoading, setIsLoading] = useState(false);
const [value, setValue] = useState("");
Expand Down Expand Up @@ -514,6 +568,8 @@ export async function getQuote(options: QuoteOptions) {
: options.startsAt.toISOString(),
},
},
// timeout after 600 seconds
signal: AbortSignal.timeout(600 * 1000),
});

if (!response.ok) {
Expand Down
15 changes: 8 additions & 7 deletions src/lib/clusters/kubeconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -105,7 +106,7 @@ export function createKubeconfig(props: {

export function mergeNamedItems<T extends { name: string }>(
items1: T[],
items2: T[]
items2: T[],
): T[] {
const map = new Map<string, T>();
for (const item of items1) {
Expand All @@ -119,7 +120,7 @@ export function mergeNamedItems<T extends { name: string }>(

export function mergeKubeconfigs(
oldConfig: Kubeconfig,
newConfig?: Kubeconfig
newConfig?: Kubeconfig,
): Kubeconfig {
if (!newConfig) {
return oldConfig;
Expand All @@ -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 },
};
Expand Down
6 changes: 5 additions & 1 deletion src/lib/contracts/ContractDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ export function ContractDisplay(props: { contract: Contract }) {
return (
<Box key={interval} gap={1}>
<Box width={17} alignItems="flex-end">
<Text>{quantity * GPUS_PER_NODE} x {props.contract.instance_type} (gpus)</Text>
<Text>
{quantity * GPUS_PER_NODE} x {props.contract.instance_type}
{" "}
(gpus)
</Text>
</Box>
<Text dimColor></Text>
<Box gap={1}>
Expand Down
25 changes: 14 additions & 11 deletions src/lib/sell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function registerSell(program: Command) {
.option(
"-f, --flags <flags>",
"Specify additional flags as JSON",
JSON.parse
JSON.parse,
)
.action(async (options) => {
await placeSellOrder(options);
Expand All @@ -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 };
Expand Down Expand Up @@ -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}`,
);
}

Expand Down Expand Up @@ -133,7 +134,7 @@ async function placeSellOrder(options: {
priceCents,
totalDurationSecs,
nodes,
GPUS_PER_NODE
GPUS_PER_NODE,
);

const params: PlaceSellOrderParameters = {
Expand All @@ -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:
Expand Down

0 comments on commit 382a4bc

Please sign in to comment.