如何将计时器指标添加到java.util.Stream

时间:2018-12-02 18:26:40

标签: java

通常要关注的是如何向java.util.Stream执行的各个部分添加计时指标。在终止时,很容易为整个操作计时,例如(使用codahale库)

try (Context ctx = timer.time()){
   stream.count();
}

但是“逐项”计时呢?或者如何在流的中间部分添加计时器,例如,对10级流的前5个级花费多长时间进行计时?

仅通过向那些方法添加计时器,就可以很容易地对中间阶段的各个步骤进行计时。最初的Spliterator代码可以测量首次出现tryAdvance和close()方法之间的时间(它必须向其生成的流中添加onClose Runnable)。这至少允许流提供库使用Timer,即使它们不知道如何对流进行转换和使用也是如此。

写这样的东西真是太好了

List result = stream
   // stream ops ...etc...etc
   .timeTotalOperation(totalOpTimer) //time between first traverse and close()
   .timePerItemOperation(perItemTimer) //"forEach" timer at this stage
   .collect(Collectors.toList());

但是显然我们不能将这些方法添加到Stream接口。

用委托模式包装Stream似乎没有任何意义。据我所知,“正确”的实现是利用Pipeline类,它们是不可访问的,并且(可能)会发生变化。

由于类是最终的或程序包可见性,我什至无法将收集器扩展到结束阶段。虽然我可以滚动自己的收集器并自己调用stream(Collector),但收集器中有所有有用的功能。但是,应该可以编写一个CollectorDelegate类,该类包装从Collections返回的项目,例如

List result = stream
   .collect(new TimingCollector(Collectors.toList(), totalOpTimer, perItemTimer));

考虑到Stream用例的复杂性,必须承认“每个项目”的概念是“ iffy”。在某些操作中,“逐项”计时甚至没有意义。但是,即使对于Streams最简单的用例,我也无法找出一种干净的方法来实现。

这样的开放性问题对于一个好的线程提出了太多的问题,所以让我尝试提出一个问题。从数据库中读取流,转换为Java对象,仅测量从数据库中读取的数据并转换为Java,然后将流转发给使用者以进行更多工作,但不要计时该部分:

import java.util.function.Consumer;
import java.util.stream.Stream;


interface SQLResultSetSupplier {

    default Stream<Object[]> generateStream() {
        return Stream.generate(this::getExpensiveResultSet);
    }
    Object[] getExpensiveResultSet();

    Object expensivelyConvertToJava(Object[] row);
}


public class StreamTimerExample {

    public void example(SQLResultSetSupplier supplier, Consumer<Object> reportConsumer) {
        /**
         * Supplier performs a database query and returns a Stream on the ResultSet.
         * Convert each row of the ResultSet to a Java object.
         * Measure JUST THE ABOVE on a per-item basis.
         *
         * Then send the stream on to a Consumer, e.g., to generate a report.
         * Do NOT measure this second portion.
         */
        Stream<Object[]> baseStream = supplier.generateStream();
        Stream<Object> expensiveOperationStream = baseStream.map(t -> supplier.expensivelyConvertToJava(t)); // measure this
        expensiveOperationStream.forEach(reportConsumer); //don't measure this
    }

}

1 个答案:

答案 0 :(得分:0)

我的胆量在这里:您将时间浪费在了错误的地方。

最后,您打算花费大量时间和精力来实现自己的代码工具。

含义:为什么关注“流”?最后,重要的是“最终用户”功能的整体性能。当然,流可能占其中的很大一部分。但是您仍在投入大量精力来创建可见性...针对系统的非常特定的“角落”。

我会建议一种不同的策略:宁可使用探查器并测量端到端用例。然后,您仍然可以(非常容易地)配置探查器以将测量值限制为流操作。