Repository: kubernetes-sigs/security-profiles-operator
Pull request #519 (2021-07-05 09:36:03)

Add streaming support for enricher metrics
#### What type of PR is this?

/kind cleanup

#### What this PR does / why we need it:
This uses the native GRPC streaming feature to increase the metrics
performance.
#### Which issue(s) this PR fixes:

None

#### Does this PR have test?

None

#### Special notes for your reviewer:

None

#### Does this PR introduce a user-facing change?

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->

```release-note
None
```


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/519#" title="Author self-approved">saschagrunert</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/master/OWNERS)~~ [saschagrunert]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
k8s-ci-robot: @saschagrunert: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-unit | f06452c32e0d19943cd3c0cc9345f9de6bd25286 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/519/pull-security-profiles-operator-test-unit/1411982197946060800) | `/test pull-security-profiles-operator-test-unit`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=519). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaschagrunert). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @saschagrunert: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-unit | f06452c32e0d19943cd3c0cc9345f9de6bd25286 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/519/pull-security-profiles-operator-test-unit/1411982197946060800) | `/test pull-security-profiles-operator-test-unit`
pull-security-profiles-operator-verify | f06452c32e0d19943cd3c0cc9345f9de6bd25286 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/519/pull-security-profiles-operator-verify/1411982197899923456) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=519). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaschagrunert). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @saschagrunert: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 2b18c67559bc4e648fc7308df7047ce82b9a519a | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/519/pull-security-profiles-operator-test-e2e/1411985566752313344) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=519). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaschagrunert). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
saschagrunert: /retest
codecov-commenter: # [Codecov](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/519?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#519](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/519?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (2b18c67) into [master](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/commit/ac205ac7fb35ca8fd28796f156fc7bb219420d69?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (ac205ac) will **decrease** coverage by `0.08%`.
> The diff coverage is `33.33%`.


```diff
@@            Coverage Diff             @@
##           master     #519      +/-   ##
==========================================
- Coverage   43.11%   43.02%   -0.09%     
==========================================
  Files          24       24              
  Lines        1285     1290       +5     
==========================================
+ Hits          554      555       +1     
- Misses        692      695       +3     
- Partials       39       40       +1     
```

saschagrunert: PTAL @JAORMX @jhrozek

