Skip to content
Open
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
3 changes: 1 addition & 2 deletions gprofiler/consts.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
CPU_PROFILING_MODE = "cpu"
DEFAULT_PROFILING_MODE = CPU_PROFILING_MODE
from granulate_utils.gprofiler.consts import * # noqa: F403,F401
110 changes: 1 addition & 109 deletions gprofiler/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,109 +1 @@
#
# Copyright (C) 2022 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import signal
import subprocess
from typing import List, Union


class PerfNoSupportedEvent(Exception):
pass


class StopEventSetException(Exception):
pass


class ProcessStoppedException(Exception):
pass


class CalledProcessError(subprocess.CalledProcessError):
# Enough characters for 200 long lines
MAX_STDIO_LENGTH = 120 * 200

def __init__(
self,
returncode: int,
cmd: Union[str, List[str]],
output: str,
stderr: str,
):
assert isinstance(returncode, int), returncode
assert isinstance(cmd, str) or all(isinstance(s, str) for s in cmd), cmd
assert output is None or isinstance(output, str), output
assert stderr is None or isinstance(stderr, str), stderr
super().__init__(returncode, cmd, output, stderr)

def _truncate_stdio(self, stdio: str) -> str:
if len(stdio) > self.MAX_STDIO_LENGTH:
stdio = stdio[: self.MAX_STDIO_LENGTH - 3] + "..."
return stdio

def __str__(self) -> str:
if self.returncode and self.returncode < 0:
try:
base = f"Command {self.cmd!r} died with {signal.Signals(-self.returncode)!r}."
except ValueError:
base = f"Command {self.cmd!r} died with unknown signal {-self.returncode}."
else:
base = f"Command {self.cmd!r} returned non-zero exit status {self.returncode}."
return f"{base}\nstdout: {self._truncate_stdio(self.stdout)}\nstderr: {self._truncate_stdio(self.stderr)}"


class CalledProcessTimeoutError(CalledProcessError):
def __init__(
self,
timeout: float,
returncode: int,
cmd: Union[str, List[str]],
output: str,
stderr: str,
):
super().__init__(returncode, cmd, output, stderr)
self.timeout = timeout

def __str__(self) -> str:
return f"Timed out after {self.timeout} seconds\n" + super().__str__()


class ProgramMissingException(Exception):
def __init__(self, program: str):
super().__init__(f"The program {program!r} is missing! Please install it")


class APIError(Exception):
def __init__(self, message: str, full_data: dict = None):
self.message = message
self.full_data = full_data

def __str__(self) -> str:
return self.message


class ThreadStopTimeoutError(Exception):
pass


class SystemProfilerStartFailure(Exception):
pass


class NoProfilersEnabledError(Exception):
pass


class NoRwExecDirectoryFoundError(Exception):
pass
from granulate_utils.gprofiler.exceptions import * # noqa: F403,F401
31 changes: 1 addition & 30 deletions gprofiler/platform.py
Original file line number Diff line number Diff line change
@@ -1,30 +1 @@
#
# Copyright (C) 2022 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import sys
from functools import lru_cache

WINDOWS_PLATFORM_NAME = "win32"
LINUX_PLATFORM_NAME = "linux"


@lru_cache(maxsize=None)
def is_windows() -> bool:
return sys.platform == WINDOWS_PLATFORM_NAME


@lru_cache(maxsize=None)
def is_linux() -> bool:
return sys.platform == LINUX_PLATFORM_NAME
from granulate_utils.gprofiler.platform import * # noqa: F403,F401
110 changes: 1 addition & 109 deletions gprofiler/utils/fs.py
Original file line number Diff line number Diff line change
@@ -1,109 +1 @@
#
# Copyright (C) 2022 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import errno
import os
import shutil
from pathlib import Path
from secrets import token_hex
from typing import Union

from gprofiler.platform import is_windows
from gprofiler.utils import is_root, remove_path, run_process


def safe_copy(src: str, dst: str) -> None:
"""
Safely copies 'src' to 'dst'. Safely means that writing 'dst' is performed at a temporary location,
and the file is then moved, making the filesystem-level change atomic.
"""
dst_tmp = f"{dst}.tmp"
shutil.copy(src, dst_tmp)
os.rename(dst_tmp, dst)


def is_rw_exec_dir(path: Path) -> bool:
"""
Is 'path' rw and exec?
"""
assert is_owned_by_root(path), f"expected {path} to be owned by root!"

# randomize the name - this function runs concurrently on paths of in same mnt namespace.
test_script = path / f"t-{token_hex(10)}.sh"

# try creating & writing
try:
test_script.write_text("#!/bin/sh\nexit 0")
test_script.chmod(0o755) # make sure it's executable. file is already writable only by root due to umask.
except OSError as e:
if e.errno == errno.EROFS:
# ro
return False
remove_path(test_script)
raise

# try executing
try:
run_process([str(test_script)], suppress_log=True)
except PermissionError:
# noexec
return False
finally:
test_script.unlink()

return True


def escape_filename(filename: str) -> str:
return filename.replace(":", "-" if is_windows() else ":")


def is_owned_by_root(path: Path) -> bool:
statbuf = path.stat()
return statbuf.st_uid == 0 and statbuf.st_gid == 0


def mkdir_owned_root(path: Union[str, Path], mode: int = 0o755) -> None:
"""
Ensures a directory exists and is owned by root.

If the directory exists and is owned by root, it is left as is.
If the directory exists and is not owned by root, it is removed and recreated. If after recreation
it is still not owned by root, the function raises.
"""
assert is_root() # this function behaves as we expect only when run as root

path = path if isinstance(path, Path) else Path(path)
# parent is expected to be root - otherwise, after we create the root-owned directory, it can be removed
# as re-created as non-root by a regular user.
if not is_owned_by_root(path.parent):
raise Exception(f"expected {path.parent} to be owned by root!")

if path.exists():
if is_owned_by_root(path):
return

shutil.rmtree(path)

try:
os.mkdir(path, mode=mode)
except FileExistsError:
# likely racing with another thread of gprofiler. as long as the directory is root after all, we're good.
pass

if not is_owned_by_root(path):
# lost race with someone else?
raise Exception(f"Failed to create directory {str(path)} as owned by root")
from granulate_utils.gprofiler.utils.fs import * # noqa: F403,F401