Skip to content

Commit

Permalink
Win support (#5)
Browse files Browse the repository at this point in the history
Add windows support set up workflow to validate on windows as well
  • Loading branch information
gaogaotiantian authored Aug 26, 2020
1 parent 263be8e commit 62da7ab
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 18 deletions.
16 changes: 15 additions & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
Expand All @@ -23,18 +23,32 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
if: matrix.os != 'windows-latest'
run: |
python -m pip install --upgrade pip
pip install flake8 setuptools wheel twine
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Install dependencies on Windows
if: matrix.os == 'windows-latest'
run: |
python -m pip install --upgrade pip
pip install flake8 setuptools wheel twine
if (Test-Path -Path '.\requirements.txt' -PathType Leaf) {pip install -r requirements.txt}
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Build dist and test with unittest
if: matrix.os != 'windows-latest'
run: |
python setup.py sdist bdist_wheel
pip install dist/*.whl
python -m unittest
- name: Build dist and test with unittest on Windows
if: matrix.os == 'windows-latest'
run: |
python setup.py sdist bdist_wheel
pip install (Get-ChildItem dist/*.whl)
python -m unittest
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ VizTracer generates HTML report for flamegraph using [d3-flamegraph](https://git
* Optional function filter to ignore functions you are not interested
* Customize events to log and track data through time
* Stand alone HTML report with powerful front-end, or chrome-compatible json
* Works on Linux/MacOS/Windows

## Install

Expand Down
2 changes: 1 addition & 1 deletion docs/source/installation.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Installation
============

VizTracer requires python 3.6+. No other dependency. For now, VizTracer only supports CPython + Linux/MacOS.
VizTracer requires python 3.6+ and can work on Linux/MacOs/Windows. No other dependency. For now, VizTracer only supports CPython.

The prefered way to install VizTracer is via pip

Expand Down
78 changes: 67 additions & 11 deletions src/viztracer/modules/snaptrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
#include <Python.h>
#include <frameobject.h>
#include <time.h>
#if _WIN32
#include <windows.h>
#elif __APPLE
#include <pthread.h>

#if __APPLE__
#else
#include <pthread.h>
#include <sys/syscall.h>
#endif

Expand All @@ -34,7 +36,12 @@ static void snaptrace_threaddestructor(void* key);
static struct ThreadInfo* snaptrace_createthreadinfo(void);

// the key is used to locate thread specific info
#if _WIN32
DWORD dwTlsIndex;
LARGE_INTEGER qpc_freq = {0};
#else
static pthread_key_t thread_key = 0;
#endif
// We need to ignore the first events until we get an entry
int collecting = 0;
unsigned long total_entries = 0;
Expand Down Expand Up @@ -113,11 +120,30 @@ static void Print_Py(PyObject* o)
printf("%s\n", PyUnicode_AsUTF8(PyObject_Repr(o)));
}

static struct ThreadInfo* get_thread_info()
{
struct ThreadInfo* info = NULL;
#if _WIN32
info = TlsGetValue(dwTlsIndex);
#else
info = pthread_getspecific(thread_key);
#endif
return info;
}

static inline double get_ts()
{
#if _WIN32
LARGE_INTEGER counter = {0};
QueryPerformanceCounter(&counter);
counter.QuadPart *= 1000000000LL;
counter.QuadPart /= qpc_freq.QuadPart;
return (double) counter.QuadPart;
#else
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return ((double)t.tv_sec * 1e9 + t.tv_nsec);
#endif
}

static inline struct EventNode* get_next_node()
Expand Down Expand Up @@ -152,9 +178,22 @@ static void verbose_printf(int v, const char* fmt, ...)
static inline int startswith(const char* target, const char* prefix)
{
while(*target != 0 && *prefix != 0) {
#if _WIN32
// Windows path has double slashes and case-insensitive
if (*prefix == '\\' && prefix[-1] == '\\') {
prefix++;
}
if (*target == '\\' && target[-1] == '\\') {
target++;
}
if (*target != *prefix && *target != *prefix - ('a'-'A') && *target != *prefix + ('a'-'A')) {
return 0;
}
#else
if (*target != *prefix) {
return 0;
}
#endif
target++;
prefix++;
}
Expand Down Expand Up @@ -200,7 +239,7 @@ snaptrace_tracefunc(PyObject* obj, PyFrameObject* frame, int what, PyObject* arg
if (what == PyTrace_CALL || what == PyTrace_RETURN ||
(!CHECK_FLAG(check_flags, SNAPTRACE_IGNORE_C_FUNCTION) && (what == PyTrace_C_CALL || what == PyTrace_C_RETURN || what == PyTrace_C_EXCEPTION))) {
struct EventNode* node = NULL;
struct ThreadInfo* info = pthread_getspecific(thread_key);
struct ThreadInfo* info = get_thread_info();

int is_call = (what == PyTrace_CALL || what == PyTrace_C_CALL);
int is_return = (what == PyTrace_RETURN || what == PyTrace_C_RETURN || what == PyTrace_C_EXCEPTION);
Expand Down Expand Up @@ -236,7 +275,6 @@ snaptrace_tracefunc(PyObject* obj, PyFrameObject* frame, int what, PyObject* arg
}
} else if (is_python && is_call) {
PyObject* file_name = frame->f_code->co_filename;
//Print_Py(file_name);
if (lib_file_path && startswith(PyUnicode_AsUTF8(file_name), lib_file_path)) {
info->ignore_stack_depth += 1;
return 0;
Expand Down Expand Up @@ -393,7 +431,7 @@ snaptrace_stop(PyObject* self, PyObject* args)
{
PyEval_SetProfile(NULL, NULL);
if (collecting == 1) {
struct ThreadInfo* info = pthread_getspecific(thread_key);
struct ThreadInfo* info = get_thread_info();
snaptrace_threaddestructor(info);
}

Expand All @@ -404,7 +442,7 @@ static PyObject*
snaptrace_pause(PyObject* self, PyObject* args)
{
if (collecting) {
struct ThreadInfo* info = pthread_getspecific(thread_key);
struct ThreadInfo* info = get_thread_info();
if (info) {
info->paused += 1;
}
Expand All @@ -417,7 +455,7 @@ static PyObject*
snaptrace_resume(PyObject* self, PyObject* args)
{
if (collecting) {
struct ThreadInfo* info = pthread_getspecific(thread_key);
struct ThreadInfo* info = get_thread_info();
if (info && info->paused > 0) {
info->paused -= 1;
}
Expand All @@ -431,7 +469,11 @@ snaptrace_load(PyObject* self, PyObject* args)
{
PyObject* lst = PyList_New(0);
struct EventNode* curr = buffer_head;
#if _WIN32
PyObject* pid = PyLong_FromLong(GetCurrentProcessId());
#else
PyObject* pid = PyLong_FromLong(getpid());
#endif
PyObject* cat_fee = PyUnicode_FromString("FEE");
PyObject* cat_instant = PyUnicode_FromString("INSTANT");
PyObject* ph_B = PyUnicode_FromString("B");
Expand Down Expand Up @@ -685,7 +727,7 @@ snaptrace_addinstant(PyObject* self, PyObject* args)
PyObject* name = NULL;
PyObject* instant_args = NULL;
PyObject* scope = NULL;
struct ThreadInfo* info = pthread_getspecific(thread_key);
struct ThreadInfo* info = get_thread_info();
struct EventNode* node = NULL;
if (!PyArg_ParseTuple(args, "OOO", &name, &instant_args, &scope)) {
printf("Error when parsing arguments!\n");
Expand All @@ -711,7 +753,7 @@ snaptrace_addcounter(PyObject* self, PyObject* args)
{
PyObject* name = NULL;
PyObject* counter_args = NULL;
struct ThreadInfo* info = pthread_getspecific(thread_key);
struct ThreadInfo* info = get_thread_info();
struct EventNode* node = NULL;
if (!PyArg_ParseTuple(args, "OO", &name, &counter_args)) {
printf("Error when parsing arguments!\n");
Expand All @@ -737,7 +779,7 @@ snaptrace_addobject(PyObject* self, PyObject* args)
PyObject* id = NULL;
PyObject* name = NULL;
PyObject* object_args = NULL;
struct ThreadInfo* info = pthread_getspecific(thread_key);
struct ThreadInfo* info = get_thread_info();
struct EventNode* node = NULL;
if (!PyArg_ParseTuple(args, "OOOO", &ph, &id, &name, &object_args)) {
printf("Error when parsing arguments!\n");
Expand All @@ -763,13 +805,19 @@ snaptrace_addobject(PyObject* self, PyObject* args)
static struct ThreadInfo* snaptrace_createthreadinfo(void) {
struct ThreadInfo* info = calloc(1, sizeof(struct ThreadInfo));

#if __APPLE__
#if _WIN32
info->tid = GetCurrentThreadId();
#elif __APPLE__
info->tid = pthread_threadid_np(NULL, NULL);
#else
info->tid = syscall(SYS_gettid);
#endif

#if _WIN32
TlsSetValue(dwTlsIndex, info);
#else
pthread_setspecific(thread_key, info);
#endif

return info;
}
Expand All @@ -794,10 +842,18 @@ PyInit_snaptrace(void)
buffer_head->prev = NULL;
buffer_tail = buffer_head;
collecting = 0;
#if _WIN32
if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) {
printf("Error on TLS!\n");
exit(-1);
}
QueryPerformanceFrequency(&qpc_freq);
#else
if (pthread_key_create(&thread_key, snaptrace_threaddestructor)) {
perror("Failed to create Tss_Key");
exit(-1);
}
#endif
snaptrace_createthreadinfo();

thread_module = PyImport_ImportModule("threading");
Expand Down
4 changes: 2 additions & 2 deletions src/viztracer/report_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def generate_report(self):
sub = {}
with open(os.path.join(os.path.dirname(__file__), "html/trace_viewer_embedder.html")) as f:
tmpl = f.read()
with open(os.path.join(os.path.dirname(__file__), "html/trace_viewer_full.html")) as f:
with open(os.path.join(os.path.dirname(__file__), "html/trace_viewer_full.html"), encoding="utf-8") as f:
sub["trace_viewer_full"] = f.read()
sub["json_data"] = self.generate_json()

Expand All @@ -70,5 +70,5 @@ def generate_report(self):
return Template(tmpl).substitute(sub)

def save(self, output_file="result.html"):
with open(output_file, "w") as f:
with open(output_file, "w", encoding="utf-8") as f:
f.write(self.generate_report())
2 changes: 1 addition & 1 deletion src/viztracer/viztracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def save(self, output_file=None, save_flamegraph=False):
output_file = ".".join(output_file_parts)
file_type = output_file.split(".")[-1]
if file_type == "html":
with open(output_file, "w") as f:
with open(output_file, "w", encoding="utf-8") as f:
f.write(self.generate_report())
elif file_type == "json":
data = self.generate_json(allow_binary=True)
Expand Down
2 changes: 0 additions & 2 deletions tests/test_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ def test_c_load(self):
fib(5)
tracer.stop()
tracer.parse()
with open("result.html", "w") as f:
f.write(tracer.generate_report())

def test_c_run_after_clear(self):
tracer = _VizTracer(tracer="c")
Expand Down

0 comments on commit 62da7ab

Please sign in to comment.