Revised the ways statistics are computed

It is no longer done in calc_posts, but recursively on each account.
This allows value expressions to ask statistical questions, like
"earliest cleared posting?" (TBD) from any specific account, computed
lazily.
This commit is contained in:
John Wiegley 2009-03-06 04:05:00 -04:00
parent 4a0f5f9034
commit f340d50362
8 changed files with 239 additions and 189 deletions

View file

@ -65,6 +65,7 @@ libledger_data_la_LDFLAGS = -release $(VERSION).0
libledger_report_la_SOURCES = \
src/quotes.cc \
src/stats.cc \
src/generate.cc \
src/derive.cc \
src/emacs.cc \
@ -123,6 +124,7 @@ pkginclude_HEADERS = \
src/precmd.h \
src/derive.h \
src/generate.h \
src/stats.h \
src/output.h \
src/emacs.h \
src/quotes.h \

View file

@ -309,7 +309,36 @@ std::size_t account_t::children_with_flags(xdata_t::flags_t flags) const
account_t::xdata_t::details_t&
account_t::xdata_t::details_t::operator+=(const details_t& other)
{
// jww (2009-03-05): NYI
posts_count += other.posts_count;
posts_virtuals_count += other.posts_virtuals_count;
posts_cleared_count += other.posts_cleared_count;
posts_last_7_count += other.posts_last_7_count;
posts_last_30_count += other.posts_last_30_count;
posts_this_month_count += other.posts_this_month_count;
if (! is_valid(earliest_post) ||
(is_valid(other.earliest_post) &&
other.earliest_post < earliest_post))
earliest_post = other.earliest_post;
if (! is_valid(earliest_cleared_post) ||
(is_valid(other.earliest_cleared_post) &&
other.earliest_cleared_post < earliest_cleared_post))
earliest_cleared_post = other.earliest_cleared_post;
if (! is_valid(latest_post) ||
(is_valid(other.latest_post) &&
other.latest_post > latest_post))
latest_post = other.latest_post;
if (! is_valid(latest_cleared_post) ||
(is_valid(other.latest_cleared_post) &&
other.latest_cleared_post > latest_cleared_post))
latest_cleared_post = other.latest_cleared_post;
filenames.insert(other.filenames.begin(), other.filenames.end());
accounts_referenced.insert(other.accounts_referenced.begin(),
other.accounts_referenced.end());
payees_referenced.insert(other.payees_referenced.begin(),
other.payees_referenced.end());
return *this;
}
@ -377,7 +406,7 @@ account_t::family_details(bool gather_all) const
foreach (const accounts_map::value_type& pair, accounts)
xdata_->family_details += pair.second->family_details(gather_all);
xdata_->self_details += self_details(gather_all);
xdata_->family_details += self_details(gather_all);
}
return xdata_->family_details;
}
@ -385,16 +414,8 @@ account_t::family_details(bool gather_all) const
void account_t::xdata_t::details_t::update(post_t& post,
bool gather_all)
{
if (last_xact != post.xact) {
xacts_count++;
last_xact = post.xact;
}
if (last_post == &post)
return;
last_post = &post;
posts_count++;
if (post.has_flags(POST_VIRTUAL))
posts_virtuals_count++;

View file

@ -143,8 +143,6 @@ class account_t : public scope_t
bool gathered;
// The following are only calculated if --totals is enabled
std::size_t xacts_count;
std::size_t posts_count;
std::size_t posts_virtuals_count;
std::size_t posts_cleared_count;
@ -157,9 +155,6 @@ class account_t : public scope_t
date_t latest_post;
date_t latest_cleared_post;
xact_t * last_xact;
post_t * last_post;
std::size_t last_size;
// The following are only calculated if --gather is enabled
@ -171,8 +166,6 @@ class account_t : public scope_t
: calculated(false),
gathered(false),
xacts_count(0),
posts_count(0),
posts_virtuals_count(0),
posts_cleared_count(0),
@ -180,14 +173,6 @@ class account_t : public scope_t
posts_last_30_count(0),
posts_this_month_count(0),
earliest_post(0),
earliest_cleared_post(0),
latest_post(0),
latest_cleared_post(0),
last_xact(NULL),
last_post(NULL),
last_size(0) {}
details_t& operator+=(const details_t& other);

View file

@ -111,104 +111,6 @@ void format_posts::operator()(post_t& post)
}
}
void gather_statistics::flush()
{
std::ostream& out(report.output_stream);
{
straccstream accum;
out << ACCUM(accum << "Time period: %1 to %2" << statistics.earliest_post
<< statistics.latest_post) << std::endl << std::endl;
}
out << _(" Files these postings came from:") << std::endl;
foreach (const path& pathname, statistics.filenames)
if (! pathname.empty())
out << " " << pathname.string() << std::endl;
out << std::endl;
out << _(" Unique payees: ");
out.width(8);
out << std::right << statistics.payees_referenced.size() << std::endl;
out << _(" Unique accounts: ");
out.width(8);
out << std::right << statistics.accounts_referenced.size() << std::endl;
out << _(" Number of transactions: ") ;
out.width(8);
out << std::right << statistics.total_xacts << std::endl;
out << _(" Number of postings: ");
out.width(8);
out << std::right << statistics.total_posts;
out << " (";
out.precision(2);
out << (double((statistics.latest_post - statistics.earliest_post).days()) /
double(statistics.total_posts)) << _(" per day)") << std::endl;
out << _(" Days since last post: ");
out.width(8);
out << std::right << (CURRENT_DATE() - statistics.latest_post).days()
<< std::endl;
out << _(" Posts in last 7 days: ");
out.width(8);
out << std::right << statistics.total_last_7_days << std::endl;
out << _(" Posts in last 30 days: ");
out.width(8);
out << std::right << statistics.total_last_30_days << std::endl;
out << _(" Posts seen this month: ");
out.width(8);
out << std::right << statistics.total_this_month << std::endl;
out << _(" Uncleared postings: ");
out.width(8);
out << std::right << statistics.total_uncleared_posts << std::endl;
out.flush();
}
void gather_statistics::operator()(post_t& post)
{
if (last_xact != post.xact) {
statistics.total_xacts++;
last_xact = post.xact;
}
if (last_post != &post) {
statistics.total_posts++;
last_post = &post;
statistics.filenames.insert(post.pathname);
date_t date = post.date();
if (date.year() == CURRENT_DATE().year() &&
date.month() == CURRENT_DATE().month())
statistics.total_this_month++;
if ((CURRENT_DATE() - date).days() <= 30)
statistics.total_last_30_days++;
if ((CURRENT_DATE() - date).days() <= 7)
statistics.total_last_7_days++;
if (post.state() != item_t::CLEARED)
statistics.total_uncleared_posts++;
if (! is_valid(statistics.earliest_post) ||
post.date() < statistics.earliest_post)
statistics.earliest_post = post.date();
if (! is_valid(statistics.latest_post) ||
post.date() > statistics.latest_post)
statistics.latest_post = post.date();
statistics.accounts_referenced.insert(post.account->fullname());
statistics.payees_referenced.insert(post.xact->payee);
}
}
format_accounts::format_accounts(report_t& _report,
const string& format)
: report(_report), disp_pred()

