@Override
    public List<String> listResourcesOfApp(String app) {
        List<String> results = new ArrayList<>();
        if (StringUtil.isBlank(app)) {
            return results;
        }
        // resource -> timestamp -> metric
        Map<String, LinkedHashMap<Long, MetricEntity>> resourceMap = allMetrics.get(app);
        if (resourceMap == null) {
            return results;
        }
        final long minTimeMs = System.currentTimeMillis() - 1000 * 60;
        Map<String, MetricEntity> resourceCount = new ConcurrentHashMap<>(32);

        readWriteLock.readLock().lock();
        try {
            for (Entry<String, LinkedHashMap<Long, MetricEntity>> resourceMetrics : resourceMap.entrySet()) {
                for (Entry<Long, MetricEntity> metrics : resourceMetrics.getValue().entrySet()) {
                    if (metrics.getKey() < minTimeMs) {
                        continue;
                    }
                    MetricEntity newEntity = metrics.getValue();
                    if (resourceCount.containsKey(resourceMetrics.getKey())) {
                        MetricEntity oldEntity = resourceCount.get(resourceMetrics.getKey());
                        oldEntity.addPassQps(newEntity.getPassQps());
                        oldEntity.addRtAndSuccessQps(newEntity.getRt(), newEntity.getSuccessQps());
                        oldEntity.addBlockQps(newEntity.getBlockQps());
                        oldEntity.addExceptionQps(newEntity.getExceptionQps());
                        oldEntity.addCount(1);
                    } else {
                        resourceCount.put(resourceMetrics.getKey(), MetricEntity.copyOf(newEntity));
                    }
                }
            }
            // Order by last minute b_qps DESC.
            return resourceCount.entrySet()
                    .stream()
                    .sorted((o1, o2) -> {
                        MetricEntity e1 = o1.getValue();
                        MetricEntity e2 = o2.getValue();
                        int t = e2.getBlockQps().compareTo(e1.getBlockQps());
                        if (t != 0) {
                            return t;
                        }
                        return e2.getPassQps().compareTo(e1.getPassQps());
                    })
                    .map(Entry::getKey)
                    .collect(Collectors.toList());
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
@Test
    public void testConcurrentPutAndGet() {

        List<CompletableFuture> futures = new ArrayList<>(10000);
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(8);

        for (int j = 0; j < 10000; j++) {
            final int finalJ = j;
            futures.add(CompletableFuture.runAsync(() -> {
                        try {
                            cyclicBarrier.await();
                            if (finalJ % 2 == 0) {
                                batchSave();
                            } else {
                                inMemoryMetricsRepository.listResourcesOfApp(DEFAULT_APP);
                            }

                        } catch (InterruptedException | BrokenBarrierException e) {
                            e.printStackTrace();
                        }

                    }, executorService)
            );
        }


        CompletableFuture all = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

        try {
            all.get(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.getCause().printStackTrace();
            if (e.getCause() instanceof ConcurrentModificationException) {
                fail("concurrent error occurred");
            } else {
                fail("unexpected exception");
            }
        } catch (TimeoutException e) {
            fail("allOf future timeout");
        }
    }