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

Side by Side Diff: net/disk_cache/block_files.cc

Issue 17816008: Disk cache: Introduce BlockBitmaps for V3. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 7 years, 5 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 "net/disk_cache/block_files.h" 5 #include "net/disk_cache/block_files.h"
6 6
7 #include "base/atomicops.h" 7 #include "base/atomicops.h"
8 #include "base/files/file_path.h" 8 #include "base/files/file_path.h"
9 #include "base/metrics/histogram.h" 9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_util.h" 10 #include "base/strings/string_util.h"
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after
170 uint32 map_block = header_->allocation_map[i]; 170 uint32 map_block = header_->allocation_map[i];
171 171
172 for (int j = 0; j < 8; j++, map_block >>= 4) { 172 for (int j = 0; j < 8; j++, map_block >>= 4) {
173 int type = GetMapBlockType(map_block); 173 int type = GetMapBlockType(map_block);
174 if (type) 174 if (type)
175 header_->empty[type -1]++; 175 header_->empty[type -1]++;
176 } 176 }
177 } 177 }
178 } 178 }
179 179
180 bool BlockHeader::NeedToGrowBlockFile(int block_count) { 180 bool BlockHeader::NeedToGrowBlockFile(int block_count) const {
181 bool have_space = false; 181 bool have_space = false;
182 int empty_blocks = 0; 182 int empty_blocks = 0;
183 for (int i = 0; i < kMaxNumBlocks; i++) { 183 for (int i = 0; i < kMaxNumBlocks; i++) {
184 empty_blocks += header_->empty[i] * (i + 1); 184 empty_blocks += header_->empty[i] * (i + 1);
185 if (i >= block_count - 1 && header_->empty[i]) 185 if (i >= block_count - 1 && header_->empty[i])
186 have_space = true; 186 have_space = true;
187 } 187 }
188 188
189 if (header_->next_file && (empty_blocks < kMaxBlocks / 10)) { 189 if (header_->next_file && (empty_blocks < kMaxBlocks / 10)) {
190 // This file is almost full but we already created another one, don't use 190 // This file is almost full but we already created another one, don't use
191 // this file yet so that it is easier to find empty blocks when we start 191 // this file yet so that it is easier to find empty blocks when we start
192 // using this file again. 192 // using this file again.
193 return true; 193 return true;
194 } 194 }
195 return !have_space; 195 return !have_space;
196 } 196 }
197 197
198 bool BlockHeader::CanAllocate(int block_count) const {
199 for (int i = 0; i < kMaxNumBlocks; i++) {
gavinp 2013/08/05 17:06:15 I did a real double take reading this loop. Why do
rvargas (doing something else) 2013/08/05 19:50:45 To avoid a negative values? done
gavinp 2013/08/06 01:11:02 Is it possible for block_count to have a zero or n
rvargas (doing something else) 2013/08/08 02:50:01 Do you mean like the first line of this method (on
200 if (i >= (block_count - 1) && header_->empty[i])
201 return true;
202 }
203
204 return false;
205 }
206
198 int BlockHeader::EmptyBlocks() const { 207 int BlockHeader::EmptyBlocks() const {
199 int empty_blocks = 0; 208 int empty_blocks = 0;
200 for (int i = 0; i < disk_cache::kMaxNumBlocks; i++) { 209 for (int i = 0; i < disk_cache::kMaxNumBlocks; i++) {
201 empty_blocks += header_->empty[i] * (i + 1); 210 empty_blocks += header_->empty[i] * (i + 1);
202 if (header_->empty[i] < 0) 211 if (header_->empty[i] < 0)
203 return 0; 212 return 0;
204 } 213 }
205 return empty_blocks; 214 return empty_blocks;
206 } 215 }
207 216
208 bool BlockHeader::ValidateCounters() const { 217 bool BlockHeader::ValidateCounters() const {
209 if (header_->max_entries < 0 || header_->max_entries > kMaxBlocks || 218 if (header_->max_entries < 0 || header_->max_entries > kMaxBlocks ||
210 header_->num_entries < 0) 219 header_->num_entries < 0)
211 return false; 220 return false;
212 221
213 int empty_blocks = EmptyBlocks(); 222 int empty_blocks = EmptyBlocks();
214 if (empty_blocks + header_->num_entries > header_->max_entries) 223 if (empty_blocks + header_->num_entries > header_->max_entries)
215 return false; 224 return false;
216 225
217 return true; 226 return true;
218 } 227 }
219 228
220 int BlockHeader::Size() const { 229 int BlockHeader::Size() const {
221 return static_cast<int>(sizeof(*header_)); 230 return static_cast<int>(sizeof(*header_));
222 } 231 }
223 232
233 BlockFileHeader* BlockHeader::Header() {
234 return header_;
235 }
236
224 // ------------------------------------------------------------------------ 237 // ------------------------------------------------------------------------
225 238
226 BlockFiles::BlockFiles(const base::FilePath& path) 239 BlockFiles::BlockFiles(const base::FilePath& path)
227 : init_(false), zero_buffer_(NULL), path_(path) { 240 : init_(false), zero_buffer_(NULL), path_(path) {
228 } 241 }
229 242
230 BlockFiles::~BlockFiles() { 243 BlockFiles::~BlockFiles() {
231 if (zero_buffer_) 244 if (zero_buffer_)
232 delete[] zero_buffer_; 245 delete[] zero_buffer_;
233 CloseFiles(); 246 CloseFiles();
(...skipping 19 matching lines...) Expand all
253 if (!RemoveEmptyFile(static_cast<FileType>(i + 1))) 266 if (!RemoveEmptyFile(static_cast<FileType>(i + 1)))
254 return false; 267 return false;
255 } 268 }
256 269
257 init_ = true; 270 init_ = true;
258 return true; 271 return true;
259 } 272 }
260 273
261 MappedFile* BlockFiles::GetFile(Addr address) { 274 MappedFile* BlockFiles::GetFile(Addr address) {
262 DCHECK(thread_checker_->CalledOnValidThread()); 275 DCHECK(thread_checker_->CalledOnValidThread());
263 DCHECK(block_files_.size() >= 4); 276 DCHECK_GE(block_files_.size(),
gavinp 2013/07/15 18:23:36 Convention is to put the constant first, and the t
rvargas (doing something else) 2013/08/05 19:50:45 I believe you are confusing the requirements of EX
gavinp 2013/08/06 01:11:02 I am not aware of this recommended convention, and
rvargas (doing something else) 2013/08/08 02:50:01 I have not access to all emails that have talked a
277 static_cast<size_t>(kFirstAdditionalBlockFile));
264 DCHECK(address.is_block_file() || !address.is_initialized()); 278 DCHECK(address.is_block_file() || !address.is_initialized());
265 if (!address.is_initialized()) 279 if (!address.is_initialized())
266 return NULL; 280 return NULL;
267 281
268 int file_index = address.FileNumber(); 282 int file_index = address.FileNumber();
269 if (static_cast<unsigned int>(file_index) >= block_files_.size() || 283 if (static_cast<unsigned int>(file_index) >= block_files_.size() ||
270 !block_files_[file_index]) { 284 !block_files_[file_index]) {
271 // We need to open the file 285 // We need to open the file
272 if (!OpenBlockFile(file_index)) 286 if (!OpenBlockFile(file_index))
273 return NULL; 287 return NULL;
274 } 288 }
275 DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index)); 289 DCHECK_GE(block_files_.size(), static_cast<unsigned int>(file_index));
276 return block_files_[file_index]; 290 return block_files_[file_index];
277 } 291 }
278 292
279 bool BlockFiles::CreateBlock(FileType block_type, int block_count, 293 bool BlockFiles::CreateBlock(FileType block_type, int block_count,
280 Addr* block_address) { 294 Addr* block_address) {
281 DCHECK(thread_checker_->CalledOnValidThread()); 295 DCHECK(thread_checker_->CalledOnValidThread());
282 if (block_type < RANKINGS || block_type > BLOCK_4K || 296 DCHECK_NE(block_type, EXTERNAL);
gavinp 2013/08/05 17:06:15 This code changes from runtime checks to debug ass
283 block_count < 1 || block_count > 4) 297 DCHECK_NE(block_type, BLOCK_FILES);
298 DCHECK_NE(block_type, BLOCK_ENTRIES);
299 DCHECK_NE(block_type, BLOCK_EVICTED);
300 if (block_count < 1 || block_count > kMaxNumBlocks)
284 return false; 301 return false;
302
285 if (!init_) 303 if (!init_)
286 return false; 304 return false;
287 305
288 MappedFile* file = FileForNewBlock(block_type, block_count); 306 MappedFile* file = FileForNewBlock(block_type, block_count);
289 if (!file) 307 if (!file)
290 return false; 308 return false;
291 309
292 ScopedFlush flush(file); 310 ScopedFlush flush(file);
293 BlockHeader header(file); 311 BlockHeader file_header(file);
294 312
295 int target_size = 0; 313 int target_size = 0;
296 for (int i = block_count; i <= 4; i++) { 314 for (int i = block_count; i <= kMaxNumBlocks; i++) {
297 if (header->empty[i - 1]) { 315 if (file_header.Header()->empty[i - 1]) {
298 target_size = i; 316 target_size = i;
299 break; 317 break;
300 } 318 }
301 } 319 }
302 320
303 DCHECK(target_size); 321 DCHECK(target_size);
304 int index; 322 int index;
305 if (!header.CreateMapBlock(target_size, block_count, &index)) 323 if (!file_header.CreateMapBlock(target_size, block_count, &index))
306 return false; 324 return false;
307 325
308 Addr address(block_type, block_count, header->this_file, index); 326 Addr address(block_type, block_count, file_header.Header()->this_file, index);
309 block_address->set_value(address.value()); 327 block_address->set_value(address.value());
310 Trace("CreateBlock 0x%x", address.value()); 328 Trace("CreateBlock 0x%x", address.value());
311 return true; 329 return true;
312 } 330 }
313 331
314 void BlockFiles::DeleteBlock(Addr address, bool deep) { 332 void BlockFiles::DeleteBlock(Addr address, bool deep) {
315 DCHECK(thread_checker_->CalledOnValidThread()); 333 DCHECK(thread_checker_->CalledOnValidThread());
316 if (!address.is_initialized() || address.is_separate_file()) 334 if (!address.is_initialized() || address.is_separate_file())
317 return; 335 return;
318 336
319 if (!zero_buffer_) { 337 if (!zero_buffer_) {
320 zero_buffer_ = new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4]; 338 zero_buffer_ = new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4];
321 memset(zero_buffer_, 0, Addr::BlockSizeForFileType(BLOCK_4K) * 4); 339 memset(zero_buffer_, 0, Addr::BlockSizeForFileType(BLOCK_4K) * 4);
322 } 340 }
323 MappedFile* file = GetFile(address); 341 MappedFile* file = GetFile(address);
324 if (!file) 342 if (!file)
325 return; 343 return;
326 344
327 Trace("DeleteBlock 0x%x", address.value()); 345 Trace("DeleteBlock 0x%x", address.value());
328 346
329 size_t size = address.BlockSize() * address.num_blocks(); 347 size_t size = address.BlockSize() * address.num_blocks();
330 size_t offset = address.start_block() * address.BlockSize() + 348 size_t offset = address.start_block() * address.BlockSize() +
331 kBlockHeaderSize; 349 kBlockHeaderSize;
332 if (deep) 350 if (deep)
333 file->Write(zero_buffer_, size, offset); 351 file->Write(zero_buffer_, size, offset);
334 352
335 BlockHeader header(file); 353 BlockHeader file_header(file);
336 header.DeleteMapBlock(address.start_block(), address.num_blocks()); 354 file_header.DeleteMapBlock(address.start_block(), address.num_blocks());
337 file->Flush(); 355 file->Flush();
338 356
339 if (!header->num_entries) { 357 if (!file_header.Header()->num_entries) {
340 // This file is now empty. Let's try to delete it. 358 // This file is now empty. Let's try to delete it.
341 FileType type = Addr::RequiredFileType(header->entry_size); 359 FileType type = Addr::RequiredFileType(file_header.Header()->entry_size);
342 if (Addr::BlockSizeForFileType(RANKINGS) == header->entry_size) 360 if (Addr::BlockSizeForFileType(RANKINGS) ==
361 file_header.Header()->entry_size) {
343 type = RANKINGS; 362 type = RANKINGS;
363 }
344 RemoveEmptyFile(type); // Ignore failures. 364 RemoveEmptyFile(type); // Ignore failures.
345 } 365 }
346 } 366 }
347 367
348 void BlockFiles::CloseFiles() { 368 void BlockFiles::CloseFiles() {
349 if (init_) { 369 if (init_) {
350 DCHECK(thread_checker_->CalledOnValidThread()); 370 DCHECK(thread_checker_->CalledOnValidThread());
351 } 371 }
352 init_ = false; 372 init_ = false;
353 for (unsigned int i = 0; i < block_files_.size(); i++) { 373 for (unsigned int i = 0; i < block_files_.size(); i++) {
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
443 LOG(ERROR) << "Failed to open " << name.value(); 463 LOG(ERROR) << "Failed to open " << name.value();
444 return false; 464 return false;
445 } 465 }
446 466
447 size_t file_len = file->GetLength(); 467 size_t file_len = file->GetLength();
448 if (file_len < static_cast<size_t>(kBlockHeaderSize)) { 468 if (file_len < static_cast<size_t>(kBlockHeaderSize)) {
449 LOG(ERROR) << "File too small " << name.value(); 469 LOG(ERROR) << "File too small " << name.value();
450 return false; 470 return false;
451 } 471 }
452 472
453 BlockHeader header(file.get()); 473 BlockHeader file_header(file.get());
474 BlockFileHeader* header = file_header.Header();
454 if (kBlockMagic != header->magic || kBlockVersion2 != header->version) { 475 if (kBlockMagic != header->magic || kBlockVersion2 != header->version) {
455 LOG(ERROR) << "Invalid file version or magic " << name.value(); 476 LOG(ERROR) << "Invalid file version or magic " << name.value();
456 return false; 477 return false;
457 } 478 }
458 479
459 if (header->updating || !header.ValidateCounters()) { 480 if (header->updating || !file_header.ValidateCounters()) {
460 // Last instance was not properly shutdown, or counters are out of sync. 481 // Last instance was not properly shutdown, or counters are out of sync.
461 if (!FixBlockFileHeader(file.get())) { 482 if (!FixBlockFileHeader(file.get())) {
462 LOG(ERROR) << "Unable to fix block file " << name.value(); 483 LOG(ERROR) << "Unable to fix block file " << name.value();
463 return false; 484 return false;
464 } 485 }
465 } 486 }
466 487
467 if (static_cast<int>(file_len) < 488 if (static_cast<int>(file_len) <
468 header->max_entries * header->entry_size + kBlockHeaderSize) { 489 header->max_entries * header->entry_size + kBlockHeaderSize) {
469 LOG(ERROR) << "File too small " << name.value(); 490 LOG(ERROR) << "File too small " << name.value();
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
509 FileLock lock(header); 530 FileLock lock(header);
510 header->empty[3] = (new_size - header->max_entries) / 4; // 4 blocks entries 531 header->empty[3] = (new_size - header->max_entries) / 4; // 4 blocks entries
511 header->max_entries = new_size; 532 header->max_entries = new_size;
512 533
513 return true; 534 return true;
514 } 535 }
515 536
516 MappedFile* BlockFiles::FileForNewBlock(FileType block_type, int block_count) { 537 MappedFile* BlockFiles::FileForNewBlock(FileType block_type, int block_count) {
517 COMPILE_ASSERT(RANKINGS == 1, invalid_file_type); 538 COMPILE_ASSERT(RANKINGS == 1, invalid_file_type);
518 MappedFile* file = block_files_[block_type - 1]; 539 MappedFile* file = block_files_[block_type - 1];
519 BlockHeader header(file); 540 BlockHeader file_header(file);
520 541
521 TimeTicks start = TimeTicks::Now(); 542 TimeTicks start = TimeTicks::Now();
522 while (header.NeedToGrowBlockFile(block_count)) { 543 while (file_header.NeedToGrowBlockFile(block_count)) {
523 if (kMaxBlocks == header->max_entries) { 544 if (kMaxBlocks == file_header.Header()->max_entries) {
524 file = NextFile(file); 545 file = NextFile(file);
525 if (!file) 546 if (!file)
526 return NULL; 547 return NULL;
527 header = BlockHeader(file); 548 file_header = BlockHeader(file);
528 continue; 549 continue;
529 } 550 }
530 551
531 if (!GrowBlockFile(file, header.Get())) 552 if (!GrowBlockFile(file, file_header.Header()))
532 return NULL; 553 return NULL;
533 break; 554 break;
534 } 555 }
535 HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start); 556 HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start);
536 return file; 557 return file;
537 } 558 }
538 559
539 MappedFile* BlockFiles::NextFile(MappedFile* file) { 560 MappedFile* BlockFiles::NextFile(MappedFile* file) {
540 ScopedFlush flush(file); 561 ScopedFlush flush(file);
541 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer()); 562 BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
609 header = next_header; 630 header = next_header;
610 file = next_file; 631 file = next_file;
611 } 632 }
612 return true; 633 return true;
613 } 634 }
614 635
615 // Note that we expect to be called outside of a FileLock... however, we cannot 636 // Note that we expect to be called outside of a FileLock... however, we cannot
616 // DCHECK on header->updating because we may be fixing a crash. 637 // DCHECK on header->updating because we may be fixing a crash.
617 bool BlockFiles::FixBlockFileHeader(MappedFile* file) { 638 bool BlockFiles::FixBlockFileHeader(MappedFile* file) {
618 ScopedFlush flush(file); 639 ScopedFlush flush(file);
619 BlockHeader header(file); 640 BlockHeader file_header(file);
620 int file_size = static_cast<int>(file->GetLength()); 641 int file_size = static_cast<int>(file->GetLength());
621 if (file_size < header.Size()) 642 if (file_size < file_header.Size())
622 return false; // file_size > 2GB is also an error. 643 return false; // file_size > 2GB is also an error.
623 644
624 const int kMinBlockSize = 36; 645 const int kMinBlockSize = 36;
625 const int kMaxBlockSize = 4096; 646 const int kMaxBlockSize = 4096;
647 BlockFileHeader* header = file_header.Header();
626 if (header->entry_size < kMinBlockSize || 648 if (header->entry_size < kMinBlockSize ||
627 header->entry_size > kMaxBlockSize || header->num_entries < 0) 649 header->entry_size > kMaxBlockSize || header->num_entries < 0)
628 return false; 650 return false;
629 651
630 // Make sure that we survive crashes. 652 // Make sure that we survive crashes.
631 header->updating = 1; 653 header->updating = 1;
632 int expected = header->entry_size * header->max_entries + header.Size(); 654 int expected = header->entry_size * header->max_entries + file_header.Size();
633 if (file_size != expected) { 655 if (file_size != expected) {
634 int max_expected = header->entry_size * kMaxBlocks + header.Size(); 656 int max_expected = header->entry_size * kMaxBlocks + file_header.Size();
635 if (file_size < expected || header->empty[3] || file_size > max_expected) { 657 if (file_size < expected || header->empty[3] || file_size > max_expected) {
636 NOTREACHED(); 658 NOTREACHED();
637 LOG(ERROR) << "Unexpected file size"; 659 LOG(ERROR) << "Unexpected file size";
638 return false; 660 return false;
639 } 661 }
640 // We were in the middle of growing the file. 662 // We were in the middle of growing the file.
641 int num_entries = (file_size - header.Size()) / header->entry_size; 663 int num_entries = (file_size - file_header.Size()) / header->entry_size;
642 header->max_entries = num_entries; 664 header->max_entries = num_entries;
643 } 665 }
644 666
645 header.FixAllocationCounters(); 667 file_header.FixAllocationCounters();
646 int empty_blocks = header.EmptyBlocks(); 668 int empty_blocks = file_header.EmptyBlocks();
647 if (empty_blocks + header->num_entries > header->max_entries) 669 if (empty_blocks + header->num_entries > header->max_entries)
648 header->num_entries = header->max_entries - empty_blocks; 670 header->num_entries = header->max_entries - empty_blocks;
649 671
650 if (!header.ValidateCounters()) 672 if (!file_header.ValidateCounters())
651 return false; 673 return false;
652 674
653 header->updating = 0; 675 header->updating = 0;
654 return true; 676 return true;
655 } 677 }
656 678
657 // We are interested in the total number of blocks used by this file type, and 679 // We are interested in the total number of blocks used by this file type, and
658 // the max number of blocks that we can store (reported as the percentage of 680 // the max number of blocks that we can store (reported as the percentage of
659 // used blocks). In order to find out the number of used blocks, we have to 681 // used blocks). In order to find out the number of used blocks, we have to
660 // substract the empty blocks from the total blocks for each file in the chain. 682 // substract the empty blocks from the total blocks for each file in the chain.
661 void BlockFiles::GetFileStats(int index, int* used_count, int* load) { 683 void BlockFiles::GetFileStats(int index, int* used_count, int* load) {
662 int max_blocks = 0; 684 int max_blocks = 0;
663 *used_count = 0; 685 *used_count = 0;
664 *load = 0; 686 *load = 0;
665 for (;;) { 687 for (;;) {
666 if (!block_files_[index] && !OpenBlockFile(index)) 688 if (!block_files_[index] && !OpenBlockFile(index))
667 return; 689 return;
668 690
669 BlockFileHeader* header = 691 BlockFileHeader* header =
670 reinterpret_cast<BlockFileHeader*>(block_files_[index]->buffer()); 692 reinterpret_cast<BlockFileHeader*>(block_files_[index]->buffer());
671 693
672 max_blocks += header->max_entries; 694 max_blocks += header->max_entries;
673 int used = header->max_entries; 695 int used = header->max_entries;
674 for (int i = 0; i < 4; i++) { 696 for (int i = 0; i < kMaxNumBlocks; i++) {
675 used -= header->empty[i] * (i + 1); 697 used -= header->empty[i] * (i + 1);
676 DCHECK_GE(used, 0); 698 DCHECK_GE(used, 0);
677 } 699 }
678 *used_count += used; 700 *used_count += used;
679 701
680 if (!header->next_file) 702 if (!header->next_file)
681 break; 703 break;
682 index = header->next_file; 704 index = header->next_file;
683 } 705 }
684 if (max_blocks) 706 if (max_blocks)
685 *load = *used_count * 100 / max_blocks; 707 *load = *used_count * 100 / max_blocks;
686 } 708 }
687 709
688 base::FilePath BlockFiles::Name(int index) { 710 base::FilePath BlockFiles::Name(int index) {
689 // The file format allows for 256 files. 711 // The file format allows for 256 files.
690 DCHECK(index < 256 || index >= 0); 712 DCHECK(index < 256 || index >= 0);
691 std::string tmp = base::StringPrintf("%s%d", kBlockName, index); 713 std::string tmp = base::StringPrintf("%s%d", kBlockName, index);
692 return path_.AppendASCII(tmp); 714 return path_.AppendASCII(tmp);
693 } 715 }
694 716
695 } // namespace disk_cache 717 } // namespace disk_cache
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698