View file

@ -84,52 +84,6 @@ public:
virtual void operator()(post_t& post);
};
/**
* @brief Brief
*
* Long.
*/
class gather_statistics : public item_handler<post_t>
{
protected:
report_t& report;
xact_t * last_xact;
post_t * last_post;
struct statistics_t {
std::set<path> filenames;
std::size_t total_xacts;
std::size_t total_posts;
std::size_t total_uncleared_posts;
std::size_t total_last_7_days;
std::size_t total_last_30_days;
std::size_t total_this_month;
date_t earliest_post;
date_t latest_post;
std::set<string> accounts_referenced;
std::set<string> payees_referenced;
statistics_t()
: total_xacts(0), total_posts(0), total_uncleared_posts(0),
total_last_7_days(0), total_last_30_days(0), total_this_month(0) {}
} statistics;
public:
gather_statistics(report_t& _report)
: report(_report), last_xact(NULL), last_post(NULL) {
TRACE_CTOR(gather_statistics, "report&");
}
virtual ~gather_statistics() {
TRACE_DTOR(gather_statistics);
}
virtual void flush();
virtual void operator()(post_t& post);
};
/**
* @brief Brief
*

View file

@ -37,6 +37,7 @@
#include "iterators.h"
#include "filters.h"
#include "precmd.h"
#include "stats.h"
#include "generate.h"
#include "derive.h"
#include "emacs.h"
@ -728,8 +729,9 @@ expr_t::ptr_op_t report_t::lookup(const string& name)
case 's':
if (is_eq(q, "stats") || is_eq(q, "stat"))
return WRAP_FUNCTOR(reporter<>(new gather_statistics(*this), *this));
else if (is_eq(q, "server") && maybe_import("ledger.server"))
return WRAP_FUNCTOR(report_statistics);
else
if (is_eq(q, "server") && maybe_import("ledger.server"))
return session.lookup(string(CMD_PREFIX) + "server");
break;
}

125
src/stats.cc Normal file
View file

@ -0,0 +1,125 @@
/*
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "derive.h"
#include "xact.h"
#include "post.h"
#include "account.h"
#include "report.h"
#include "session.h"
namespace ledger {
value_t report_statistics(call_scope_t& args)
{
report_t& report(find_scope<report_t>(args));
std::ostream& out(report.output_stream);
const account_t::xdata_t::details_t&
statistics(report.session.master->family_details(true));
if (! is_valid(statistics.earliest_post) &&
! is_valid(statistics.latest_post))
return NULL_VALUE;
assert(is_valid(statistics.earliest_post));
assert(is_valid(statistics.latest_post));
{
straccstream accum;
out << ACCUM(accum << _("Time period: %1 to %2 (%3 days)")
<< format_date(statistics.earliest_post)
<< format_date(statistics.latest_post)
<< (statistics.latest_post -
statistics.earliest_post).days())
<< std::endl << std::endl;
}
out << _(" Files these postings came from:") << std::endl;
foreach (const path& pathname, statistics.filenames)
if (! pathname.empty())
out << " " << pathname.string() << std::endl;
out << std::endl;
out << _(" Unique payees: ");
out.width(6);
out << statistics.payees_referenced.size() << std::endl;
out << _(" Unique accounts: ");
out.width(6);
out << statistics.accounts_referenced.size() << std::endl;
out << std::endl;
#if 0
out << _(" Number of transactions: ") ;
out.width(6);
out << statistics.xacts_count << std::endl;
#endif
out << _(" Number of postings: ");
out.width(6);
out << statistics.posts_count;
out << " (";
out.precision(2);
out << (double((statistics.latest_post - statistics.earliest_post).days()) /
double(statistics.posts_count)) << _(" per day)") << std::endl;
out << _(" Uncleared postings: ");
out.width(6);
out << (statistics.posts_count -
statistics.posts_cleared_count) << std::endl;
out << std::endl;
out << _(" Days since last post: ");
out.width(6);
out << (CURRENT_DATE() - statistics.latest_post).days()
<< std::endl;
out << _(" Posts in last 7 days: ");
out.width(6);
out << statistics.posts_last_7_count << std::endl;
out << _(" Posts in last 30 days: ");
out.width(6);
out << statistics.posts_last_30_count << std::endl;
out << _(" Posts seen this month: ");
out.width(6);
out << statistics.posts_this_month_count << std::endl;
out.flush();
return NULL_VALUE;
}
} // namespace ledger

59
src/stats.h Normal file
View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of New Artisans LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @addtogroup stats
*/
/**
* @file stats.h
* @author John Wiegley
*
* @ingroup report
*
* @brief Brief
*
* Long.
*/
#ifndef _STATS_H
#define _STATS_H
#include "value.h"
namespace ledger {
class call_scope_t;
value_t report_statistics(call_scope_t& scope);
} // namespace ledger
#endif // _STATS_H