Skip to content
Draft
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
23 changes: 22 additions & 1 deletion src/hypofuzz/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ async def initial(self, tests: dict[str, Test]) -> None:
}
await self.send_event(report_event)

# then its observations.
# then its observations, sending the observations (without their repr)
# first, followed by the repr of each observation.
for obs_type, observations in [
("rolling", test.rolling_observations),
("corpus", test.corpus_observations),
Expand All @@ -224,6 +225,26 @@ async def initial(self, tests: dict[str, Test]) -> None:
},
)

for obs_type, observations in [
("rolling", test.rolling_observations),
("corpus", test.corpus_observations),
]:
await self.send_event(
{
"type": DashboardEventType.SET_OBSERVATION_REPRS,
"nodeid": self.nodeid,
"observation_type": obs_type, # type: ignore
"representations": [
{
# using run_start as the observation primary key
"run_start": obs.run_start,
Comment on lines +239 to +240
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes me nervous; some clocks have pretty low resolution. Seems safe enough if we add the worker_uuid though.

"repr": obs.representation,
}
for obs in observations
],
},
)

async def on_event(
self, event_type: Literal["save", "delete"], key: DatabaseEventKey, value: Any
) -> None:
Expand Down
18 changes: 15 additions & 3 deletions src/hypofuzz/dashboard/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class DashboardObservation(TypedDict):
type: str
status: ObservationStatus
status_reason: str
representation: str
representation: None
arguments: dict[str, Any]
how_generated: str
features: dict[str, Any]
Expand All @@ -29,7 +29,8 @@ def dashboard_observation(observation: Observation) -> DashboardObservation:
"type": observation.type,
"status": observation.status,
"status_reason": observation.status_reason,
"representation": observation.representation,
# explicitly dropping representation
"representation": None,
"arguments": observation.arguments,
"how_generated": observation.how_generated,
"features": observation.features,
Expand Down Expand Up @@ -69,7 +70,8 @@ class DashboardEventType(IntEnum):
ADD_TESTS = 1
ADD_REPORTS = 2
ADD_OBSERVATIONS = 3
SET_FAILURE = 4
SET_OBSERVATION_REPRS = 4
SET_FAILURE = 5


ObservationType = Literal["rolling", "corpus"]
Expand Down Expand Up @@ -101,6 +103,15 @@ class AddObservationsEvent(TypedDict):
observations: list[DashboardObservation]


class SetObservationReprsEvent(TypedDict):
type: Literal[DashboardEventType.SET_OBSERVATION_REPRS]
nodeid: str
# TODO we might want to make this names shorter (obs_type / reprs) for json
# size
observation_type: ObservationType
representations: list[dict[str, Any]]


class SetFailureEvent(TypedDict):
type: Literal[DashboardEventType.SET_FAILURE]
failure: DashboardObservation
Expand All @@ -110,5 +121,6 @@ class SetFailureEvent(TypedDict):
AddTestsEvent,
AddReportsEvent,
AddObservationsEvent,
SetObservationReprsEvent,
SetFailureEvent,
]
36 changes: 35 additions & 1 deletion src/hypofuzz/frontend/src/context/DataProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ enum DashboardEventType {
ADD_TESTS = 1,
ADD_REPORTS = 2,
ADD_OBSERVATIONS = 3,
SET_FAILURE = 4,
SET_OBSERVATION_REPRS = 4,
SET_FAILURE = 5,
}

type TestsAction =
Expand All @@ -48,6 +49,12 @@ type TestsAction =
observation_type: "rolling" | "corpus"
observations: Observation[]
}
| {
type: DashboardEventType.SET_OBSERVATION_REPRS
nodeid: string
observation_type: "rolling" | "corpus"
representations: { run_start: number; representation: string }[]
}

function testsReducer(
state: Map<string, Test>,
Expand Down Expand Up @@ -110,6 +117,20 @@ function testsReducer(
return newState
}

case DashboardEventType.SET_OBSERVATION_REPRS: {
const { nodeid, observation_type, representations } = action
const test = getOrCreateTest(nodeid)
const observations =
observation_type === "rolling"
? test.rolling_observations
: test.corpus_observations
for (const { run_start, representation } of representations) {
const observation = observations.find(o => o.run_start === run_start)!
observation.representation = representation
}
return newState
}

default:
throw new Error("non-exhaustive switch in testsReducer")
}
Expand Down Expand Up @@ -250,6 +271,19 @@ export function DataProvider({ children }: DataProviderProps) {
break
}

case DashboardEventType.SET_OBSERVATION_REPRS: {
dispatch({
type: DashboardEventType.SET_OBSERVATION_REPRS,
nodeid: data.nodeid,
observation_type: data.observation_type,
representations: data.representations.map((r: any) => ({
run_start: Number(r.run_start),
representation: r.repr,
})),
})
break
}

default:
throw new Error(`Unknown event type: ${data.type}`)
}
Expand Down
3 changes: 3 additions & 0 deletions src/hypofuzz/frontend/src/tyche/Representation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export function Representation({ observations, observationType }: Props) {
const rawRepresentations = new Map<string, number>()
observations.forEach(observation => {
const repr = observation.representation
if (repr === null) {
return
}
rawRepresentations.set(repr, (rawRepresentations.get(repr) || 0) + 1)
})

Expand Down
9 changes: 9 additions & 0 deletions src/hypofuzz/frontend/src/tyche/Samples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,25 @@ export function Samples({ observations }: { observations: Observation[] }) {
// unique observation for that representation.
const uniqueReprIndex = new Map<string, number>()
for (const [index, observation] of observations.entries()) {
if (observation.representation === null) {
continue
}
uniqueReprIndex.set(observation.representation, index)
}

function isUnique(observation: Observation) {
if (observation.representation === null) {
return false
}
return (
observations.indexOf(observation) ===
uniqueReprIndex.get(observation.representation)
)
}
function isDuplicate(observation: Observation) {
if (observation.representation === null) {
return false
}
return (
observations.indexOf(observation) !==
uniqueReprIndex.get(observation.representation)
Expand Down
2 changes: 1 addition & 1 deletion src/hypofuzz/frontend/src/types/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export class Observation extends Dataclass<Observation> {
public type: string,
public status: ObservationStatus,
public status_reason: string,
public representation: string,
public representation: string | null,
// arguments is a reserved keyword in javascript
public arguments_: Map<string, any>,
public how_generated: string,
Expand Down
2 changes: 2 additions & 0 deletions src/hypofuzz/hypofuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,8 @@ def callback(test_case: dict) -> None:
# re-use per FuzzProcess. Overwrite with the current timestamp for use
# in sorting observations. This is not perfectly reliable in a
# distributed setting, but is good enough.
#
# We also re-use this as the primary key for this observation.
test_case["run_start"] = time.time()
# "arguments" duplicates part of the call repr in "representation".
# We don't use this for anything, so drop it.
Expand Down