Patch:
diff --git a/api/server/api.pb.go b/api/server/api.pb.go
index 53300181b..06f30cd44 100644
--- a/api/server/api.pb.go
+++ b/api/server/api.pb.go
@@ -395,26 +395,26 @@ var file_api_server_api_proto_rawDesc = []byte{
 	0x10, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
 	0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x18, 0x01, 0x20,
 	0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x22, 0x0f, 0x0a,
-	0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x97,
+	0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x99,
 	0x02, 0x0a, 0x18, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x50, 0x72, 0x6f, 0x66, 0x69,
-	0x6c, 0x65, 0x73, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x41, 0x0a, 0x0f, 0x4d,
+	0x6c, 0x65, 0x73, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x43, 0x0a, 0x0f, 0x4d,
 	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x41, 0x75, 0x64, 0x69, 0x74, 0x49, 0x6e, 0x63, 0x12, 0x18,
 	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x41, 0x75, 0x64, 0x69,
 	0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45,
-	0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40,
-	0x0a, 0x0d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x12,
-	0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x53, 0x79, 0x73, 0x63,
-	0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69,
-	0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
-	0x12, 0x39, 0x0a, 0x08, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x12, 0x14, 0x2e, 0x61,
-	0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c,
-	0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0d, 0x52,
-	0x65, 0x73, 0x65, 0x74, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x12, 0x14, 0x2e, 0x61,
-	0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61, 0x70, 0x69,
-	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01,
+	0x12, 0x40, 0x0a, 0x0d, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c,
+	0x6c, 0x12, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x53, 0x79,
+	0x73, 0x63, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61,
+	0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x22, 0x00, 0x12, 0x39, 0x0a, 0x08, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x12, 0x14,
+	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61,
+	0x6c, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a,
+	0x0d, 0x52, 0x65, 0x73, 0x65, 0x74, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x12, 0x14,
+	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x06, 0x5a, 0x04, 0x2f, 0x61,
+	0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/api/server/api.proto b/api/server/api.proto
index 3cc050d23..8b1c64012 100644
--- a/api/server/api.proto
+++ b/api/server/api.proto
@@ -20,7 +20,7 @@ package api;
 option go_package = "/api";
 
 service SecurityProfilesOperator {
-    rpc MetricsAuditInc(MetricsAuditRequest) returns (EmptyResponse) {}
+    rpc MetricsAuditInc(stream MetricsAuditRequest) returns (EmptyResponse) {}
     rpc RecordSyscall(RecordSyscallRequest) returns (EmptyResponse) {}
     rpc Syscalls(SyscallsRequest) returns (SyscallsResponse) {}
     rpc ResetSyscalls(SyscallsRequest) returns (EmptyResponse) {}
diff --git a/api/server/api_grpc.pb.go b/api/server/api_grpc.pb.go
index 9cfa6c7ab..a4dbdd4f1 100644
--- a/api/server/api_grpc.pb.go
+++ b/api/server/api_grpc.pb.go
@@ -18,7 +18,7 @@ const _ = grpc.SupportPackageIsVersion7
 //
 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
 type SecurityProfilesOperatorClient interface {
-	MetricsAuditInc(ctx context.Context, in *MetricsAuditRequest, opts ...grpc.CallOption) (*EmptyResponse, error)
+	MetricsAuditInc(ctx context.Context, opts ...grpc.CallOption) (SecurityProfilesOperator_MetricsAuditIncClient, error)
 	RecordSyscall(ctx context.Context, in *RecordSyscallRequest, opts ...grpc.CallOption) (*EmptyResponse, error)
 	Syscalls(ctx context.Context, in *SyscallsRequest, opts ...grpc.CallOption) (*SyscallsResponse, error)
 	ResetSyscalls(ctx context.Context, in *SyscallsRequest, opts ...grpc.CallOption) (*EmptyResponse, error)
@@ -32,13 +32,38 @@ func NewSecurityProfilesOperatorClient(cc grpc.ClientConnInterface) SecurityProf
 	return &securityProfilesOperatorClient{cc}
 }
 
-func (c *securityProfilesOperatorClient) MetricsAuditInc(ctx context.Context, in *MetricsAuditRequest, opts ...grpc.CallOption) (*EmptyResponse, error) {
-	out := new(EmptyResponse)
-	err := c.cc.Invoke(ctx, "/api.SecurityProfilesOperator/MetricsAuditInc", in, out, opts...)
+func (c *securityProfilesOperatorClient) MetricsAuditInc(ctx context.Context, opts ...grpc.CallOption) (SecurityProfilesOperator_MetricsAuditIncClient, error) {
+	stream, err := c.cc.NewStream(ctx, &SecurityProfilesOperator_ServiceDesc.Streams[0], "/api.SecurityProfilesOperator/MetricsAuditInc", opts...)
 	if err != nil {
 		return nil, err
 	}
-	return out, nil
+	x := &securityProfilesOperatorMetricsAuditIncClient{stream}
+	return x, nil
+}
+
+type SecurityProfilesOperator_MetricsAuditIncClient interface {
+	Send(*MetricsAuditRequest) error
+	CloseAndRecv() (*EmptyResponse, error)
+	grpc.ClientStream
+}
+
+type securityProfilesOperatorMetricsAuditIncClient struct {
+	grpc.ClientStream
+}
+
+func (x *securityProfilesOperatorMetricsAuditIncClient) Send(m *MetricsAuditRequest) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *securityProfilesOperatorMetricsAuditIncClient) CloseAndRecv() (*EmptyResponse, error) {
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	m := new(EmptyResponse)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
 }
 
 func (c *securityProfilesOperatorClient) RecordSyscall(ctx context.Context, in *RecordSyscallRequest, opts ...grpc.CallOption) (*EmptyResponse, error) {
@@ -72,7 +97,7 @@ func (c *securityProfilesOperatorClient) ResetSyscalls(ctx context.Context, in *
 // All implementations must embed UnimplementedSecurityProfilesOperatorServer
 // for forward compatibility
 type SecurityProfilesOperatorServer interface {
-	MetricsAuditInc(context.Context, *MetricsAuditRequest) (*EmptyResponse, error)
+	MetricsAuditInc(SecurityProfilesOperator_MetricsAuditIncServer) error
 	RecordSyscall(context.Context, *RecordSyscallRequest) (*EmptyResponse, error)
 	Syscalls(context.Context, *SyscallsRequest) (*SyscallsResponse, error)
 	ResetSyscalls(context.Context, *SyscallsRequest) (*EmptyResponse, error)
@@ -83,8 +108,8 @@ type SecurityProfilesOperatorServer interface {
 type UnimplementedSecurityProfilesOperatorServer struct {
 }
 
-func (UnimplementedSecurityProfilesOperatorServer) MetricsAuditInc(context.Context, *MetricsAuditRequest) (*EmptyResponse, error) {
-	return nil, status.Errorf(codes.Unimplemented, "method MetricsAuditInc not implemented")
+func (UnimplementedSecurityProfilesOperatorServer) MetricsAuditInc(SecurityProfilesOperator_MetricsAuditIncServer) error {
+	return status.Errorf(codes.Unimplemented, "method MetricsAuditInc not implemented")
 }
 func (UnimplementedSecurityProfilesOperatorServer) RecordSyscall(context.Context, *RecordSyscallRequest) (*EmptyResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method RecordSyscall not implemented")
@@ -109,22 +134,30 @@ func RegisterSecurityProfilesOperatorServer(s grpc.ServiceRegistrar, srv Securit
 	s.RegisterService(&SecurityProfilesOperator_ServiceDesc, srv)
 }
 
-func _SecurityProfilesOperator_MetricsAuditInc_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(MetricsAuditRequest)
-	if err := dec(in); err != nil {
+func _SecurityProfilesOperator_MetricsAuditInc_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(SecurityProfilesOperatorServer).MetricsAuditInc(&securityProfilesOperatorMetricsAuditIncServer{stream})
+}
+
+type SecurityProfilesOperator_MetricsAuditIncServer interface {
+	SendAndClose(*EmptyResponse) error
+	Recv() (*MetricsAuditRequest, error)
+	grpc.ServerStream
+}
+
+type securityProfilesOperatorMetricsAuditIncServer struct {
+	grpc.ServerStream
+}
+
+func (x *securityProfilesOperatorMetricsAuditIncServer) SendAndClose(m *EmptyResponse) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *securityProfilesOperatorMetricsAuditIncServer) Recv() (*MetricsAuditRequest, error) {
+	m := new(MetricsAuditRequest)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
 		return nil, err
 	}
-	if interceptor == nil {
-		return srv.(SecurityProfilesOperatorServer).MetricsAuditInc(ctx, in)
-	}
-	info := &grpc.UnaryServerInfo{
-		Server:     srv,
-		FullMethod: "/api.SecurityProfilesOperator/MetricsAuditInc",
-	}
-	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(SecurityProfilesOperatorServer).MetricsAuditInc(ctx, req.(*MetricsAuditRequest))
-	}
-	return interceptor(ctx, in, info, handler)
+	return m, nil
 }
 
 func _SecurityProfilesOperator_RecordSyscall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
@@ -188,10 +221,6 @@ var SecurityProfilesOperator_ServiceDesc = grpc.ServiceDesc{
 	ServiceName: "api.SecurityProfilesOperator",
 	HandlerType: (*SecurityProfilesOperatorServer)(nil),
 	Methods: []grpc.MethodDesc{
-		{
-			MethodName: "MetricsAuditInc",
-			Handler:    _SecurityProfilesOperator_MetricsAuditInc_Handler,
-		},
 		{
 			MethodName: "RecordSyscall",
 			Handler:    _SecurityProfilesOperator_RecordSyscall_Handler,
@@ -205,6 +234,12 @@ var SecurityProfilesOperator_ServiceDesc = grpc.ServiceDesc{
 			Handler:    _SecurityProfilesOperator_ResetSyscalls_Handler,
 		},
 	},
-	Streams:  []grpc.StreamDesc{},
+	Streams: []grpc.StreamDesc{
+		{
+			StreamName:    "MetricsAuditInc",
+			Handler:       _SecurityProfilesOperator_MetricsAuditInc_Handler,
+			ClientStreams: true,
+		},
+	},
 	Metadata: "api/server/api.proto",
 }
diff --git a/installation-usage.md b/installation-usage.md
index fd2bd5c98..e7ea2eee5 100644
--- a/installation-usage.md
+++ b/installation-usage.md
@@ -9,7 +9,9 @@
   - [Apply profile to pod](#apply-profile-to-pod)
   - [Base syscalls for a container runtime](#base-syscalls-for-a-container-runtime)
   - [Bind workloads to profiles with ProfileBindings](#bind-workloads-to-profiles-with-profilebindings)
-  - [Record profiles from workloads with ProfileRecordings](#record-profiles-from-workloads-with-profilerecordings)
+  - [Record profiles from workloads with <code>ProfileRecordings</code>](#record-profiles-from-workloads-with-)
+    - [Hook based recording](#hook-based-recording)
+    - [Log enricher based recording](#log-enricher-based-recording)
 - [Restricting to a Single Namespace](#restricting-to-a-single-namespace)
 - [Using metrics](#using-metrics)
   - [Available metrics](#available-metrics)
@@ -206,15 +208,19 @@ $ kubectl get pod test-pod -o jsonpath='{.spec.containers[*].securityContext.sec
 {"localhostProfile":"operator/default/generic/profile-complain-unsafe.json","type":"Localhost"}
 ```
 
-### Record profiles from workloads with ProfileRecordings
+### Record profiles from workloads with `ProfileRecordings`
 
 The operator is capable of recording seccomp profiles by the usage of the
-[oci-seccomp-bpf-hook][bpf-hook]. [OCI hooks][hooks] are part of the OCI
-runtime-spec, which allow to hook into the container creation process. The
-Kubernetes container runtime [CRI-O][cri-o] supports those hooks out of the box,
-but has to be configured to listen on the hooks directory where the
-[oci-seccomp-bpf-hook.json][hook-json] located. This can be done via a drop-in
-configuration file, for example:
+[oci-seccomp-bpf-hook][bpf-hook] or by evaluating the [audit][auditd] logs. Both
+methods have its pros and cons as well as separate technical requirements.
+
+#### Hook based recording
+
+[OCI hooks][hooks] are part of the OCI runtime-spec, which allow to hook into
+the container creation process. The Kubernetes container runtime [CRI-O][cri-o]
+supports those hooks out of the box, but has to be configured to listen on the
+hooks directory where the [oci-seccomp-bpf-hook.json][hook-json] is located.
+This can be done via a drop-in configuration file, for example:
 
 ```
 $ cat /etc/crio/crio.conf.d/03-hooks.conf
@@ -234,6 +240,12 @@ and CRI-O work nicely together.
 [hook-json]: https://github.com/containers/oci-seccomp-bpf-hook/blob/50e711/oci-seccomp-bpf-hook.json
 [path]: https://github.com/containers/oci-seccomp-bpf-hook/blob/50e711/oci-seccomp-bpf-hook.json#L4
 
+Unfortunately, [containerd][containerd] is not [capable of running OCI
+hooks][containerd-hooks], yet.
+
+[containerd]: https://containerd.io
+[containerd-hooks]: https://github.com/containerd/cri/issues/405
+
 We can create a new `ProfileRecording` to indicate to the operator that a
 specific workload should be recorded:
 
@@ -244,15 +256,18 @@ metadata:
   name: test-recording
 spec:
   kind: SeccompProfile
+  recorder: hook
   podSelector:
     matchLabels:
       app: alpine
 ```
 
-This means that every workload which contains the label `app=alpine` will from
-now on be recorded into the `SeccompProfile` CRD with the name `test-recording`.
-Please be aware that the resource will be overwritten if recordings are executed
-multiple times.
+Please note the `recorder: hook`, which has to be used to use the hook based
+recording feature. Recordings are linked to workloads by using a `podSelector`,
+which means that every workload, which contains the label `app=alpine`, will
+from now on be recorded into the `SeccompProfile` CRD with the name
+`test-recording`. Please be aware that the resource will be overwritten if
+recordings are executed multiple times.
 
 Now we can start the recording by running a workload which contains the label:
 
@@ -315,6 +330,7 @@ metadata:
   name: recording
 spec:
   kind: SeccompProfile
+  recorder: hook
   podSelector:
     matchLabels:
       app: my-app
@@ -386,6 +402,79 @@ development purposes. It is not recommended to use the OCI hook in production
 clusters because it runs as highly privileged process to trace the container
 workload via a BPF module.
 
+#### Log enricher based recording
+
+When using the log enricher for recording seccomp profiles, please ensure that
+the feature [is enabled within the spod][using-the-log-enricher] configuration
+resource. The log based recording works in the same way with
+[containerd][containerd] and [CRI-O][cri-o], while using the node local
+[audit][auditd] logs as input source of truth.
+
+To record by using the enricher, create a `ProfileRecording` which is using
+`recorder: logs`:
+
+```yaml
+apiVersion: security-profiles-operator.x-k8s.io/v1alpha1
+kind: ProfileRecording
+metadata:
+  name: test-recording
+spec:
+  kind: SeccompProfile
+  recorder: logs
+  podSelector:
+    matchLabels:
+      app: my-app
+```
+
+Then we can create a workload to be recorded, for example two containers within
+a single pod:
+
+```yaml
+apiVersion: v1
+kind: Pod
+metadata:
+  name: my-pod
+  labels:
+    app: my-app
+spec:
+  containers:
+    - name: nginx
+      image: quay.io/security-profiles-operator/test-nginx:1.19.1
+    - name: redis
+      image: quay.io/security-profiles-operator/redis:6.2.1
+```
+
+If the pod is up and running:
+
+```
+> kubectl get pods
+NAME     READY   STATUS    RESTARTS   AGE
+my-pod   2/2     Running   0          18s
+```
+
+Then the enricher should indicate that it receives audit logs for those containers:
+
+```
+> kubectl -n security-profiles-operator logs --since=1m ds/spod log-enricher
+…
+I0705 12:08:18.729660 1843190 enricher.go:136] log-enricher "msg"="audit"  "container"="redis" "executable"="/usr/local/bin/redis-server" "namespace"="default" "node"="127.0.0.1" "pid"=1847839 "pod"="my-pod" "syscallID"=232 "syscallName"="epoll_wait" "timestamp"="1625486870.273:187492" "type"="seccomp"
+```
+
+Now, if we remove the pod:
+
+```
+> kubectl delete pod my-pod
+```
+
+Then the operator will reconcile two seccomp profiles:
+
+```
+> kubectl get sp
+NAME                   STATUS      AGE
+test-recording-nginx   Installed   15s
+test-recording-redis   Installed   15s
+```
+
 ## Restricting to a Single Namespace
 
 The security-profiles-operator can optionally be run to watch SeccompProfiles in
diff --git a/internal/pkg/daemon/enricher/enricher.go b/internal/pkg/daemon/enricher/enricher.go
index ce11ea1b0..8371a6d65 100644
--- a/internal/pkg/daemon/enricher/enricher.go
+++ b/internal/pkg/daemon/enricher/enricher.go
@@ -82,6 +82,11 @@ func (e *Enricher) Run() error {
 	defer e.impl.Close(conn)
 	client := api.NewSecurityProfilesOperatorClient(conn)
 
+	metricsAuditIncClient, err := e.impl.MetricsAuditInc(client)
+	if err != nil {
+		return errors.Wrap(err, "create metrics audit client")
+	}
+
 	// If the file does not exist, then tail will wait for it to appear
 	tailFile, err := e.impl.TailFile(
 		config.AuditLogPath,
@@ -150,8 +155,8 @@ func (e *Enricher) Run() error {
 		if auditLine.type_ == auditTypeSelinux {
 			metricsType = api.MetricsAuditRequest_SELINUX
 		}
-		if _, err := e.impl.MetricsAuditInc(
-			client,
+		if err := e.impl.SendMetric(
+			metricsAuditIncClient,
 			&api.MetricsAuditRequest{
 				Type:       metricsType,
 				Node:       nodeName,
diff --git a/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go b/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
index e9a776a64..a1e8683a5 100644
--- a/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
+++ b/internal/pkg/daemon/enricher/enricherfakes/fake_impl.go
@@ -101,18 +101,17 @@ type FakeImpl struct {
 		result1 *v1.PodList
 		result2 error
 	}
-	MetricsAuditIncStub        func(api.SecurityProfilesOperatorClient, *api.MetricsAuditRequest) (*api.EmptyResponse, error)
+	MetricsAuditIncStub        func(api.SecurityProfilesOperatorClient) (api.SecurityProfilesOperator_MetricsAuditIncClient, error)
 	metricsAuditIncMutex       sync.RWMutex
 	metricsAuditIncArgsForCall []struct {
 		arg1 api.SecurityProfilesOperatorClient
-		arg2 *api.MetricsAuditRequest
 	}
 	metricsAuditIncReturns struct {
-		result1 *api.EmptyResponse
+		result1 api.SecurityProfilesOperator_MetricsAuditIncClient
 		result2 error
 	}
 	metricsAuditIncReturnsOnCall map[int]struct {
-		result1 *api.EmptyResponse
+		result1 api.SecurityProfilesOperator_MetricsAuditIncClient
 		result2 error
 	}
 	NewForConfigStub        func(*rest.Config) (*kubernetes.Clientset, error)
@@ -155,6 +154,18 @@ type FakeImpl struct {
 		result1 *api.EmptyResponse
 		result2 error
 	}
+	SendMetricStub        func(api.SecurityProfilesOperator_MetricsAuditIncClient, *api.MetricsAuditRequest) error
+	sendMetricMutex       sync.RWMutex
+	sendMetricArgsForCall []struct {
+		arg1 api.SecurityProfilesOperator_MetricsAuditIncClient
+		arg2 *api.MetricsAuditRequest
+	}
+	sendMetricReturns struct {
+		result1 error
+	}
+	sendMetricReturnsOnCall map[int]struct {
+		result1 error
+	}
 	TailFileStub        func(string, tail.Config) (*tail.Tail, error)
 	tailFileMutex       sync.RWMutex
 	tailFileArgsForCall []struct {
@@ -533,19 +544,18 @@ func (fake *FakeImpl) ListPodsReturnsOnCall(i int, result1 *v1.PodList, result2
 	}{result1, result2}
 }
 
-func (fake *FakeImpl) MetricsAuditInc(arg1 api.SecurityProfilesOperatorClient, arg2 *api.MetricsAuditRequest) (*api.EmptyResponse, error) {
+func (fake *FakeImpl) MetricsAuditInc(arg1 api.SecurityProfilesOperatorClient) (api.SecurityProfilesOperator_MetricsAuditIncClient, error) {
 	fake.metricsAuditIncMutex.Lock()
 	ret, specificReturn := fake.metricsAuditIncReturnsOnCall[len(fake.metricsAuditIncArgsForCall)]
 	fake.metricsAuditIncArgsForCall = append(fake.metricsAuditIncArgsForCall, struct {
 		arg1 api.SecurityProfilesOperatorClient
-		arg2 *api.MetricsAuditRequest
-	}{arg1, arg2})
+	}{arg1})
 	stub := fake.MetricsAuditIncStub
 	fakeReturns := fake.metricsAuditIncReturns
-	fake.recordInvocation("MetricsAuditInc", []interface{}{arg1, arg2})
+	fake.recordInvocation("MetricsAuditInc", []interface{}{arg1})
 	fake.metricsAuditIncMutex.Unlock()
 	if stub != nil {
-		return stub(arg1, arg2)
+		return stub(arg1)
 	}
 	if specificReturn {
 		return ret.result1, ret.result2
@@ -559,41 +569,41 @@ func (fake *FakeImpl) MetricsAuditIncCallCount() int {
 	return len(fake.metricsAuditIncArgsForCall)
 }
 
-func (fake *FakeImpl) MetricsAuditIncCalls(stub func(api.SecurityProfilesOperatorClient, *api.MetricsAuditRequest) (*api.EmptyResponse, error)) {
+func (fake *FakeImpl) MetricsAuditIncCalls(stub func(api.SecurityProfilesOperatorClient) (api.SecurityProfilesOperator_MetricsAuditIncClient, error)) {
 	fake.metricsAuditIncMutex.Lock()
 	defer fake.metricsAuditIncMutex.Unlock()
 	fake.MetricsAuditIncStub = stub
 }
 
-func (fake *FakeImpl) MetricsAuditIncArgsForCall(i int) (api.SecurityProfilesOperatorClient, *api.MetricsAuditRequest) {
+func (fake *FakeImpl) MetricsAuditIncArgsForCall(i int) api.SecurityProfilesOperatorClient {
 	fake.metricsAuditIncMutex.RLock()
 	defer fake.metricsAuditIncMutex.RUnlock()
 	argsForCall := fake.metricsAuditIncArgsForCall[i]
-	return argsForCall.arg1, argsForCall.arg2
+	return argsForCall.arg1
 }
 
-func (fake *FakeImpl) MetricsAuditIncReturns(result1 *api.EmptyResponse, result2 error) {
+func (fake *FakeImpl) MetricsAuditIncReturns(result1 api.SecurityProfilesOperator_MetricsAuditIncClient, result2 error) {
 	fake.metricsAuditIncMutex.Lock()
 	defer fake.metricsAuditIncMutex.Unlock()
 	fake.MetricsAuditIncStub = nil
 	fake.metricsAuditIncReturns = struct {
-		result1 *api.EmptyResponse
+		result1 api.SecurityProfilesOperator_MetricsAuditIncClient
 		result2 error
 	}{result1, result2}
 }
 
-func (fake *FakeImpl) MetricsAuditIncReturnsOnCall(i int, result1 *api.EmptyResponse, result2 error) {
+func (fake *FakeImpl) MetricsAuditIncReturnsOnCall(i int, result1 api.SecurityProfilesOperator_MetricsAuditIncClient, result2 error) {
 	fake.metricsAuditIncMutex.Lock()
 	defer fake.metricsAuditIncMutex.Unlock()
 	fake.MetricsAuditIncStub = nil
 	if fake.metricsAuditIncReturnsOnCall == nil {
 		fake.metricsAuditIncReturnsOnCall = make(map[int]struct {
-			result1 *api.EmptyResponse
+			result1 api.SecurityProfilesOperator_MetricsAuditIncClient
 			result2 error
 		})
 	}
 	fake.metricsAuditIncReturnsOnCall[i] = struct {
-		result1 *api.EmptyResponse
+		result1 api.SecurityProfilesOperator_MetricsAuditIncClient
 		result2 error
 	}{result1, result2}
 }
@@ -791,6 +801,68 @@ func (fake *FakeImpl) RecordSyscallReturnsOnCall(i int, result1 *api.EmptyRespon
 	}{result1, result2}
 }
 
+func (fake *FakeImpl) SendMetric(arg1 api.SecurityProfilesOperator_MetricsAuditIncClient, arg2 *api.MetricsAuditRequest) error {
+	fake.sendMetricMutex.Lock()
+	ret, specificReturn := fake.sendMetricReturnsOnCall[len(fake.sendMetricArgsForCall)]
+	fake.sendMetricArgsForCall = append(fake.sendMetricArgsForCall, struct {
+		arg1 api.SecurityProfilesOperator_MetricsAuditIncClient
+		arg2 *api.MetricsAuditRequest
+	}{arg1, arg2})
+	stub := fake.SendMetricStub
+	fakeReturns := fake.sendMetricReturns
+	fake.recordInvocation("SendMetric", []interface{}{arg1, arg2})
+	fake.sendMetricMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *FakeImpl) SendMetricCallCount() int {
+	fake.sendMetricMutex.RLock()
+	defer fake.sendMetricMutex.RUnlock()
+	return len(fake.sendMetricArgsForCall)
+}
+
+func (fake *FakeImpl) SendMetricCalls(stub func(api.SecurityProfilesOperator_MetricsAuditIncClient, *api.MetricsAuditRequest) error) {
+	fake.sendMetricMutex.Lock()
+	defer fake.sendMetricMutex.Unlock()
+	fake.SendMetricStub = stub
+}
+
+func (fake *FakeImpl) SendMetricArgsForCall(i int) (api.SecurityProfilesOperator_MetricsAuditIncClient, *api.MetricsAuditRequest) {
+	fake.sendMetricMutex.RLock()
+	defer fake.sendMetricMutex.RUnlock()
+	argsForCall := fake.sendMetricArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *FakeImpl) SendMetricReturns(result1 error) {
+	fake.sendMetricMutex.Lock()
+	defer fake.sendMetricMutex.Unlock()
+	fake.SendMetricStub = nil
+	fake.sendMetricReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *FakeImpl) SendMetricReturnsOnCall(i int, result1 error) {
+	fake.sendMetricMutex.Lock()
+	defer fake.sendMetricMutex.Unlock()
+	fake.SendMetricStub = nil
+	if fake.sendMetricReturnsOnCall == nil {
+		fake.sendMetricReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.sendMetricReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
 func (fake *FakeImpl) TailFile(arg1 string, arg2 tail.Config) (*tail.Tail, error) {
 	fake.tailFileMutex.Lock()
 	ret, specificReturn := fake.tailFileReturnsOnCall[len(fake.tailFileArgsForCall)]
@@ -879,6 +951,8 @@ func (fake *FakeImpl) Invocations() map[string][][]interface{} {
 	defer fake.openMutex.RUnlock()
 	fake.recordSyscallMutex.RLock()
 	defer fake.recordSyscallMutex.RUnlock()
+	fake.sendMetricMutex.RLock()
+	defer fake.sendMetricMutex.RUnlock()
 	fake.tailFileMutex.RLock()
 	defer fake.tailFileMutex.RUnlock()
 	copiedInvocations := map[string][][]interface{}{}
diff --git a/internal/pkg/daemon/enricher/impl.go b/internal/pkg/daemon/enricher/impl.go
index 2bd6c7e55..0a54cd978 100644
--- a/internal/pkg/daemon/enricher/impl.go
+++ b/internal/pkg/daemon/enricher/impl.go
@@ -47,8 +47,11 @@ type impl interface {
 	ListPods(c *kubernetes.Clientset, nodeName string) (*v1.PodList, error)
 	MetricsAuditInc(
 		client api.SecurityProfilesOperatorClient,
+	) (api.SecurityProfilesOperator_MetricsAuditIncClient, error)
+	SendMetric(
+		client api.SecurityProfilesOperator_MetricsAuditIncClient,
 		in *api.MetricsAuditRequest,
-	) (*api.EmptyResponse, error)
+	) error
 	RecordSyscall(
 		client api.SecurityProfilesOperatorClient,
 		in *api.RecordSyscallRequest,
@@ -101,9 +104,15 @@ func (d *defaultImpl) ListPods(
 
 func (d *defaultImpl) MetricsAuditInc(
 	client api.SecurityProfilesOperatorClient,
+) (api.SecurityProfilesOperator_MetricsAuditIncClient, error) {
+	return client.MetricsAuditInc(context.Background())
+}
+
+func (d *defaultImpl) SendMetric(
+	client api.SecurityProfilesOperator_MetricsAuditIncClient,
 	in *api.MetricsAuditRequest,
-) (*api.EmptyResponse, error) {
-	return client.MetricsAuditInc(context.Background(), in)
+) error {
+	return client.Send(in)
 }
 
 func (d *defaultImpl) RecordSyscall(
diff --git a/internal/pkg/daemon/server/server.go b/internal/pkg/daemon/server/server.go
index 216231c5f..2f3b1e009 100644
--- a/internal/pkg/daemon/server/server.go
+++ b/internal/pkg/daemon/server/server.go
@@ -18,6 +18,7 @@ package server
 
 import (
 	"context"
+	"io"
 	"net"
 	"sync"
 
@@ -81,28 +82,38 @@ func Addr() string {
 // MetricSeccompProfileAuditInc updates the metrics for the seccomp audit
 // counter.
 func (s *Server) MetricsAuditInc(
-	ctx context.Context, r *api.MetricsAuditRequest,
-) (*api.EmptyResponse, error) {
-	if r.GetType() == api.MetricsAuditRequest_SECCOMP {
-		s.metrics.IncSeccompProfileAudit(
-			r.GetNode(),
-			r.GetNamespace(),
-			r.GetPod(),
-			r.GetContainer(),
-			r.GetExecutable(),
-			r.GetSyscall(),
-		)
-	} else if r.GetType() == api.MetricsAuditRequest_SELINUX {
-		s.metrics.IncSelinuxProfileAudit(
-			r.GetNode(),
-			r.GetNamespace(),
-			r.GetPod(),
-			r.GetContainer(),
-			r.GetExecutable(),
-			r.GetSyscall(),
-		)
+	stream api.SecurityProfilesOperator_MetricsAuditIncServer,
+) error {
+	for {
+		r, err := stream.Recv()
+		if errors.Is(err, io.EOF) {
+			return stream.SendAndClose(&api.EmptyResponse{})
+		}
+		if err != nil {
+			return errors.Wrap(err, "record syscalls")
+		}
+
+		switch r.GetType() {
+		case api.MetricsAuditRequest_SECCOMP:
+			s.metrics.IncSeccompProfileAudit(
+				r.GetNode(),
+				r.GetNamespace(),
+				r.GetPod(),
+				r.GetContainer(),
+				r.GetExecutable(),
+				r.GetSyscall(),
+			)
+		case api.MetricsAuditRequest_SELINUX:
+			s.metrics.IncSelinuxProfileAudit(
+				r.GetNode(),
+				r.GetNamespace(),
+				r.GetPod(),
+				r.GetContainer(),
+				r.GetExecutable(),
+				r.GetSyscall(),
+			)
+		}
 	}
-	return &api.EmptyResponse{}, nil
 }
 
 // RecordSyscall traces a single syscall for a provided profile.


Test patch:
diff --git a/internal/pkg/daemon/enricher/enricher_test.go b/internal/pkg/daemon/enricher/enricher_test.go
index 47a8b19f7..e3e23d507 100644
--- a/internal/pkg/daemon/enricher/enricher_test.go
+++ b/internal/pkg/daemon/enricher/enricher_test.go
@@ -93,11 +93,11 @@ func TestRun(t *testing.T) {
 					Time: time.Now(),
 				}
 
-				for mock.MetricsAuditIncCallCount() != 1 {
+				for mock.SendMetricCallCount() != 1 {
 					// Wait for MetricsAuditIncCallCount to be called
 				}
 
-				_, res := mock.MetricsAuditIncArgsForCall(0)
+				_, res := mock.SendMetricArgsForCall(0)
 				require.Equal(t, node, res.Node)
 				require.Equal(t, namespace, res.Namespace)
 				require.Equal(t, pod, res.Pod)
diff --git a/test/e2e_test.go b/test/e2e_test.go
index 81b590c4f..c6027c645 100644
--- a/test/e2e_test.go
+++ b/test/e2e_test.go
@@ -126,9 +126,11 @@ func (e *e2e) TestSecurityProfilesOperator() {
 	e.Run("cluster-wide: Seccomp: Verify profile recording", func() {
 		e.testCaseProfileRecordingStaticPodHook()
 		e.testCaseProfileRecordingStaticPodLogs()
-		e.testCaseProfileRecordingKubectlRun()
-		e.testCaseProfileRecordingMultiContainer()
-		e.testCaseProfileRecordingDeployment()
+		e.testCaseProfileRecordingKubectlRunHook()
+		e.testCaseProfileRecordingMultiContainerHook()
+		e.testCaseProfileRecordingMultiContainerLogs()
+		e.testCaseProfileRecordingDeploymentHook()
+		e.testCaseProfileRecordingDeploymentLogs()
 	})
 
 	// Clean up cluster-wide deployment to prepare for namespace deployment
diff --git a/test/tc_profilerecordings_test.go b/test/tc_profilerecordings_test.go
index 48a4dac48..9e22672a4 100644
--- a/test/tc_profilerecordings_test.go
+++ b/test/tc_profilerecordings_test.go
@@ -19,6 +19,7 @@ package e2e_test
 import (
 	"io/ioutil"
 	"os"
+	"regexp"
 	"time"
 )
 
@@ -28,18 +29,45 @@ const (
 	recordingName            = "test-recording"
 )
 
+func (e *e2e) waitForEnricherLogs(since time.Time, conditions ...*regexp.Regexp) {
+	for i := 0; i < 10; i++ {
+		e.logf("Waiting for enricher to record syscalls")
+		logs := e.kubectlOperatorNS(
+			"logs",
+			"--since-time="+since.Format(time.RFC3339),
+			"ds/spod",
+			"log-enricher",
+		)
+
+		matchAll := true
+		for _, condition := range conditions {
+			if !condition.MatchString(logs) {
+				matchAll = false
+			}
+		}
+		if matchAll {
+			break
+		}
+
+		time.Sleep(3 * time.Second)
+	}
+}
+
 func (e *e2e) testCaseProfileRecordingStaticPodHook() {
 	e.profileRecordingTestCase()
-	e.profileRecordingStaticPod(exampleRecordingHookPath, 0)
+	e.profileRecordingStaticPod(exampleRecordingHookPath)
 }
 
 func (e *e2e) testCaseProfileRecordingStaticPodLogs() {
 	e.profileRecordingTestCase()
 	e.logEnricherOnlyTestCase()
-	e.profileRecordingStaticPod(exampleRecordingLogsPath, 10*time.Second)
+	e.profileRecordingStaticPod(
+		exampleRecordingLogsPath,
+		regexp.MustCompile(`(?m)"syscallName"="setuid"`),
+	)
 }
 
-func (e *e2e) profileRecordingStaticPod(recording string, sleepTime time.Duration) {
+func (e *e2e) profileRecordingStaticPod(recording string, waitConditions ...*regexp.Regexp) {
 	e.logf("Creating recording for static pod test")
 	e.kubectl("create", "-f", recording)
 
@@ -63,6 +91,8 @@ spec:
 	e.Nil(err)
 	err = testPodFile.Close()
 	e.Nil(err)
+
+	since := time.Now() // nolint: ifshort
 	e.kubectl("create", "-f", testPodFile.Name())
 
 	e.logf("Waiting for test pod to be initialized")
@@ -70,41 +100,61 @@ spec:
 	e.retryGet("pod", podName)
 	e.waitFor("condition=ready", "pod", podName)
 
-	time.Sleep(sleepTime)
+	if waitConditions != nil {
+		e.waitForEnricherLogs(since, waitConditions...)
+	}
+
 	e.kubectl("delete", "pod", podName)
 
 	resourceName := recordingName + "-nginx"
 	profile := e.retryGetSeccompProfile(resourceName)
-	e.Contains(profile, "close")
+	e.Contains(profile, "setuid")
 
 	e.kubectl("delete", "-f", recording)
 	e.Nil(os.Remove(testPodFile.Name()))
 	e.kubectl("delete", "sp", resourceName)
 }
 
-func (e *e2e) testCaseProfileRecordingKubectlRun() {
+func (e *e2e) testCaseProfileRecordingKubectlRunHook() {
 	e.profileRecordingTestCase()
 
 	e.logf("Creating recording for kubectl run test")
 	e.kubectl("create", "-f", exampleRecordingHookPath)
-	defer e.kubectl("delete", "-f", exampleRecordingHookPath)
 
 	e.logf("Creating test pod")
 	e.kubectlRun("--labels=app=alpine", "fedora", "--", "echo", "test")
 
 	resourceName := recordingName + "-fedora"
 	profile := e.retryGetSeccompProfile(resourceName)
-	defer e.kubectl("delete", "sp", resourceName)
 	e.Contains(profile, "prctl")
 	e.NotContains(profile, "mkdir")
+
+	e.kubectl("delete", "-f", exampleRecordingHookPath)
+	e.kubectl("delete", "sp", resourceName)
+}
+
+func (e *e2e) testCaseProfileRecordingMultiContainerHook() {
+	e.profileRecordingTestCase()
+	e.profileRecordingMultiContainer(exampleRecordingHookPath)
+}
+
+func (e *e2e) testCaseProfileRecordingMultiContainerLogs() {
+	e.profileRecordingTestCase()
+	e.logEnricherOnlyTestCase()
+	e.profileRecordingMultiContainer(
+		exampleRecordingLogsPath,
+		regexp.MustCompile(`(?m)"container"="nginx".*"syscallName"="setuid"`),
+		regexp.MustCompile(`(?m)"container"="redis".*"syscallName"="epoll_wait"`),
+	)
 }
 
-func (e *e2e) testCaseProfileRecordingMultiContainer() {
+func (e *e2e) profileRecordingMultiContainer(
+	recording string, waitConditions ...*regexp.Regexp,
+) {
 	e.profileRecordingTestCase()
 
 	e.logf("Creating recording for multi container test")
-	e.kubectl("create", "-f", exampleRecordingHookPath)
-	defer e.kubectl("delete", "-f", exampleRecordingHookPath)
+	e.kubectl("create", "-f", recording)
 
 	e.logf("Creating test pod")
 	const testPod = `
@@ -124,32 +174,61 @@ spec:
 `
 	testPodFile, err := ioutil.TempFile(os.TempDir(), "recording-pod*.yaml")
 	e.Nil(err)
-	defer os.Remove(testPodFile.Name())
 	_, err = testPodFile.Write([]byte(testPod))
 	e.Nil(err)
 	err = testPodFile.Close()
 	e.Nil(err)
+
+	since := time.Now() // nolint: ifshort
 	e.kubectl("create", "-f", testPodFile.Name())
 
 	e.logf("Waiting for test pod to be initialized")
 	const podName = "my-pod"
 	e.retryGet("pod", podName)
 	e.waitFor("condition=ready", "pod", podName)
+
+	if waitConditions != nil {
+		e.waitForEnricherLogs(since, waitConditions...)
+	}
+
 	e.kubectl("delete", "pod", podName)
 
-	profileRedis := e.retryGetSeccompProfile(recordingName + "-redis")
-	profileNginx := e.retryGetSeccompProfile(recordingName + "-nginx")
-	defer e.kubectl("delete", "sp", "--all")
-	e.Contains(profileNginx, "unlink")
-	e.Contains(profileRedis, "nanosleep")
+	const profileNameRedis = recordingName + "-redis"
+	profileRedis := e.retryGetSeccompProfile(profileNameRedis)
+	e.Contains(profileRedis, "epoll_wait")
+
+	const profileNameNginx = recordingName + "-nginx"
+	profileNginx := e.retryGetSeccompProfile(profileNameNginx)
+	e.Contains(profileNginx, "close")
+
+	e.kubectl("delete", "-f", recording)
+	e.Nil(os.Remove(testPodFile.Name()))
+	e.kubectl("delete", "sp", profileNameRedis, profileNameNginx)
+}
+
+func (e *e2e) testCaseProfileRecordingDeploymentHook() {
+	e.profileRecordingTestCase()
+	e.profileRecordingDeployment(exampleRecordingHookPath)
 }
 
-func (e *e2e) testCaseProfileRecordingDeployment() {
+func (e *e2e) testCaseProfileRecordingDeploymentLogs() {
+	e.profileRecordingTestCase()
+	e.logEnricherOnlyTestCase()
+	e.profileRecordingDeployment(
+		exampleRecordingLogsPath,
+		regexp.MustCompile(
+			`(?s)"container"="nginx".*"syscallName"="setuid"`+
+				`.*"container"="nginx".*"syscallName"="setuid"`),
+	)
+}
+
+func (e *e2e) profileRecordingDeployment(
+	recording string, waitConditions ...*regexp.Regexp,
+) {
 	e.profileRecordingTestCase()
 
 	e.logf("Creating recording for deployment test")
-	e.kubectl("create", "-f", exampleRecordingHookPath)
-	defer e.kubectl("delete", "-f", exampleRecordingHookPath)
+	e.kubectl("create", "-f", recording)
 
 	e.logf("Creating test deployment")
 	const testDeployment = `
@@ -173,23 +252,34 @@ spec:
 `
 	testFile, err := ioutil.TempFile(os.TempDir(), "recording-deployment*.yaml")
 	e.Nil(err)
-	defer os.Remove(testFile.Name())
 	_, err = testFile.Write([]byte(testDeployment))
 	e.Nil(err)
 	err = testFile.Close()
 	e.Nil(err)
+
+	since := time.Now() // nolint: ifshort
 	e.kubectl("create", "-f", testFile.Name())
+
 	const deployName = "my-deployment"
 	e.retryGet("deploy", deployName)
 	e.waitFor("condition=available", "deploy", deployName)
+
+	if waitConditions != nil {
+		e.waitForEnricherLogs(since, waitConditions...)
+	}
+
 	e.kubectl("delete", "deploy", deployName)
 
-	profile0 := e.retryGetSeccompProfile(recordingName + "-nginx-0")
-	profile1 := e.retryGetSeccompProfile(recordingName + "-nginx-1")
-	e.Contains(profile0, "unlink")
-	e.Contains(profile1, "unlink")
-	defer e.kubectl("delete", "sp", recordingName+"-nginx-0")
-	defer e.kubectl("delete", "sp", recordingName+"-nginx-1")
+	const profileName0 = recordingName + "-nginx-0"
+	const profileName1 = recordingName + "-nginx-1"
+	profile0 := e.retryGetSeccompProfile(profileName0)
+	profile1 := e.retryGetSeccompProfile(profileName1)
+	e.Contains(profile0, "setuid")
+	e.Contains(profile1, "setuid")
+
+	e.kubectl("delete", "-f", recording)
+	e.Nil(os.Remove(testFile.Name()))
+	e.kubectl("delete", "sp", profileName0, profileName1)
 }
 
 func (e *e2e) retryGetSeccompProfile(args ...string) string {