1--[[ 2Copyright 2016 GitHub, Inc 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15]] 16local ffi = require("ffi") 17local libbcc = require("bcc.libbcc") 18local Posix = require("bcc.vendor.posix") 19 20local BaseTable = class("BaseTable") 21 22BaseTable.static.BPF_MAP_TYPE_HASH = 1 23BaseTable.static.BPF_MAP_TYPE_ARRAY = 2 24BaseTable.static.BPF_MAP_TYPE_PROG_ARRAY = 3 25BaseTable.static.BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4 26BaseTable.static.BPF_MAP_TYPE_PERCPU_HASH = 5 27BaseTable.static.BPF_MAP_TYPE_PERCPU_ARRAY = 6 28BaseTable.static.BPF_MAP_TYPE_STACK_TRACE = 7 29BaseTable.static.BPF_MAP_TYPE_CGROUP_ARRAY = 8 30BaseTable.static.BPF_MAP_TYPE_LRU_HASH = 9 31BaseTable.static.BPF_MAP_TYPE_LRU_PERCPU_HASH = 10 32BaseTable.static.BPF_MAP_TYPE_LPM_TRIE = 11 33 34function BaseTable:initialize(t_type, bpf, map_id, map_fd, key_type, leaf_type) 35 assert(t_type == libbcc.bpf_table_type_id(bpf.module, map_id)) 36 37 self.t_type = t_type 38 self.bpf = bpf 39 self.map_id = map_id 40 self.map_fd = map_fd 41 self.c_key = ffi.typeof(key_type.."[1]") 42 self.c_leaf = ffi.typeof(leaf_type.."[1]") 43end 44 45function BaseTable:key_sprintf(key) 46 local pkey = self.c_key(key) 47 local buf_len = ffi.sizeof(self.c_key) * 8 48 local pbuf = ffi.new("char[?]", buf_len) 49 50 local res = libbcc.bpf_table_key_snprintf( 51 self.bpf.module, self.map_id, pbuf, buf_len, pkey) 52 assert(res == 0, "could not print key") 53 54 return ffi.string(pbuf) 55end 56 57function BaseTable:leaf_sprintf(leaf) 58 local pleaf = self.c_leaf(leaf) 59 local buf_len = ffi.sizeof(self.c_leaf) * 8 60 local pbuf = ffi.new("char[?]", buf_len) 61 62 local res = libbcc.bpf_table_leaf_snprintf( 63 self.bpf.module, self.map_id, pbuf, buf_len, pleaf) 64 assert(res == 0, "could not print leaf") 65 66 return ffi.string(pbuf) 67end 68 69function BaseTable:key_scanf(key_str) 70 local pkey = self.c_key() 71 local res = libbcc.bpf_table_key_sscanf( 72 self.bpf.module, self.map_id, key_str, pkey) 73 assert(res == 0, "could not scanf key") 74 return pkey[0] 75end 76 77function BaseTable:leaf_scanf(leaf_str) 78 local pleaf = self.c_leaf() 79 local res = libbcc.bpf_table_leaf_sscanf( 80 self.bpf.module, self.map_id, leaf_str, pleaf) 81 assert(res == 0, "could not scanf leaf") 82 return pleaf[0] 83end 84 85function BaseTable:get(key) 86 local pkey = self.c_key(key) 87 local pvalue = self.c_leaf() 88 89 if libbcc.bpf_lookup_elem(self.map_fd, pkey, pvalue) < 0 then 90 return nil 91 end 92 93 return pvalue[0] 94end 95 96function BaseTable:set(key, value) 97 local pkey = self.c_key(key) 98 local pvalue = self.c_leaf(value) 99 assert(libbcc.bpf_update_elem(self.map_fd, pkey, pvalue, 0) == 0, "could not update table") 100end 101 102function BaseTable:_empty_key() 103 local pkey = self.c_key() 104 local pvalue = self.c_leaf() 105 106 for _, v in ipairs({0x0, 0x55, 0xff}) do 107 ffi.fill(pkey, ffi.sizeof(pkey[0]), v) 108 if libbcc.bpf_lookup_elem(self.map_fd, pkey, pvalue) < 0 then 109 return pkey 110 end 111 end 112 113 error("failed to find an empty key for table iteration") 114end 115 116function BaseTable:keys() 117 local pkey = self:_empty_key() 118 119 return function() 120 local pkey_next = self.c_key() 121 122 if libbcc.bpf_get_next_key(self.map_fd, pkey, pkey_next) < 0 then 123 return nil 124 end 125 126 pkey = pkey_next 127 return pkey[0] 128 end 129end 130 131function BaseTable:items() 132 local pkey = self:_empty_key() 133 134 return function() 135 local pkey_next = self.c_key() 136 local pvalue = self.c_leaf() 137 138 if libbcc.bpf_get_next_key(self.map_fd, pkey, pkey_next) < 0 then 139 return nil 140 end 141 142 pkey = pkey_next 143 assert(libbcc.bpf_lookup_elem(self.map_fd, pkey, pvalue) == 0) 144 return pkey[0], pvalue[0] 145 end 146end 147 148 149 150local HashTable = class("HashTable", BaseTable) 151 152function HashTable:initialize(bpf, map_id, map_fd, key_type, leaf_type) 153 BaseTable.initialize(self, BaseTable.BPF_MAP_TYPE_HASH, bpf, map_id, map_fd, key_type, leaf_type) 154end 155 156function HashTable:delete(key) 157 local pkey = self.c_key(key) 158 return libbcc.bpf_delete_elem(self.map_fd, pkey) == 0 159end 160 161function HashTable:size() 162 local n = 0 163 self:each(function() n = n + 1 end) 164 return n 165end 166 167 168 169local BaseArray = class("BaseArray", BaseTable) 170 171function BaseArray:initialize(t_type, bpf, map_id, map_fd, key_type, leaf_type) 172 BaseTable.initialize(self, t_type, bpf, map_id, map_fd, key_type, leaf_type) 173 self.max_entries = tonumber(libbcc.bpf_table_max_entries_id(self.bpf.module, self.map_id)) 174end 175 176function BaseArray:_normalize_key(key) 177 assert(type(key) == "number", "invalid key (expected a number") 178 if key < 0 then 179 key = self.max_entries + key 180 end 181 assert(key < self.max_entries, string.format("out of range (%d >= %d)", key, self.max_entries)) 182 return key 183end 184 185function BaseArray:get(key) 186 return BaseTable.get(self, self:_normalize_key(key)) 187end 188 189function BaseArray:set(key, value) 190 return BaseTable.set(self, self:_normalize_key(key), value) 191end 192 193function BaseArray:delete(key) 194 assert(nil, "unsupported") 195end 196 197function BaseArray:items(with_index) 198 local pkey = self.c_key() 199 local max = self.max_entries 200 local n = 0 201 202 -- TODO 203 return function() 204 local pvalue = self.c_leaf() 205 206 if n == max then 207 return nil 208 end 209 210 pkey[0] = n 211 n = n + 1 212 213 if libbcc.bpf_lookup_elem(self.map_fd, pkey, pvalue) ~= 0 then 214 return nil 215 end 216 217 if with_index then 218 return n, pvalue[0] -- return 1-based index 219 else 220 return pvalue[0] 221 end 222 end 223end 224 225 226 227local Array = class("Array", BaseArray) 228 229function Array:initialize(bpf, map_id, map_fd, key_type, leaf_type) 230 BaseArray.initialize(self, BaseTable.BPF_MAP_TYPE_ARRAY, bpf, map_id, map_fd, key_type, leaf_type) 231end 232 233 234 235local PerfEventArray = class("PerfEventArray", BaseArray) 236 237function PerfEventArray:initialize(bpf, map_id, map_fd, key_type, leaf_type) 238 BaseArray.initialize(self, BaseTable.BPF_MAP_TYPE_PERF_EVENT_ARRAY, bpf, map_id, map_fd, key_type, leaf_type) 239 self._callbacks = {} 240end 241 242local function _perf_id(id, cpu) 243 return string.format("bcc:perf_event_array:%d:%d", tonumber(id), cpu or 0) 244end 245 246function PerfEventArray:_open_perf_buffer(cpu, callback, ctype, page_cnt, lost_cb) 247 local _cb = ffi.cast("perf_reader_raw_cb", 248 function (cookie, data, size) 249 callback(cpu, ctype(data)[0]) 250 end) 251 252 local _lost_cb = nil 253 if lost_cb then 254 _lost_cb = ffi.cast("perf_reader_lost_cb", 255 function (cookie, lost) 256 lost_cb(cookie, lost) 257 end) 258 end 259 260 -- default to 8 pages per buffer 261 local reader = libbcc.bpf_open_perf_buffer(_cb, _lost_cb, nil, -1, cpu, page_cnt or 8) 262 assert(reader, "failed to open perf buffer") 263 264 local fd = libbcc.perf_reader_fd(reader) 265 self:set(cpu, fd) 266 self.bpf:perf_buffer_store(_perf_id(self.map_id, cpu), reader) 267 self._callbacks[cpu] = _cb 268end 269 270function PerfEventArray:open_perf_buffer(callback, data_type, data_params, page_cnt, lost_cb) 271 assert(data_type, "a data type is needed for callback conversion") 272 local ctype = ffi.typeof(data_type.."*", unpack(data_params or {})) 273 for i = 0, Posix.cpu_count() - 1 do 274 self:_open_perf_buffer(i, callback, ctype, page_cnt, lost_cb) 275 end 276end 277 278 279local StackTrace = class("StackTrace", BaseTable) 280 281StackTrace.static.MAX_STACK = 127 282 283function StackTrace:initialize(bpf, map_id, map_fd, key_type, leaf_type) 284 BaseTable.initialize(self, BaseTable.BPF_MAP_TYPE_STACK_TRACE, bpf, map_id, map_fd, key_type, leaf_type) 285 self._stackp = self.c_leaf() -- FIXME: not threadsafe 286end 287 288function StackTrace:walk(id) 289 local pkey = self.c_key(id) 290 local pstack = self._stackp 291 local i = 0 292 293 if libbcc.bpf_lookup_elem(self.map_fd, pkey, pstack) < 0 then 294 return nil 295 end 296 297 return function() 298 if i >= StackTrace.MAX_STACK then 299 return nil 300 end 301 302 local addr = pstack[0].ip[i] 303 if addr == 0 then 304 return nil 305 end 306 307 i = i + 1 308 return addr 309 end 310end 311 312function StackTrace:get(id, resolver) 313 local stack = {} 314 for addr in self:walk(id) do 315 table.insert(stack, resolver and resolver(addr) or addr) 316 end 317 return stack 318end 319 320local function _decode_table_type(desc) 321 local json = require("bcc.vendor.json") 322 local json_desc = ffi.string(desc) 323 324 local function _dec(t) 325 if type(t) == "string" then 326 return t 327 end 328 329 local fields = {} 330 local struct = t[3] or "struct" 331 332 for _, value in ipairs(t[2]) do 333 local f = nil 334 335 if #value == 2 then 336 f = string.format("%s %s;", _dec(value[2]), value[1]) 337 elseif #value == 3 then 338 if type(value[3]) == "table" then 339 f = string.format("%s %s[%d];", _dec(value[2]), value[1], value[3][1]) 340 elseif type(value[3]) == "number" then 341 local t = _dec(value[2]) 342 assert(t == "int" or t == "unsigned int", 343 "bitfields can only appear in [unsigned] int types") 344 f = string.format("%s %s:%d;", t, value[1], value[3]) 345 end 346 end 347 348 assert(f ~= nil, "failed to decode type "..json_desc) 349 table.insert(fields, f) 350 end 351 352 assert(struct == "struct" or struct == "struct_packed" or struct == "union", 353 "unknown complex type: "..struct) 354 if struct == "union" then 355 return string.format("union { %s }", table.concat(fields, " ")) 356 else 357 return string.format("struct { %s }", table.concat(fields, " ")) 358 end 359 end 360 return _dec(json.parse(json_desc)) 361end 362 363local function NewTable(bpf, name, key_type, leaf_type) 364 local id = libbcc.bpf_table_id(bpf.module, name) 365 local fd = libbcc.bpf_table_fd(bpf.module, name) 366 367 if fd < 0 then 368 return nil 369 end 370 371 local t_type = libbcc.bpf_table_type_id(bpf.module, id) 372 local table = nil 373 374 if t_type == BaseTable.BPF_MAP_TYPE_HASH then 375 table = HashTable 376 elseif t_type == BaseTable.BPF_MAP_TYPE_ARRAY then 377 table = Array 378 elseif t_type == BaseTable.BPF_MAP_TYPE_PERF_EVENT_ARRAY then 379 table = PerfEventArray 380 elseif t_type == BaseTable.BPF_MAP_TYPE_STACK_TRACE then 381 table = StackTrace 382 end 383 384 assert(table, "unsupported table type %d" % t_type) 385 386 if key_type == nil then 387 local desc = libbcc.bpf_table_key_desc(bpf.module, name) 388 assert(desc, "Failed to load BPF table description for "..name) 389 key_type = _decode_table_type(desc) 390 end 391 392 if leaf_type == nil then 393 local desc = libbcc.bpf_table_leaf_desc(bpf.module, name) 394 assert(desc, "Failed to load BPF table description for "..name) 395 leaf_type = _decode_table_type(desc) 396 end 397 398 log.info("key = %s value = %s", key_type, leaf_type) 399 return table:new(bpf, id, fd, key_type, leaf_type) 400end 401 402return NewTable 403