Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

Commit

Permalink
add view filter for jobid, project, and/or name (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmc-msft authored Oct 16, 2020
1 parent 0eebd1e commit 8ff5439
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 33 deletions.
82 changes: 61 additions & 21 deletions src/cli/onefuzz/status/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from onefuzztypes.enums import ContainerType, JobState, NodeState, PoolState, TaskState
from onefuzztypes.models import Job, Node, Pool, Task
from pydantic import BaseModel

MESSAGE = Tuple[datetime, str, str]

Expand Down Expand Up @@ -64,29 +65,34 @@ def fmt(data: Any) -> Any:
raise NotImplementedError(type(data))


class JobFilter(BaseModel):
job_id: Optional[List[UUID]]
project: Optional[List[str]]
name: Optional[List[str]]


class TopCache:
JOB_FIELDS = (["Updated", "State", "Job", "Name", "Files"], [1, 1, 1, 1, "100%"])
TASK_FIELDS = (
[
"Updated",
"State",
"Job",
"Task",
"Type",
"Name",
"Files",
"Pool",
"Time left",
],
[1, 1, 1, 1, 1, 1, 1, 1, "100%"],
)
POOL_FIELDS = (
["Updated", "Pool", "Name", "OS", "State", "Nodes"],
[1, 1, 1, 1, 1, "100%"],
)

def __init__(self, onefuzz: "Onefuzz"):
JOB_FIELDS = ["Updated", "State", "Job", "Name", "Files"]
TASK_FIELDS = [
"Updated",
"State",
"Job",
"Task",
"Type",
"Name",
"Files",
"Pool",
"Time left",
]
POOL_FIELDS = ["Updated", "Pool", "Name", "OS", "State", "Nodes"]

def __init__(
self,
onefuzz: "Onefuzz",
job_filters: JobFilter,
):
self.onefuzz = onefuzz
self.job_filters = job_filters
self.tasks: Dict[UUID, Tuple[datetime, Task]] = {}
self.jobs: Dict[UUID, Tuple[datetime, Job]] = {}
self.files: Dict[str, Tuple[Optional[datetime], Set[str]]] = {}
Expand Down Expand Up @@ -222,6 +228,7 @@ def add_task(
try:
if task is None:
task = self.onefuzz.tasks.get(task_id)
self.add_job_if_missing(task.job_id)
self.tasks[task.task_id] = (datetime.now(), task)
if add_files:
for container in task.config.containers:
Expand All @@ -232,6 +239,12 @@ def add_task(
def render_tasks(self) -> List:
results = []
for (timestamp, task) in sorted(self.tasks.values(), key=lambda x: x[0]):
job_entry = self.jobs.get(task.job_id)
if job_entry:
(_, job) = job_entry
if not self.should_render_job(job):
continue

timestamps, files = self.get_file_counts([task])
timestamps += [timestamp]

Expand All @@ -253,6 +266,12 @@ def render_tasks(self) -> List:
results.append(entry)
return results

def add_job_if_missing(self, job_id: UUID) -> None:
if job_id in self.jobs:
return
job = self.onefuzz.jobs.get(job_id)
self.add_job(job_id, job.state, job)

def add_job(self, job_id: UUID, state: JobState, job: Optional[Job] = None) -> None:
if state in [JobState.stopping, JobState.stopped]:
if job_id in self.jobs:
Expand All @@ -271,10 +290,31 @@ def add_job(self, job_id: UUID, state: JobState, job: Optional[Job] = None) -> N
except Exception:
logging.debug("unable to find job: %s", job_id)

def should_render_job(self, job: Job) -> bool:
if self.job_filters.job_id is not None:
if job.job_id not in self.job_filters.job_id:
logging.info("skipping:%s", job)
return False

if self.job_filters.project is not None:
if job.config.project not in self.job_filters.project:
logging.info("skipping:%s", job)
return False

if self.job_filters.name is not None:
if job.config.name not in self.job_filters.name:
logging.info("skipping:%s", job)
return False

return True

def render_jobs(self) -> List[Tuple]:
results: List[Tuple] = []

for (timestamp, job) in sorted(self.jobs.values(), key=lambda x: x[0]):
if not self.should_render_job(job):
continue

timestamps, files = self.get_file_counts(
self.get_tasks(job.job_id), merge_inputs=True
)
Expand Down
16 changes: 14 additions & 2 deletions src/cli/onefuzz/status/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

from typing import List, Optional
from uuid import UUID

from ..api import Command
from .cache import JobFilter
from .raw import raw
from .top import Top

Expand All @@ -15,7 +19,15 @@ def raw(self) -> None:
""" Raw status update stream """
raw(self.onefuzz, self.logger)

def top(self, show_details: bool = False) -> None:
def top(
self,
*,
show_details: bool = False,
job_id: Optional[List[UUID]] = None,
project: Optional[List[str]] = None,
name: Optional[List[str]] = None,
) -> None:
""" Onefuzz Top """
top = Top(self.onefuzz, self.logger, show_details)
job_filter = JobFilter(job_id=job_id, project=project, name=name)
top = Top(self.onefuzz, self.logger, show_details, job_filter)
top.run()
9 changes: 7 additions & 2 deletions src/cli/onefuzz/status/top.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from onefuzztypes.enums import JobState, NodeState, PoolState, TaskState

from .cache import TopCache
from .cache import JobFilter, TopCache
from .signalr import Stream
from .top_view import render

Expand All @@ -35,12 +35,13 @@ def __init__(
onefuzz: "Onefuzz",
logger: logging.Logger,
show_details: bool,
job_filter: JobFilter,
):
self.onefuzz = onefuzz
self.logger = logger
self.show_details = show_details

self.cache = TopCache(onefuzz)
self.cache = TopCache(onefuzz, job_filter)
self.queue: PriorityQueue = PriorityQueue()
self.worker = Thread(target=background_task, args=(self.queue,))
self.worker.start()
Expand Down Expand Up @@ -89,6 +90,10 @@ def setup(self) -> Stream:

for job in jobs:
self.cache.add_job(job.job_id, job.state, job)
# don't add pre-add tasks that we're going to filter out
if not self.cache.should_render_job(job):
continue

for task in self.onefuzz.tasks.list(job_id=job.job_id):
self.cache.add_task(
task.task_id, task.state, task=task, add_files=False
Expand Down
29 changes: 21 additions & 8 deletions src/cli/onefuzz/status/top_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Licensed under the MIT License.

from datetime import datetime
from typing import Any, List, Sequence, Tuple
from typing import Any, List, Optional, Sequence, Tuple, Union

from asciimatics.event import KeyboardEvent
from asciimatics.exceptions import ResizeScreenError, StopApplication
Expand Down Expand Up @@ -34,6 +34,17 @@ def now() -> datetime:
EXTRA_LINES_PER_WIDGET = 3


def column_config(fields: Optional[List[str]]) -> List[Union[int, str]]:
base: List[Union[int, str]] = []
if fields:
base += [1] * (len(fields) - 1)
else:
base += [1]

base += ["100%"]
return base


class TopView(Frame):
def __init__(self, screen: Any, cache: TopCache, show_details: bool):

Expand Down Expand Up @@ -78,30 +89,32 @@ def __init__(self, screen: Any, cache: TopCache, show_details: bool):
"tasks": {"height": Widget.FILL_FRAME, "setup": self.cache.TASK_FIELDS},
"messages": {
"height": min(10, max_widget_height),
"setup": [["Updated", "Type", "Message"], [1, 1, "100%"]],
"setup": ["Updated", "Type", "Message"],
},
"status": {"height": 1, "setup": [None, [1, "100%"]]},
"status": {"height": 1},
}

for name in ["status", "pools", "jobs", "tasks", "messages"]:
if name == "messages" and not self.show_details:
continue

if dimensions[name]["setup"][0]:
titles = dimensions[name].get("setup")

if titles:
title = TextBox(1, as_string=True, name=name + "_title")
title.disabled = True
title.custom_colour = "label"
layout.add_widget(title)

widget = MultiColumnListBox(
dimensions[name]["height"],
dimensions[name]["setup"][1],
column_config(titles),
[],
titles=dimensions[name]["setup"][0],
titles=titles,
name=name,
add_scroll_bar=bool(dimensions[name]["setup"][0]),
add_scroll_bar=bool(titles),
)
if not dimensions[name]["setup"][0]:
if not titles:
widget.disabled = True

layout.add_widget(widget)
Expand Down

0 comments on commit 8ff5439

Please sign in to comment.