Add mempool statistics collector #8501

pull jonasschnelli wants to merge 2 commits into bitcoin:master from jonasschnelli:2016/08/stats_rpc changing 12 files +468 −3
  1. jonasschnelli commented at 12:00 PM on August 12, 2016: contributor

    This PR adds a statistics collector class which aims to collect various types of statistics up to the configurable maximum memory target. At the moment, only mempool statistics will be collected.

    Motivation

    Adding more statistics and visualization to the GUI would leverage its usage. To do so, we need stats that are collected even when the visualization is not visible (example: the GUI network graph will only draw data when it's visible which is kinda unusable)

    How it works

    This PR adds a simple stats manager that polls stats over a repetitive CScheduler task.

    The samples are not guaranteed to be const-time. Each sample contains a time delta to the last one (uint16_t).

    API

    • -statsenable default disabled
    • -statsmaxmemorytarget 10MB default maximal memory target to use for statistics.
    • RPC: getmempoolstats

    [
      {
        "percision_interval": 2,
        "time_from": 1494252401,
        "samples": [
          [
            11, 
            1, 
            1008, 
            0
          ], ....
       }...
    ]
    

    Features

    -> CScheduler driven sample collecting (current interval 2000ms) -> Relevant mempool data (size, memory requirement, minfee) gets written to an atomic cache (no additional locking required) -> Multiple precision levels (currently three, 2s, 60s, 1800s) -> Memory target that will calculate how many samples do fit in the given target -> Sample do only have a 2byte time-delta to the last sample, allowing to save some memory -> Flexible design, adding more data-points should be simple (bandwidth, utxo-stats, etc.).

  2. jonasschnelli added the label RPC/REST/ZMQ on Aug 12, 2016
  3. jonasschnelli force-pushed on Aug 12, 2016
  4. jonasschnelli force-pushed on Aug 12, 2016
  5. in src/stats/stats.cpp:None in 4149b34c0a outdated
     114 | +        return subset;
     115 | +    }
     116 | +
     117 | +    // return all available samples
     118 | +    fromTime = mempoolStats.startTime + mempoolStats.vSamples.front().timeDelta;
     119 | +    ;
    


    isle2983 commented at 3:35 AM on August 13, 2016:

    is this semicolon unintentional?

  6. in src/stats/stats.h:None in 4149b34c0a outdated
      12 | +#include <vector>
      13 | +
      14 | +#include <boost/signals2/signal.hpp>
      15 | +
      16 | +struct CStatsMempoolSample {
      17 | +    uint32_t timeDelta;  //use 32bit time delta to save memmory
    


    isle2983 commented at 12:03 AM on August 14, 2016:

    s/memmory/memory/

  7. in src/stats/stats.h:None in 4149b34c0a outdated
      67 | +    mempoolSamples_t mempoolGetValuesInRange(uint64_t& fromTime, uint64_t& toTime);
      68 | +
      69 | +    /* set the target for the maximum memory consuption (in bytes) */
      70 | +    void setMaxMemoryUsageTarget(size_t maxMem);
      71 | +
      72 | +    /* get the statictis module help strings */
    


    isle2983 commented at 12:07 AM on August 14, 2016:

    s/statictis/statistics/

  8. in src/stats/stats.h:None in 4149b34c0a outdated
      64 | +    void addMempoolSample(int64_t txcount, int64_t dynUsage, int64_t currentMinRelayFee);
      65 | +
      66 | +    /* get all mempool samples (non interpolated) */
      67 | +    mempoolSamples_t mempoolGetValuesInRange(uint64_t& fromTime, uint64_t& toTime);
      68 | +
      69 | +    /* set the target for the maximum memory consuption (in bytes) */
    


    isle2983 commented at 12:08 AM on August 14, 2016:

    s/consuption/consumption/

  9. in src/stats/stats.cpp:None in 4149b34c0a outdated
      43 | +
      44 | +        // ensure the minimum time delta between samples
      45 | +        if (mempoolStats.vSamples.size() && mempoolStats.vSamples.back().timeDelta + SAMPLE_MIN_DELTA_IN_SEC >= now - mempoolStats.startTime)
      46 | +            return;
      47 | +
      48 | +        // calculate the current time detla and add a sample
    


    isle2983 commented at 12:12 AM on August 14, 2016:

    s/detla/delta/

  10. in src/stats/stats.cpp:None in 4149b34c0a outdated
      14 | +size_t CStats::maxStatsMemory = 0;
      15 | +const size_t CStats::DEFAULT_MAX_STATS_MEMORY = 10 * 1024 * 1024; //10 MB
      16 | +const bool CStats::DEFAULT_STATISTICS_ENABLED = false;
      17 | +std::atomic<bool> CStats::statsEnabled(false); //disable stats by default
      18 | +
      19 | +CStats defaultStats;
    


    isle2983 commented at 12:33 AM on August 14, 2016:

    This 'defaultStats' instance is not used.

  11. isle2983 commented at 4:03 AM on August 15, 2016: contributor

    Hi Jonas,

    I understand the concern in your description for the addMempoolSample() stat bookkeeping designed to be as lightweight as possible in the critical execution path. However, I have a few (perhaps under-informed, neophyte) questions which would help me understand the design considerations better:

    1. the comment in rpc_stats.cpp hints that the overhead of the JSON string generation is best optimized to be this 'flat' encoding as opposed to some encoding like:

    { "fieldNames" : ["delta_in_secs", "tx_count", "dynamic_mem_usage", "min_fee_per_k"], "samples" : [[val1, val2, val3, val4], [val1, val2, val3, val4], ] }

    Is the 'flat' encoding strictly needed? or is there some other concern with outputting a slightly more convenient format than 'flat'?

    1. It appears possible to set the maximum memory target very hight such that many, many samples are collected and the overhead of the computation mempoolGetValuesInRange() inside the lock might become onerus (assuming I am correctly understanding how the lock works and the implications of holding it too long). Have you considered taking a copy of 'mempoolStats' in a way that lets you return the lock earlier, and doing the dataset computation outside the lock? (Is that even currently possible under the current execution model?)

    Cheers,

    Isle

  12. jonasschnelli commented at 12:03 PM on August 15, 2016: contributor

    @isle2983 Welcome to github. Thanks for your feedback and your nitpicks. I really appreciate this and i'll process them during the next hours.

    For your questions/inputs:

    1. My idea with the JSON flat output was to bypass the JSON encoding/decoding.[val1, val2, val3, val4], [val1, val2, val3, val4], should also work. I just though a single string would result in faster encoding and decoding performance. But your approach seems to be the better choice, although not sure if we want to use UniValue for encoding or just appending strings... maybe we should start with the first and use a more optimized encoding if the JSON overhead is a problem.

    2. Yes. That's a good point. Copying the samples vector could result in a memory peak when using large amount of -maxmemorytarget.

  13. jonasschnelli force-pushed on Aug 15, 2016
  14. jonasschnelli force-pushed on Aug 15, 2016
  15. jonasschnelli force-pushed on Aug 15, 2016
  16. jonasschnelli force-pushed on Aug 17, 2016
  17. in src/stats/rpc_stats.cpp:None in c76840b835 outdated
      37 | +    for (struct CStatsMempoolSample& sample : samples) {
      38 | +        flatData += std::to_string(sample.timeDelta) + ",";
      39 | +        flatData += std::to_string(sample.txCount) + ",";
      40 | +        flatData += std::to_string(sample.dynMemUsage) + ",";
      41 | +        flatData += std::to_string(sample.minFeePerK) + ",";
      42 | +    }
    


    MarcoFalke commented at 8:03 PM on August 19, 2016:

    Would it make sense to add a line break after each sample's values?


    MarcoFalke commented at 9:06 PM on August 19, 2016:

    Or just add the names of the columns as another entry in the dict.

    Otherwise I fail to see how this rpc call is useful.

  18. isle2983 commented at 2:36 AM on August 20, 2016: contributor

    I have been playing around making my own changes off these commits (isle2983:getmempoolstats). Mostly to just to get some hands on with the code and try to get my C++ up to par.

    But anyway, I made the rpc output of the samples full JSON:

    {
      "enabled": true,
      "maxmemorytarget": 10485760,
      "currentusage": 1734416,
      "time_from": 1471573271,
      "time_to": 1471657376,
      "sampleCount": 27131,
      "sampleFieldNames": [
        "timeDelta", 
        "txCount", 
        "dynMemUsage", 
        "minFeePerK"
      ],
      "samples": [
        [
          0, 
          1, 
          1728, 
          0
        ], 
        [
          4, 
          11, 
          15232, 
          0
        ], 
        ...
        (snip)
        ]
    }
    

    The JSON 'pretty' print through bitcoin-cli is definitely unwieldy. However, the computational overhead in doing the wrangling doesn't seem so bad.

    The 1.7MM of stat data is from collecting just overnight. With that data, I can pull it off the node, parse and convert the JSON into CSV with a python script and plot it in gnuplot in under a second.

    $ time myJunk/plotTxCount.sh 
    
    real    0m0.966s
    user    0m0.460s
    sys     0m0.128s
    

    Not sure what the comparable is with the qt gui stuff branch that is running, but this doesn't seem too bad on the face of it.

    Also, if getting this info from the node to the UI quickly is a concern, perhaps a more dense, binary-like format is worth considering i.e:

    {"stats_blob":"8b292cf....."}
    

    One could imagine it being more efficient than even the 'flat' format, depending on the sophistication.

  19. jonasschnelli commented at 1:02 PM on August 22, 2016: contributor

    Thanks @isle2983 for the testing, benchmarks and improvements. I have switched to the proposed array format for the samples (rather then the flat structure). A more performant binary format (inside of the JSON format) would be a hack. More performance would probably be possible over ZMQ.. but its currently a push only channel.

    I also though again about copy the samples hash before filtering it. I came to the conclusion that it's not worth generating a memory peak (by a copy of the whole samples vector) in order to allow a faster release of the LOCK. The filtering should be very fast because it only compares some uint32 and does construct a new vector with a from-/to-iterators (should also preform fast).

  20. jonasschnelli force-pushed on Sep 9, 2016
  21. jonasschnelli commented at 6:54 AM on September 9, 2016: contributor

    Needed rebase.

  22. jonasschnelli force-pushed on Oct 20, 2016
  23. jonasschnelli commented at 8:31 AM on October 20, 2016: contributor

    Rebased.

  24. jonasschnelli force-pushed on Oct 20, 2016
  25. MarcoFalke added this to the milestone 0.14.0 on Nov 10, 2016
  26. MarcoFalke commented at 10:02 PM on November 10, 2016: member

    Assigning "would be nice to have" for 0.14 per meeting today.

  27. morcos commented at 12:51 AM on December 5, 2016: member

    Just saw @gmaxwell's comment on #8550 (which I completely agree with) and it reminded to look at that PR and this one. Sorry for not getting involved sooner, but I really like the idea. Unfortunately I can think of many many stats (dozens and dozens) that we might want to collect, both to potentially show to users in whiz-bangy gui's and also would be useful for developers and businesses trying to understand unusual behavior on the network.

    If we envision that there might be 1 KB of different stats data, then maybe rather than just saving sample data points and trimming when they hit memory usage, we should be smart about saving it along different time frames. For instance we could have second, minute, hour, day sampling intervals and we could save 1000 points or more on each and still have quite reasonable memory usage, but they could be auto trimmed.. so if you wanted to look at data from hours ago, you couldn't look at it on the second time frame...

  28. jonasschnelli commented at 1:06 PM on December 5, 2016: contributor

    @morcos: thanks for the comment. Yes. I completely agree. I think this is a first start and the current design allows features like you mentioned. I once started with interpolating values instead of just trimming the back, you could in theory just reduce the "density" of the sample and interpolate the in-between values (to a point where this could make sense). But yes, adding more stats probably require individual limits and trim-behaviours.

  29. morcos commented at 3:04 PM on December 5, 2016: member

    @jonasschnelli Well I guess what I was thinking was that one general framework might fit all stats. You log it with whatever frequency you want. And it's stored in up to 4 different histories (by second, minute, hour, day) and each of those is trimmed to some limit (say 1000 or 2000 data points each). Is there any type of stat that such a general framework might not work well with?

  30. jonasschnelli commented at 3:15 PM on December 5, 2016: contributor

    @morcos: the original idea I was trying to follow was to not collect in a fixed frequency. A) To avoid locking a thread for just collecting stats samples. B) To not collect over and over the same value if it was unchanged. Take the traffic report as an example. If you like to collect stats of all peers traffic segmented into all available p2p commands, then you would probably "loose" plenty of memory by storing samples with identical values.

    I had the idea of recording samples in the most restrained way possible. Collect lock free and only if values have changes; collect the according timestamp. If you want to retrieve data with a fixed frequency/step-size, interpolate.

    But not sure if this is stupid.

  31. jonasschnelli force-pushed on Dec 6, 2016
  32. jonasschnelli commented at 10:37 AM on December 6, 2016: contributor

    Rebased (main split)

  33. luke-jr commented at 10:22 PM on December 10, 2016: member

    c0af3664360e26333f8e16f20b0e21efa6ce2f1a has unresolved conflicts :(

  34. paveljanik commented at 10:25 PM on December 10, 2016: contributor

    @luke-jr All checks have passed ;-)

  35. luke-jr commented at 10:36 PM on December 10, 2016: member

    @paveljanik They're removed in the subsequent commit.

  36. paveljanik commented at 10:40 PM on December 10, 2016: contributor

    Yes (I noticed that), this is why we should take Travis' results as a help only.

  37. luke-jr referenced this in commit 039d1d54ca on Dec 21, 2016
  38. luke-jr referenced this in commit 2619df5df4 on Dec 21, 2016
  39. morcos commented at 6:09 PM on January 5, 2017: member

    @jonasschnelli whatever happened to this plan?

    09:19:49 < jonasschnelli> I think you convinced me to do the 1000s 1000m 1000h 1000d approach.
    09:19:59 < jonasschnelli> maybe the 1000 is configurable.
    09:20:04 < morcos> doesn't matter to me how we do it... i think a delta version coudl be just as good
    09:20:14 < morcos> and you could just be smart about trimming the delta list or something
    09:20:28 < morcos> yes, 1000 should be configurable i thik... actually maybe isn't enough for a default
    09:20:45 < jonasschnelli> also, I liked the configurability of the buffer in MB.
    09:20:52 < jonasschnelli> That's what you probably care about.
    09:20:56 < jonasschnelli> Not the 1000
    09:20:59 < morcos> 1000 secs is just 16 minutes...  you would not want to have to only have 16 data points
    09:21:12 < jonasschnelli> You would say, I reserve 300MB for stats.
    09:21:37 < jonasschnelli> Right... just en 
    09:21:45 < jonasschnelli> just as an example
    09:22:14 < jonasschnelli> So,.. you convinced me for high frequency recent and low frequency long time horizon,...
    09:22:21 < morcos> ok cool...   any approach that automatically keeps both recent fine-grained and long time horizon bigger step, is fine with me
    
  40. jonasschnelli removed this from the milestone 0.14.0 on Jan 12, 2017
  41. jtimon commented at 11:45 PM on February 23, 2017: contributor

    Concept ACK

  42. laanwj commented at 1:24 PM on May 2, 2017: member

    Needs rebase / reply to @morcos' review comment

  43. jonasschnelli force-pushed on May 8, 2017
  44. jonasschnelli force-pushed on May 8, 2017
  45. jonasschnelli commented at 2:04 PM on May 8, 2017: contributor

    Completely rewrote this PR.

    Current features are -> CScheduler driven sample collecting (current interval 2000ms) -> Relevant mempool data (size, memory requirement, minfee) gets written to an atomic cache (no additional locking required) -> Multiple precision levels (currently three, 2s, 60s, 1800s) -> Memory target that will calculate how many samples do fit in the given target -> Sample do only have a 2byte time-delta to the last sample, allowing to save some memory -> Flexible design, adding more data-points should be simple (bandwidth, utxo-stats, etc.).

    ping @morcos

  46. jonasschnelli added this to the milestone 0.15.0 on May 8, 2017
  47. jonasschnelli force-pushed on May 9, 2017
  48. jonasschnelli force-pushed on May 9, 2017
  49. jonasschnelli force-pushed on May 9, 2017
  50. jonasschnelli force-pushed on May 9, 2017
  51. jonasschnelli force-pushed on May 9, 2017
  52. in src/stats/rpc_stats.cpp:26 in a2a71128d1 outdated
      21 | +            "\nSamples time interval is not guaranteed to be constant, hence there\n"
      22 | +            "is a time delta in each sample relative to the last sample.\n"
      23 | +            "\nResult:\n"
      24 | +            "  [\n"
      25 | +            "    {\n"
      26 | +            "      \"percision_interval\" : \"interval\",      (numeric) Interval target in seconds between samples\n"
    


    ryanofsky commented at 5:19 PM on May 10, 2017:

    Should s/percision/precision/ throughout PR. Also maybe this field should be called "sample_interval" to be consistent with "samples" field below.

  53. in src/init.cpp:1169 in a2a71128d1 outdated
    1165 | @@ -1164,6 +1166,9 @@ bool AppInitSanityChecks()
    1166 |  {
    1167 |      // ********************************************************* Step 4: sanity checks
    1168 |  
    1169 | +    if (!CStats::parameterInteraction())
    


    ryanofsky commented at 5:43 PM on May 10, 2017:

    This seems like it would belong in AppInitParameterInteraction more than AppInitSanityChecks

  54. in src/stats/stats.h:28 in a2a71128d1 outdated
      23 | +
      24 | +    //maximum amount of memory to use for the stats
      25 | +    std::atomic<size_t> maxStatsMemory;
      26 | +
      27 | +    // mempool collector
      28 | +    CStatsMempool *mempoolStatsCollector;
    


    ryanofsky commented at 5:46 PM on May 10, 2017:

    Seems like this could be switched to unique_ptr.

  55. in src/stats/stats_mempool.cpp:35 in a2a71128d1 outdated
      30 | +{
      31 | +    startTime = 0;
      32 | +    intervalCounter = 0;
      33 | +
      34 | +    // setup the samples per percision vector
      35 | +    for (unsigned int i = 0; i < sizeof(precisionIntervals) / sizeof(precisionIntervals[0]); i++) {
    


    ryanofsky commented at 5:51 PM on May 10, 2017:

    Should replace with for (unsigned int interval : precisionIntervals)

  56. in src/stats/stats_mempool.cpp:36 in a2a71128d1 outdated
      31 | +    startTime = 0;
      32 | +    intervalCounter = 0;
      33 | +
      34 | +    // setup the samples per percision vector
      35 | +    for (unsigned int i = 0; i < sizeof(precisionIntervals) / sizeof(precisionIntervals[0]); i++) {
      36 | +        vSamplesPerPrecision.push_back(MempoolSamplesVector());
    


    ryanofsky commented at 5:52 PM on May 10, 2017:

    Could simplify to vSamplesPerPrecision.emplace_back()

  57. in src/stats/stats_mempool.cpp:48 in a2a71128d1 outdated
      43 | +    }
      44 | +}
      45 | +
      46 | +std::vector<unsigned int> CStatsMempool::getPercisionGroupsAndIntervals() {
      47 | +    std::vector<unsigned int> grps;
      48 | +    for (unsigned int i = 0; i < sizeof(precisionIntervals) / sizeof(precisionIntervals[0]); i++) {
    


    ryanofsky commented at 5:58 PM on May 10, 2017:

    I think you could drop the loop and just return {std::begin(precisionIntervals), std::end(precisionIntervals)};

  58. in src/stats/stats_mempool.h:20 in a2a71128d1 outdated
      15 | +
      16 | +struct CStatsMempoolSample {
      17 | +
      18 | +    /* time delta to last item as 16bit unsigned integer
      19 | +     * allows a max delta of ~18h */
      20 | +    uint16_t timeDelta;
    


    ryanofsky commented at 6:06 PM on May 10, 2017:

    Using uint16_t here probably doesn't save any space due to struct padding.

  59. ryanofsky commented at 6:08 PM on May 10, 2017: member

    Started looking at this PR, left a few comments.

  60. jonasschnelli commented at 1:04 PM on May 12, 2017: contributor

    Addressed @ryanofsky points.

  61. jonasschnelli force-pushed on May 12, 2017
  62. in src/stats/rpc_stats.cpp:80 in 353a1fc875 outdated
      75 | +
      76 | +};
      77 | +
      78 | +void RegisterStatsRPCCommands(CRPCTable& tableRPC)
      79 | +{
      80 | +    for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
    


    paveljanik commented at 2:18 PM on May 12, 2017:

    Some C++11 magic needed here instead?


    jonasschnelli commented at 2:38 PM on May 12, 2017:

    I think we better fix this together with all other Register*RPCCommands calls.

  63. in src/stats/stats.cpp:13 in 353a1fc875 outdated
       8 | +#include "utiltime.h"
       9 | +
      10 | +#include "util.h"
      11 | +
      12 | +
      13 | +const size_t DEFAULT_MAX_STATS_MEMORY = 10 * 1024 * 1024; //10 MB
    


    paveljanik commented at 2:19 PM on May 12, 2017:

    supermicronit: space before 10MB

  64. in src/stats/stats.cpp:42 in 353a1fc875 outdated
      37 | +
      38 | +std::string CStats::getHelpString(bool showDebug)
      39 | +{
      40 | +    std::string strUsage = HelpMessageGroup(_("Statistic options:"));
      41 | +    strUsage += HelpMessageOpt("-statsenable=", strprintf("Enable statistics (default: %u)", DEFAULT_STATISTICS_ENABLED));
      42 | +    strUsage += HelpMessageOpt("-statsmaxmemorytarget=<n>", strprintf(_("Set the memory limit target for statictics in bytes (default: %u)"), DEFAULT_MAX_STATS_MEMORY));
    


    paveljanik commented at 2:20 PM on May 12, 2017:

    statictics -> statistics

  65. in src/stats/stats_mempool.cpp:23 in 353a1fc875 outdated
      18 | +
      19 | +const static unsigned int MIN_SAMPLES = 5;
      20 | +const static unsigned int MAX_SAMPLES = 5000;
      21 | +
      22 | +
      23 | +const static unsigned int fallbackMaxSamplesPerPercision = 1000;
    


    paveljanik commented at 2:21 PM on May 12, 2017:

    fallbackMaxSamplesPerPercision -> fallbackMaxSamplesPerPrecision

  66. jonasschnelli force-pushed on May 12, 2017
  67. jonasschnelli commented at 2:39 PM on May 12, 2017: contributor

    Fixed @paveljanik points and nits.

  68. in src/stats/rpc_stats.cpp:78 in 400eb315bc outdated
      73 | +    //  --------------------- ------------------------  -----------------------  ------ ----------
      74 | +        {"stats",             "getmempoolstats",        &getmempoolstats,        true,  {}},
      75 | +
      76 | +};
      77 | +
      78 | +void RegisterStatsRPCCommands(CRPCTable& tableRPC)
    


    paveljanik commented at 2:39 PM on May 12, 2017:

    Wshadow emits:

    stats/rpc_stats.cpp:78:42: warning: declaration shadows a variable in the global namespace [-Wshadow]
    

    jonasschnelli commented at 2:43 PM on May 12, 2017:

    Fixed.

  69. jonasschnelli force-pushed on May 12, 2017
  70. jonasschnelli force-pushed on May 12, 2017
  71. in src/stats/rpc_stats.cpp:51 in d8eb9456fc outdated
      46 | +        MempoolSamplesVector samples = CStats::DefaultStats()->mempoolCollector->getSamplesForPrecision(i, timeFrom);
      47 | +
      48 | +        // use "flat" json encoding for performance reasons
      49 | +        UniValue samplesObj(UniValue::VARR);
      50 | +        for (const struct CStatsMempoolSample& sample : samples) {
      51 | +            UniValue singleSample(UniValue::VARR);
    


    ryanofsky commented at 7:05 PM on June 13, 2017:

    In commit "Add mempool statistics collector"

    It seems like it would be more user friendly and extensible if this were an object instead of an array, so the elements could be named. It's a little awkard to have to remember that the first element in the array is time delta, second element is transaction count, etc.


    ryanofsky commented at 5:51 PM on October 5, 2017:

    Thread #8501 (review)

    Any thoughts on this? Named fields seems more natural than tuples in a JSON data structure.


    Sjors commented at 2:02 PM on January 8, 2018:

    I tend to agree. I would also use the timestamp directly instead of an offset.

  72. in src/validation.cpp:455 in d8eb9456fc outdated
     545 | @@ -545,6 +546,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
     546 |  {
     547 |      const CTransaction& tx = *ptx;
     548 |      const uint256 hash = tx.GetHash();
     549 | +    CFeeRate poolMinFeeRate;
    


    ryanofsky commented at 7:16 PM on June 13, 2017:

    In commit "Add mempool statistics collector"

    Would be good to move declaration closer to where this is being set (at least as close as possible).

  73. in src/stats/stats.cpp:15 in d8eb9456fc outdated
      10 | +#include "util.h"
      11 | +
      12 | +
      13 | +const size_t DEFAULT_MAX_STATS_MEMORY = 10 * 1024 * 1024; // 10 MB
      14 | +const bool DEFAULT_STATISTICS_ENABLED = false;
      15 | +const static unsigned int STATS_COLLECT_INTERVAL = 2000; // 2 secs
    


    ryanofsky commented at 7:17 PM on June 13, 2017:

    In commit "Add mempool statistics collector"

    Most of the other settings seem to be in seconds instead of milliseconds. Seems like it would be good to switch this one to seconds for consistency, and to be able to get rid of the division by 1000 in CStatsMempool::addMempoolSamples.


    ryanofsky commented at 5:57 PM on October 5, 2017:

    Thread #8501 (review)

    Would be nice to use either seconds or milliseconds internally in the stats classes instead of both.

  74. in src/stats/stats_mempool.cpp:83 in d8eb9456fc outdated
      78 | +                    // increase starttime by the removed deltas
      79 | +                    for (unsigned int j = (vSamplesPerPrecision[i].size() - vMaxSamplesPerPrecision[i]); j > 0; j--) {
      80 | +                        startTime += vSamplesPerPrecision[i][j].timeDelta;
      81 | +                    }
      82 | +                    // remove element(s) at vector front
      83 | +                    vSamplesPerPrecision[i].erase(vSamplesPerPrecision[i].begin(), vSamplesPerPrecision[i].begin() + (vSamplesPerPrecision[i].size() - vMaxSamplesPerPrecision[i]));
    


    ryanofsky commented at 7:27 PM on June 13, 2017:

    In commit "Add mempool statistics collector"

    Seems like it would be more efficient to switch to a circular buffer, or use std::deque instead of deleting from the beginning of the vector.


    ryanofsky commented at 5:58 PM on October 5, 2017:

    Thread #8501 (review)

    Still worth considering alternatives to erasing from the beginning of a vector, I think.

  75. in src/stats/stats_mempool.h:42 in d8eb9456fc outdated
      37 | +    mutable CCriticalSection cs_mempool_stats; //!<protects the sample/max/time vectors
      38 | +
      39 | +    std::atomic<uint64_t> startTime; //start time
      40 | +    std::vector<MempoolSamplesVector> vSamplesPerPrecision; //!<multiple samples vector per precision level
      41 | +    std::vector<size_t> vMaxSamplesPerPrecision; //!<vector with the maximum amount of samples per precision level
      42 | +    std::vector<size_t> vTimeLastSample; //!<vector with the maximum amount of samples per precision level
    


    ryanofsky commented at 7:35 PM on June 13, 2017:

    In commit "Add mempool statistics collector"

    All three of these vectors have the same length (number of precision levels). Seems like it would be cleaner to have one vector containing a struct with all the information that needs to be stored for a given precision level. It would also make the c++ data structure more consistent with the json data structure returned from rpc.


    jonasschnelli commented at 8:34 PM on June 13, 2017:

    That would be possible though I think that the current approach makes the core more readable.


    ryanofsky commented at 6:17 PM on October 5, 2017:

    Comment is wrong, and type should probably be int64_t, as returned by GetTime.


    ryanofsky commented at 6:35 PM on October 5, 2017:

    Do you think having parallel lists is more readable in c++ and having one list is more readable in json? Also, why are the first two vectors labeled perPrecision, and the third one not? I guess I think something like the following would be less ambiguous and duplicative:

    struct PrecisionLevel {
        std::vector<CStatsMempoolSample> samples;
        size_t max_num_samples;
        int64_t last_sample_time;
    };
    std::vector<PrecisionLevel> m_precision_levels;
    

    Could also rename cs_mempool_stats to cs_precision_levels to make it clearer what the lock is protecting.

  76. in src/stats/stats_mempool.cpp:96 in d8eb9456fc outdated
      91 | +            biggestInterval = precisionIntervals[i];
      92 | +        }
      93 | +
      94 | +        intervalCounter++;
      95 | +
      96 | +        if (intervalCounter > biggestInterval) {
    


    ryanofsky commented at 7:43 PM on June 13, 2017:

    In commit "Add mempool statistics collector"

    I don't understand what this is doing. It seems unnecessary, and strange because it is comparing the counter against numbers of seconds without taking collectInterval into account. If you will keep this, I think it'd be good to add a comment with an example of interval and counter values that explains how this is supposed to work.


    jonasschnelli commented at 8:32 PM on June 13, 2017:

    I have added this to avoid an overflow of intervalCounter. I though resetting it when we exceeded the highest possible interval seems sane but I agree that a size_t overflow will very likely happens never... would you recommend to remove it?


    ryanofsky commented at 9:03 PM on June 13, 2017:

    would you recommend to remove it?

    I'd say remove it unless there's a way to reset it that makes sense (I'm sure there is one but nothing immediately comes to mind).


    ryanofsky commented at 6:54 PM on October 5, 2017:

    Thread #8501 (review)

    Should follow up by removing this or adding comments suggested above.

  77. ryanofsky commented at 7:49 PM on June 13, 2017: member

    Few more comments

  78. laanwj added this to the milestone 0.16.0 on Jul 13, 2017
  79. laanwj removed this from the milestone 0.15.0 on Jul 13, 2017
  80. luke-jr commented at 10:19 PM on August 21, 2017: member

    Rebased on my repo.

    git checkout stats_rpc && git fetch git://github.com/luke-jr/bitcoin stats_rpc && git reset --hard FETCH_HEAD && git push ...
    
  81. jonasschnelli force-pushed on Oct 5, 2017
  82. jonasschnelli force-pushed on Oct 5, 2017
  83. Add mempool statistics collector 8a7cae8b8e
  84. [QA] Add basic mempool stats test 7af0ea43b2
  85. jonasschnelli force-pushed on Oct 5, 2017
  86. jonasschnelli commented at 5:00 AM on October 5, 2017: contributor

    Rebased (rebased @luke-jr's version).

  87. in test/functional/mempool_limit_stats.py:50 in 7af0ea43b2
      42 | @@ -41,6 +43,12 @@ def run_test(self):
      43 |          assert(txid not in self.nodes[0].getrawmempool())
      44 |          txdata = self.nodes[0].gettransaction(txid)
      45 |          assert(txdata['confirmations'] ==  0) #confirmation should still be 0
      46 | +        
      47 | +        # test mempool stats
      48 | +        time.sleep(15)
      49 | +        mps = self.nodes[0].getmempoolstats()
      50 | +        assert_equal(len(mps), 3) #fixed amount of percision levels
    


    ryanofsky commented at 5:43 PM on October 5, 2017:

    s/percision/precision

  88. in src/stats/stats.cpp:17 in 7af0ea43b2
      12 | +
      13 | +const size_t DEFAULT_MAX_STATS_MEMORY = 10 * 1024 * 1024; // 10 MB
      14 | +const bool DEFAULT_STATISTICS_ENABLED = false;
      15 | +const static unsigned int STATS_COLLECT_INTERVAL = 2000; // 2 secs
      16 | +
      17 | +CStats* CStats::sharedInstance = NULL;
    


    ryanofsky commented at 6:02 PM on October 5, 2017:

    Maybe use unique_ptr

  89. in src/stats/stats_mempool.cpp:112 in 7af0ea43b2
     107 | +
     108 | +    // calculate how many samples would fit in the target
     109 | +    size_t maxAmountOfSamples = maxMem / sampleSize;
     110 | +
     111 | +    // distribute the max samples equal between precision levels
     112 | +    unsigned int samplesPerPrecision = maxAmountOfSamples / sizeof(precisionIntervals) / sizeof(precisionIntervals[0]);
    


    ryanofsky commented at 6:14 PM on October 5, 2017:

    Should be * sizeof(precisionIntervals[0])? And maybe multiplication should precede division for less rounding error.

  90. in src/stats/stats_mempool.cpp:55 in 7af0ea43b2
      50 | +
      51 | +bool CStatsMempool::addMempoolSamples(const size_t maxStatsMemory)
      52 | +{
      53 | +    bool statsChanged = false;
      54 | +    uint64_t now = GetTime();
      55 | +    {
    


    ryanofsky commented at 6:44 PM on October 5, 2017:

    Indent and extra braces don't seem to accomplishing anything here.

  91. in src/stats/stats_mempool.cpp:56 in 7af0ea43b2
      51 | +bool CStatsMempool::addMempoolSamples(const size_t maxStatsMemory)
      52 | +{
      53 | +    bool statsChanged = false;
      54 | +    uint64_t now = GetTime();
      55 | +    {
      56 | +        LOCK(cs_mempool_stats);
    


    ryanofsky commented at 6:45 PM on October 5, 2017:

    Maybe acquire LOCK before GetTime for more accurate time.

  92. in src/stats/stats_mempool.cpp:116 in 7af0ea43b2
     111 | +    // distribute the max samples equal between precision levels
     112 | +    unsigned int samplesPerPrecision = maxAmountOfSamples / sizeof(precisionIntervals) / sizeof(precisionIntervals[0]);
     113 | +    samplesPerPrecision = std::max(MIN_SAMPLES, samplesPerPrecision);
     114 | +    samplesPerPrecision = std::min(MAX_SAMPLES, samplesPerPrecision);
     115 | +    for (unsigned int i = 0; i < sizeof(precisionIntervals) / sizeof(precisionIntervals[0]); i++) {
     116 | +        vMaxSamplesPerPrecision[i] = samplesPerPrecision;
    


    ryanofsky commented at 6:46 PM on October 5, 2017:

    Should this acquire cs_mempool_stats before changing these values?

  93. in src/stats/stats.h:47 in 7af0ea43b2
      42 | +
      43 | +    /* dispatch the stats collector repetitive scheduler task */
      44 | +    void startCollecting(CScheduler& scheduler);
      45 | +
      46 | +    /* set the target for the maximum memory consumption (in bytes) */
      47 | +    void setMaxMemoryUsageTarget(size_t maxMem);
    


    ryanofsky commented at 6:58 PM on October 5, 2017:

    Should this be private since it is called by parameterInteraction?

    Either way, it seems like this needs a comment about when it is safe to be called. E.g. it seems like this will not always work properly if it is called after startCollecting because it won't schedule the callback. But maybe it will work to change the size if it was previously greater than 0?

  94. in src/stats/stats.h:22 in 7af0ea43b2
      17 | +// Class that manages various types of statistics and its memory consumption
      18 | +class CStats
      19 | +{
      20 | +private:
      21 | +    static CStats* sharedInstance; //!< singleton instance
      22 | +    std::atomic<bool> statsEnabled;
    


    ryanofsky commented at 7:01 PM on October 5, 2017:

    Why have this as a separate variable when it seems to just be true when maxStatsMemory != 0. Maybe eliminate this or else add comments documenting the intended interfaction between the two variables.

  95. in src/stats/stats.h:58 in 7af0ea43b2
      53 | +    std::unique_ptr<CStatsMempool> mempoolCollector;
      54 | +
      55 | +
      56 | +    /* SIGNALS
      57 | +     * ======= */
      58 | +    boost::signals2::signal<void(void)> MempoolStatsDidChange; //mempool signal
    


    ryanofsky commented at 7:02 PM on October 5, 2017:

    Nobody seems to be listening to this signal. Should add comment explaining who intended listeners are, if keeping this.

  96. in src/stats/rpc_stats.cpp:48 in 7af0ea43b2
      43 | +    UniValue groupsUni(UniValue::VARR);
      44 | +    for (unsigned int i = 0; i < groups.size(); i++) {
      45 | +
      46 | +        MempoolSamplesVector samples = CStats::DefaultStats()->mempoolCollector->getSamplesForPrecision(i, timeFrom);
      47 | +
      48 | +        // use "flat" json encoding for performance reasons
    


    ryanofsky commented at 7:11 PM on October 5, 2017:

    Should expand comment, unclear what it's referring to.

  97. in src/stats/stats.cpp:41 in 7af0ea43b2
      36 | +}
      37 | +
      38 | +std::string CStats::getHelpString(bool showDebug)
      39 | +{
      40 | +    std::string strUsage = HelpMessageGroup(_("Statistic options:"));
      41 | +    strUsage += HelpMessageOpt("-statsenable=", strprintf("Enable statistics (default: %u)", DEFAULT_STATISTICS_ENABLED));
    


    Sjors commented at 1:49 PM on January 8, 2018:

    Should there be an RPC method to turn stats collecting on? If not, maybe the getmempoolstats help message can inform the user to launch bitcoind with -statsenable=1?

  98. Sjors commented at 2:15 PM on January 8, 2018: member

    I think a table would be more human readable. To also make it easier for other software to parse, you could make the sample interval an argument and add the timestamp to each sample:

    bitcoin-cli getmempoolstats 2
    timestamp  | tx_count | dynamic_mem_usage | min_fee_per_k  
    ------------------------------------------------------------
    1515419437 | 1042     | 1797344           | 0       
    1515419439 | 1050     | 1837344           | 0       
    ...
    

    You could add another argument for the output format (CSV, JSON, etc).

    Multiple precision intervals seems redundant, or at least something that could be added later, if users want long term charts without storing too much detail.

  99. laanwj removed this from the milestone 0.16.0 on Jan 11, 2018
  100. laanwj added this to the milestone 0.17.0 on Jan 11, 2018
  101. MarcoFalke added the label Needs rebase on Jun 6, 2018
  102. jonasschnelli removed this from the milestone 0.17.0 on Jul 19, 2018
  103. DrahtBot added the label Up for grabs on Nov 8, 2018
  104. DrahtBot commented at 11:20 PM on November 8, 2018: member

    <!--5fd3d806e98f4a0ca80977bb178665a0-->There hasn't been much activity lately and the patch still needs rebase, so I am closing this for now. Please let me know when you want to continue working on this, so the pull request can be re-opened.

  105. DrahtBot closed this on Nov 8, 2018

  106. laanwj removed the label Needs rebase on Oct 24, 2019
  107. DrahtBot locked this on Dec 16, 2021

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-13 15:15 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me