Project
Loading...
Searching...
No Matches
testUserLogicEndpointDecoder.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
14
15#define BOOST_TEST_MODULE Test MCHRaw UserLogicElinkDecoder
16#define BOOST_TEST_MAIN
17#define BOOST_TEST_DYN_LINK
18
19#include <boost/test/unit_test.hpp>
20#include "Assertions.h"
22#include "DumpBuffer.h"
29#include "MoveBuffer.h"
30#include "RDHManip.h"
32#include <fmt/printf.h>
33#include <fstream>
34#include <iostream>
35#include <boost/test/data/test_case.hpp>
36#include <boost/mpl/list.hpp>
37
38using namespace o2::mch::raw;
39namespace bdata = boost::unit_test::data;
40
42 0x00000A0000124006ul,
43 0x000C4C0F00A000A0ul,
44 0x010E853D00000570ul,
45 0x0000000000000000ul,
46 0x0000000000006000ul,
47 0x0000000000000000ul,
48 0x0000000000000000ul,
49 0x0000000000000000ul,
50 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x1555540F00113ul,
51 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x3F04ECA103E5Cul,
52 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x0000040215C0Dul,
53 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x00000C0301004ul,
54 ((0x0204ul << 50) & 0xFFFC000000000000ul) + 0x0000000000400ul,
55 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x1555540F00113ul,
56 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x1F080CA100E4Dul,
57 ((0x0204ul << 50) & 0xFFFC000000000000ul) + 0x00044C0100001ul,
58 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul,
59 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul,
60 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul,
61 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul};
62
64 0x00000A0000124006ul,
65 0x000C4C0F00A000A0ul,
66 0x010E853D00000570ul,
67 0x0000000000000000ul,
68 0x0000000000006000ul,
69 0x0000000000000000ul,
70 0x0000000000000000ul,
71 0x0000000000000000ul,
72 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x1555540F00113ul,
73 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x3F04ECA103E5Cul,
74 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x0000040215C0Eul, // <== the cluster size is increased from 13 (0xD) to 14 (0xE)
75 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x00000C0301004ul, // now the cluster size does not match anymore with the
76 ((0x0204ul << 50) & 0xFFFC000000000000ul) + 0x0000000000400ul, // number of 10-bit words in the SAMPA header, which will trigger
77 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x1555540F00113ul, // a ErrorBadClusterSize error.
78 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x1F080CA100E4Dul,
79 ((0x0204ul << 50) & 0xFFFC000000000000ul) + 0x00044C0100001ul,
80 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul,
81 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul,
82 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul,
83 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul};
84
86 0x00000A0000124006ul,
87 0x000C4C0F00A000A0ul,
88 0x010E853D00000570ul,
89 0x0000000000000000ul,
90 0x0000000000006000ul,
91 0x0000000000000000ul,
92 0x0000000000000000ul,
93 0x0000000000000000ul,
94 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x1555540F00113ul,
95 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x3F04ECA103E5Cul,
96 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x0000040215C08ul, // <== the cluster size is decreased from 13 (0xD) to 8 (0x8)
97 //((0x0200ul<<50)&0xFFFC000000000000ul) + 0x00000C0301004ul, // and one 50-bit word is removed. In this case the cluster
98 ((0x0204ul << 50) & 0xFFFC000000000000ul) + 0x0000000000400ul, // size matches the number of samples in the data, but the
99 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x1555540F00113ul, // end of the SAMPA packet arrives too early with respect to
100 ((0x0200ul << 50) & 0xFFFC000000000000ul) + 0x1F080CA100E4Dul, // the number of 10-bit words in the SAMPA header. This will
101 ((0x0204ul << 50) & 0xFFFC000000000000ul) + 0x00044C0100001ul, // trigger a ErrorBadIncompleteWord error.
102 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul,
103 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul,
104 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul,
105 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul, // <== a word is added at the end in order to match the
106 ((0x3FBBul << 50) & 0xFFFC000000000000ul) + 0x1DEEDFEEDDEEDul // payload size in the RDH
107};
108
110{
111 return [&result](DsElecId dsId, DualSampaChannelId channel, SampaCluster sc) {
112 result += fmt::format("{}-ch-{}-ts-{}-q", asString(dsId), channel, sc.sampaTime);
113 if (sc.isClusterSum()) {
114 result += fmt::format("-{}-cs-{}", sc.chargeSum, sc.clusterSize);
115 } else {
116 for (auto s : sc.samples) {
117 result += fmt::format("-{}", s);
118 }
119 }
120 result += "\n";
121 };
122}
123
125{
126 return [&result](DsElecId dsId, int8_t chip, uint32_t error) {
127 result += fmt::format("{}-chip-{}-error-{}", asString(dsId), chip, error);
128 result += "\n";
129 };
130}
131
132std::vector<std::byte> convertBuffer2PayloadBuffer(gsl::span<const std::byte> buffer,
133 std::optional<size_t> insertSync = std::nullopt)
134{
135 // some gym to go from a buffer coming from the encoder,
136 // which holds (DataBlockHeader,Payload) pairs in a byte buffer
137 // to a buffer that holds just Payload information
138 // to be fed to the decoder
139
140 // strip the headers
141 std::vector<std::byte> b8;
143 buffer, [&](const DataBlockRef& r) {
144 auto& b = r.block;
145 b8.insert(b8.end(), b.payload.begin(), b.payload.end());
146 });
147
148 // convert to a 64-bits buffer to be able to insert sync if needed
149 std::vector<uint64_t> b64;
150 impl::copyBuffer(b8, b64);
151
152 // insert a sync at the given position if required
153 if (insertSync.has_value() && insertSync.value() < b64.size()) {
154 uint64_t prefix = b64[0] & 0xFFFC00000000000F;
155 b64.insert(b64.begin() + insertSync.value(), prefix | sampaSyncWord);
156 }
157
158 // get back to byte buffer to return
159 std::vector<std::byte> bytes;
160 impl::copyBuffer(b64, bytes);
161 return bytes;
162}
163
164template <typename CHARGESUM, int VERSION>
165std::string decodeBuffer(int feeId, gsl::span<const std::byte> buffer)
166{
167 std::string results;
169 DecodedDataHandlers handlers;
170 handlers.sampaChannelHandler = handlePacket(results);
171 handlers.sampaErrorHandler = handleError(results);
172 UserLogicEndpointDecoder<CHARGESUM, VERSION> dec(feeId, fee2solar, handlers);
173 dec.append(buffer);
174 return results;
175}
176
177template <typename CHARGESUM, int VERSION>
180 const std::vector<SampaCluster>& clustersFirstChannel,
181 DsElecId ds2 = DsElecId{0, 0, 0},
182 DualSampaChannelId ch2 = 47,
183 const std::vector<SampaCluster>& clustersSecondChannel = {},
184 std::optional<size_t> insertSync = std::nullopt)
185{
186
188
189 auto encoder = createPayloadEncoder(solar2feelink, true, VERSION, isChargeSumMode<CHARGESUM>::value);
190
191 encoder->startHeartbeatFrame(0, 0);
192
193 uint16_t feeId{0};
194
195 auto f1 = solar2feelink(ds1.solarId());
196 if (!f1.has_value()) {
197 throw std::invalid_argument("invalid solarId for ds1");
198 }
199 if (!clustersSecondChannel.empty()) {
200 auto f2 = solar2feelink(ds2.solarId());
201 if (!f2.has_value()) {
202 throw std::invalid_argument("invalid solarId for ds2");
203 }
204 if (f2->feeId() != f1->feeId()) {
205 throw std::invalid_argument("this test is only meant to work with 2 solars in the same cru endpoint");
206 }
207 }
208
209 feeId = f1->feeId();
210
211 encoder->addChannelData(ds1, ch1, clustersFirstChannel);
212 if (!clustersSecondChannel.empty()) {
213 encoder->addChannelData(ds2, ch2, clustersSecondChannel);
214 }
215
216 std::vector<std::byte> buffer;
217 encoder->moveToBuffer(buffer);
218 auto payloadBuffer = convertBuffer2PayloadBuffer(buffer, insertSync);
219
220 return decodeBuffer<CHARGESUM, VERSION>(feeId, payloadBuffer);
221}
222
223template <int VERSION>
224std::vector<uint64_t> convert(gsl::span<const uint64_t> page);
225
226template <>
227std::vector<uint64_t> convert<0>(gsl::span<const uint64_t> page)
228{
229 return {page.begin(), page.end()};
230}
231
232template <>
233std::vector<uint64_t> convert<1>(gsl::span<const uint64_t> page)
234{
235 // convert the 14 MSB bits of page, expressed using V0 spec,
236 // to match the V1 spec
237 std::vector<uint64_t> pagev1{page.begin(), page.end()};
238 constexpr int rdhSize{8};
239 for (int i = rdhSize; i < pagev1.size(); i++) {
240 if (pagev1[i] == 0xFEEDDEEDFEEDDEED || pagev1[i] == 0) {
241 // do not mess with padding words
242 continue;
243 }
244 ULHeaderWord<0> v0{pagev1[i]};
246 v1.data = v0.data;
247 v1.error = v0.error;
248 v1.incomplete = v0.incomplete;
249 v1.dsID = v0.dsID;
250 v1.linkID = v0.linkID;
251 pagev1[i] = v1.word;
252 }
253 return pagev1;
254}
255
256template <int VERSION = 0>
257std::string testPayloadDecodeCruPages(gsl::span<const uint64_t> ipage)
258{
259 std::vector<uint64_t> page = convert<VERSION>(ipage);
260
261 const void* rdhP = reinterpret_cast<const void*>(page.data());
262 uint16_t feeId = o2::raw::RDHUtils::getFEEID(rdhP);
263 auto rdhSize = o2::raw::RDHUtils::getHeaderSize(rdhP);
264 auto payloadSize = o2::raw::RDHUtils::getMemorySize(rdhP) - rdhSize;
265
266 gsl::span<const std::byte> buffer(reinterpret_cast<const std::byte*>(page.data()), page.size() * 8);
267 gsl::span<const std::byte> payloadBuffer = buffer.subspan(rdhSize, payloadSize);
268
269 o2::mch::raw::FEEID f{feeId};
270
271 if (f.chargeSum) {
272 return decodeBuffer<ChargeSumMode, VERSION>(f.id, payloadBuffer);
273 } else {
274 return decodeBuffer<SampleMode, VERSION>(f.id, payloadBuffer);
275 }
276}
277
278struct V0 {
279 static constexpr int value = 0;
280};
281struct V1 {
282 static constexpr int value = 1;
283};
284
285typedef boost::mpl::list<V0, V1> testTypes;
286
287BOOST_AUTO_TEST_SUITE(o2_mch_raw)
288
289BOOST_AUTO_TEST_SUITE(userlogicdsdecoder)
290
292{
293 // only one channel with one very small cluster
294 // fitting within one 64-bits word
295 SampaCluster cl(345, 6789, {123, 456});
296 auto r = testPayloadDecode<SampleMode, V::value>(DsElecId{728, 1, 0}, 63, {cl});
297 BOOST_CHECK_EQUAL(r, "S728-J1-DS0-ch-63-ts-345-q-123-456\n");
298}
299
301{
302 // only one channel with one cluster, but the cluster
303 // spans 2 64-bits words.
304 SampaCluster cl(345, 6789, {123, 456, 789, 901, 902});
305 auto r = testPayloadDecode<SampleMode, V::value>(DsElecId{448, 6, 4}, 63, {cl});
306 BOOST_CHECK_EQUAL(r, "S448-J6-DS4-ch-63-ts-345-q-123-456-789-901-902\n");
307}
308
309BOOST_AUTO_TEST_CASE_TEMPLATE(SampleModeTwoChannels, V, testTypes)
310{
311 // 2 channels with one cluster
312 SampaCluster cl(345, 6789, {123, 456, 789, 901, 902});
313 SampaCluster cl2(346, 6789, {1001, 1002, 1003, 1004, 1005, 1006, 1007});
314 auto r = testPayloadDecode<SampleMode, V::value>(DsElecId{361, 6, 2}, 63, {cl}, DsElecId{361, 6, 2}, 47, {cl2});
316 "S361-J6-DS2-ch-63-ts-345-q-123-456-789-901-902\n"
317 "S361-J6-DS2-ch-47-ts-346-q-1001-1002-1003-1004-1005-1006-1007\n");
318}
319
320BOOST_AUTO_TEST_CASE_TEMPLATE(ChargeSumModeSimplest, V, testTypes)
321{
322 // only one channel with one cluster
323 // (hence fitting within one 64 bits word)
324 SampaCluster cl(345, 6789, 123456, 789);
325 auto r = testPayloadDecode<ChargeSumMode, V::value>(DsElecId{728, 1, 0}, 63, {cl});
326 BOOST_CHECK_EQUAL(r, "S728-J1-DS0-ch-63-ts-345-q-123456-cs-789\n");
327}
328
330{
331 // only one channel with 2 clusters
332 // (hence spanning 2 64-bits words)
333 SampaCluster cl1(345, 6789, 123456, 789);
334 SampaCluster cl2(346, 6789, 789012, 345);
335 auto r = testPayloadDecode<ChargeSumMode, V::value>(DsElecId{448, 6, 4}, 63, {cl1, cl2});
337 "S448-J6-DS4-ch-63-ts-345-q-123456-cs-789\n"
338 "S448-J6-DS4-ch-63-ts-346-q-789012-cs-345\n");
339}
340
341BOOST_AUTO_TEST_CASE_TEMPLATE(ChargeSumModeTwoChannels, V, testTypes)
342{
343 // two channels with 2 clusters
344 SampaCluster cl1(345, 6789, 123456, 789);
345 SampaCluster cl2(346, 6789, 789012, 345);
346 SampaCluster cl3(347, 6789, 1357, 890);
347 SampaCluster cl4(348, 6789, 7912, 345);
348 auto r = testPayloadDecode<ChargeSumMode, V::value>(DsElecId{361, 6, 2}, 63, {cl1, cl2}, DsElecId{361, 6, 2}, 47, {cl3, cl4});
350 "S361-J6-DS2-ch-63-ts-345-q-123456-cs-789\n"
351 "S361-J6-DS2-ch-63-ts-346-q-789012-cs-345\n"
352 "S361-J6-DS2-ch-47-ts-347-q-1357-cs-890\n"
353 "S361-J6-DS2-ch-47-ts-348-q-7912-cs-345\n");
354}
355
356BOOST_AUTO_TEST_CASE_TEMPLATE(SyncInTheMiddleChargeSumModeTwoChannels, V, testTypes)
357{
358 // Insert a sync word in the middle of
359 // the TwoChannels case and check the decoder is handling this fine
360 // (by just returning to wait for sync mode, i.e. dropping the 2nd part
361 // of the communication until a second sync)
362 SampaCluster cl1(345, 6789, 123456, 789);
363 SampaCluster cl2(346, 6789, 789012, 345);
364 SampaCluster cl3(347, 6789, 1357, 890);
365 SampaCluster cl4(348, 6789, 7912, 345);
366 auto r = testPayloadDecode<ChargeSumMode, V::value>(
367 DsElecId{361, 6, 2}, 63, {cl1, cl2},
368 DsElecId{361, 6, 2}, 47, {cl3, cl4},
369 5);
370 std::string r2 = "S361-J6-DS2-ch-63-ts-345-q-123456-cs-789\n";
371 r2 += "S361-J6-DS2-ch-63-ts-346-q-789012-cs-345\n";
372 r2 += fmt::format("S361-J6-DS2-chip-5-error-{}\n", (int)ErrorUnexpectedSyncPacket);
373 BOOST_CHECK_EQUAL(r, r2);
374}
375
377{
378 gsl::span<const uint64_t> page = CruPageOK;
379 std::string r = testPayloadDecodeCruPages<V::value>(page);
381 "S81-J0-DS0-ch-42-ts-87-q-2-1-0-4-4-3-3-0-0-1-0-0-0\n"
382 "S81-J0-DS0-ch-42-ts-0-q-1\n");
383}
384
385BOOST_AUTO_TEST_CASE_TEMPLATE(TestCruPageBadClusterSize, V, testTypes)
386{
387 gsl::span<const uint64_t> page = CruPageBadClusterSize;
388 std::string r = testPayloadDecodeCruPages<V::value>(page);
390 fmt::format("S81-J0-DS0-chip-1-error-{}\nS81-J0-DS0-ch-42-ts-0-q-1\n", (int)ErrorBadClusterSize));
391}
392
393BOOST_AUTO_TEST_CASE_TEMPLATE(TestCruPageBadN10bitWords, V, testTypes)
394{
395 gsl::span<const uint64_t> page = CruPageBadN10bitWords;
396 std::string r = testPayloadDecodeCruPages<V::value>(page);
397 std::string expected =
398 fmt::format("S81-J0-DS0-ch-42-ts-87-q-2-1-0-0-1-0-0-0\nS81-J0-DS0-chip-1-error-{}\nS81-J0-DS0-ch-42-ts-0-q-1\n",
401}
402
403BOOST_AUTO_TEST_SUITE_END()
404BOOST_AUTO_TEST_SUITE_END()
unsigned short int uint16_t
int32_t i
zsPage * page
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error s
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char Directory with events to Events directory is and not inside events Show standalone event Run event generator maximum time bin of continuous for triggered for automatic continuous for automatic continuous triggered uint64_t
char error[CURL_ERROR_SIZE]
size_t bytes
constexpr uint16_t solarId() const
solarId is an identifier that uniquely identify a solar board
Definition DsElecId.h:50
A UserLogicEndpointDecoder groups 12 x (40 UserLogicElinkDecoder objects)
size_t append(Payload bytes)
Append the equivalent n 64-bits words bytes size (=n) must be a multiple of 8.
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint buffer
Definition glcorearb.h:655
GLdouble f
Definition glcorearb.h:310
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLfloat v0
Definition glcorearb.h:811
GLfloat GLfloat v1
Definition glcorearb.h:812
std::function< std::optional< uint16_t >(FeeLinkId)> createFeeLink2SolarMapper< ElectronicMapperGenerated >()
@ ErrorBadIncompleteWord
Definition ErrorCodes.h:33
@ ErrorBadClusterSize
Definition ErrorCodes.h:32
@ ErrorUnexpectedSyncPacket
Definition ErrorCodes.h:36
constexpr uint64_t sampaSyncWord
std::function< void(DsElecId dsId, DualSampaChannelId channel, SampaCluster)> SampaChannelHandler
std::string asString(const SampaCluster &sc)
int forEachDataBlockRef(gsl::span< const std::byte > buffer, std::function< void(DataBlockRef ref)> f)
Definition DataBlock.cxx:23
std::function< void(DsElecId dsId, int8_t chip, uint32_t error)> SampaErrorHandler
std::function< std::optional< FeeLinkId >(uint16_t)> createSolar2FeeLinkMapper< ElectronicMapperGenerated >()
std::unique_ptr< PayloadEncoder > createPayloadEncoder(Solar2FeeLinkMapper solar2feelink, bool userLogic, int version, bool chargeSumMode)
value_T f1
Definition TrackUtils.h:91
value_T f2
Definition TrackUtils.h:92
a DataBlockRef is a pair (DataBlock,offset) The offset is an offset into some external buffer
Definition DataBlock.h:45
Piece of data for one Sampa channel.
std::map< std::string, ID > expected
boost::mpl::list< o2::dcs::DataPointIdentifier, o2::dcs::DataPointValue, o2::dcs::DataPointCompositeObject > testTypes
SampaErrorHandler handleError(std::string &result)
const uint64_t CruPageBadN10bitWords[]
std::vector< uint64_t > convert< 0 >(gsl::span< const uint64_t > page)
std::vector< std::byte > convertBuffer2PayloadBuffer(gsl::span< const std::byte > buffer, std::optional< size_t > insertSync=std::nullopt)
std::vector< uint64_t > convert(gsl::span< const uint64_t > page)
BOOST_AUTO_TEST_CASE_TEMPLATE(SampleModeSimplest, V, testTypes)
std::vector< uint64_t > convert< 1 >(gsl::span< const uint64_t > page)
std::string testPayloadDecodeCruPages(gsl::span< const uint64_t > ipage)
const uint64_t CruPageBadClusterSize[]
boost::mpl::list< V0, V1 > testTypes
std::string decodeBuffer(int feeId, gsl::span< const std::byte > buffer)
SampaChannelHandler handlePacket(std::string &result)
std::string testPayloadDecode(DsElecId ds1, DualSampaChannelId ch1, const std::vector< SampaCluster > &clustersFirstChannel, DsElecId ds2=DsElecId{0, 0, 0}, DualSampaChannelId ch2=47, const std::vector< SampaCluster > &clustersSecondChannel={}, std::optional< size_t > insertSync=std::nullopt)
const uint64_t CruPageOK[]
BOOST_CHECK_EQUAL(triggersD.size(), triggers.size())