Skip to content

Commit b6e2b87

Browse files
authored
change aggregation flow map to hashmap instead perCPU hashmap (#118)
Signed-off-by: msherif1234 <[email protected]>
1 parent ec9ca7a commit b6e2b87

File tree

15 files changed

+57
-147
lines changed

15 files changed

+57
-147
lines changed

bpf/flows.c

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ struct {
5858
} direct_flows SEC(".maps");
5959

6060
// Key: the flow identifier. Value: the flow metrics for that identifier.
61-
// The userspace will aggregate them into a single flow.
6261
struct {
63-
__uint(type, BPF_MAP_TYPE_PERCPU_HASH);
62+
__uint(type, BPF_MAP_TYPE_HASH);
6463
__type(key, flow_id);
6564
__type(value, flow_metrics);
65+
__uint(max_entries, 1 << 24);
66+
__uint(map_flags, BPF_F_NO_PREALLOC);
6667
} aggregated_flows SEC(".maps");
6768

6869
// Constant definitions, to be overridden by the invoker
@@ -260,11 +261,6 @@ static inline int flow_monitor(struct __sk_buff *skb, u8 direction) {
260261
aggregate_flow->packets += 1;
261262
aggregate_flow->bytes += skb->len;
262263
aggregate_flow->end_mono_time_ts = current_time;
263-
// it might happen that start_mono_time hasn't been set due to
264-
// the way percpu hashmap deal with concurrent map entries
265-
if (aggregate_flow->start_mono_time_ts == 0) {
266-
aggregate_flow->start_mono_time_ts = current_time;
267-
}
268264
aggregate_flow->flags |= flags;
269265
long ret = bpf_map_update_elem(&aggregated_flows, &id, aggregate_flow, BPF_ANY);
270266
if (trace_messages && ret != 0) {

docs/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ flowchart TD
1111
E(ebpf.FlowFetcher) --> |"pushes via<br/>RingBuffer"| RB(flow.RingBufTracer)
1212
style E fill:#990
1313
14-
E --> |"polls<br/>PerCPUHashMap"| M(flow.MapTracer)
14+
E --> |"polls<br/>HashMap"| M(flow.MapTracer)
1515
RB --> |chan *flow.Record| ACC(flow.Accounter)
1616
RB -.-> |flushes| M
1717
ACC --> |"chan []*flow.Record"| DD(flow.Deduper)

pkg/agent/agent.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ type ebpfFlowFetcher interface {
7878
io.Closer
7979
Register(iface ifaces.Interface) error
8080

81-
LookupAndDeleteMap() map[ebpf.BpfFlowId][]ebpf.BpfFlowMetrics
81+
LookupAndDeleteMap() map[ebpf.BpfFlowId]ebpf.BpfFlowMetrics
8282
ReadRingBuf() (ringbuf.Record, error)
8383
}
8484

pkg/agent/agent_test.go

Lines changed: 15 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,6 @@ var (
4949
DstPort: 456,
5050
IfIndex: 3,
5151
}
52-
key1Dupe = ebpf.BpfFlowId{
53-
SrcPort: 123,
54-
DstPort: 456,
55-
IfIndex: 4,
56-
}
5752

5853
key2 = ebpf.BpfFlowId{
5954
SrcPort: 333,
@@ -71,7 +66,7 @@ func TestFlowsAgent_Deduplication(t *testing.T) {
7166
})
7267

7368
exported := export.Get(t, timeout)
74-
assert.Len(t, exported, 2)
69+
assert.Len(t, exported, 1)
7570

7671
receivedKeys := map[ebpf.BpfFlowId]struct{}{}
7772

@@ -81,21 +76,11 @@ func TestFlowsAgent_Deduplication(t *testing.T) {
8176
receivedKeys[f.Id] = struct{}{}
8277
switch f.Id {
8378
case key1:
84-
assert.EqualValues(t, 4, f.Metrics.Packets)
85-
assert.EqualValues(t, 66, f.Metrics.Bytes)
79+
assert.EqualValues(t, 3, f.Metrics.Packets)
80+
assert.EqualValues(t, 44, f.Metrics.Bytes)
8681
assert.False(t, f.Duplicate)
8782
assert.Equal(t, "foo", f.Interface)
8883
key1Flows = append(key1Flows, f)
89-
case key1Dupe:
90-
assert.EqualValues(t, 4, f.Metrics.Packets)
91-
assert.EqualValues(t, 66, f.Metrics.Bytes)
92-
assert.False(t, f.Duplicate)
93-
assert.Equal(t, "bar", f.Interface)
94-
key1Flows = append(key1Flows, f)
95-
case key2:
96-
assert.EqualValues(t, 7, f.Metrics.Packets)
97-
assert.EqualValues(t, 33, f.Metrics.Bytes)
98-
assert.False(t, f.Duplicate)
9984
}
10085
}
10186
assert.Lenf(t, key1Flows, 1, "only one flow should have been forwarded: %#v", key1Flows)
@@ -112,33 +97,22 @@ func TestFlowsAgent_DeduplicationJustMark(t *testing.T) {
11297
exported := export.Get(t, timeout)
11398
receivedKeys := map[ebpf.BpfFlowId]struct{}{}
11499

115-
assert.Len(t, exported, 3)
100+
assert.Len(t, exported, 1)
116101
duplicates := 0
117102
for _, f := range exported {
118103
require.NotContains(t, receivedKeys, f.Id)
119104
receivedKeys[f.Id] = struct{}{}
120105
switch f.Id {
121106
case key1:
122-
assert.EqualValues(t, 4, f.Metrics.Packets)
123-
assert.EqualValues(t, 66, f.Metrics.Bytes)
107+
assert.EqualValues(t, 3, f.Metrics.Packets)
108+
assert.EqualValues(t, 44, f.Metrics.Bytes)
124109
if f.Duplicate {
125110
duplicates++
126111
}
127112
assert.Equal(t, "foo", f.Interface)
128-
case key1Dupe:
129-
assert.EqualValues(t, 4, f.Metrics.Packets)
130-
assert.EqualValues(t, 66, f.Metrics.Bytes)
131-
if f.Duplicate {
132-
duplicates++
133-
}
134-
assert.Equal(t, "bar", f.Interface)
135-
case key2:
136-
assert.EqualValues(t, 7, f.Metrics.Packets)
137-
assert.EqualValues(t, 33, f.Metrics.Bytes)
138-
assert.False(t, f.Duplicate)
139113
}
140114
}
141-
assert.Equalf(t, 1, duplicates, "exported flows should have only one duplicate: %#v", exported)
115+
assert.Equalf(t, 0, duplicates, "exported flows should have only one duplicate: %#v", exported)
142116
}
143117

144118
func TestFlowsAgent_Deduplication_None(t *testing.T) {
@@ -149,7 +123,7 @@ func TestFlowsAgent_Deduplication_None(t *testing.T) {
149123
})
150124

151125
exported := export.Get(t, timeout)
152-
assert.Len(t, exported, 3)
126+
assert.Len(t, exported, 1)
153127
receivedKeys := map[ebpf.BpfFlowId]struct{}{}
154128

155129
var key1Flows []*flow.Record
@@ -158,24 +132,14 @@ func TestFlowsAgent_Deduplication_None(t *testing.T) {
158132
receivedKeys[f.Id] = struct{}{}
159133
switch f.Id {
160134
case key1:
161-
assert.EqualValues(t, 4, f.Metrics.Packets)
162-
assert.EqualValues(t, 66, f.Metrics.Bytes)
135+
assert.EqualValues(t, 3, f.Metrics.Packets)
136+
assert.EqualValues(t, 44, f.Metrics.Bytes)
163137
assert.False(t, f.Duplicate)
164138
assert.Equal(t, "foo", f.Interface)
165139
key1Flows = append(key1Flows, f)
166-
case key1Dupe:
167-
assert.EqualValues(t, 4, f.Metrics.Packets)
168-
assert.EqualValues(t, 66, f.Metrics.Bytes)
169-
assert.False(t, f.Duplicate)
170-
assert.Equal(t, "bar", f.Interface)
171-
key1Flows = append(key1Flows, f)
172-
case key2:
173-
assert.EqualValues(t, 7, f.Metrics.Packets)
174-
assert.EqualValues(t, 33, f.Metrics.Bytes)
175-
assert.False(t, f.Duplicate)
176140
}
177141
}
178-
assert.Lenf(t, key1Flows, 2, "both key1 flows should have been forwarded: %#v", key1Flows)
142+
assert.Lenf(t, key1Flows, 1, "both key1 flows should have been forwarded: %#v", key1Flows)
179143
}
180144

181145
func TestFlowsAgent_Decoration(t *testing.T) {
@@ -185,7 +149,7 @@ func TestFlowsAgent_Decoration(t *testing.T) {
185149
})
186150

187151
exported := export.Get(t, timeout)
188-
assert.Len(t, exported, 3)
152+
assert.Len(t, exported, 1)
189153

190154
// Tests that the decoration stage has been properly executed. It should
191155
// add the interface name and the agent IP
@@ -219,18 +183,10 @@ func testAgent(t *testing.T, cfg *Config) *test.ExporterFake {
219183
})
220184

221185
now := uint64(monotime.Now())
222-
key1Metrics := []ebpf.BpfFlowMetrics{
223-
{Packets: 3, Bytes: 44, StartMonoTimeTs: now + 1000, EndMonoTimeTs: now + 1_000_000_000},
224-
{Packets: 1, Bytes: 22, StartMonoTimeTs: now, EndMonoTimeTs: now + 3000},
225-
}
226-
key2Metrics := []ebpf.BpfFlowMetrics{
227-
{Packets: 7, Bytes: 33, StartMonoTimeTs: now, EndMonoTimeTs: now + 2_000_000_000},
228-
}
186+
key1Metrics := ebpf.BpfFlowMetrics{Packets: 3, Bytes: 44, StartMonoTimeTs: now + 1000, EndMonoTimeTs: now + 1_000_000_000}
229187

230-
ebpfTracer.AppendLookupResults(map[ebpf.BpfFlowId][]ebpf.BpfFlowMetrics{
231-
key1: key1Metrics,
232-
key1Dupe: key1Metrics,
233-
key2: key2Metrics,
188+
ebpfTracer.AppendLookupResults(map[ebpf.BpfFlowId]ebpf.BpfFlowMetrics{
189+
key1: key1Metrics,
234190
})
235191
return export
236192
}

pkg/ebpf/bpf_bpfeb.go

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/ebpf/bpf_bpfeb.o

-544 Bytes
Binary file not shown.

pkg/ebpf/bpf_bpfel.go

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/ebpf/bpf_bpfel.o

-536 Bytes
Binary file not shown.

pkg/ebpf/tracer.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -308,25 +308,25 @@ func (m *FlowFetcher) ReadRingBuf() (ringbuf.Record, error) {
308308
// TODO: detect whether BatchLookupAndDelete is supported (Kernel>=5.6) and use it selectively
309309
// Supported Lookup/Delete operations by kernel: https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md
310310
// Race conditions here causes that some flows are lost in high-load scenarios
311-
func (m *FlowFetcher) LookupAndDeleteMap() map[BpfFlowId][]BpfFlowMetrics {
311+
func (m *FlowFetcher) LookupAndDeleteMap() map[BpfFlowId]BpfFlowMetrics {
312312
flowMap := m.objects.AggregatedFlows
313313

314314
iterator := flowMap.Iterate()
315-
flows := make(map[BpfFlowId][]BpfFlowMetrics, m.cacheMaxSize)
315+
var flow = make(map[BpfFlowId]BpfFlowMetrics, m.cacheMaxSize)
316316

317317
id := BpfFlowId{}
318-
var metrics []BpfFlowMetrics
318+
var metric BpfFlowMetrics
319319
// Changing Iterate+Delete by LookupAndDelete would prevent some possible race conditions
320320
// TODO: detect whether LookupAndDelete is supported (Kernel>=4.20) and use it selectively
321-
for iterator.Next(&id, &metrics) {
321+
for iterator.Next(&id, &metric) {
322322
if err := flowMap.Delete(id); err != nil {
323323
log.WithError(err).WithField("flowId", id).
324324
Warnf("couldn't delete flow entry")
325325
}
326326
// We observed that eBFP PerCPU map might insert multiple times the same key in the map
327327
// (probably due to race conditions) so we need to re-join metrics again at userspace
328328
// TODO: instrument how many times the keys are is repeated in the same eviction
329-
flows[id] = append(flows[id], metrics...)
329+
flow[id] = metric
330330
}
331-
return flows
331+
return flow
332332
}

pkg/flow/account.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,7 @@ func (c *Accounter) Account(in <-chan *RawRecord, out chan<- []*Record) {
6565
alog.Debug("exiting account routine")
6666
return
6767
}
68-
if stored, ok := c.entries[record.Id]; ok {
69-
Accumulate(stored, &record.Metrics)
70-
} else {
68+
if _, ok := c.entries[record.Id]; !ok {
7169
if len(c.entries) >= c.maxEntries {
7270
evictingEntries := c.entries
7371
c.entries = map[ebpf.BpfFlowId]*ebpf.BpfFlowMetrics{}

0 commit comments

Comments
 (0)