-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathhawktracer_core_python.cpp
262 lines (217 loc) · 7.14 KB
/
hawktracer_core_python.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#include "hawktracer.h"
#include <Python.h>
static PyMethodDef* trace_method = NULL;
static HT_Boolean tracing_enabled = HT_FALSE;
static HT_FileDumpListener* file_listener;
static HT_Boolean is_initialized = HT_FALSE;
static PyObject* tracepoint_map = NULL;
static PyObject*
ht_python_core_trace_function(PyObject* function, PyObject *args);
static PyMethodDef ht_python_core_trace_function_md = {"trace_function", ht_python_core_trace_function, METH_VARARGS, ""};
static uintptr_t
ht_python_core_get_label_address(PyObject* function_name_attr)
{
if (!PyDict_Contains(tracepoint_map, function_name_attr))
{
/* TODO: improve cached string feature so it can handle custom pointers.
Then we can simply push all the mapping to a listener if it's registered too late. */
uintptr_t address = (uintptr_t)function_name_attr;
PyDict_SetItem(tracepoint_map, function_name_attr, PyLong_FromSize_t(address));
PyObject* function_name = PyUnicode_AsEncodedString(function_name_attr, "UTF-8", "strict");
HT_TIMELINE_PUSH_EVENT(ht_global_timeline_get(), HT_StringMappingEvent, address, PyBytes_AsString(function_name));
Py_DECREF(function_name);
return address;
}
else
{
return PyLong_AsSize_t(PyDict_GetItem(tracepoint_map, function_name_attr));
}
}
static PyObject*
ht_python_core_trace_function(PyObject* function, PyObject *args)
{
uintptr_t address = 0;
if (!PyCallable_Check(function))
{
Py_RETURN_NONE;
}
if (tracing_enabled)
{
PyObject* function_name_attr = PyObject_GetAttrString(function, "__name__");
if (function_name_attr)
{
address = ht_python_core_get_label_address(function_name_attr);
ht_feature_callstack_start_int(ht_global_timeline_get(), address);
Py_DECREF(function_name_attr);
}
}
PyObject* ret = PyEval_CallObject(function, args);
if (address)
{
ht_feature_callstack_stop(ht_global_timeline_get());
}
return ret;
}
static PyObject *
ht_python_core_trace(PyObject* Py_UNUSED(self), PyObject* args)
{
PyObject *traced_function;
if (!PyArg_ParseTuple(args, "O", &traced_function))
{
return NULL;
}
if (trace_method)
{
return PyCFunction_New(trace_method, traced_function);
}
else
{
Py_XINCREF(traced_function);
return traced_function;
}
}
static PyObject *
ht_python_core_enable_decorator(PyObject* Py_UNUSED(self), PyObject* Py_UNUSED(ignored))
{
if (!trace_method)
{
trace_method = &ht_python_core_trace_function_md;
}
Py_RETURN_NONE;
}
static PyObject*
ht_python_core_disable_decorator(PyObject* Py_UNUSED(self), PyObject* Py_UNUSED(ignored))
{
trace_method = NULL;
Py_RETURN_NONE;
}
static PyObject*
ht_python_core_enable_tracing(PyObject* Py_UNUSED(self), PyObject* Py_UNUSED(ignored))
{
tracing_enabled = HT_TRUE;
Py_RETURN_NONE;
}
static PyObject*
ht_python_core_disable_tracing(PyObject* Py_UNUSED(self), PyObject* Py_UNUSED(ignored))
{
tracing_enabled = HT_FALSE;
Py_RETURN_NONE;
}
static int
ht_python_core_clear_module(PyObject* m)
{
if (is_initialized)
{
ht_timeline_flush(ht_global_timeline_get());
if (file_listener)
{
ht_file_dump_listener_stop(file_listener);
}
ht_deinit();
}
Py_DECREF(tracepoint_map);
Py_CLEAR(m);
return 0;
}
static PyObject*
ht_python_core_register_file_listener(PyObject* Py_UNUSED(self), PyObject* args)
{
const char* file_path;
unsigned int buffer_size = 4096;
HT_ErrorCode err;
if (!is_initialized)
{
return PyErr_Format(PyExc_Exception, "HawkTracer library not initialized! init() must be called first.");
}
if (!PyArg_ParseTuple(args, "s|I", &file_path, &buffer_size))
{
return NULL;
}
/* TODO: check if file_listener is already defined, and if so - unregister and destroy it.
We need ht_timeline_unregister_listener to implement that.
In the future we might support multiple listeners as well.
*/
file_listener = ht_file_dump_listener_register(ht_global_timeline_get(), file_path, buffer_size, &err);
if (!file_listener)
{
return PyErr_Format(PyExc_Exception, "Unable to initalize file listener. Error code: %d", err);
}
Py_RETURN_NONE;
}
static PyObject*
ht_python_core_init()
{
if (!is_initialized)
{
ht_init(0, NULL);
is_initialized = HT_TRUE;
}
Py_RETURN_NONE;
}
static PyMethodDef HawkTracer_methods[] =
{
{
"trace", (PyCFunction)ht_python_core_trace, METH_VARARGS,
"Attribute that determines whether the time spent in function should be traced."
},
{
"enable_trace_decorator", (PyCFunction)ht_python_core_enable_decorator, METH_NOARGS,
"Enables @trace decorator. If the decorator is not enabled, adding @trace to the function does not "
"have any effect, and doesn't introduce any overhead. Code instrumentation can also be enabled "
"through the HT_PYTHON_TRACE_ENABLED environment variable."
},
{
"disable_trace_decorator", (PyCFunction)ht_python_core_disable_decorator, METH_NOARGS,
"Disables @trace decorator. See enable_trace_decorator for details."
},
{
"enable_tracing", (PyCFunction)ht_python_core_enable_tracing, METH_NOARGS,
"Enables tracing. See disable_tracing for details."
},
{
"disable_tracing", (PyCFunction)ht_python_core_disable_tracing, METH_NOARGS,
"Temporarily disables tracing. The feature is useful if you want to instrument functions in your codebase, "
"but want to disable the tracing temporarily in a specific place in the code."
},
{
"init", (PyCFunction)ht_python_core_init, METH_NOARGS,
"Initializes HawkTracer system. This function should not be called if you already initialized "
"HawkTracer system in your environment, e.g. in your native stack."
},
{
"register_file_listener", (PyCFunction)ht_python_core_register_file_listener, METH_VARARGS,
"Registers file listener for tracepoints. Calling this function cause all tracepoints to be "
"saved to a binary file.\n"
":param file_path: A path to a file where all the tracepoints will be saved.\n"
":param buffer_size: (optional) A size of the internal buffer (default 4096).\n"
},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef HawkTracer_Python_Core_module = {
PyModuleDef_HEAD_INIT,
"HawkTracer",
"Utils for writing custom HawkTracer clients.",
-1,
HawkTracer_methods,
NULL,
NULL,
ht_python_core_clear_module,
NULL
};
PyMODINIT_FUNC
PyInit_core(void)
{
PyObject* module = PyModule_Create(&HawkTracer_Python_Core_module);
if (module)
{
char* tp_state = getenv("HT_PYTHON_TRACE_ENABLED");
tracing_enabled = tp_state != NULL && strncmp(tp_state, "0", 1) != 0;
if (tracing_enabled)
{
ht_python_core_init();
trace_method = &ht_python_core_trace_function_md;
}
tracepoint_map = PyDict_New();
}
return module;
}