Commit 39468d9b authored by Vicent Marti's avatar Vicent Marti

Lua Tools for BCC

parent ff9f231e
Lua Tools for BCC
-----------------
This directory contains Lua tooling for [BCC](https://github.com/iovisor/bcc)
(the BPF Compiler Collection).
BCC is a toolkit for creating userspace and kernel tracing programs. By
default, it comes with a library `libbcc`, some example tooling and a Python
frontend for the library.
Here we present an alternate frontend for `libbcc` implemented in LuaJIT. This
lets you write the userspace part of your tracer in Lua instead of Python.
Since LuaJIT is a JIT compiled language, tracers implemented in `bcc-lua`
exhibit significantly reduced overhead compared to their Python equivalents.
This is particularly noticeable in tracers that actively use the table APIs to
get information from the kernel.
If your tracer makes extensive use of `BPF_MAP_TYPE_PERF_EVENT_ARRAY` or
`BPF_MAP_TYPE_HASH`, you may find the performance characteristics of this
implementation very appealing, as LuaJIT can compile to native code a lot of
the callchain to process the events, and this wrapper has been designed to
benefit from such JIT compilation.
## Quickstart Guide
The following instructions assume Ubuntu 14.04 LTS.
1. Install a **very new kernel**. It has to be new and shiny for this to work. 4.3+
```
VER=4.4.2-040402
PREFIX=http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.4.2-wily/
REL=201602171633
wget ${PREFIX}/linux-headers-${VER}-generic_${VER}.${REL}_amd64.deb
wget ${PREFIX}/linux-headers-${VER}_${VER}.${REL}_all.deb
wget ${PREFIX}/linux-image-${VER}-generic_${VER}.${REL}_amd64.deb
sudo dpkg -i linux-*${VER}.${REL}*.deb
```
2. Install the `libbcc` binary packages and `luajit`
```
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D4284CDD
echo "deb http://52.8.15.63/apt trusty main" | sudo tee /etc/apt/sources.list.d/iovisor.list
sudo apt-get update
sudo apt-get install libbcc luajit
```
3. Test one of the examples to ensure `libbcc` is properly installed
```
sudo ./bcc-probe examples/lua/task_switch.lua
```
#!/usr/bin/env luajit
--[[
Copyright 2016 GitHub, Inc
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.
]]
package.path = "./?/init.lua;"..package.path
local BCC = require("bcc")
local BPF = BCC.BPF
BPF.script_root(arg[1])
local utils = {
argparse = require("bcc.vendor.argparse"),
posix = require("bcc.vendor.posix"),
sym = BCC.sym
}
local tracefile = table.remove(arg, 1)
local command = require(tracefile:gsub("%.lua", ""))
local res, err = pcall(command, BPF, utils)
if not res then
io.stderr:write("[ERROR] "..err.."\n")
end
BPF.cleanup_probes()
--[[
Copyright 2016 GitHub, Inc
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.
]]
local ffi = require("ffi")
local libbcc = require("bcc.libbcc")
local TracerPipe = require("bcc.tracerpipe")
local Table = require("bcc.table")
local LD = require("bcc.ld")
local Bpf = class("BPF")
Bpf.static.open_kprobes = {}
Bpf.static.open_uprobes = {}
Bpf.static.process_symbols = {}
Bpf.static.KPROBE_LIMIT = 1000
Bpf.static.tracer_pipe = nil
function Bpf.static.check_probe_quota(n)
local cur = table.count(Bpf.static.open_kprobes) + table.count(Bpf.static.open_uprobes)
assert(cur + n <= Bpf.static.KPROBE_LIMIT, "number of open probes would exceed quota")
end
function Bpf.static.cleanup_probes()
local function detach_all(probe_type, all_probes)
for key, probe in pairs(all_probes) do
libbcc.perf_reader_free(probe)
if type(key) == "string" then
local desc = string.format("-:%s/%s", probe_type, key)
-- TODO: why so noisy?
--libbcc.bpf_detach_kprobe(desc)
end
all_probes[key] = nil
end
end
detach_all("kprobes", Bpf.static.open_kprobes)
detach_all("uprobes", Bpf.static.open_uprobes)
if Bpf.static.tracer_pipe ~= nil then
Bpf.static.tracer_pipe:close()
end
end
function Bpf.static.num_open_kprobes()
return table.count(Bpf.static.open_kprobes)
end
function Bpf.static.usymaddr(pid, addr, refresh)
local proc_sym = Bpf.static.process_symbols[pid]
if proc_sym == nil then
proc_sym = ProcSymbols(pid)
Bpf.static.process_symbols[pid] = proc_sym
elseif refresh then
proc_sym.refresh()
end
return proc_sym.decode_addr(addr)
end
Bpf.static.SCRIPT_ROOT = "./"
function Bpf.static.script_root(root)
local dir, file = root:match'(.*/)(.*)'
Bpf.static.SCRIPT_ROOT = dir or "./"
return Bpf
end
local function _find_file(script_root, filename)
if filename == nil then
return nil
end
if os.exists(filename) then
return filename
end
if not filename:starts("/") then
filename = script_root .. filename
if os.exists(filename) then
return filename
end
end
assert(nil, "failed to find file "..filename.." (root="..script_root..")")
end
function Bpf:initialize(args)
self.do_debug = args.debug or false
self.funcs = {}
self.tables = {}
if args.text then
log.info("\n%s\n", args.text)
self.module = libbcc.bpf_module_create_c_from_string(args.text, args.llvm_debug or 0, nil, 0)
elseif args.src_file then
local src = _find_file(Bpf.SCRIPT_ROOT, args.src_file)
if src:ends(".b") then
local hdr = _find_file(Bpf.SCRIPT_ROOT, args.hdr_file)
self.module = libbcc.bpf_module_create_b(src, hdr, args.llvm_debug or 0)
else
self.module = libbcc.bpf_module_create_c(src, args.llvm_debug or 0, nil, 0)
end
end
assert(self.module ~= nil, "failed to compile BPF module")
end
function Bpf:load_func(fn_name, prog_type)
if self.funcs[fn_name] ~= nil then
return self.funcs[fn_name]
end
assert(libbcc.bpf_function_start(self.module, fn_name), "unknown program: "..fn_name)
local fd = libbcc.bpf_prog_load(prog_type,
libbcc.bpf_function_start(self.module, fn_name),
libbcc.bpf_function_size(self.module, fn_name),
libbcc.bpf_module_license(self.module),
libbcc.bpf_module_kern_version(self.module), nil, 0)
assert(fd >= 0, "failed to load BPF program "..fn_name)
log.info("loaded %s (%d)", fn_name, fd)
local fn = {bpf=self, name=fn_name, fd=fd}
self.funcs[fn_name] = fn
return fn
end
function Bpf:attach_uprobe(args)
Bpf.check_probe_quota(1)
local path, addr = LD.check_path_symbol(args.name, args.sym, args.addr)
local fn = self:load_func(args.fn_name, 'BPF_PROG_TYPE_KPROBE')
local ptype = args.retprobe and "r" or "p"
local ev_name = string.format("%s_%s_0x%x", ptype, path:gsub("[^%a%d]", "_"), addr)
local desc = string.format("%s:uprobes/%s %s:0x%x", ptype, ev_name, path, addr)
log.info(desc)
local res = libbcc.bpf_attach_uprobe(fn.fd, ev_name, desc,
args.pid or -1,
args.cpu or 0,
args.group_fd or -1, nil, nil) -- TODO; reader callback
assert(res ~= nil, "failed to attach BPF to uprobe")
self:probe_store("uprobe", ev_name, res)
return self
end
function Bpf:attach_kprobe(args)
-- TODO: allow the caller to glob multiple functions together
Bpf.check_probe_quota(1)
local fn = self:load_func(args.fn_name, 'BPF_PROG_TYPE_KPROBE')
local event = args.event or ""
local ptype = args.retprobe and "r" or "p"
local ev_name = string.format("%s_%s", ptype, event:gsub("[%+%.]", "_"))
local desc = string.format("%s:kprobes/%s %s", ptype, ev_name, event)
log.info(desc)
local res = libbcc.bpf_attach_kprobe(fn.fd, ev_name, desc,
args.pid or -1,
args.cpu or 0,
args.group_fd or -1, nil, nil) -- TODO; reader callback
assert(res ~= nil, "failed to attach BPF to kprobe")
self:probe_store("kprobe", ev_name, res)
return self
end
function Bpf:pipe()
if Bpf.tracer_pipe == nil then
Bpf.tracer_pipe = TracerPipe:new()
end
return Bpf.tracer_pipe
end
function Bpf:get_table(name, key_type, leaf_type)
if self.tables[name] == nil then
self.tables[name] = Table(self, name, key_type, leaf_type)
end
return self.tables[name]
end
function Bpf:probe_store(t, id, reader)
if t == "kprobe" then
Bpf.open_kprobes[id] = reader
elseif t == "uprobe" then
Bpf.open_uprobes[id] = reader
else
error("unknown probe type '%s'" % t)
end
log.info("%s -> %s", id, reader)
end
function Bpf:probe_lookup(t, id)
if t == "kprobe" then
return Bpf.open_kprobes[id]
elseif t == "uprobe" then
return Bpf.open_uprobes[id]
else
return nil
end
end
function Bpf:_kprobe_array()
local kprobe_count = table.count(Bpf.open_kprobes)
local readers = ffi.new("struct perf_reader*[?]", kprobe_count)
local n = 0
for _, r in pairs(Bpf.open_kprobes) do
readers[n] = r
n = n + 1
end
assert(n == kprobe_count, "wtf")
return readers, n
end
function Bpf:kprobe_poll_loop()
local probes, probe_count = self:_kprobe_array()
return pcall(function()
while true do
libbcc.perf_reader_poll(probe_count, probes, -1)
end
end)
end
function Bpf:kprobe_poll(timeout)
local probes, probe_count = self:_kprobe_array()
libbcc.perf_reader_poll(probe_count, probes, timeout or -1)
end
return Bpf
--[[
Copyright 2016 GitHub, Inc
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.
]]
require("bcc.vendor.strict")
require("bcc.vendor.helpers")
class = require("bcc.vendor.middleclass")
return {
BPF = require("bcc.bpf"),
sym = require("bcc.sym"),
}
--[[
Copyright 2016 GitHub, Inc
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.
]]
local ffi = require("ffi")
local _find_library_cache = {}
local function _find_library(name)
if _find_library_cache[name] ~= nil then
return _find_library_cache[name]
end
local arch = ffi.arch
local abi_type = "libc6"
if ffi.abi("64bit") then
if arch == "x64" then
abi_type = abi_type .. ",x86-64"
elseif arch == "ppc" or arch == "mips" then
abi_type = abi_type .. ",64bit"
end
end
local pattern = "%s+lib" .. name:escape() .. "%.%S+ %(" .. abi_type:escape() .. ".-%) => (%S+)"
local f = assert(io.popen("/sbin/ldconfig -p"))
local path = nil
for line in f:lines() do
path = line:match(pattern)
if path then break end
end
f:close()
if path then
_find_library_cache[name] = path
end
return path
end
local _find_load_address_cache = {}
local function _find_load_address(path)
if _find_load_address_cache[path] ~= nil then
return _find_load_address_cache[path]
end
local addr = os.spawn(
[[/usr/bin/objdump -x %s | awk '$1 == "LOAD" && $3 ~ /^[0x]*$/ { print $5 }']],
path)
if addr then
_find_load_address_cache[path] = addr
end
return addr
end
local _find_symbol_cache = {}
local function _find_symbol(path, sym)
assert(path and sym)
if _find_symbol_cache[path] == nil then
_find_symbol_cache[path] = {}
end
local symbols = _find_symbol_cache[path]
if symbols[sym] ~= nil then
return symbols[sym]
end
local addr = os.spawn(
[[/usr/bin/objdump -tT %s | awk -v sym=%s '$NF == sym && $4 == ".text" { print $1; exit }']],
path, sym)
if addr then
symbols[sym] = addr
end
return addr
end
local function _check_path_symbol(name, sym, addr)
assert(name)
local path = name:sub(1,1) == "/" and name or _find_library(name)
assert(path, "could not find library "..name)
-- TODO: realpath
local load_addr = _find_load_address(path)
assert(load_addr, "could not find load address for "..path)
if addr == nil and sym ~= nil then
addr = _find_symbol(path, sym)
end
assert(addr, "could not find address of symbol "..sym)
return path, (addr - load_addr)
end
return {
check_path_symbol=_check_path_symbol,
find_symbol=_find_symbol,
find_load_address=_find_load_address,
find_library=_find_library
}
--[[
Copyright 2016 GitHub, Inc
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.
]]
local ffi = require("ffi")
ffi.cdef[[
enum bpf_prog_type {
BPF_PROG_TYPE_UNSPEC,
BPF_PROG_TYPE_SOCKET_FILTER,
BPF_PROG_TYPE_KPROBE,
BPF_PROG_TYPE_SCHED_CLS,
BPF_PROG_TYPE_SCHED_ACT,
};
int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, int max_entries);
int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags);
int bpf_lookup_elem(int fd, void *key, void *value);
int bpf_delete_elem(int fd, void *key);
int bpf_get_next_key(int fd, void *key, void *next_key);
int bpf_prog_load(enum bpf_prog_type prog_type, const struct bpf_insn *insns, int insn_len,
const char *license, unsigned kern_version, char *log_buf, unsigned log_buf_size);
int bpf_attach_socket(int sockfd, int progfd);
/* create RAW socket and bind to interface 'name' */
int bpf_open_raw_sock(const char *name);
typedef void (*perf_reader_cb)(void *cb_cookie, int pid, uint64_t callchain_num, void *callchain);
typedef void (*perf_reader_raw_cb)(void *cb_cookie, void *raw, int raw_size);
void * bpf_attach_kprobe(int progfd, const char *event, const char *event_desc,
int pid, int cpu, int group_fd, perf_reader_cb cb, void *cb_cookie);
int bpf_detach_kprobe(const char *event_desc);
void * bpf_attach_uprobe(int progfd, const char *event, const char *event_desc,
int pid, int cpu, int group_fd, perf_reader_cb cb, void *cb_cookie);
int bpf_detach_uprobe(const char *event_desc);
void * bpf_open_perf_buffer(perf_reader_raw_cb raw_cb, void *cb_cookie, int pid, int cpu);
]]
ffi.cdef[[
void * bpf_module_create_b(const char *filename, const char *proto_filename, unsigned flags);
void * bpf_module_create_c(const char *filename, unsigned flags, const char *cflags[], int ncflags);
void * bpf_module_create_c_from_string(const char *text, unsigned flags, const char *cflags[], int ncflags);
void bpf_module_destroy(void *program);
char * bpf_module_license(void *program);
unsigned bpf_module_kern_version(void *program);
size_t bpf_num_functions(void *program);
const char * bpf_function_name(void *program, size_t id);
void * bpf_function_start_id(void *program, size_t id);
void * bpf_function_start(void *program, const char *name);
size_t bpf_function_size_id(void *program, size_t id);
size_t bpf_function_size(void *program, const char *name);
size_t bpf_num_tables(void *program);
size_t bpf_table_id(void *program, const char *table_name);
int bpf_table_fd(void *program, const char *table_name);
int bpf_table_fd_id(void *program, size_t id);
int bpf_table_type(void *program, const char *table_name);
int bpf_table_type_id(void *program, size_t id);
size_t bpf_table_max_entries(void *program, const char *table_name);
size_t bpf_table_max_entries_id(void *program, size_t id);
const char * bpf_table_name(void *program, size_t id);
const char * bpf_table_key_desc(void *program, const char *table_name);
const char * bpf_table_key_desc_id(void *program, size_t id);
const char * bpf_table_leaf_desc(void *program, const char *table_name);
const char * bpf_table_leaf_desc_id(void *program, size_t id);
size_t bpf_table_key_size(void *program, const char *table_name);
size_t bpf_table_key_size_id(void *program, size_t id);
size_t bpf_table_leaf_size(void *program, const char *table_name);
size_t bpf_table_leaf_size_id(void *program, size_t id);
int bpf_table_key_snprintf(void *program, size_t id, char *buf, size_t buflen, const void *key);
int bpf_table_leaf_snprintf(void *program, size_t id, char *buf, size_t buflen, const void *leaf);
int bpf_table_key_sscanf(void *program, size_t id, const char *buf, void *key);
int bpf_table_leaf_sscanf(void *program, size_t id, const char *buf, void *leaf);
]]
ffi.cdef[[
struct perf_reader;
struct perf_reader * perf_reader_new(perf_reader_cb cb, perf_reader_raw_cb raw_cb, void *cb_cookie);
void perf_reader_free(void *ptr);
int perf_reader_mmap(struct perf_reader *reader, unsigned type, unsigned long sample_type);
int perf_reader_poll(int num_readers, struct perf_reader **readers, int timeout);
int perf_reader_fd(struct perf_reader *reader);
void perf_reader_set_fd(struct perf_reader *reader, int fd);
]]
local libbcc = ffi.load("bcc")
return libbcc
--[[
Copyright 2016 GitHub, Inc
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.
]]
local ProcSymbols = class("ProcSymbols")
function ProcSymbols:initialize(pid)
self.pid = pid
self:refresh()
end
function ProcSymbols:_get_exe()
return os.spawn("readlink -f /proc/%d/exe", self.pid)
end
function ProcSymbols:_get_start_time()
return tonumber(os.spawn("cut -d' ' -f 22 /proc/%d/stat", self.pid))
end
function ProcSymbols:_get_code_ranges()
local function is_binary_segment(parts)
if #parts ~= 6 then return false end
if parts[6]:starts("[") then return false end
if parts[2]:find("x") == nil then return false end
return true
end
local ranges = {}
local cmd = string.format("/proc/%d/maps", self.pid)
for line in io.lines(cmd) do
local parts = line:split()
if is_binary_segment(parts) then
local binary = parts[6]
local range = parts[1]:split("-", true)
assert(#range == 2)
ranges[binary] = {tonumber(range[1], 16), tonumber(range[2], 16)}
end
end
return ranges
end
function ProcSymbols:refresh()
self.code_ranges = self:_get_code_ranges()
self.ranges_cache = {}
self.exe = self:_get_exe()
self.start_time = self:_get_start_time()
end
function ProcSymbols:_check_pid_wrap()
local new_exe = self:_get_exe()
local new_time = self:_get_start_time()
if self.exe ~= new_exe or self.start_time ~= new_time then
self:refresh()
end
end
function ProcSymbols:_get_sym_ranges(binary)
if self.ranges_cache[binary] ~= nil then
return self.ranges_cache[binary]
end
local function is_function_sym(parts)
return #parts == 6 and parts[4] == ".text" and parts[3] == "F"
end
local sym_ranges = {}
local proc = assert(io.popen("objdump -t "..binary))
for line in proc:lines() do
local parts = line:split()
if is_function_sym(parts) then
local sym_start = tonumber(parts[1], 16)
local sym_len = tonumber(parts[5], 16)
local sym_name = parts[6]
sym_ranges[sym_name] = {sym_start, sym_len}
end
end
proc:close()
self.ranges_cache[binary] = sym_ranges
return sym_ranges
end
function ProcSymbols:_decode_sym(binary, offset)
local sym_ranges = self:_get_sym_ranges(binary)
for name, range in pairs(sym_ranges) do
local start = range[1]
local length = range[2]
if offset >= start and offset <= (start + length) then
return string.format("%s+0x%x", name, offset - start)
end
end
return string.format("%x", offset)
end
function ProcSymbols:lookup(addr)
self:_check_pid_wrap()
for binary, range in pairs(self.code_ranges) do
local start = range[1]
local tend = range[2]
if addr >= start and addr <= tend then
local offset = binary:ends(".so") and (addr - start) or addr
return string.format("%s [%s]", self:_decode_sym(binary, offset), binary)
end
end
return string.format("%x", addr)
end
local KSymbols = class("KSymbols")
KSymbols.static.KALLSYMS = "/proc/kallsyms"
function KSymbols:initialize()
self.ksyms = {}
self.ksym_names = {}
self.loaded = false
end
function KSymbols:_load()
if self.loaded then return end
local first_line = true
for line in io.lines(KSymbols.KALLSYMS) do
if not first_line then
local cols = line:split()
local name = cols[3]
local addr = tonumber(cols[1], 16)
table.insert(self.ksyms, {name, addr})
self.ksym_names[name] = #self.ksyms
end
first_line = false
end
self.loaded = true
end
function KSymbols:_addr2index(addr)
self:_load()
return table.bsearch(self.ksyms, addr, function(v) return v[2] end)
end
function KSymbols:lookup(addr, with_offset)
local idx = self:_addr2index(addr)
if idx == nil then
return "[unknown]"
end
if with_offset then
local offset = addr - self.ksyms[idx][2]
return "%s %x" % {self.ksyms[idx][1], offset}
else
return self.ksyms[idx][1]
end
end
function KSymbols:refresh()
-- NOOP
end
return { ProcSymbols=ProcSymbols, KSymbols=KSymbols }
--[[
Copyright 2016 GitHub, Inc
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.
]]
local ffi = require("ffi")
local libbcc = require("bcc.libbcc")
local Posix = require("bcc.vendor.posix")
local BaseTable = class("BaseTable")
BaseTable.static.BPF_MAP_TYPE_HASH = 1
BaseTable.static.BPF_MAP_TYPE_ARRAY = 2
BaseTable.static.BPF_MAP_TYPE_PROG_ARRAY = 3
BaseTable.static.BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4
BaseTable.static.BPF_MAP_TYPE_PERCPU_HASH = 5
BaseTable.static.BPF_MAP_TYPE_PERCPU_ARRAY = 6
BaseTable.static.BPF_MAP_TYPE_STACK_TRACE = 7
function BaseTable:initialize(t_type, bpf, map_id, map_fd, key_type, leaf_type)
assert(t_type == libbcc.bpf_table_type_id(bpf.module, map_id))
self.t_type = t_type
self.bpf = bpf
self.map_id = map_id
self.map_fd = map_fd
self.c_key = ffi.typeof(key_type.."[1]")
self.c_leaf = ffi.typeof(leaf_type.."[1]")
end
function BaseTable:get(key)
local pkey = self.c_key(key)
local pvalue = self.c_leaf()
if libbcc.bpf_lookup_elem(self.map_fd, pkey, pvalue) < 0 then
return nil
end
return pvalue[0]
end
function BaseTable:set(key, value)
local pkey = self.c_key(key)
local pvalue = self.c_leaf(value)
assert(libbcc.bpf_update_elem(self.map_fd, pkey, pvalue, 0) == 0, "could not update table")
end
function BaseTable:_empty_key()
local pkey = self.c_key()
local pvalue = self.c_leaf()
for _, v in ipairs({0x0, 0x55, 0xff}) do
ffi.fill(pkey, ffi.sizeof(pkey[0]), v)
if libbcc.bpf_lookup_elem(self.map_fd, pkey, pvalue) < 0 then
return pkey
end
end
error("failed to find an empty key for table iteration")
end
function BaseTable:keys()
local pkey = self:_empty_key()
return function()
local pkey_next = self.c_key()
if libbcc.bpf_get_next_key(self.map_fd, pkey, pkey_next) < 0 then
return nil
end
pkey = pkey_next
return pkey[0]
end
end
function BaseTable:items()
local pkey = self:_empty_key()
return function()
local pkey_next = self.c_key()
local pvalue = self.c_leaf()
if libbcc.bpf_get_next_key(self.map_fd, pkey, pkey_next) < 0 then
return nil
end
pkey = pkey_next
assert(libbcc.bpf_lookup_elem(self.map_fd, pkey, pvalue) == 0)
return pkey[0], pvalue[0]
end
end
local HashTable = class("HashTable", BaseTable)
function HashTable:initialize(bpf, map_id, map_fd, key_type, leaf_type)
BaseTable.initialize(self, BaseTable.BPF_MAP_TYPE_HASH, bpf, map_id, map_fd, key_type, leaf_type)
end
function HashTable:delete(key)
local pkey = self.c_key(key)
return libbcc.bpf_delete_elem(self.map_fd, pkey) == 0
end
function HashTable:size()
local n = 0
self:each(function() n = n + 1 end)
return n
end
local BaseArray = class("BaseArray", BaseTable)
function BaseArray:initialize(t_type, bpf, map_id, map_fd, key_type, leaf_type)
BaseTable.initialize(self, t_type, bpf, map_id, map_fd, key_type, leaf_type)
self.max_entries = tonumber(libbcc.bpf_table_max_entries_id(self.bpf.module, self.map_id))
end
function BaseArray:_normalize_key(key)
assert(type(key) == "number", "invalid key (expected a number")
if key < 0 then
key = self.max_entries + key
end
assert(key < self.max_entries, string.format("out of range (%d >= %d)", key, self.max_entries))
return key
end
function BaseArray:get(key)
return BaseTable.get(self, self:_normalize_key(key))
end
function BaseArray:set(key, value)
return BaseTable.set(self, self:_normalize_key(key), value)
end
function BaseArray:delete(key)
assert(nil, "unsupported")
end
function BaseArray:items(with_index)
local pkey = self.c_key()
local max = self.max_entries
local n = 0
-- TODO
return function()
local pvalue = self.c_leaf()
if n == max then
return nil
end
pkey[0] = n
n = n + 1
if libbcc.bpf_lookup_elem(self.map_fd, pkey, pvalue) ~= 0 then
return nil
end
if with_index then
return n, pvalue[0] -- return 1-based index
else
return pvalue[0]
end
end
end
local Array = class("Array", BaseArray)
function Array:initialize(bpf, map_id, map_fd, key_type, leaf_type)
BaseArray.initialize(self, BaseTable.BPF_MAP_TYPE_ARRAY, bpf, map_id, map_fd, key_type, leaf_type)
end
local PerfEventArray = class("PerfEventArray", BaseArray)
function PerfEventArray:initialize(bpf, map_id, map_fd, key_type, leaf_type)
BaseArray.initialize(self, BaseTable.BPF_MAP_TYPE_PERF_EVENT_ARRAY, bpf, map_id, map_fd, key_type, leaf_type)
self._callbacks = {}
end
local function _perf_id(id, cpu)
return string.format("perf_event_array:%d:%d", tonumber(id), cpu or 0)
end
function PerfEventArray:_open_perf_buffer(cpu, callback, ctype)
local _cb = ffi.cast("perf_reader_raw_cb",
function (cookie, data, size)
callback(cpu, ctype(data)[0])
end)
local reader = libbcc.bpf_open_perf_buffer(_cb, nil, -1, cpu)
assert(reader, "failed to open perf buffer")
local fd = libbcc.perf_reader_fd(reader)
self:set(cpu, fd)
self.bpf:probe_store("kprobe", _perf_id(self.map_id, cpu), reader)
self._callbacks[cpu] = _cb
end
function PerfEventArray:open_perf_buffer(callback, data_type)
assert(data_type, "a data type is needed for callback conversion")
local ctype = ffi.typeof(data_type.."*")
for i = 0, Posix.cpu_count() - 1 do
self:_open_perf_buffer(i, callback, ctype)
end
end
local StackTrace = class("StackTrace", BaseTable)
StackTrace.static.MAX_STACK = 127
function StackTrace:initialize(bpf, map_id, map_fd, key_type, leaf_type)
BaseTable.initialize(self, BaseTable.BPF_MAP_TYPE_STACK_TRACE, bpf, map_id, map_fd, key_type, leaf_type)
self._stackp = self.c_leaf() -- FIXME: not threadsafe
end
function StackTrace:walk(id)
local pkey = self.c_key(id)
local pstack = self._stackp
local i = 0
if libbcc.bpf_lookup_elem(self.map_fd, pkey, pstack) < 0 then
return nil
end
return function()
if i >= StackTrace.MAX_STACK then
return nil
end
local addr = tonumber(pstack[0].ip[i])
if addr == 0 then
return nil
end
i = i + 1
return addr
end
end
function StackTrace:get(id, resolver)
local stack = {}
for addr in self:walk(id) do
table.insert(stack, resolver and resolver(addr) or addr)
end
return stack
end
local function _decode_table_type(desc)
local json = require("bcc.vendor.json")
local json_desc = ffi.string(desc)
local function _dec(t)
if type(t) == "string" then
return t
end
local fields = ""
local struct = t[3] or "struct"
for _, value in ipairs(t[2]) do
if #value == 2 then
fields = fields .. string.format("%s %s; ", _dec(value[2]), value[1])
elseif #value == 3 then
if type(value[3]) == "table" then
fields = fields .. string.format("%s %s[%d]; ", _dec(value[2]), value[1], value[3][1])
else
error("not implemented: "..json_desc)
end
else
error("failed to decode type "..json_desc)
end
end
assert(struct == "struct" or struct == "union", "unknown complex type: "..struct)
return string.format("%s { %s}", struct, fields)
end
return _dec(json.parse(json_desc))
end
local function NewTable(bpf, name, key_type, leaf_type)
local id = libbcc.bpf_table_id(bpf.module, name)
local fd = libbcc.bpf_table_fd(bpf.module, name)
if fd < 0 then
return nil
end
local t_type = libbcc.bpf_table_type_id(bpf.module, id)
local table = nil
if t_type == BaseTable.BPF_MAP_TYPE_HASH then
table = HashTable
elseif t_type == BaseTable.BPF_MAP_TYPE_ARRAY then
table = Array
elseif t_type == BaseTable.BPF_MAP_TYPE_PERF_EVENT_ARRAY then
table = PerfEventArray
elseif t_type == BaseTable.BPF_MAP_TYPE_STACK_TRACE then
table = StackTrace
end
assert(table, "unsupported table type %d" % t_type)
if key_type == nil then
local desc = libbcc.bpf_table_key_desc(bpf.module, name)
assert(desc, "Failed to load BPF table description for "..name)
key_type = _decode_table_type(desc)
end
if leaf_type == nil then
local desc = libbcc.bpf_table_leaf_desc(bpf.module, name)
assert(desc, "Failed to load BPF table description for "..name)
leaf_type = _decode_table_type(desc)
end
log.info("key = %s value = %s", key_type, leaf_type)
return table:new(bpf, id, fd, key_type, leaf_type)
end
return NewTable
--[[
Copyright 2016 GitHub, Inc
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.
]]
local TracerPipe = class("TracerPipe")
TracerPipe.static.TRACEFS = "/sys/kernel/debug/tracing"
TracerPipe.static.fields = "%s+(.-)%-(%d+)%s+%[(%d+)%]%s+(....)%s+([%d%.]+):.-:%s+(.+)"
function TracerPipe:close()
if self.pipe ~= nil then
self.pipe:close()
end
end
function TracerPipe:open()
if self.pipe == nil then
self.pipe = assert(io.open(TracerPipe.TRACEFS .. "/trace_pipe"))
end
return self.pipe
end
function TracerPipe:readline()
return self:open():read()
end
function TracerPipe:trace_fields()
while true do
local line = self:readline()
if not line and self.nonblocking then
return nil
end
if not line:starts("CPU:") then
local task, pid, cpu, flags, ts, msg = line:match(TracerPipe.fields)
if task ~= nil then
return task, tonumber(pid), tonumber(cpu), flags, tonumber(ts), msg
end
end
end
end
function TracerPipe:initialize(nonblocking)
self.nonblocking = nonblocking
end
return TracerPipe
This diff is collapsed.
function string.starts(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
function string.ends(String,End)
return End=='' or string.sub(String,-string.len(End))==End
end
function string.escape(s)
return s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1')
end
--- split a string into a list of strings separated by a delimiter.
-- @param s The input string
-- @param re A Lua string pattern; defaults to '%s+'
-- @param plain don't use Lua patterns
-- @param n optional maximum number of splits
-- @return a list-like table
-- @raise error if s is not a string
function string.split(s,re,plain,n)
local find,sub,append = string.find, string.sub, table.insert
local i1,ls = 1,{}
if not re then re = '%s+' end
if re == '' then return {s} end
while true do
local i2,i3 = find(s,re,i1,plain)
if not i2 then
local last = sub(s,i1)
if last ~= '' then append(ls,last) end
if #ls == 1 and ls[1] == '' then
return {}
else
return ls
end
end
append(ls,sub(s,i1,i2-1))
if n and #ls == n then
ls[#ls] = sub(s,i1)
return ls
end
i1 = i3+1
end
end
function table.count(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end
function table.bsearch(list, value, mkval)
local low = 1
local high = #list
while low <= high do
local mid = math.floor((low+high)/2)
local this = mkval and mkval(list[mid]) or list[mid]
if this > value then
high = mid - 1
elseif this < value then
low = mid + 1
else
return mid
end
end
return nil
end
function table.build(iterator_fn, build_fn)
build_fn = (build_fn or function(arg) return arg end)
local res = {}
while true do
local vars = {iterator_fn()}
if vars[1] == nil then break end
table.insert(res, build_fn(vars))
end
return res
end
function table.values(T)
local V = {}
for k, v in pairs(T) do
table.insert(V, v)
end
return V
end
function table.tuples(T)
local i = 0
local n = table.getn(t)
return function ()
i = i + 1
if i <= n then return t[i][1], t[i][2] end
end
end
getmetatable("").__mod = function(a, b)
if not b then
return a
elseif type(b) == "table" then
return string.format(a, unpack(b))
else
return string.format(a, b)
end
end
function os.exists(path)
local f=io.open(path,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
function os.spawn(...)
local cmd = string.format(...)
local proc = assert(io.popen(cmd))
local out = proc:read("*a")
proc:close()
return out
end
local function logline(...)
if not log.enabled then
return
end
local c_green = "\27[32m"
local c_grey = "\27[1;30m"
local c_clear = "\27[0m"
local msg = string.format(...)
local info = debug.getinfo(2, "Sln")
local line = string.format("%s[%s:%s]%s %s", c_grey,
info.short_src:match("^.+/(.+)$"), info.currentline, c_clear, info.name)
io.stderr:write(
string.format("%s[%s]%s %s: %s\n", c_green,
os.date("%H:%M:%S"), c_clear, line, msg))
end
log = { info = logline, enabled = true }
--[[ json.lua
A compact pure-Lua JSON library.
This code is in the public domain:
https://gist.github.com/tylerneylon/59f4bcf316be525b30ab
The main functions are: json.stringify, json.parse.
## json.stringify:
This expects the following to be true of any tables being encoded:
* They only have string or number keys. Number keys must be represented as
strings in json; this is part of the json spec.
* They are not recursive. Such a structure cannot be specified in json.
A Lua table is considered to be an array if and only if its set of keys is a
consecutive sequence of positive integers starting at 1. Arrays are encoded like
so: `[2, 3, false, "hi"]`. Any other type of Lua table is encoded as a json
object, encoded like so: `{"key1": 2, "key2": false}`.
Because the Lua nil value cannot be a key, and as a table value is considerd
equivalent to a missing key, there is no way to express the json "null" value in
a Lua table. The only way this will output "null" is if your entire input obj is
nil itself.
An empty Lua table, {}, could be considered either a json object or array -
it's an ambiguous edge case. We choose to treat this as an object as it is the
more general type.
To be clear, none of the above considerations is a limitation of this code.
Rather, it is what we get when we completely observe the json specification for
as arbitrary a Lua object as json is capable of expressing.
## json.parse:
This function parses json, with the exception that it does not pay attention to
\u-escaped unicode code points in strings.
It is difficult for Lua to return null as a value. In order to prevent the loss
of keys with a null value in a json string, this function uses the one-off
table value json.null (which is just an empty table) to indicate null values.
This way you can check if a value is null with the conditional
`val == json.null`.
If you have control over the data and are using Lua, I would recommend just
avoiding null values in your data to begin with.
--]]
local json = {}
-- Internal functions.
local function kind_of(obj)
if type(obj) ~= 'table' then return type(obj) end
local i = 1
for _ in pairs(obj) do
if obj[i] ~= nil then i = i + 1 else return 'table' end
end
if i == 1 then return 'table' else return 'array' end
end
local function escape_str(s)
local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'}
for i, c in ipairs(in_char) do
s = s:gsub(c, '\\' .. out_char[i])
end
return s
end
-- Returns pos, did_find; there are two cases:
-- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.
-- 2. Delimiter not found: pos = pos after leading space; did_find = false.
-- This throws an error if err_if_missing is true and the delim is not found.
local function skip_delim(str, pos, delim, err_if_missing)
pos = pos + #str:match('^%s*', pos)
if str:sub(pos, pos) ~= delim then
if err_if_missing then
error('Expected ' .. delim .. ' near position ' .. pos)
end
return pos, false
end
return pos + 1, true
end
-- Expects the given pos to be the first character after the opening quote.
-- Returns val, pos; the returned pos is after the closing quote character.
local function parse_str_val(str, pos, val)
val = val or ''
local early_end_error = 'End of input found while parsing string.'
if pos > #str then error(early_end_error) end
local c = str:sub(pos, pos)
if c == '"' then return val, pos + 1 end
if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
-- We must have a \ character.
local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
local nextc = str:sub(pos + 1, pos + 1)
if not nextc then error(early_end_error) end
return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
end
-- Returns val, pos; the returned pos is after the number's final character.
local function parse_num_val(str, pos)
local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
local val = tonumber(num_str)
if not val then error('Error parsing number at position ' .. pos .. '.') end
return val, pos + #num_str
end
-- Public values and functions.
function json.stringify(obj, as_key)
local s = {} -- We'll build the string as an array of strings to be concatenated.
local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise.
if kind == 'array' then
if as_key then error('Can\'t encode array as key.') end
s[#s + 1] = '['
for i, val in ipairs(obj) do
if i > 1 then s[#s + 1] = ', ' end
s[#s + 1] = json.stringify(val)
end
s[#s + 1] = ']'
elseif kind == 'table' then
if as_key then error('Can\'t encode table as key.') end
s[#s + 1] = '{'
for k, v in pairs(obj) do
if #s > 1 then s[#s + 1] = ', ' end
s[#s + 1] = json.stringify(k, true)
s[#s + 1] = ':'
s[#s + 1] = json.stringify(v)
end
s[#s + 1] = '}'
elseif kind == 'string' then
return '"' .. escape_str(obj) .. '"'
elseif kind == 'number' then
if as_key then return '"' .. tostring(obj) .. '"' end
return tostring(obj)
elseif kind == 'boolean' then
return tostring(obj)
elseif kind == 'nil' then
return 'null'
else
error('Unjsonifiable type: ' .. kind .. '.')
end
return table.concat(s)
end
json.null = {} -- This is a one-off table to represent the null value.
function json.parse(str, pos, end_delim)
pos = pos or 1
if pos > #str then error('Reached unexpected end of input.') end
local pos = pos + #str:match('^%s*', pos) -- Skip whitespace.
local first = str:sub(pos, pos)
if first == '{' then -- Parse an object.
local obj, key, delim_found = {}, true, true
pos = pos + 1
while true do
key, pos = json.parse(str, pos, '}')
if key == nil then return obj, pos end
if not delim_found then error('Comma missing between object items.') end
pos = skip_delim(str, pos, ':', true) -- true -> error if missing.
obj[key], pos = json.parse(str, pos)
pos, delim_found = skip_delim(str, pos, ',')
end
elseif first == '[' then -- Parse an array.
local arr, val, delim_found = {}, true, true
pos = pos + 1
while true do
val, pos = json.parse(str, pos, ']')
if val == nil then return arr, pos end
if not delim_found then error('Comma missing between array items.') end
arr[#arr + 1] = val
pos, delim_found = skip_delim(str, pos, ',')
end
elseif first == '"' then -- Parse a string.
return parse_str_val(str, pos + 1)
elseif first == '-' or first:match('%d') then -- Parse a number.
return parse_num_val(str, pos)
elseif first == end_delim then -- End of an object or array.
return nil, pos + 1
else -- Parse true, false, or null.
local literals = {['true'] = true, ['false'] = false, ['null'] = json.null}
for lit_str, lit_val in pairs(literals) do
local lit_end = pos + #lit_str - 1
if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
end
local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
error('Invalid json syntax starting at ' .. pos_info_str)
end
end
return json
local middleclass = {
_VERSION = 'middleclass v4.0.0',
_DESCRIPTION = 'Object Orientation for Lua',
_URL = 'https://github.com/kikito/middleclass',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2011 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
else
return function(self, name)
local value = aClass.__instanceDict[name]
if value ~= nil then
return value
elseif type(f) == "function" then
return (f(self, name))
else
return f[name]
end
end
end
end
local function _propagateInstanceMethod(aClass, name, f)
f = name == "__index" and _createIndexWrapper(aClass, f) or f
aClass.__instanceDict[name] = f
for subclass in pairs(aClass.subclasses) do
if rawget(subclass.__declaredMethods, name) == nil then
_propagateInstanceMethod(subclass, name, f)
end
end
end
local function _declareInstanceMethod(aClass, name, f)
aClass.__declaredMethods[name] = f
if f == nil and aClass.super then
f = aClass.super.__instanceDict[name]
end
_propagateInstanceMethod(aClass, name, f)
end
local function _tostring(self) return "class " .. self.name end
local function _call(self, ...) return self:new(...) end
local function _createClass(name, super)
local dict = {}
dict.__index = dict
local aClass = { name = name, super = super, static = {},
__instanceDict = dict, __declaredMethods = {},
subclasses = setmetatable({}, {__mode='k'}) }
if super then
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) or super.static[k] end })
else
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
end
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
__call = _call, __newindex = _declareInstanceMethod })
return aClass
end
local function _includeMixin(aClass, mixin)
assert(type(mixin) == 'table', "mixin must be a table")
for name,method in pairs(mixin) do
if name ~= "included" and name ~= "static" then aClass[name] = method end
end
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end
if type(mixin.included)=="function" then mixin:included(aClass) end
return aClass
end
local DefaultMixin = {
__tostring = function(self) return "instance of " .. tostring(self.class) end,
initialize = function(self, ...) end,
isInstanceOf = function(self, aClass)
return type(self) == 'table' and
type(self.class) == 'table' and
type(aClass) == 'table' and
( aClass == self.class or
type(aClass.isSubclassOf) == 'function' and
self.class:isSubclassOf(aClass) )
end,
static = {
allocate = function(self)
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end,
new = function(self, ...)
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = self:allocate()
instance:initialize(...)
return instance
end,
subclass = function(self, name)
assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")
local subclass = _createClass(name, self)
for methodName, f in pairs(self.__instanceDict) do
_propagateInstanceMethod(subclass, methodName, f)
end
subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
self.subclasses[subclass] = true
self:subclassed(subclass)
return subclass
end,
subclassed = function(self, other) end,
isSubclassOf = function(self, other)
return type(other) == 'table' and
type(self) == 'table' and
type(self.super) == 'table' and
( self.super == other or
type(self.super.isSubclassOf) == 'function' and
self.super:isSubclassOf(other) )
end,
include = function(self, ...)
assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
}
}
function middleclass.class(name, super)
assert(type(name) == 'string', "A name (string) is needed for the new class")
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
return middleclass
--[[
Copyright 2016 GitHub, Inc
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.
]]
local ffi = require("ffi")
ffi.cdef[[
typedef int clockid_t;
typedef long time_t;
struct timespec {
time_t tv_sec;
long tv_nsec;
};
int clock_gettime(clockid_t clk_id, struct timespec *tp);
int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *request, struct timespec *remain);
int get_nprocs(void);
]]
local CLOCK = {
REALTIME = 0,
MONOTONIC = 1,
PROCESS_CPUTIME_ID = 2,
THREAD_CPUTIME_ID = 3,
MONOTONIC_RAW = 4,
REALTIME_COARSE = 5,
MONOTONIC_COARSE = 6,
}
local function time_ns(clock)
local ts = ffi.new("struct timespec[1]")
assert(ffi.C.clock_gettime(clock or CLOCK.MONOTONIC_RAW, ts) == 0,
"clock_gettime() failed: "..ffi.errno())
return tonumber(ts[0].tv_sec * 1e9 + ts[0].tv_nsec)
end
local function sleep(seconds, clock)
local s, ns = math.modf(seconds)
local ts = ffi.new("struct timespec[1]")
ts[0].tv_sec = s
ts[0].tv_nsec = ns / 1e9
ffi.C.clock_nanosleep(clock or CLOCK.MONOTONIC, 0, ts, nil)
end
local function cpu_count()
return tonumber(ffi.C.get_nprocs())
end
return {
time_ns=time_ns,
sleep=sleep,
CLOCK=CLOCK,
cpu_count=cpu_count,
}
--[[
Copyright (C) 2009 Steve Donovan, David Manura.
This code is licensed under the MIT License
https://github.com/stevedonovan/Penlight
--]]
--- Checks uses of undeclared global variables.
-- All global variables must be 'declared' through a regular assignment
-- (even assigning `nil` will do) in a main chunk before being used
-- anywhere or assigned to inside a function. Existing metatables `__newindex` and `__index`
-- metamethods are respected.
--
-- You can set any table to have strict behaviour using `strict.module`. Creating a new
-- module with `strict.closed_module` makes the module immune to monkey-patching, if
-- you don't wish to encourage monkey business.
--
-- If the global `PENLIGHT_NO_GLOBAL_STRICT` is defined, then this module won't make the
-- global environment strict - if you just want to explicitly set table strictness.
--
-- @module pl.strict
require 'debug' -- for Lua 5.2
local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget
local strict = {}
local function what ()
local d = getinfo(3, "S")
return d and d.what or "C"
end
--- make an existing table strict.
-- @string name name of table (optional)
-- @tab[opt] mod table - if `nil` then we'll return a new table
-- @tab[opt] predeclared - table of variables that are to be considered predeclared.
-- @return the given table, or a new table
function strict.module (name,mod,predeclared)
local mt, old_newindex, old_index, old_index_type, global, closed
if predeclared then
global = predeclared.__global
closed = predeclared.__closed
end
if type(mod) == 'table' then
mt = getmetatable(mod)
if mt and rawget(mt,'__declared') then return end -- already patched...
else
mod = {}
end
if mt == nil then
mt = {}
setmetatable(mod, mt)
else
old_newindex = mt.__newindex
old_index = mt.__index
old_index_type = type(old_index)
end
mt.__declared = predeclared or {}
mt.__newindex = function(t, n, v)
if old_newindex then
old_newindex(t, n, v)
if rawget(t,n)~=nil then return end
end
if not mt.__declared[n] then
if global then
local w = what()
if w ~= "main" and w ~= "C" then
error("assign to undeclared global '"..n.."'", 2)
end
end
mt.__declared[n] = true
end
rawset(t, n, v)
end
mt.__index = function(t,n)
if not mt.__declared[n] and what() ~= "C" then
if old_index then
if old_index_type == "table" then
local fallback = old_index[n]
if fallback ~= nil then
return fallback
end
else
local res = old_index(t, n)
if res then return res end
end
end
local msg = "variable '"..n.."' is not declared"
if name then
msg = msg .. " in '"..name.."'"
end
error(msg, 2)
end
return rawget(t, n)
end
return mod
end
--- make all tables in a table strict.
-- So `strict.make_all_strict(_G)` prevents monkey-patching
-- of any global table
-- @tab T
function strict.make_all_strict (T)
for k,v in pairs(T) do
if type(v) == 'table' and v ~= T then
strict.module(k,v)
end
end
end
--- make a new module table which is closed to further changes.
function strict.closed_module (mod,name)
local M = {}
mod = mod or {}
local mt = getmetatable(mod)
if not mt then
mt = {}
setmetatable(mod,mt)
end
mt.__newindex = function(t,k,v)
M[k] = v
end
return strict.module(name,M)
end
if not rawget(_G,'PENLIGHT_NO_GLOBAL_STRICT') then
strict.module(nil,_G,{_PROMPT=true,__global=true})
end
return strict
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment