Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy client count change requests through BRAD to avoid CORS restrictions #485

Merged
merged 4 commits into from
Mar 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
"fastapi",
"uvicorn[standard]",
"pydantic",
"requests",
"types-requests",
]

KEYWORDS = []
Expand Down
63 changes: 42 additions & 21 deletions src/brad/ui/manager_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import logging
import importlib.resources as pkg_resources
import numpy as np
from fastapi import FastAPI
import requests
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from typing import Optional, List
from pydantic import BaseModel

import brad.ui.static as brad_app
from brad.blueprint import Blueprint
Expand All @@ -26,6 +26,8 @@
VirtualInfrastructure,
DisplayableTable,
Status,
ClientState,
SetClientState,
)
from brad.daemon.front_end_metrics import FrontEndMetric
from brad.daemon.system_event_logger import SystemEventLogger, SystemEventRecord
Expand Down Expand Up @@ -190,25 +192,44 @@ def get_system_state(filter_tables_for_demo: bool = False) -> SystemState:
return system_state


class ClientState(BaseModel):
max_clients: int
curr_clients: int


class SetClientState(BaseModel):
curr_clients: int


@app.get("/clients")
def get_clients_dummy() -> ClientState:
# Used for debugging without starting the variable client runner.
return ClientState(max_clients=12, curr_clients=3)


@app.post("/clients")
def set_clients_dummy(clients: SetClientState) -> ClientState:
# Used for debugging without starting the variable client runner.
return ClientState(max_clients=12, curr_clients=clients.curr_clients)
@app.get("/api/1/clients")
def get_workload_clients(runner_port: Optional[int] = None) -> ClientState:
# This proxies the request to the runner, which runs as a different process
# and listens for requests on a different port. We require a proxy to avoid
# CORS restrictions.
if runner_port is None:
# Used for debugging without starting the variable client runner.
return ClientState(max_clients=12, curr_clients=3)
else:
try:
r = requests.get(f"http://localhost:{runner_port}/clients", timeout=2)
if r.status_code != 200:
raise HTTPException(r.status_code, r.reason)
return ClientState(**r.json())
except requests.ConnectionError as ex:
raise HTTPException(400, f"Unable to connect to port {runner_port}") from ex


@app.post("/api/1/clients")
def set_clients(clients: SetClientState) -> ClientState:
# This proxies the request to the runner, which runs as a different process
# and listens for requests on a different port. We require a proxy to avoid
# CORS restrictions.
if clients.runner_port is None:
# Used for debugging without starting the variable client runner.
return ClientState(max_clients=12, curr_clients=clients.curr_clients)
else:
try:
r = requests.post(
f"http://localhost:{clients.runner_port}/clients", timeout=2
)
if r.status_code != 200:
raise HTTPException(r.status_code, r.reason)
return ClientState(**r.json())
except requests.ConnectionError as ex:
raise HTTPException(
400, f"Unable to connect to port {clients.runner_port}"
) from ex


def _analytics_table_mapper_temp(table_name: str, blueprint: Blueprint) -> List[str]:
Expand Down
10 changes: 10 additions & 0 deletions src/brad/ui/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,13 @@ class SystemState(BaseModel):
virtual_infra: VirtualInfrastructure
blueprint: DisplayableBlueprint
next_blueprint: Optional[DisplayableBlueprint]


class ClientState(BaseModel):
max_clients: int
curr_clients: int


class SetClientState(BaseModel):
runner_port: Optional[int] = None
curr_clients: int
4 changes: 1 addition & 3 deletions ui/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@ function App() {
{
host: "localhost",
port: 8585,
// port: 7583,
},
{
host: "localhost",
port: 8586,
// port: 7583,
},
],
});
Expand Down Expand Up @@ -72,7 +70,7 @@ function App() {
let timeoutId = null;
const refreshData = async () => {
const newSystemState = await fetchSystemState(
/*filterTablesForDemo=*/ true,
/*filterTablesForDemo=*/ false,
);
// TODO: Not the best way to check for equality.
if (JSON.stringify(systemState) !== JSON.stringify(newSystemState)) {
Expand Down
22 changes: 21 additions & 1 deletion ui/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,24 @@ async function fetchSystemState(filterTablesForDemo) {
return result.data;
}

export { fetchMetrics, fetchSystemState };
async function fetchWorkloadClients(port) {
const args = port != null ? { params: { runner_port: port } } : {};
const result = await axios.get(`${API_PREFIX}/clients`, args);
return result.data;
}

async function setWorkloadClients(port, numClients) {
const args = { curr_clients: numClients };
if (port != null) {
args.runner_port = port;
}
const result = await axios.post(`${API_PREFIX}/clients`, args);
return result.data;
}

export {
fetchMetrics,
fetchSystemState,
fetchWorkloadClients,
setWorkloadClients,
};
2 changes: 1 addition & 1 deletion ui/src/components/PerfView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function PerfView({ virtualInfra }) {
useEffect(() => {
let timeoutId = null;
const refreshData = async () => {
const rawMetrics = await fetchMetrics(60, /*useGenerated=*/ true);
const rawMetrics = await fetchMetrics(60, /*useGenerated=*/ false);
const fetchedMetrics = parseMetrics(rawMetrics);
const metricsManager = getMetricsManager();
const addedNewMetrics = metricsManager.mergeInMetrics(fetchedMetrics);
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/VdbeView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function VdbeView({
max={workloadState.max_clients}
value={workloadState.curr_clients}
onChange={updateWorkloadNumClients}
debounceMs={2000}
debounceMs={800}
/>
)}
<DbCylinder color="green" onClick={toggleWorkloadAdjuster}>
Expand Down
24 changes: 10 additions & 14 deletions ui/src/components/VirtualInfraView.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import axios from "axios";
import Panel from "./Panel";
import VdbeView from "./VdbeView";
import "./styles/VirtualInfraView.css";
import { useEffect, useState, useCallback } from "react";

function baseEndpointFromObj({ host, port }) {
return `http://${host}:${port}`;
}
import { fetchWorkloadClients, setWorkloadClients } from "../api";

function VirtualInfraView({
virtualInfra,
Expand All @@ -25,11 +21,11 @@ function VirtualInfraView({
) {
return;
}
const baseEndpoint = baseEndpointFromObj(workloadRunners[vdbeIndex]);
const result = await axios.post(`${baseEndpoint}/clients`, {
curr_clients: numClients,
});
const newWorkloadState = result.data;
const endpoint = workloadRunners[vdbeIndex];
const newWorkloadState = await setWorkloadClients(
endpoint.port,
numClients,
);

// Skip the state update if there was no change.
const existingWorkloadState = workloadStates[vdbeIndex];
Expand All @@ -51,11 +47,11 @@ function VirtualInfraView({

useEffect(async () => {
const { workloadRunners } = endpoints;
const promises = workloadRunners
.map(baseEndpointFromObj)
.map((baseEndpoint) => axios.get(`${baseEndpoint}/clients`));
const promises = workloadRunners.map((endpoint) =>
fetchWorkloadClients(endpoint.port),
);
const results = await Promise.all(promises);
setWorkloadStates(results.map(({ data }) => data));
setWorkloadStates(results);
}, [endpoints]);

return (
Expand Down
11 changes: 1 addition & 10 deletions workloads/IMDB_extended/workload_utils/change_clients_api.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional

from brad.ui.models import ClientState, SetClientState
from .pause_controller import PauseController


class ClientState(BaseModel):
max_clients: int
curr_clients: int


class SetClientState(BaseModel):
curr_clients: int


class Manager:
def __init__(self, pc: PauseController) -> None:
self.pc = pc
Expand Down