/src/tarantool/third_party/luajit/src/lib_misc.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | ** Miscellaneous Lua extensions library. |
3 | | ** |
4 | | ** Major portions taken verbatim or adapted from the LuaVela interpreter. |
5 | | ** Copyright (C) 2015-2019 IPONWEB Ltd. |
6 | | */ |
7 | | |
8 | | #define lib_misc_c |
9 | | #define LUA_LIB |
10 | | |
11 | | #include "lua.h" |
12 | | #include "lmisclib.h" |
13 | | #include "lauxlib.h" |
14 | | |
15 | | #include "lj_obj.h" |
16 | | #include "lj_str.h" |
17 | | #include "lj_tab.h" |
18 | | #include "lj_lib.h" |
19 | | #include "lj_gc.h" |
20 | | #include "lj_err.h" |
21 | | |
22 | | #include "lj_memprof.h" |
23 | | |
24 | | #include <errno.h> |
25 | | #include <fcntl.h> |
26 | | #if !LJ_TARGET_WINDOWS |
27 | | #include <unistd.h> |
28 | | #endif |
29 | | |
30 | | /* ------------------------------------------------------------------------ */ |
31 | | |
32 | | static LJ_AINLINE void setnumfield(struct lua_State *L, GCtab *t, |
33 | | const char *name, int64_t val) |
34 | 0 | { |
35 | 0 | setnumV(lj_tab_setstr(L, t, lj_str_newz(L, name)), (double)val); |
36 | 0 | } |
37 | | |
38 | | #define LJLIB_MODULE_misc |
39 | | |
40 | | LJLIB_CF(misc_getmetrics) |
41 | 0 | { |
42 | 0 | struct luam_Metrics metrics; |
43 | 0 | GCtab *m; |
44 | |
|
45 | 0 | lua_createtable(L, 0, 19); |
46 | 0 | m = tabV(L->top - 1); |
47 | | |
48 | 0 | luaM_metrics(L, &metrics); |
49 | |
|
50 | 0 | setnumfield(L, m, "strhash_hit", metrics.strhash_hit); |
51 | 0 | setnumfield(L, m, "strhash_miss", metrics.strhash_miss); |
52 | |
|
53 | 0 | setnumfield(L, m, "gc_strnum", metrics.gc_strnum); |
54 | 0 | setnumfield(L, m, "gc_tabnum", metrics.gc_tabnum); |
55 | 0 | setnumfield(L, m, "gc_udatanum", metrics.gc_udatanum); |
56 | 0 | setnumfield(L, m, "gc_cdatanum", metrics.gc_cdatanum); |
57 | |
|
58 | 0 | setnumfield(L, m, "gc_total", metrics.gc_total); |
59 | 0 | setnumfield(L, m, "gc_freed", metrics.gc_freed); |
60 | 0 | setnumfield(L, m, "gc_allocated", metrics.gc_allocated); |
61 | |
|
62 | 0 | setnumfield(L, m, "gc_steps_pause", metrics.gc_steps_pause); |
63 | 0 | setnumfield(L, m, "gc_steps_propagate", metrics.gc_steps_propagate); |
64 | 0 | setnumfield(L, m, "gc_steps_atomic", metrics.gc_steps_atomic); |
65 | 0 | setnumfield(L, m, "gc_steps_sweepstring", metrics.gc_steps_sweepstring); |
66 | 0 | setnumfield(L, m, "gc_steps_sweep", metrics.gc_steps_sweep); |
67 | 0 | setnumfield(L, m, "gc_steps_finalize", metrics.gc_steps_finalize); |
68 | |
|
69 | 0 | setnumfield(L, m, "jit_snap_restore", metrics.jit_snap_restore); |
70 | 0 | setnumfield(L, m, "jit_trace_abort", metrics.jit_trace_abort); |
71 | 0 | setnumfield(L, m, "jit_mcode_size", metrics.jit_mcode_size); |
72 | 0 | setnumfield(L, m, "jit_trace_num", metrics.jit_trace_num); |
73 | |
|
74 | 0 | return 1; |
75 | 0 | } |
76 | | |
77 | | /* ------------------------------------------------------------------------ */ |
78 | | |
79 | | #include "lj_libdef.h" |
80 | | |
81 | | /* --------- profile common section --------------------------------------- */ |
82 | | |
83 | | #if !LJ_TARGET_WINDOWS |
84 | | /* |
85 | | ** Yep, 8Mb. Tuned in order not to bother the platform with too often flushes. |
86 | | */ |
87 | 0 | #define STREAM_BUFFER_SIZE (8 * 1024 * 1024) |
88 | | |
89 | | /* Structure given as ctx to memprof writer and on_stop callback. */ |
90 | | struct profile_ctx { |
91 | | /* Output file descriptor for data. */ |
92 | | int fd; |
93 | | /* Profiled global_State for lj_mem_free at on_stop callback. */ |
94 | | global_State *g; |
95 | | /* Buffer for data. */ |
96 | | uint8_t buf[STREAM_BUFFER_SIZE]; |
97 | | }; |
98 | | |
99 | | /* |
100 | | ** Default buffer writer function. |
101 | | ** Just call write to the corresponding descriptor. |
102 | | */ |
103 | | static size_t buffer_writer_default(const void **buf_addr, size_t len, |
104 | | void *opt) |
105 | 0 | { |
106 | 0 | struct profile_ctx *ctx = opt; |
107 | 0 | const int fd = ctx->fd; |
108 | 0 | const void * const buf_start = *buf_addr; |
109 | 0 | const void *data = *buf_addr; |
110 | 0 | size_t write_total = 0; |
111 | |
|
112 | 0 | lj_assertX(len <= STREAM_BUFFER_SIZE, "stream buffer overflow"); |
113 | | |
114 | 0 | for (;;) { |
115 | 0 | const ssize_t written = write(fd, data, len - write_total); |
116 | |
|
117 | 0 | if (LJ_UNLIKELY(written == -1)) { |
118 | | /* Re-tries write in case of EINTR. */ |
119 | 0 | if (errno != EINTR) { |
120 | | /* Will be freed as whole chunk later. */ |
121 | 0 | *buf_addr = NULL; |
122 | 0 | return write_total; |
123 | 0 | } |
124 | | |
125 | 0 | errno = 0; |
126 | 0 | continue; |
127 | 0 | } |
128 | | |
129 | 0 | write_total += written; |
130 | 0 | lj_assertX(write_total <= len, "invalid stream buffer write"); |
131 | | |
132 | 0 | if (write_total == len) |
133 | 0 | break; |
134 | | |
135 | 0 | data = (uint8_t *)data + (ptrdiff_t)written; |
136 | 0 | } |
137 | | |
138 | 0 | *buf_addr = buf_start; |
139 | 0 | return write_total; |
140 | 0 | } |
141 | | |
142 | | /* Default on stop callback. Just close the corresponding descriptor. */ |
143 | | static int on_stop_cb_default(void *opt, uint8_t *buf) |
144 | 0 | { |
145 | 0 | struct profile_ctx *ctx = NULL; |
146 | 0 | int fd = 0; |
147 | |
|
148 | 0 | if (opt == NULL) { |
149 | | /* Nothing to do. */ |
150 | 0 | return 0; |
151 | 0 | } |
152 | | |
153 | 0 | ctx = opt; |
154 | 0 | fd = ctx->fd; |
155 | 0 | UNUSED(buf); |
156 | 0 | lj_mem_free(ctx->g, ctx, sizeof(*ctx)); |
157 | 0 | return close(fd); |
158 | 0 | } |
159 | | |
160 | | /* ----- misc.sysprof module ---------------------------------------------- */ |
161 | | |
162 | | #define LJLIB_MODULE_misc_sysprof |
163 | | |
164 | | /* The default profiling interval equals to 11 ms. */ |
165 | 0 | #define SYSPROF_DEFAULT_INTERVAL 10 |
166 | 0 | #define SYSPROF_DEFAULT_OUTPUT "sysprof.bin" |
167 | | |
168 | 0 | static int set_output_path(const char *path, struct luam_Sysprof_Options *opt) { |
169 | 0 | struct profile_ctx *ctx = opt->ctx; |
170 | 0 | int fd = 0; |
171 | 0 | lj_assertX(path != NULL, "no file to open by sysprof"); |
172 | 0 | fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644); |
173 | 0 | if(fd == -1) { |
174 | 0 | return PROFILE_ERRIO; |
175 | 0 | } |
176 | 0 | ctx->fd = fd; |
177 | 0 | return PROFILE_SUCCESS; |
178 | 0 | } |
179 | | |
180 | 0 | static int parse_sysprof_opts(lua_State *L, struct luam_Sysprof_Options *opt, int idx) { |
181 | 0 | GCtab *options = lj_lib_checktab(L, idx); |
182 | | |
183 | | /* Get profiling mode. */ |
184 | 0 | { |
185 | 0 | const char *mode = NULL; |
186 | |
|
187 | 0 | cTValue *mode_opt = lj_tab_getstr(options, lj_str_newlit(L, "mode")); |
188 | 0 | if (!mode_opt || !tvisstr(mode_opt)) { |
189 | 0 | return PROFILE_ERRUSE; |
190 | 0 | } |
191 | | |
192 | 0 | mode = strVdata(mode_opt); |
193 | 0 | if (mode[1] != '\0') |
194 | 0 | return PROFILE_ERRUSE; |
195 | | |
196 | 0 | switch (*mode) { |
197 | 0 | case 'D': |
198 | 0 | opt->mode = LUAM_SYSPROF_DEFAULT; |
199 | 0 | break; |
200 | 0 | case 'L': |
201 | 0 | opt->mode = LUAM_SYSPROF_LEAF; |
202 | 0 | break; |
203 | 0 | case 'C': |
204 | 0 | opt->mode = LUAM_SYSPROF_CALLGRAPH; |
205 | 0 | break; |
206 | 0 | default: |
207 | 0 | return PROFILE_ERRUSE; |
208 | 0 | } |
209 | 0 | } |
210 | | |
211 | | /* Get profiling interval. */ |
212 | 0 | { |
213 | 0 | cTValue *interval = lj_tab_getstr(options, lj_str_newlit(L, "interval")); |
214 | 0 | opt->interval = SYSPROF_DEFAULT_INTERVAL; |
215 | 0 | if (interval && tvisnumber(interval)) { |
216 | 0 | int32_t signed_interval = numberVint(interval); |
217 | 0 | if (signed_interval < 1) |
218 | 0 | return PROFILE_ERRUSE; |
219 | 0 | opt->interval = signed_interval; |
220 | 0 | } |
221 | 0 | } |
222 | | |
223 | | /* Get output path. */ |
224 | 0 | if (opt->mode != LUAM_SYSPROF_DEFAULT) |
225 | 0 | { |
226 | 0 | const char *path = NULL; |
227 | 0 | struct profile_ctx *ctx = NULL; |
228 | 0 | int status = 0; |
229 | |
|
230 | 0 | cTValue *pathtv = lj_tab_getstr(options, lj_str_newlit(L, "path")); |
231 | 0 | if (!pathtv) |
232 | 0 | path = SYSPROF_DEFAULT_OUTPUT; |
233 | 0 | else if (!tvisstr(pathtv)) |
234 | 0 | return PROFILE_ERRUSE; |
235 | 0 | else |
236 | 0 | path = strVdata(pathtv); |
237 | | |
238 | 0 | ctx = lj_mem_new(L, sizeof(*ctx)); |
239 | 0 | ctx->g = G(L); |
240 | 0 | opt->ctx = ctx; |
241 | 0 | opt->buf = ctx->buf; |
242 | 0 | opt->len = STREAM_BUFFER_SIZE; |
243 | |
|
244 | 0 | status = set_output_path(path, opt); |
245 | 0 | if (status != PROFILE_SUCCESS) { |
246 | 0 | lj_mem_free(ctx->g, ctx, sizeof(*ctx)); |
247 | 0 | return status; |
248 | 0 | } |
249 | 0 | } |
250 | | |
251 | 0 | return PROFILE_SUCCESS; |
252 | 0 | } |
253 | | |
254 | | static int parse_options(lua_State *L, struct luam_Sysprof_Options *opt) |
255 | 0 | { |
256 | 0 | if (lua_gettop(L) != 1) |
257 | 0 | return PROFILE_ERRUSE; |
258 | | |
259 | 0 | if (!lua_istable(L, 1)) |
260 | 0 | return PROFILE_ERRUSE; |
261 | | |
262 | 0 | return parse_sysprof_opts(L, opt, 1); |
263 | 0 | } |
264 | | |
265 | | static int sysprof_error(lua_State *L, int status) |
266 | 0 | { |
267 | 0 | switch (status) { |
268 | 0 | case PROFILE_ERRUSE: |
269 | 0 | lua_pushnil(L); |
270 | 0 | lua_pushstring(L, err2msg(LJ_ERR_PROF_MISUSE)); |
271 | 0 | lua_pushinteger(L, EINVAL); |
272 | 0 | return 3; |
273 | 0 | #if LJ_HASSYSPROF |
274 | 0 | case PROFILE_ERRRUN: |
275 | 0 | lua_pushnil(L); |
276 | 0 | lua_pushstring(L, err2msg(LJ_ERR_PROF_ISRUNNING)); |
277 | 0 | lua_pushinteger(L, EINVAL); |
278 | 0 | return 3; |
279 | 0 | case PROFILE_ERRIO: |
280 | 0 | return luaL_fileresult(L, 0, NULL); |
281 | 0 | #endif |
282 | 0 | default: |
283 | 0 | lj_assertL(0, "bad sysprof error %d", status); |
284 | 0 | return 0; |
285 | 0 | } |
286 | 0 | } |
287 | | |
288 | | /* local res, err, errno = sysprof.start(options) */ |
289 | | LJLIB_CF(misc_sysprof_start) |
290 | 0 | { |
291 | 0 | int status = PROFILE_SUCCESS; |
292 | |
|
293 | 0 | struct luam_Sysprof_Options opt = {}; |
294 | |
|
295 | 0 | status = parse_options(L, &opt); |
296 | 0 | if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) |
297 | 0 | return sysprof_error(L, status); |
298 | | |
299 | 0 | status = luaM_sysprof_start(L, &opt); |
300 | 0 | if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) |
301 | | /* Allocated memory will be freed in on_stop callback. */ |
302 | 0 | return sysprof_error(L, status); |
303 | | |
304 | 0 | lua_pushboolean(L, 1); |
305 | 0 | return 1; |
306 | 0 | } |
307 | | |
308 | | /* local res, err, errno = profile.sysprof_stop() */ |
309 | | LJLIB_CF(misc_sysprof_stop) |
310 | 0 | { |
311 | 0 | int status = luaM_sysprof_stop(L); |
312 | 0 | if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) |
313 | 0 | return sysprof_error(L, status); |
314 | | |
315 | 0 | lua_pushboolean(L, 1); |
316 | 0 | return 1; |
317 | 0 | } |
318 | | |
319 | | /* local counters, err, errno = sysprof.report() */ |
320 | | LJLIB_CF(misc_sysprof_report) |
321 | 0 | { |
322 | 0 | struct luam_Sysprof_Counters counters = {}; |
323 | 0 | GCtab *data_tab = NULL; |
324 | 0 | GCtab *count_tab = NULL; |
325 | |
|
326 | 0 | int status = luaM_sysprof_report(&counters); |
327 | 0 | if (status != PROFILE_SUCCESS) |
328 | 0 | return sysprof_error(L, status); |
329 | | |
330 | 0 | lua_createtable(L, 0, 3); |
331 | 0 | data_tab = tabV(L->top - 1); |
332 | | |
333 | 0 | setnumfield(L, data_tab, "samples", counters.samples); |
334 | |
|
335 | 0 | lua_createtable(L, 0, LJ_VMST__MAX + 1); |
336 | 0 | count_tab = tabV(L->top - 1); |
337 | | |
338 | 0 | setnumfield(L, count_tab, "INTERP", counters.vmst_interp); |
339 | 0 | setnumfield(L, count_tab, "LFUNC", counters.vmst_lfunc); |
340 | 0 | setnumfield(L, count_tab, "FFUNC", counters.vmst_ffunc); |
341 | 0 | setnumfield(L, count_tab, "CFUNC", counters.vmst_cfunc); |
342 | 0 | setnumfield(L, count_tab, "GC", counters.vmst_gc); |
343 | 0 | setnumfield(L, count_tab, "EXIT", counters.vmst_exit); |
344 | 0 | setnumfield(L, count_tab, "RECORD", counters.vmst_record); |
345 | 0 | setnumfield(L, count_tab, "OPT", counters.vmst_opt); |
346 | 0 | setnumfield(L, count_tab, "ASM", counters.vmst_asm); |
347 | 0 | setnumfield(L, count_tab, "TRACE", counters.vmst_trace); |
348 | |
|
349 | 0 | lua_setfield(L, -2, "vmstate"); |
350 | |
|
351 | 0 | return 1; |
352 | 0 | } |
353 | | |
354 | | /* ----- misc.memprof module ---------------------------------------------- */ |
355 | | |
356 | | #define LJLIB_MODULE_misc_memprof |
357 | | |
358 | | /* local started, err, errno = misc.memprof.start(fname) */ |
359 | | LJLIB_CF(misc_memprof_start) |
360 | 0 | { |
361 | 0 | struct lj_memprof_options opt = {0}; |
362 | 0 | const char *fname = strdata(lj_lib_checkstr(L, 1)); |
363 | 0 | struct profile_ctx *ctx; |
364 | 0 | int memprof_status; |
365 | | |
366 | | /* |
367 | | ** FIXME: more elegant solution with ctx. |
368 | | ** Throws in case of OOM. |
369 | | */ |
370 | 0 | ctx = lj_mem_new(L, sizeof(*ctx)); |
371 | 0 | opt.ctx = ctx; |
372 | 0 | opt.buf = ctx->buf; |
373 | 0 | opt.writer = buffer_writer_default; |
374 | 0 | opt.on_stop = on_stop_cb_default; |
375 | 0 | opt.len = STREAM_BUFFER_SIZE; |
376 | |
|
377 | 0 | ctx->g = G(L); |
378 | 0 | ctx->fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, 0644); |
379 | |
|
380 | 0 | if (ctx->fd == -1) { |
381 | 0 | lj_mem_free(ctx->g, ctx, sizeof(*ctx)); |
382 | 0 | return luaL_fileresult(L, 0, fname); |
383 | 0 | } |
384 | | |
385 | 0 | memprof_status = lj_memprof_start(L, &opt); |
386 | |
|
387 | 0 | if (LJ_UNLIKELY(memprof_status != PROFILE_SUCCESS)) { |
388 | 0 | switch (memprof_status) { |
389 | 0 | case PROFILE_ERRUSE: |
390 | 0 | lua_pushnil(L); |
391 | 0 | lua_pushstring(L, err2msg(LJ_ERR_PROF_MISUSE)); |
392 | 0 | lua_pushinteger(L, EINVAL); |
393 | 0 | return 3; |
394 | 0 | #if LJ_HASMEMPROF |
395 | 0 | case PROFILE_ERRRUN: |
396 | 0 | lua_pushnil(L); |
397 | 0 | lua_pushstring(L, err2msg(LJ_ERR_PROF_ISRUNNING)); |
398 | 0 | lua_pushinteger(L, EINVAL); |
399 | 0 | return 3; |
400 | 0 | case PROFILE_ERRIO: |
401 | 0 | return luaL_fileresult(L, 0, fname); |
402 | 0 | #endif |
403 | 0 | default: |
404 | 0 | lj_assertL(0, "bad memprof error %d", memprof_status); |
405 | 0 | return 0; |
406 | 0 | } |
407 | 0 | } |
408 | 0 | lua_pushboolean(L, 1); |
409 | 0 | return 1; |
410 | 0 | } |
411 | | |
412 | | /* local stopped, err, errno = misc.memprof.stop() */ |
413 | | LJLIB_CF(misc_memprof_stop) |
414 | 0 | { |
415 | 0 | int status = lj_memprof_stop(L); |
416 | 0 | if (status != PROFILE_SUCCESS) { |
417 | 0 | switch (status) { |
418 | 0 | case PROFILE_ERRUSE: |
419 | 0 | lua_pushnil(L); |
420 | 0 | lua_pushstring(L, err2msg(LJ_ERR_PROF_MISUSE)); |
421 | 0 | lua_pushinteger(L, EINVAL); |
422 | 0 | return 3; |
423 | 0 | #if LJ_HASMEMPROF |
424 | 0 | case PROFILE_ERRRUN: |
425 | 0 | lua_pushnil(L); |
426 | 0 | lua_pushstring(L, err2msg(LJ_ERR_PROF_NOTRUNNING)); |
427 | 0 | lua_pushinteger(L, EINVAL); |
428 | 0 | return 3; |
429 | 0 | case PROFILE_ERRIO: |
430 | 0 | return luaL_fileresult(L, 0, NULL); |
431 | 0 | #endif |
432 | 0 | default: |
433 | 0 | lj_assertL(0, "bad memprof error %d", status); |
434 | 0 | return 0; |
435 | 0 | } |
436 | 0 | } |
437 | 0 | lua_pushboolean(L, 1); |
438 | 0 | return 1; |
439 | 0 | } |
440 | | #endif /* !LJ_TARGET_WINDOWS */ |
441 | | |
442 | | #include "lj_libdef.h" |
443 | | |
444 | | /* ------------------------------------------------------------------------ */ |
445 | | |
446 | | LUALIB_API int luaopen_misc(struct lua_State *L) |
447 | 2.59k | { |
448 | 2.59k | #if !LJ_TARGET_WINDOWS |
449 | 2.59k | luaM_sysprof_set_writer(buffer_writer_default); |
450 | 2.59k | luaM_sysprof_set_on_stop(on_stop_cb_default); |
451 | | /* |
452 | | ** XXX: Passing NULL to the backtracer configuration handle sets the default |
453 | | ** backtracing function. |
454 | | */ |
455 | 2.59k | luaM_sysprof_set_backtracer(NULL); |
456 | 2.59k | #endif /* !LJ_TARGET_WINDOWS */ |
457 | | |
458 | 2.59k | LJ_LIB_REG(L, LUAM_MISCLIBNAME, misc); |
459 | 2.59k | #if !LJ_TARGET_WINDOWS |
460 | 2.59k | LJ_LIB_REG(L, LUAM_MISCLIBNAME ".memprof", misc_memprof); |
461 | 2.59k | LJ_LIB_REG(L, LUAM_MISCLIBNAME ".sysprof", misc_sysprof); |
462 | 2.59k | #endif /* !LJ_TARGET_WINDOWS */ |
463 | 2.59k | return 1; |
464 | 2.59k | } |