#include "huge_page_deducer.h" #include "base/logging.h" #include "compat/string.h" #include "compat/test.h" namespace quipper { namespace { using PerfEvent = PerfDataProto::PerfEvent; using MMapEvent = PerfDataProto::MMapEvent; using ::testing::EqualsProto; using ::testing::Pointwise; using ::testing::proto::Partially; // AddMmap is a helper function to create simple MMapEvents, with which // testcases can encode "maps" entries similar to /proc/self/maps in a tabular // one-line-per-entry. void AddMmap(uint32_t pid, uint64_t mmap_start, uint64_t length, uint64_t pgoff, const string& file, RepeatedPtrField<PerfEvent>* events) { MMapEvent* ev = events->Add()->mutable_mmap_event(); ev->set_pid(pid); ev->set_start(mmap_start); ev->set_len(length); ev->set_pgoff(pgoff); ev->set_filename(file); } TEST(HugePageDeducer, HugePagesMappings) { RepeatedPtrField<PerfEvent> events; { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x40000000); ev->set_len(0x18000); ev->set_pgoff(0); ev->set_filename("/usr/lib/libfoo.so"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x40018000); ev->set_len(0x1e8000); ev->set_pgoff(0); ev->set_filename("/opt/google/chrome/chrome"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x40200000); ev->set_len(0x1c00000); ev->set_pgoff(0); ev->set_filename("//anon"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x41e00000); ev->set_len(0x4000000); ev->set_pgoff(0x1de8000); ev->set_filename("/opt/google/chrome/chrome"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(2345); ev->set_start(0x45e00000); ev->set_len(0x1e00000); ev->set_pgoff(0); ev->set_filename("//anon"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(2345); ev->set_start(0x47c00000); ev->set_len(0x4000000); ev->set_pgoff(0x1e00000); ev->set_filename("/opt/google/chrome/chrome"); } DeduceHugePages(&events); CombineMappings(&events); ASSERT_GE(events.size(), 3); EXPECT_EQ(events.size(), 3); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), { "mmap_event: { start: 0x40000000 len:0x18000 " "pgoff: 0 filename: '/usr/lib/libfoo.so'}", "mmap_event: { start: 0x40018000 len:0x5de8000 " "pgoff: 0 filename: '/opt/google/chrome/chrome'}", "mmap_event: { start: 0x45e00000 len:0x5e00000 " "pgoff: 0 filename: '/opt/google/chrome/chrome'}", })); EXPECT_EQ("/usr/lib/libfoo.so", events[0].mmap_event().filename()); EXPECT_EQ(0x40000000, events[0].mmap_event().start()); EXPECT_EQ(0x18000, events[0].mmap_event().len()); EXPECT_EQ(0x0, events[0].mmap_event().pgoff()); // The split Chrome mappings should have been combined. EXPECT_EQ("/opt/google/chrome/chrome", events[2].mmap_event().filename()); EXPECT_EQ(0x40018000, events[1].mmap_event().start()); EXPECT_EQ(0x5de8000, events[1].mmap_event().len()); EXPECT_EQ(0x0, events[1].mmap_event().pgoff()); EXPECT_EQ("/opt/google/chrome/chrome", events[2].mmap_event().filename()); EXPECT_EQ(0x45e00000, events[2].mmap_event().start()); EXPECT_EQ(0x5e00000, events[2].mmap_event().len()); EXPECT_EQ(0x0, events[2].mmap_event().pgoff()); } enum HugepageTextStyle { kAnonHugepageText, kNoHugepageText, }; class HugepageTextStyleDependent : public ::testing::TestWithParam<HugepageTextStyle> { protected: void AddHugepageTextMmap(uint32_t pid, uint64_t mmap_start, uint64_t length, uint64_t pgoff, string file, RepeatedPtrField<PerfEvent>* events) { // Various hugepage implementations and perf versions result in various // quirks in how hugepages are reported. switch (GetParam()) { case kNoHugepageText: // Do nothing; the maps are complete and file-backed break; case kAnonHugepageText: // exec is remapped into anonymous memory, which perf reports as // '//anon'. Anonymous sections have no pgoff. file = "//anon"; pgoff = 0; break; default: CHECK(false) << "Unimplemented"; } AddMmap(pid, mmap_start, length, pgoff, file, events); } }; TEST_P(HugepageTextStyleDependent, OnlyOneMappingThatIsHuge) { RepeatedPtrField<PerfEvent> events; AddHugepageTextMmap(1, 0x100200000, 0x200000, 0, "file", &events); DeduceHugePages(&events); CombineMappings(&events); // Don't check filename='file'; if it's backed by anonymous memory, it isn't // possible for quipper to deduce the filename without other mmaps immediately // adjacent. EXPECT_THAT( events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0x100200000 len: 0x200000 pgoff: 0}"})); } TEST_P(HugepageTextStyleDependent, OnlyOneMappingUnaligned) { RepeatedPtrField<PerfEvent> events; AddMmap(2, 0x200201000, 0x200000, 0, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0x200201000 " "len:0x200000 pgoff: 0 filename: 'file'}"})); } TEST_P(HugepageTextStyleDependent, FirstPageIsHugeWithSmallTail) { RepeatedPtrField<PerfEvent> events; AddHugepageTextMmap(3, 0x300400000, 0x400000, 0, "file", &events); AddMmap(3, 0x300800000, 0x001000, 0x400000, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0x300400000 " "len:0x401000 pgoff: 0 filename: 'file'}"})); } TEST_P(HugepageTextStyleDependent, DISABLED_FirstPageIsSmallWithHugeTail) { // This test is disabled because DeduceHugePage requires a non-zero pgoff // *after* a hugepage_text section in order to correctly deduce it, so it // is unable to deduce these cases. RepeatedPtrField<PerfEvent> events; AddMmap(4, 0x4003ff000, 0x001000, 0, "file", &events); AddHugepageTextMmap(4, 0x400400000, 0x200000, 0x001000, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0x4003ff000 " "len:0x201000 pgoff: 0 filename: 'file'}"})); } TEST_P(HugepageTextStyleDependent, HugePageBetweenTwoSmallSections) { RepeatedPtrField<PerfEvent> events; AddMmap(5, 0x5003ff000, 0x001000, 0, "file", &events); AddHugepageTextMmap(5, 0x500400000, 0x200000, 0x001000, "file", &events); AddMmap(5, 0x500600000, 0x001000, 0x201000, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0x5003ff000 " "len:0x202000 pgoff: 0 filename: 'file'}"})); } TEST_P(HugepageTextStyleDependent, HugePageSplitByEarlyMlockBetweenTwoSmall) { RepeatedPtrField<PerfEvent> events; AddMmap(6, 0x6003ff000, 0x001000, 0, "file", &events); AddHugepageTextMmap(6, 0x600400000, 0x3f8000, 0x001000, "file", &events); AddHugepageTextMmap(6, 0x6007f8000, 0x008000, 0x3f9000, "file", &events); AddMmap(6, 0x600800000, 0x001000, 0x401000, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0x6003ff000 " "len:0x402000 pgoff: 0 filename: 'file'}"})); } TEST_P(HugepageTextStyleDependent, HugePageSplitByLateMlockBetweenTwoSmall) { RepeatedPtrField<PerfEvent> events; AddMmap(7, 0x7003ff000, 0x001000, 0, "file", &events); AddHugepageTextMmap(7, 0x700400000, 0x008000, 0x001000, "file", &events); AddHugepageTextMmap(7, 0x700408000, 0x3f8000, 0x009000, "file", &events); AddMmap(7, 0x700800000, 0x001000, 0x401000, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0x7003ff000 " "len:0x402000 pgoff: 0 filename: 'file'}"})); } TEST_P(HugepageTextStyleDependent, HugePageSplitEvenlyByMlockBetweenTwoSmall) { RepeatedPtrField<PerfEvent> events; AddMmap(8, 0x8003ff000, 0x001000, 0, "file", &events); AddHugepageTextMmap(8, 0x800400000, 0x0f8000, 0x001000, "file", &events); AddHugepageTextMmap(8, 0x8004f8000, 0x008000, 0x0f9000, "file", &events); AddHugepageTextMmap(8, 0x800500000, 0x100000, 0x101000, "file", &events); AddMmap(8, 0x800600000, 0x001000, 0x201000, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0x8003ff000 " "len:0x202000 pgoff: 0 filename: 'file'}"})); } TEST_P(HugepageTextStyleDependent, MultipleContiguousHugepages) { RepeatedPtrField<PerfEvent> events; AddMmap(9, 0x9003ff000, 0x001000, 0, "file", &events); AddHugepageTextMmap(9, 0x900400000, 0x200000, 0x001000, "file", &events); AddHugepageTextMmap(9, 0x900600000, 0x200000, 0x201000, "file", &events); AddMmap(9, 0x900800000, 0x001000, 0x401000, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0x9003ff000 " "len:0x402000 pgoff: 0 filename: 'file'}"})); } TEST_P(HugepageTextStyleDependent, MultipleContiguousMlockSplitHugepages) { // Think: // - hugepage_text 4MiB range // - mlock alternating 512-KiB chunks RepeatedPtrField<PerfEvent> events; AddMmap(10, 0xa003ff000, 0x001000, 0, "file", &events); AddHugepageTextMmap(10, 0xa00400000, 0x080000, 0x001000, "file", &events); AddHugepageTextMmap(10, 0xa00480000, 0x080000, 0x081000, "file", &events); AddHugepageTextMmap(10, 0xa00500000, 0x080000, 0x101000, "file", &events); AddHugepageTextMmap(10, 0xa00580000, 0x080000, 0x181000, "file", &events); AddHugepageTextMmap(10, 0xa00600000, 0x080000, 0x201000, "file", &events); AddHugepageTextMmap(10, 0xa00680000, 0x080000, 0x281000, "file", &events); AddHugepageTextMmap(10, 0xa00700000, 0x080000, 0x301000, "file", &events); AddHugepageTextMmap(10, 0xa00780000, 0x080000, 0x381000, "file", &events); AddMmap(10, 0xa00800000, 0x001000, 0x401000, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0xa003ff000 " "len:0x402000 pgoff: 0 filename: 'file'}"})); } TEST_P(HugepageTextStyleDependent, MultipleWithUnalignedInitialHugePage) { // Base on real program RepeatedPtrField<PerfEvent> events; AddHugepageTextMmap(11, 0x85d32e000, 0x6d2000, 0x0, "file", &events); AddHugepageTextMmap(11, 0x85da00000, 0x6a00000, 0x6d2000, "file", &events); AddMmap(11, 0x864400000, 0x200000, 0x70d2000, "file", &events); AddHugepageTextMmap(11, 0x864600000, 0x200000, 0x72d2000, "file", &events); AddMmap(11, 0x864800000, 0x600000, 0x74d2000, "file", &events); AddHugepageTextMmap(11, 0x864e00000, 0x200000, 0x7ad2000, "file", &events); AddMmap(11, 0x865000000, 0x4a000, 0x7cd2000, "file", &events); AddMmap(11, 0x86504a000, 0x1000, 0x7d1c000, "file", &events); AddMmap(11, 0xa3d368000, 0x3a96000, 0x0, "file2", &events); AddMmap(11, 0xa467cc000, 0x2000, 0x0, "file3", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), { "mmap_event: { start: 0x85d32e000 " "len:0x7d1d000 pgoff: 0 filename: 'file'}", "mmap_event: { start: 0xa3d368000 " "len:0x3a96000 pgoff: 0 filename: 'file2'}", "mmap_event: { start: 0xa467cc000 " "len:0x2000, pgoff: 0 filename: 'file3'}", })); } TEST_P(HugepageTextStyleDependent, MultipleWithUnalignedInitialHugePage2) { // Base on real program RepeatedPtrField<PerfEvent> events; AddHugepageTextMmap(12, 0xbcff6000, 0x200000, 0x00000000, "file", &events); AddMmap(12, 0xbd1f6000, 0x300a000, 0x200000, "file", &events); AddHugepageTextMmap(12, 0xc0200000, 0x2b374000, 0x320a000, "file", &events); AddHugepageTextMmap(12, 0xeb574000, 0x514000, 0x2e57e000, "file", &events); AddHugepageTextMmap(12, 0xeba88000, 0x1d78000, 0x2ea92000, "file", &events); AddMmap(12, 0xed800000, 0x1200000, 0x3080a000, "file", &events); AddHugepageTextMmap(12, 0xeea00000, 0x200000, 0x31a0a000, "file", &events); AddMmap(12, 0xeec00000, 0x2800000, 0x31c0a000, "file", &events); AddHugepageTextMmap(12, 0xf1400000, 0x200000, 0x3440a000, "file", &events); AddMmap(12, 0xf1600000, 0x89f000, 0x3460a000, "file", &events); AddMmap(12, 0xf1e9f000, 0x1000, 0x34ea9000, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { start: 0xbcff6000 " "len:0x34eaa000 pgoff: 0 filename: 'file'}"})); } TEST_P(HugepageTextStyleDependent, NoMmaps) { RepeatedPtrField<PerfEvent> events; events.Add(); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(EqualsProto(), std::vector<PerfEvent>(1))); } TEST_P(HugepageTextStyleDependent, MultipleNonMmaps) { RepeatedPtrField<PerfEvent> events; events.Add(); events.Add(); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(EqualsProto(), std::vector<PerfEvent>(2))); } TEST_P(HugepageTextStyleDependent, NonMmapFirstMmap) { RepeatedPtrField<PerfEvent> events; events.Add(); AddHugepageTextMmap(12, 0, 0x200000, 0, "file", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"", "mmap_event: { pgoff: 0 }"})); } TEST_P(HugepageTextStyleDependent, NonMmapAfterLastMmap) { RepeatedPtrField<PerfEvent> events; AddHugepageTextMmap(12, 0, 0x200000, 0, "file", &events); events.Add(); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), {"mmap_event: { pgoff: 0 }", ""})); } INSTANTIATE_TEST_CASE_P(NoHugepageText, HugepageTextStyleDependent, ::testing::Values(kNoHugepageText)); INSTANTIATE_TEST_CASE_P(AnonHugepageText, HugepageTextStyleDependent, ::testing::Values(kAnonHugepageText)); TEST(HugePageDeducer, DoesNotChangeVirtuallyContiguousPgoffNonContiguous) { // We've seen programs with strange memory layouts having virtually contiguous // memory backed by non-contiguous bits of a file. RepeatedPtrField<PerfEvent> events; AddMmap(758463, 0x2f278000, 0x20000, 0, "lib0.so", &events); AddMmap(758463, 0x2f29d000, 0x2000, 0, "shm", &events); AddMmap(758463, 0x2f2a2000, 0xa000, 0, "lib1.so", &events); AddMmap(758463, 0x3d400000, 0x9ee000, 0, "lib2.so", &events); AddMmap(758463, 0x3e000000, 0x16000, 0, "lib3.so", &events); AddMmap(758463, 0x3e400000, 0x270000, 0x1a00000, "shm", &events); AddMmap(758463, 0x3e670000, 0x10000, 0x1aaac000, "shm", &events); AddMmap(758463, 0x3e680000, 0x10000, 0x1b410000, "shm", &events); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT(events, Pointwise(Partially(EqualsProto()), { "mmap_event: { pgoff: 0 filename: 'lib0.so' }", "mmap_event: { pgoff: 0 filename: 'shm' }", "mmap_event: { pgoff: 0 filename: 'lib1.so' }", "mmap_event: { pgoff: 0 filename: 'lib2.so' }", "mmap_event: { pgoff: 0 filename: 'lib3.so' }", "mmap_event: { pgoff: 0x1a00000 filename: 'shm' }", "mmap_event: { pgoff: 0x1aaac000 filename: 'shm' }", "mmap_event: { pgoff: 0x1b410000 filename: 'shm' }", })); } TEST(HugePageDeducer, IgnoresDynamicMmaps) { // Now, let's watch a binary hugepage_text itself. RepeatedPtrField<PerfEvent> events; AddMmap(6531, 0x560d76b25000, 0x24ce000, 0, "main", &events); events.rbegin()->set_timestamp(700413232676401); AddMmap(6531, 0x7f686a1ec000, 0x24000, 0, "ld.so", &events); events.rbegin()->set_timestamp(700413232691935); AddMmap(6531, 0x7ffea5dc8000, 0x2000, 0, "[vdso]", &events); events.rbegin()->set_timestamp(700413232701418); AddMmap(6531, 0x7f686a1e3000, 0x5000, 0, "lib1.so", &events); events.rbegin()->set_timestamp(700413232824216); AddMmap(6531, 0x7f686a1a8000, 0x3a000, 0, "lib2.so", &events); events.rbegin()->set_timestamp(700413232854520); AddMmap(6531, 0x7f6869ea7000, 0x5000, 0, "lib3.so", &events); events.rbegin()->set_timestamp(700413248827794); AddMmap(6531, 0x7f6867e00000, 0x200000, 0, "/anon_hugepage (deleted)", &events); events.rbegin()->set_timestamp(700413295816043); AddMmap(6531, 0x7f6867c00000, 0x200000, 0, "/anon_hugepage (deleted)", &events); events.rbegin()->set_timestamp(700413305947499); AddMmap(6531, 0x7f68663f8000, 0x1e00000, 0x7f68663f8000, "//anon", &events); events.rbegin()->set_timestamp(700413306012797); AddMmap(6531, 0x7f6866525000, 0x1a00000, 0x7f6866525000, "//anon", &events); events.rbegin()->set_timestamp(700413312132909); DeduceHugePages(&events); CombineMappings(&events); EXPECT_THAT( events, Pointwise( Partially(EqualsProto()), { "mmap_event: { pgoff: 0 filename: 'main' }", "mmap_event: { pgoff: 0 filename: 'ld.so' }", "mmap_event: { pgoff: 0 filename: '[vdso]' }", "mmap_event: { pgoff: 0 filename: 'lib1.so' }", "mmap_event: { pgoff: 0 filename: 'lib2.so' }", "mmap_event: { pgoff: 0 filename: 'lib3.so' }", "mmap_event: { pgoff: 0 filename: '/anon_hugepage (deleted)' }", "mmap_event: { pgoff: 0 filename: '/anon_hugepage (deleted)' }", "mmap_event: { pgoff: 0x7f68663f8000 filename: '//anon' }", "mmap_event: { pgoff: 0x7f6866525000 filename: '//anon' }", })); } TEST(HugePageDeducer, Regression62446346) { RepeatedPtrField<PerfEvent> events; // Perf infers the filename is "file", but at offset 0 for // hugepage-backed, anonymous mappings. // // vaddr start - vaddr end vaddr-size elf-offset // [0x55a685bfb000-55a685dfb000) (0x200000) @ 0]: file // [0x55a685dfb000-55a687c00000) (0x1e05000) @ 0x200000]: file // [0x55a687c00000-55a6a5200000) (0x1d600000) @ 0]: file // [0x55a6a5200000-55a6a6400000) (0x1200000) @ 0x1f605000]: file // [0x55a6a6400000-55a6a6600000) (0x200000) @ 0]: file // [0x55a6a6600000-55a6a8800000) (0x2200000) @ 0x20a05000]: file // [0x55a6a8800000-55a6a8a00000) (0x200000) @ 0]: file // [0x55a6a8a00000-55a6a90ca000) (0x6ca000) @ 0x22e05000]: file // [0x55a6a90ca000-55a6a90cb000) (0x1000) @ 0x234cf000]: file { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x55a685bfb000); ev->set_len(0x200000); ev->set_pgoff(0); ev->set_filename("file"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x55a685dfb000); ev->set_len(0x1e05000); ev->set_pgoff(0x200000); ev->set_filename("file"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x55a687c00000); ev->set_len(0x1d600000); ev->set_pgoff(0); ev->set_filename("file"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x55a6a5200000); ev->set_len(0x1200000); ev->set_pgoff(0x1f605000); ev->set_filename("file"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x55a6a6400000); ev->set_len(0x200000); ev->set_pgoff(0); ev->set_filename("file"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x55a6a6600000); ev->set_len(0x2200000); ev->set_pgoff(0x20a05000); ev->set_filename("file"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x55a6a8800000); ev->set_len(0x200000); ev->set_pgoff(0); ev->set_filename("file"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x55a6a8a00000); ev->set_len(0x6ca000); ev->set_pgoff(0x22e05000); ev->set_filename("file"); } { MMapEvent* ev = events.Add()->mutable_mmap_event(); ev->set_pid(1234); ev->set_start(0x55a6a90ca000); ev->set_len(0x1000); ev->set_pgoff(0x234cf000); ev->set_filename("file"); } DeduceHugePages(&events); CombineMappings(&events); ASSERT_EQ(1, events.size()); EXPECT_EQ("file", events[0].mmap_event().filename()); EXPECT_EQ(0x55a685bfb000, events[0].mmap_event().start()); EXPECT_EQ(0x55a6a90cb000 - 0x55a685bfb000, events[0].mmap_event().len()); EXPECT_EQ(0, events[0].mmap_event().pgoff()); } } // namespace } // namespace quipper