1
// SPDX-License-Identifier: LGPL-2.1-or-later
3
/***************************************************************************
4
* Copyright (c) 2023 Werner Mayer <wmayer[at]users.sourceforge.net> *
6
* This file is part of FreeCAD. *
8
* FreeCAD is free software: you can redistribute it and/or modify it *
9
* under the terms of the GNU Lesser General Public License as *
10
* published by the Free Software Foundation, either version 2.1 of the *
11
* License, or (at your option) any later version. *
13
* FreeCAD is distributed in the hope that it will be useful, but *
14
* WITHOUT ANY WARRANTY; without even the implied warranty of *
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
16
* Lesser General Public License for more details. *
18
* You should have received a copy of the GNU Lesser General Public *
19
* License along with FreeCAD. If not, see *
20
* <https://www.gnu.org/licenses/>. *
22
**************************************************************************/
24
#include "PreCompiled.h"
26
#include <QApplication>
29
#include <QGuiApplication>
32
#include "PythonTracing.h"
33
#include <App/Application.h>
34
#include <Base/Interpreter.h>
38
struct PythonTracing::Private
41
int timeout{200}; //NOLINT
44
static int profilerInterval;
45
static bool profilerDisabled;
50
int PythonTracing::Private::profilerInterval = 200;
51
bool PythonTracing::Private::profilerDisabled = false;
54
PythonTracing::PythonTracing()
55
: d{std::make_unique<Private>()}
59
PythonTracing::~PythonTracing() = default;
61
bool PythonTracing::isActive() const
66
void PythonTracing::activate()
69
setPythonTraceEnabled(true);
72
void PythonTracing::deactivate()
75
setPythonTraceEnabled(false);
78
void PythonTracing::fetchFromSettings()
80
const long defaultTimeout = 200;
82
auto parameterGroup = App::GetApplication().GetParameterGroupByPath(
83
"User parameter:BaseApp/Preferences/PythonConsole");
84
int interval = static_cast<int>(parameterGroup->GetInt("ProfilerInterval", defaultTimeout));
88
bool PythonTracing::interrupt() const
98
void PythonTracing::setTimeout(int ms)
103
int PythonTracing::timeout() const
108
void PythonTracing::setPythonTraceEnabled(bool enabled) const
110
Py_tracefunc trace = nullptr;
111
if (enabled && timeout() > 0) {
112
Private::profilerInterval = timeout();
113
trace = &tracer_callback;
116
Private::profilerDisabled = true;
119
Base::PyGILStateLocker lock;
120
PyEval_SetTrace(trace, nullptr);
124
* This callback ensures that Qt runs its event loop (i.e. updates the GUI, processes keyboard and
125
* mouse events, etc.) at least every 200 ms, even when there is long-running Python code on the
126
* main thread. It is registered as the global trace function of the Python environment.
128
int PythonTracing::tracer_callback(PyObject *obj, PyFrameObject *frame, int what, PyObject *arg)
135
static QTime lastCalledTime = QTime::currentTime();
136
QTime currTime = QTime::currentTime();
138
// if previous code object was executed
139
if (Private::profilerDisabled) {
140
Private::profilerDisabled = false;
141
lastCalledTime = currTime;
144
int ms = lastCalledTime.msecsTo(currTime);
146
if (ms >= Private::profilerInterval) {
147
lastCalledTime = currTime;
148
QGuiApplication::processEvents();
154
// ------------------------------------------------------------------------------------------------
156
PythonTracingLocker::PythonTracingLocker(PythonTracing& trace)
162
PythonTracingLocker::~PythonTracingLocker()
167
// ------------------------------------------------------------------------------------------------
169
PythonTracingWatcher::PythonTracingWatcher(QObject* parent)
172
qApp->installEventFilter(this);
175
PythonTracingWatcher::~PythonTracingWatcher()
177
qApp->removeEventFilter(this);
180
bool PythonTracingWatcher::eventFilter(QObject* object, QEvent* event)
182
if (event && event->type() == QEvent::ShortcutOverride) {
183
auto kevent = static_cast<QKeyEvent*>(event);
184
if (kevent->key() == Qt::Key_C && kevent->modifiers() == Qt::ControlModifier) {
185
if (trace.interrupt()) {
191
return QObject::eventFilter(object, event);
194
PythonTracing& PythonTracingWatcher::getTrace()
196
trace.fetchFromSettings();