Skip to content

Commit

Permalink
v2.0.0 (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
GJFR authored Apr 9, 2024
2 parents 2149943 + 3c9b14b commit 54b3847
Show file tree
Hide file tree
Showing 72 changed files with 1,401 additions and 1,010 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
**/__pycache__
**/dist
**/node_modules
.env
.flake8
.git
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ browser/binaries/
database/data/
!**/.gitkeep
**/node_modules
**/junit.xml
# Created by https://www.toptal.com/developers/gitignore/api/intellij,python,flask,macos
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij,python,flask,macos

Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ FROM python:3.11-slim-buster AS base
WORKDIR /app

RUN apt-get update -y
RUN apt install -y curl gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils libgbm-dev xvfb dbus-x11 libnss3-tools python3-pip vim multiarch-support wget git procps \
&& rm -rf /var/lib/apt/lists/*
RUN apt install -y curl gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils libgbm-dev xvfb dbus-x11 libnss3-tools python3-pip vim multiarch-support wget git procps &&\
rm -rf /var/lib/apt/lists/*

RUN curl -sSL https://get.docker.com/ | sh

Expand Down
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ Follow these steps to get started:

Launch BugHog using the following command:
```bash
docker compose up
docker compose up -d
```

> :warning: If you use `sudo` with this command, the `PWD` environment variable won't be passed to the BugHog containers, which is necessary for dynamically starting worker containers.
> To avoid this, explicitly pass on this variable: `sudo PWD=$PWD docker compose up`.
Open your web browser and navigate to [http://localhost:5000](http://localhost:5000) to access the graphical interface.
BugHog is started on a remote server, substitute 'localhost' with its IP address.
If BugHog is started on a remote server, substitute 'localhost' with the appropriate IP address.
BugHog can be stopped through:
```bash
Expand Down Expand Up @@ -98,7 +98,7 @@ Be sure to restart the BugHog framework when you add a new experiment:

```bash
docker compose down
docker compose up
docker compose up -d
```

## Development
Expand All @@ -114,13 +114,26 @@ For debugging the core application, consider using the VS Code dev container.
You can utilize the configuration in [.devcontainer](.devcontainer) for this.


## Additional help
## Contact

Don't hesitate to open a [GitHub issue](https://github.com/DistriNet/BugHog/issues/new) if you come across a bug, want to suggest a feature, or have any questions!
Don't hesitate to open a [GitHub issue](https://github.com/DistriNet/BugHog/issues/new) if you encounter a bug or want to suggest a feature!
For questions or collaboration, you can reach out to [Gertjan Franken](https://distrinet.cs.kuleuven.be/people/GertjanFranken).
## Troubleshooting
If something isn't working as expected, check out the troubleshooting tips below.
If you don't find a solution, don't hesitate to open a [GitHub issue](https://github.com/DistriNet/BugHog/issues/new).
Feel free to include any relevant logs.


### Consult the logs

- Try launching BugHog without the `-d` flag to see logging output in the terminal, which might provide more information about the issue.
- For more detailed logs at the `DEBUG` level, check out the [logs](/logs) folder for all logging files.


### WSL on Windows

- Ensure you clone the BugHog project to the WSL file system instead of the Windows file system, and launch it from there.
Expand Down
67 changes: 48 additions & 19 deletions analysis/plot_factory.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from bokeh.colors.named import green, black
from bokeh.core.validation import silence
from bokeh.core.validation.warnings import PALETTE_LENGTH_FACTORS_MISMATCH
from bokeh.embed import file_html
from bokeh.models import BasicTickFormatter, ColumnDataSource, HoverTool
from bokeh.models.glyphs import Circle
from bokeh.models.glyphs import Circle, Rect, Text
from bokeh.palettes import Iridescent23
from bokeh.plotting import figure, output_file, show
from bokeh.resources import CDN
Expand All @@ -11,9 +12,9 @@
from bci.database.mongo.mongodb import MongoDB
from bci.evaluations.logic import PlotParameters


silence(PALETTE_LENGTH_FACTORS_MISMATCH, True)


class PlotFactory:

@staticmethod
Expand Down Expand Up @@ -54,28 +55,49 @@ def create_html_plot_string(params: PlotParameters, db: MongoDB) -> tuple[str, i

@staticmethod
def __create_plot(params: PlotParameters, db: MongoDB):
docs = db.get_documents_for_plotting(params)
if len(docs) == 0:
return None, 0

data = PlotFactory.__add_outcome_info(params, docs)

# Fetch results docs for revisions
revision_docs = db.get_documents_for_plotting(params)
revision_results = PlotFactory.__add_outcome_info(params, revision_docs)
# Create a data source with task start and end times, task names, and task colors
source = ColumnDataSource(data=data)
revision_source = ColumnDataSource(data=revision_results)
if revision_results:
# define a color map based on the 'version' column
revision_color_map = factor_cmap('browser_version_str', Iridescent23, list(set(revision_source.data['browser_version_str'])))

# Fetch results focs for versions
version_docs = db.get_documents_for_plotting(params, releases=True)
version_results = PlotFactory.__add_outcome_info(params, version_docs)
version_source = ColumnDataSource(data=version_results)

total_number_of_docs = len(revision_docs) + len(version_docs)
if total_number_of_docs == 0:
return None, 0

# define a color map based on the 'version' column
color_map = factor_cmap('browser_version_str', Iridescent23, list(set(source.data['browser_version_str'])))
if revision_docs and version_docs:
x_min = min(revision_source.data['revision_number'] + version_source.data['revision_number'])
x_max = max(revision_source.data['revision_number'] + version_source.data['revision_number'])
elif revision_docs:
x_min = min(revision_source.data['revision_number'])
x_max = max(revision_source.data['revision_number'])
else:
x_min = min(version_source.data['revision_number'])
x_max = max(version_source.data['revision_number'])

# Create a figure and add the task circles
plot = figure(
title='Gantt Chart with Points',
x_range=(min(source.data['revision_number']), max(source.data['revision_number'])),
x_range=(x_min, x_max),
y_range=['Error', 'Not reproduced', 'Reproduced'],
height=350,
width=700,
height=470,
width=900,
tools='xwheel_zoom,reset,pan',
active_scroll='xwheel_zoom')
plot.add_glyph(source, Circle(x='revision_number', y='outcome', fill_color=color_map, fill_alpha=0.8, size=15))
if revision_results:
plot.add_glyph(revision_source, Circle(x='revision_number', y='outcome', fill_color=revision_color_map, fill_alpha=0.8, radius=6, radius_units='screen'))
if version_results:
plot.add_glyph(version_source, Rect(x='revision_number', y='outcome', fill_color=green, width=12, height=12, angle=45, fill_alpha=0.8, width_units='screen', height_units='screen', angle_units='deg'))
plot.add_glyph(version_source, Text(x='revision_number', y='outcome', x_offset=0, y_offset=-20, text='browser_version_str', text_color=black, text_align='center', text_font_size='14px', angle=45, angle_units='deg'))

# Formatting
plot.xaxis[0].formatter = BasicTickFormatter(use_scientific=False)
Expand All @@ -87,7 +109,7 @@ def __create_plot(params: PlotParameters, db: MongoDB):
)
plot.add_tools(hover)

return plot, len(docs)
return plot, total_number_of_docs

@staticmethod
def __transform_to_bokeh_compatible(docs: list) -> dict:
Expand All @@ -105,17 +127,24 @@ def __add_outcome_info(params: PlotParameters, docs: dict):
target_mech_id = params.target_mech_id if params.target_mech_id else params.mech_group

for doc in docs:
# Backwards compatibility
requests_to_target = list(filter(lambda x: f'/report/?leak={target_mech_id}' in x['url'], doc['results']['requests']))
requests_to_baseline = list(filter(lambda x: '/report/?leak=baseline' in x['url'], doc['results']['requests']))
# New way
if [req_var for req_var in doc['results']['req_vars'] if req_var['var'] == 'reproduced' and req_var['val'] == 'OK'] or \
[log_var for log_var in doc['results']['log_vars'] if log_var['var'] == 'reproduced' and log_var['val'] == 'OK']:
reproduced = True
else:
reproduced = False

new_doc = {
'revision_number': doc['revision_number'],
'revision_number': doc['state']['revision_number'],
'browser_version': int(doc['browser_version'].split('.')[0]),
'browser_version_str': doc['browser_version'].split('.')[0]
}
if doc['dirty'] or len(requests_to_baseline) == 0:
if doc['dirty']:
new_doc['outcome'] = 'Error'
docs_with_outcome.append(new_doc)
elif len(requests_to_target) > 0:
elif len(requests_to_target) > 0 or reproduced:
new_doc['outcome'] = 'Reproduced'
docs_with_outcome.append(new_doc)
else:
Expand Down
14 changes: 7 additions & 7 deletions bci/browser/automation/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import subprocess
import time


logger = logging.getLogger('bci')
logger = logging.getLogger(__name__)


class TerminalAutomation:
Expand All @@ -14,11 +13,12 @@ def run(url: str, args: list[str], seconds_per_visit: int):
logger.debug("Starting browser process...")
args.append(url)
logger.debug(f'Command string: \'{" ".join(args)}\'')
proc = subprocess.Popen(
args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
with open('/tmp/browser.log', 'a') as file:
proc = subprocess.Popen(
args,
stdout=file,
stderr=file
)

time.sleep(seconds_per_visit)

Expand Down
38 changes: 18 additions & 20 deletions bci/browser/binary/binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,14 @@
from bci.browser.binary.artisanal_manager import ArtisanalBuildManager
from bci.version_control.states.state import State

logger = logging.getLogger('bci')
logger = logging.getLogger(__name__)


class Binary:

def __init__(self, state: State):
self.state = state
self.__version = None
self.only_releases = None

def set_only_releases(self, only_releases):
self.only_releases = only_releases

@property
def version(self) -> str:
Expand Down Expand Up @@ -73,24 +69,26 @@ def fetch_binary(self):
if self.is_built():
return
# Try to download binary
elif self.__class__.has_available_binary_online(self.state):
elif self.is_available_online():
self.download_binary()
else:
raise BuildNotAvailableError(self.browser_name, self.state.revision_number)
raise BuildNotAvailableError(self.browser_name, self.state)

@abstractmethod
def download_binary(self):
pass
def is_available(self):
'''
Returns True if the binary is available either locally or online.
'''
return self.is_available_locally() or self.is_available_online()

def is_available_locally_or_online(self):
return self.has_available_binary_locally() or self.has_available_binary_online()

def has_available_binary_locally(self):
def is_available_locally(self):
bin_path = self.get_bin_path()
return bin_path is not None

def is_available_online(self):
return self.state.has_online_binary()

@abstractmethod
def has_available_binary_online(self):
def download_binary(self):
pass

def is_built(self):
Expand All @@ -114,8 +112,8 @@ def get_potential_bin_path(self, artisanal=False):
Returns path to potential binary. It does not guarantee whether the binary is available locally.
"""
if artisanal:
return os.path.join(self.bin_folder_path, "artisanal", str(self.state.revision_number), self.executable_name)
return os.path.join(self.bin_folder_path, "downloaded", str(self.state.revision_number), self.executable_name)
return os.path.join(self.bin_folder_path, "artisanal", self.state.name, self.executable_name)
return os.path.join(self.bin_folder_path, "downloaded", self.state.name, self.executable_name)

def get_bin_folder_path(self):
path_downloaded = self.get_potential_bin_folder_path()
Expand All @@ -128,14 +126,14 @@ def get_bin_folder_path(self):

def get_potential_bin_folder_path(self, artisanal=False):
if artisanal:
return os.path.join(self.bin_folder_path, "artisanal", str(self.state.revision_number))
return os.path.join(self.bin_folder_path, "downloaded", str(self.state.revision_number))
return os.path.join(self.bin_folder_path, "artisanal", self.state.name)
return os.path.join(self.bin_folder_path, "downloaded", self.state.name)

def remove_bin_folder(self):
path = self.get_bin_folder_path()
if path and "artisanal" not in path:
if not util.rmtree(path):
self.logger.error("Could not remove folder '%s'" % path)
logger.error("Could not remove folder '%s'" % path)

@abstractmethod
def get_driver_version(self, browser_version):
Expand Down
8 changes: 4 additions & 4 deletions bci/browser/binary/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ def binary_is_available(state: State) -> bool:


def __has_available_binary_online(state: State) -> bool:
return __get_class(state.browser_name).has_available_binary_online(state)
return __get_class(state.browser_name).has_available_binary_online()


def __has_available_binary_artisanal(state: State) -> bool:
return __get_class(state.browser_name).get_artisanal_manager().has_artisanal_binary_for(state)


def get_binary(state: State) -> Binary:
return __get_object(state.browser_name, state)
return __get_object(state)


def __get_class(browser_name: str) -> Binary.__class__:
Expand All @@ -46,8 +46,8 @@ def __get_class(browser_name: str) -> Binary.__class__:
raise ValueError(f'Unknown browser {browser_name}')


def __get_object(browser_name: str, state: State) -> Binary:
match browser_name:
def __get_object(state: State) -> Binary:
match state.browser_name:
case 'chromium':
return ChromiumBinary(state)
case 'firefox':
Expand Down
28 changes: 6 additions & 22 deletions bci/browser/binary/vendors/chromium.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from bci import cli, util
from bci.browser.binary.artisanal_manager import ArtisanalBuildManager
from bci.browser.binary.binary import Binary
from bci.database.mongo.mongodb import MongoDB
from bci.version_control.states.state import State

logger = logging.getLogger('bci')
Expand Down Expand Up @@ -51,32 +50,17 @@ def bin_folder_path(self) -> str:

# Downloadable binaries

@staticmethod
def has_available_binary_online(state: State) -> bool:
cached_binary_available_online = MongoDB.has_binary_available_online('chromium', state)
if cached_binary_available_online is not None:
return cached_binary_available_online
url = f'https://www.googleapis.com/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F{state.revision_number}%2Fchrome-linux.zip'
req = requests.get(url)
has_binary_online = req.status_code == 200
MongoDB.store_binary_availability_online_cache('chromium', state, has_binary_online)
return has_binary_online

def download_binary(self):
rev_number = self.state.revision_number

if self.has_available_binary_locally():
logger.debug(f'{self.rev_number} was already downloaded ({self.get_bin_path()})')
if self.is_available_locally():
logger.debug(f'Binary for {self.state} was already downloaded ({self.get_bin_path()})')
return
url = \
"https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/%s%%2F%s%%2Fchrome-%s.zip?alt=media"\
% ('Linux_x64', rev_number, 'linux')
logger.info(f'Downloading {rev_number} from \'{url}\'')
zip_file_path = f'/tmp/{rev_number}/archive.zip'
binary_url = self.state.get_online_binary_url()
logger.info(f'Downloading binary for {self.state} from \'{binary_url}\'')
zip_file_path = f'/tmp/{self.state.name}/archive.zip'
if os.path.exists(os.path.dirname(zip_file_path)):
shutil.rmtree(os.path.dirname(zip_file_path))
os.makedirs(os.path.dirname(zip_file_path))
with requests.get(url, stream=True) as req:
with requests.get(binary_url, stream=True) as req:
with open(zip_file_path, 'wb') as file:
shutil.copyfileobj(req.raw, file)
with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
Expand Down
Loading

0 comments on commit 54b3847

Please sign in to comment.