Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(132)

Side by Side Diff: base/trace_event/memory_dump_manager_unittest.cc

Issue 1618703004: [MemoryInfra] Support dump providers running on SequencedTaskRunner (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: nits. Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/trace_event/memory_dump_manager.h" 5 #include "base/trace_event/memory_dump_manager.h"
6 6
7 #include <stdint.h> 7 #include <stdint.h>
8 8
9 #include <vector> 9 #include <vector>
10 10
11 #include "base/bind_helpers.h" 11 #include "base/bind_helpers.h"
12 #include "base/memory/scoped_ptr.h" 12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h" 13 #include "base/message_loop/message_loop.h"
14 #include "base/run_loop.h" 14 #include "base/run_loop.h"
15 #include "base/strings/stringprintf.h" 15 #include "base/strings/stringprintf.h"
16 #include "base/synchronization/waitable_event.h" 16 #include "base/synchronization/waitable_event.h"
17 #include "base/test/test_io_thread.h" 17 #include "base/test/test_io_thread.h"
18 #include "base/test/trace_event_analyzer.h" 18 #include "base/test/trace_event_analyzer.h"
19 #include "base/thread_task_runner_handle.h" 19 #include "base/thread_task_runner_handle.h"
20 #include "base/threading/platform_thread.h" 20 #include "base/threading/platform_thread.h"
21 #include "base/threading/sequenced_worker_pool.h"
21 #include "base/threading/thread.h" 22 #include "base/threading/thread.h"
22 #include "base/trace_event/memory_dump_provider.h" 23 #include "base/trace_event/memory_dump_provider.h"
23 #include "base/trace_event/process_memory_dump.h" 24 #include "base/trace_event/process_memory_dump.h"
24 #include "base/trace_event/trace_buffer.h" 25 #include "base/trace_event/trace_buffer.h"
25 #include "base/trace_event/trace_config_memory_test_util.h" 26 #include "base/trace_event/trace_config_memory_test_util.h"
26 #include "testing/gmock/include/gmock/gmock.h" 27 #include "testing/gmock/include/gmock/gmock.h"
27 #include "testing/gtest/include/gtest/gtest.h" 28 #include "testing/gtest/include/gtest/gtest.h"
28 29
29 using testing::_; 30 using testing::_;
30 using testing::AnyNumber; 31 using testing::AnyNumber;
(...skipping 11 matching lines...) Expand all
42 } 43 }
43 44
44 MATCHER(IsLightDump, "") { 45 MATCHER(IsLightDump, "") {
45 return arg.level_of_detail == MemoryDumpLevelOfDetail::LIGHT; 46 return arg.level_of_detail == MemoryDumpLevelOfDetail::LIGHT;
46 } 47 }
47 48
48 namespace { 49 namespace {
49 50
50 void RegisterDumpProvider( 51 void RegisterDumpProvider(
51 MemoryDumpProvider* mdp, 52 MemoryDumpProvider* mdp,
53 const scoped_refptr<base::SequencedTaskRunner>& task_runner,
54 const MemoryDumpProvider::Options& options,
55 bool dumps_on_single_thread_task_runner) {
56 MemoryDumpManager* mdm = MemoryDumpManager::GetInstance();
57 mdm->set_dumper_registrations_ignored_for_testing(false);
58 const char* kMDPName = "TestDumpProvider";
59 if (dumps_on_single_thread_task_runner) {
60 mdm->RegisterDumpProvider(
61 mdp, kMDPName,
62 reinterpret_cast<const scoped_refptr<base::SingleThreadTaskRunner>&>(
Primiano Tucci (use gerrit) 2016/02/18 16:14:37 Hmm this would be a long conversation but TL;DR I
ssid 2016/02/24 19:19:58 Done.
63 task_runner),
64 options);
65 } else {
66 mdm->RegisterDumpProviderWithSequencedTaskRunner(mdp, kMDPName, task_runner,
67 options);
68 }
69 mdm->set_dumper_registrations_ignored_for_testing(true);
70 }
71
72 void RegisterDumpProvider(
73 MemoryDumpProvider* mdp,
52 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, 74 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
53 const MemoryDumpProvider::Options& options) { 75 const MemoryDumpProvider::Options& options) {
54 MemoryDumpManager* mdm = MemoryDumpManager::GetInstance(); 76 RegisterDumpProvider(mdp, task_runner, options,
55 mdm->set_dumper_registrations_ignored_for_testing(false); 77 true /* dumps_on_single_thread_task_runner */);
56 mdm->RegisterDumpProvider(mdp, "TestDumpProvider", task_runner, options);
57 mdm->set_dumper_registrations_ignored_for_testing(true);
58 } 78 }
59 79
60 void RegisterDumpProvider(MemoryDumpProvider* mdp) { 80 void RegisterDumpProvider(MemoryDumpProvider* mdp) {
61 RegisterDumpProvider(mdp, nullptr, MemoryDumpProvider::Options()); 81 RegisterDumpProvider(mdp, nullptr, MemoryDumpProvider::Options());
62 } 82 }
63 83
84 void RegisterDumpProviderWithSequencedTaskRunner(
85 MemoryDumpProvider* mdp,
86 const scoped_refptr<base::SequencedTaskRunner>& task_runner,
87 const MemoryDumpProvider::Options& options) {
88 RegisterDumpProvider(mdp, task_runner, options,
89 false /* dumps_on_single_thread_task_runner */);
90 }
91
64 void OnTraceDataCollected(Closure quit_closure, 92 void OnTraceDataCollected(Closure quit_closure,
65 trace_event::TraceResultBuffer* buffer, 93 trace_event::TraceResultBuffer* buffer,
66 const scoped_refptr<RefCountedString>& json, 94 const scoped_refptr<RefCountedString>& json,
67 bool has_more_events) { 95 bool has_more_events) {
68 buffer->AddFragment(json->data()); 96 buffer->AddFragment(json->data());
69 if (!has_more_events) 97 if (!has_more_events)
70 quit_closure.Run(); 98 quit_closure.Run();
71 } 99 }
72 100
73 } // namespace 101 } // namespace
(...skipping 29 matching lines...) Expand all
103 131
104 MockMemoryDumpProvider() : enable_mock_destructor(false) {} 132 MockMemoryDumpProvider() : enable_mock_destructor(false) {}
105 ~MockMemoryDumpProvider() override { 133 ~MockMemoryDumpProvider() override {
106 if (enable_mock_destructor) 134 if (enable_mock_destructor)
107 Destructor(); 135 Destructor();
108 } 136 }
109 137
110 bool enable_mock_destructor; 138 bool enable_mock_destructor;
111 }; 139 };
112 140
141 class TestSequencedTaskRunner : public SequencedTaskRunner {
142 public:
143 TestSequencedTaskRunner()
144 : worker_pool_(
145 new SequencedWorkerPool(2 /* max_threads */, "Test Task Runner")),
146 disabled_(false),
Primiano Tucci (use gerrit) 2016/02/18 16:14:37 why calling it with negative names? Just call it e
ssid 2016/02/24 19:19:58 fixed.
147 no_of_post_tasks_(0) {}
148
149 void set_enabled() { disabled_ = false; }
Primiano Tucci (use gerrit) 2016/02/18 16:14:37 just set_enabled(bool value) { enabled_ = value; }
ssid 2016/02/24 19:19:58 Done.
150 void set_disabled() { disabled_ = true; }
151 unsigned no_of_post_tasks() const { return no_of_post_tasks_; }
152
153 bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
154 const Closure& task,
155 TimeDelta delay) override {
156 NOTREACHED();
157 return false;
158 }
159
160 bool PostDelayedTask(const tracked_objects::Location& from_here,
161 const Closure& task,
162 TimeDelta delay) override {
163 EXPECT_EQ(TimeDelta(), delay);
Primiano Tucci (use gerrit) 2016/02/18 16:14:37 not sure why you are asserting this. At most this
ssid 2016/02/24 19:19:58 yeah it's not required.
164 no_of_post_tasks_++;
165 if (disabled_)
166 return false;
167 return worker_pool_->PostSequencedWorkerTask(token_, from_here, task);
168 }
169
170 bool RunsTasksOnCurrentThread() const override {
171 return worker_pool_->IsRunningSequenceOnCurrentThread(token_);
172 }
173
174 private:
175 ~TestSequencedTaskRunner() override {}
176
177 scoped_refptr<SequencedWorkerPool> worker_pool_;
178 const SequencedWorkerPool::SequenceToken token_;
179 bool disabled_;
180 unsigned no_of_post_tasks_;
Primiano Tucci (use gerrit) 2016/02/18 16:14:37 s/so/num/
ssid 2016/02/24 19:19:58 Done.
181 };
182
113 class MemoryDumpManagerTest : public testing::Test { 183 class MemoryDumpManagerTest : public testing::Test {
114 public: 184 public:
115 MemoryDumpManagerTest() : testing::Test(), kDefaultOptions() {} 185 MemoryDumpManagerTest() : testing::Test(), kDefaultOptions() {}
116 186
117 void SetUp() override { 187 void SetUp() override {
118 last_callback_success_ = false; 188 last_callback_success_ = false;
119 message_loop_.reset(new MessageLoop()); 189 message_loop_.reset(new MessageLoop());
120 mdm_.reset(new MemoryDumpManager()); 190 mdm_.reset(new MemoryDumpManager());
121 MemoryDumpManager::SetInstanceForTesting(mdm_.get()); 191 MemoryDumpManager::SetInstanceForTesting(mdm_.get());
122 ASSERT_EQ(mdm_.get(), MemoryDumpManager::GetInstance()); 192 ASSERT_EQ(mdm_.get(), MemoryDumpManager::GetInstance());
(...skipping 252 matching lines...) Expand 10 before | Expand all | Expand 10 after
375 { 445 {
376 EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); 446 EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1);
377 EXPECT_CALL(mdp, OnMemoryDump(_, _)).WillOnce(Return(true)); 447 EXPECT_CALL(mdp, OnMemoryDump(_, _)).WillOnce(Return(true));
378 EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); 448 EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory);
379 RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, 449 RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
380 MemoryDumpLevelOfDetail::DETAILED); 450 MemoryDumpLevelOfDetail::DETAILED);
381 DisableTracing(); 451 DisableTracing();
382 } 452 }
383 } 453 }
384 454
385 // Checks that the MemoryDumpManager respects the thread affinity when a 455 // Checks that the MemoryDumpManager respects the task runner affinity when a
386 // MemoryDumpProvider specifies a task_runner(). The test starts creating 8 456 // MemoryDumpProvider specifies task runner. The test starts creating 8 threads
387 // threads and registering a MemoryDumpProvider on each of them. At each 457 // and sequenced task runners and registering a MemoryDumpProvider on each of
388 // iteration, one thread is removed, to check the live unregistration logic. 458 // them. At each iteration, one task runner of each kind is removed, to check
459 // the live unregistration logic.
389 TEST_F(MemoryDumpManagerTest, RespectTaskRunnerAffinity) { 460 TEST_F(MemoryDumpManagerTest, RespectTaskRunnerAffinity) {
390 InitializeMemoryDumpManager(false /* is_coordinator */); 461 InitializeMemoryDumpManager(false /* is_coordinator */);
391 const uint32_t kNumInitialThreads = 8; 462 const uint32_t kNumTaskRunners = 8;
392 463
393 std::vector<scoped_ptr<Thread>> threads; 464 std::vector<scoped_ptr<Thread>> threads;
394 std::vector<scoped_ptr<MockMemoryDumpProvider>> mdps; 465 std::vector<scoped_refptr<TestSequencedTaskRunner>> sequenced_task_runners;
Primiano Tucci (use gerrit) 2016/02/18 16:14:37 hmm I would keep the tests separate otherwise this
ssid 2016/02/24 19:19:58 Done.
466 std::vector<scoped_ptr<MockMemoryDumpProvider>> threaded_mdps;
467 std::vector<scoped_ptr<MockMemoryDumpProvider>> sequenced_mdps;
395 468
396 // Create the threads and setup the expectations. Given that at each iteration 469 // Create the threads and setup the expectations. Given that at each iteration
397 // we will pop out one thread/MemoryDumpProvider, each MDP is supposed to be 470 // we will pop out one task runner of each kind and it's MDP, each MDP is
398 // invoked a number of times equal to its index. 471 // supposed to be invoked a number of times equal to its index.
399 for (uint32_t i = kNumInitialThreads; i > 0; --i) { 472 for (uint32_t i = kNumTaskRunners; i > 0; --i) {
400 threads.push_back(make_scoped_ptr(new Thread("test thread"))); 473 threads.push_back(make_scoped_ptr(new Thread("test thread")));
401 auto thread = threads.back().get(); 474 auto thread = threads.back().get();
402 thread->Start(); 475 thread->Start();
403 scoped_refptr<SingleThreadTaskRunner> task_runner = thread->task_runner(); 476 scoped_refptr<SingleThreadTaskRunner> thread_task_runner =
404 mdps.push_back(make_scoped_ptr(new MockMemoryDumpProvider())); 477 thread->task_runner();
405 auto mdp = mdps.back().get(); 478 sequenced_task_runners.push_back(
406 RegisterDumpProvider(mdp, task_runner, kDefaultOptions); 479 make_scoped_refptr(new TestSequencedTaskRunner()));
407 EXPECT_CALL(*mdp, OnMemoryDump(_, _)) 480 auto sequenced_task_runner = sequenced_task_runners.back().get();
481
482 threaded_mdps.push_back(make_scoped_ptr(new MockMemoryDumpProvider()));
483 auto thread_mdp = threaded_mdps.back().get();
484 sequenced_mdps.push_back(make_scoped_ptr(new MockMemoryDumpProvider()));
485 auto sequenced_runner_mdp = sequenced_mdps.back().get();
486
487 RegisterDumpProvider(thread_mdp, thread_task_runner, kDefaultOptions);
488 RegisterDumpProviderWithSequencedTaskRunner(
489 sequenced_runner_mdp, sequenced_task_runner, kDefaultOptions);
490
491 EXPECT_CALL(*thread_mdp, OnMemoryDump(_, _))
408 .Times(i) 492 .Times(i)
409 .WillRepeatedly(Invoke( 493 .WillRepeatedly(
410 [task_runner](const MemoryDumpArgs&, ProcessMemoryDump*) -> bool { 494 Invoke([thread_task_runner](const MemoryDumpArgs&,
411 EXPECT_TRUE(task_runner->RunsTasksOnCurrentThread()); 495 ProcessMemoryDump*) -> bool {
496 EXPECT_TRUE(thread_task_runner->RunsTasksOnCurrentThread());
412 return true; 497 return true;
413 })); 498 }));
499
500 EXPECT_CALL(*sequenced_runner_mdp, OnMemoryDump(_, _))
501 .Times(i)
502 .WillRepeatedly(Invoke([sequenced_task_runner](
503 const MemoryDumpArgs&,
504 ProcessMemoryDump* pmd) -> bool {
505 EXPECT_TRUE(
506 SequencedWorkerPool::GetSequenceTokenForCurrentThread().Equals(
507 SequencedWorkerPool::SequenceToken()));
508 EXPECT_TRUE(sequenced_task_runner->RunsTasksOnCurrentThread());
509 return true;
510 }));
414 } 511 }
415 EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); 512 EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory);
416 513
417 while (!threads.empty()) { 514 for (uint32_t i = 1; i <= kNumTaskRunners; ++i) {
418 last_callback_success_ = false; 515 last_callback_success_ = false;
419 EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1); 516 EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(1);
420 RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, 517 RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
421 MemoryDumpLevelOfDetail::DETAILED); 518 MemoryDumpLevelOfDetail::DETAILED);
422 EXPECT_TRUE(last_callback_success_); 519 EXPECT_TRUE(last_callback_success_);
520 // Check that each dump call was posted for the last sequenced task runner.
521 EXPECT_EQ(i, sequenced_task_runners.back()->no_of_post_tasks());
423 522
424 // Unregister a MDP and destroy one thread at each iteration to check the 523 // Destroy one thread and one sequenced task runner and their corresponding
425 // live unregistration logic. The unregistration needs to happen on the same 524 // MDPs at each iteration to check the live unregistration logic. The
426 // thread the MDP belongs to. 525 // unregistration needs to happen on the same task runner the MDP belongs
526 // to.
427 { 527 {
428 RunLoop run_loop; 528 RunLoop run_loop;
429 Closure unregistration = 529 Closure unregistration1 =
430 Bind(&MemoryDumpManager::UnregisterDumpProvider, 530 Bind(&MemoryDumpManager::UnregisterDumpProvider,
431 Unretained(mdm_.get()), Unretained(mdps.back().get())); 531 Unretained(mdm_.get()), Unretained(threaded_mdps.back().get()));
432 threads.back()->task_runner()->PostTaskAndReply(FROM_HERE, unregistration, 532 Closure unregistration2 =
433 run_loop.QuitClosure()); 533 Bind(&MemoryDumpManager::UnregisterDumpProvider,
534 Unretained(mdm_.get()), Unretained(sequenced_mdps.back().get()));
535 threads.back()->task_runner()->PostTask(FROM_HERE, unregistration1);
536 sequenced_task_runners.back()->PostTaskAndReply(
537 FROM_HERE, unregistration2, run_loop.QuitClosure());
434 run_loop.Run(); 538 run_loop.Run();
435 } 539 }
436 mdps.pop_back(); 540
541 threaded_mdps.pop_back();
542 sequenced_mdps.pop_back();
437 threads.back()->Stop(); 543 threads.back()->Stop();
544 sequenced_task_runners.pop_back();
438 threads.pop_back(); 545 threads.pop_back();
439 } 546 }
440 547
441 DisableTracing(); 548 DisableTracing();
442 } 549 }
443 550
551 // Check that the memory dump calls are always posted on task runner for
552 // SequencedTaskRunner case and that the dump provider gets disabled when
553 // PostTask fails, but the dump still succeeds.
554 TEST_F(MemoryDumpManagerTest, PostTaskForSequencedTaskRunner) {
555 InitializeMemoryDumpManager(false /* is_coordinator */);
556 std::vector<MockMemoryDumpProvider> mdps(3);
557 scoped_refptr<TestSequencedTaskRunner> task_runner1(
558 make_scoped_refptr(new TestSequencedTaskRunner()));
559 scoped_refptr<TestSequencedTaskRunner> task_runner2(
560 make_scoped_refptr(new TestSequencedTaskRunner()));
561 RegisterDumpProviderWithSequencedTaskRunner(&mdps[0], task_runner1,
562 kDefaultOptions);
563 RegisterDumpProviderWithSequencedTaskRunner(&mdps[1], task_runner2,
564 kDefaultOptions);
565 RegisterDumpProviderWithSequencedTaskRunner(&mdps[2], task_runner2,
566 kDefaultOptions);
567 task_runner1->set_disabled();
568 EXPECT_CALL(mdps[0], OnMemoryDump(_, _)).Times(0);
569 EXPECT_CALL(mdps[1], OnMemoryDump(_, _)).Times(2);
570 EXPECT_CALL(mdps[2], OnMemoryDump(_, _)).Times(2);
571 EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(2);
572
573 EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory);
574 last_callback_success_ = false;
575 RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
576 MemoryDumpLevelOfDetail::DETAILED);
577 // Tasks should be posted even if |mdps[1]| and |mdps[2]| belong to same task
Primiano Tucci (use gerrit) 2016/02/18 16:14:37 s/posted/individually posted/
ssid 2016/02/24 19:19:59 Done.
578 // runner.
579 EXPECT_EQ(1u, task_runner1->no_of_post_tasks());
580 EXPECT_EQ(2u, task_runner2->no_of_post_tasks());
581 EXPECT_TRUE(last_callback_success_);
582
583 // Check that |mdps[0]|'s OnMemoryDump is not called even if the task runner
584 // is enabled since |mdps[0]| was disabled previously.
585 task_runner1->set_enabled();
586 last_callback_success_ = false;
587 RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
588 MemoryDumpLevelOfDetail::DETAILED);
589 EXPECT_EQ(2u, task_runner1->no_of_post_tasks());
Primiano Tucci (use gerrit) 2016/02/18 16:14:37 Hmm this I don't follow: how does task_runner1 get
ssid 2016/02/24 19:19:58 The comment is at wrong place. The post task happe
590 EXPECT_EQ(4u, task_runner2->no_of_post_tasks());
591 EXPECT_TRUE(last_callback_success_);
592 DisableTracing();
593 }
594
444 // Checks that providers get disabled after 3 consecutive failures, but not 595 // Checks that providers get disabled after 3 consecutive failures, but not
445 // otherwise (e.g., if interleaved). 596 // otherwise (e.g., if interleaved).
446 TEST_F(MemoryDumpManagerTest, DisableFailingDumpers) { 597 TEST_F(MemoryDumpManagerTest, DisableFailingDumpers) {
447 InitializeMemoryDumpManager(false /* is_coordinator */); 598 InitializeMemoryDumpManager(false /* is_coordinator */);
448 MockMemoryDumpProvider mdp1; 599 MockMemoryDumpProvider mdp1;
449 MockMemoryDumpProvider mdp2; 600 MockMemoryDumpProvider mdp2;
450 601
451 RegisterDumpProvider(&mdp1); 602 RegisterDumpProvider(&mdp1);
452 RegisterDumpProvider(&mdp2); 603 RegisterDumpProvider(&mdp2);
453 EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory); 604 EnableTracingWithLegacyCategories(MemoryDumpManager::kTraceCategory);
(...skipping 519 matching lines...) Expand 10 before | Expand all | Expand 10 after
973 EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(2); 1124 EXPECT_CALL(*delegate_, RequestGlobalMemoryDump(_, _)).Times(2);
974 for (int i = 0; i < 2; ++i) { 1125 for (int i = 0; i < 2; ++i) {
975 RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED, 1126 RequestGlobalDumpAndWait(MemoryDumpType::EXPLICITLY_TRIGGERED,
976 MemoryDumpLevelOfDetail::DETAILED); 1127 MemoryDumpLevelOfDetail::DETAILED);
977 } 1128 }
978 DisableTracing(); 1129 DisableTracing();
979 } 1130 }
980 1131
981 } // namespace trace_event 1132 } // namespace trace_event
982 } // namespace base 1133 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698