• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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