source: trunk/lib/Stats.cc @ 530

Last change on this file since 530 was 530, checked in by Peter Johansson, 14 years ago

For large projects, i.e. many files and many contributors, the statistics is typically not changing between tow revisisions for a specific file and author. Therefore, we are caching the 'differentiated' stats instead. This will be sparse (contain mostly zeros) so we can use a sparse notation and typically save disc space i.e. read/write time.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 10.9 KB
Line 
1// $Id: Stats.cc 530 2007-12-25 13:59:58Z peter $
2
3/*
4  Copyright (C) 2005 Peter Johansson
5  Copyright (C) 2006, 2007 Jari Häkkinen, Peter Johansson
6
7  This file is part of svndigest, http://trac.thep.lu.se/svndigest
8
9  svndigest is free software; you can redistribute it and/or modify it
10  under the terms of the GNU General Public License as published by
11  the Free Software Foundation; either version 2 of the License, or
12  (at your option) any later version.
13
14  svndigest is distributed in the hope that it will be useful, but
15  WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  General Public License for more details.
18
19  You should have received a copy of the GNU General Public License
20  along with this program; if not, write to the Free Software
21  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22  02111-1307, USA.
23*/
24
25#include "Stats.h"
26
27#include "Functor.h"
28#include "GnuplotFE.h"
29#include "SVNblame.h"
30#include "SVNinfo.h"
31#include "utility.h"
32
33#include <algorithm>
34#include <cassert>
35#include <cstdlib>
36#include <fstream>
37#include <iostream>
38#include <iterator>
39#include <map>
40#include <numeric>
41#include <string>
42#include <sstream>
43#include <unistd.h>
44#include <utility>
45#include <vector>
46
47
48namespace theplu{
49namespace svndigest{
50
51
52  Stats::Stats(const std::string& path)
53  {
54    // Make sure latest revision is set properly
55    SVNinfo svn_info(path);
56    revision_=svn_info.rev();
57    last_changed_rev_=svn_info.last_changed_rev();
58  }
59
60
61  Stats::~Stats(void)
62  {
63  }
64
65
66  void Stats::accumulate(std::vector<u_int>& vec) const
67  {
68    if (vec.empty()){
69      vec.resize(last_changed_rev(),0);
70      return;
71    }
72    std::partial_sum(vec.begin(),vec.end(),vec.begin());
73    // static_cast to remove annoying compiler warning
74    if (vec.size() < static_cast<size_t>(revision()+1))
75      vec.insert(vec.end(), revision()+1-vec.size(), vec.back());
76  }
77
78
79  void Stats::accumulate_stats(void)
80  {
81    for (std::set<std::string>::const_iterator iter(authors().begin());
82         iter!=authors().end(); ++iter) {
83      std::vector<u_int>& code = code_[*iter];
84      accumulate(code);
85      std::vector<u_int>& comments = comments_[*iter];
86      accumulate(comments);
87      std::vector<u_int>& other = other_[*iter];
88      accumulate(other);
89
90      VectorPlus<u_int> vp;
91      total_[*iter] = vp(vp(code, comments),other);
92    }
93    std::vector<u_int> init(revision()+1);
94    code_["all"]=std::accumulate(code_.begin(), code_.end(), init,
95                                 PairValuePlus<std::string,u_int>());
96    comments_["all"]=std::accumulate(comments_.begin(), comments_.end(), init,
97                                     PairValuePlus<std::string,u_int>());
98    other_["all"]=std::accumulate(other_.begin(), other_.end(), init,
99                                  PairValuePlus<std::string,u_int>());
100    VectorPlus<u_int> vp;
101    total_["all"] = vp(vp(code_["all"], comments_["all"]), other_["all"]);
102  }
103
104
105  void Stats::add_author(std::string name)
106  {
107    authors_.insert(name);
108  }
109
110
111  void Stats::add_authors(std::set<std::string>::const_iterator first, 
112                          std::set<std::string>::const_iterator last)
113  {
114    authors_.insert(first, last);
115  }
116
117
118  const std::set<std::string>& Stats::authors(void) const
119  {
120    return authors_;
121  }
122
123
124  u_int Stats::code(const std::string& user) const
125  {
126    return get_back(code_, user);
127  }
128
129
130  u_int Stats::comments(const std::string& user) const
131  {
132    return get_back(comments_, user);
133  }
134
135
136  u_int Stats::empty(const std::string& user) const
137  {
138    return get_back(other_, user);
139  }
140
141
142  u_int Stats::get_back(const Author2Vector& m, std::string user) const
143  {
144    A2VConstIter iter(m.find(std::string(user)));
145    if (iter==m.end() || iter->second.empty()) 
146      return 0;
147    return iter->second.back();
148  }
149
150
151  const std::vector<u_int>& Stats::get_vector(const Author2Vector& m, 
152                                              std::string user) const
153  {
154    A2VConstIter iter(m.find(std::string(user)));
155    if (iter==m.end()) 
156      throw std::runtime_error(user+std::string(" not found i Stats"));
157    return iter->second;
158  }
159
160
161  svn_revnum_t Stats::last_changed_rev(void) const
162  {
163    return last_changed_rev_;
164  }
165
166
167  u_int Stats::lines(const std::string& user) const
168  {
169    return get_back(total_, user);
170  }
171
172
173  void Stats::load(std::istream& is, Author2Vector& m)
174  {
175    while (m.size() < authors().size()+1 && is.good()) {
176      std::string name;
177      std::getline(is, name);
178      if (name.empty())
179        continue;
180      std::vector<u_int>& vec=m[name];
181      svn_revnum_t rev=0;
182      while (rev < last_changed_rev() && is.good()) {
183        u_int count;
184        is >> count;
185        if ( rev+1 > static_cast<svn_revnum_t>(vec.size()) )
186        vec.resize(rev+1);
187        vec[rev] = count;
188        is >> rev;
189      }
190    }
191  }
192
193
194  bool Stats::load_cache(std::istream& is)
195  {
196    svn_revnum_t rev;
197    is >> rev;
198    if (rev<last_changed_rev()){
199      return false; // cache is not up to date
200    }
201    reset();
202    size_t a_size=0;
203    is >> a_size;
204    std::string str;
205    while (authors().size()<a_size && is.good()){
206      getline(is, str);
207      if (str.size())
208        add_author(str);
209    }
210    getline(is, str);
211    if (str!=code_cache()){
212      return false;
213    }
214    load(is, code_);
215    getline(is, str);
216    getline(is, str);
217    if (str!=comments_cache()){
218      return false;
219    }
220    load(is, comments_);
221    getline(is, str);
222    getline(is, str);
223    if (str!=other_cache()){
224      return false;
225    }
226    load(is, other_);
227    getline(is, str);
228    getline(is, str);
229    if (str!=total_cache()){
230      return false;
231    }
232    load(is, total_);
233    getline(is,str);
234    getline(is,str);
235    return str==end_of_cache();
236  }
237
238
239  void Stats::map_add(A2VConstIter first1, A2VConstIter last1, 
240                      Author2Vector& map)
241  {
242    A2VIter first2(map.begin());
243    Author2Vector::key_compare compare;
244    while ( first1 != last1) { 
245      // key of first1 less than key of first2
246      if (first2==map.end() || compare(first1->first,first2->first)) {
247        first2 = map.insert(first2, *first1);
248        ++first1;
249      }
250      // key of first2 less than key of first1
251      else if ( compare(first2->first, first1->first)) {
252        ++first2;
253      }
254      // keys are equivalent
255      else {
256        VectorPlus<Author2Vector::mapped_type::value_type> vp;
257        first2->second = vp(first1->second, first2->second);
258        ++first1;
259        ++first2;
260      }
261    }
262  }
263
264
265  void Stats::parse(const std::string& path)
266  {
267    do_parse(path);
268    accumulate_stats();
269  }
270
271  std::string Stats::plot(const std::string& filename,
272                          const std::string& linetype) const
273  {
274    plot_init(filename);
275    GnuplotFE* gp=GnuplotFE::instance();
276    const Author2Vector* stat=NULL;
277    if (linetype=="total")
278      stat = &total_;
279    else if (linetype=="code")
280      stat = &code_;
281    else if (linetype=="comments")
282      stat = &comments_;
283    else if (linetype=="empty")
284      stat = &other_;
285    assert(stat);
286    assert(stat->size());
287    std::vector<u_int> total=get_vector(*stat, "all");   
288    double yrange_max=1.03*total.back()+1;
289    gp->yrange(yrange_max);
290
291    typedef std::vector<std::pair<std::string, std::vector<u_int> > > vec_type;
292    vec_type author_cont;
293    author_cont.reserve(stat->size());
294    for (std::set<std::string>::const_iterator i=authors_.begin(); 
295         i != authors_.end(); ++i) {
296      if (lines(*i))
297        author_cont.push_back(std::make_pair(*i,get_vector(*stat,*i)));
298    }
299
300    LessReversed<std::vector<u_int> > lr;
301    PairSecondCompare<std::string, std::vector<u_int>, 
302      LessReversed<std::vector<u_int> > > compare(lr);
303    std::sort(author_cont.begin(), author_cont.end(), compare);
304
305    size_t plotno=author_cont.size();
306    std::stringstream ss;
307    vec_type::iterator end(author_cont.end());
308    for (vec_type::iterator i(author_cont.begin()); i!=end; ++i) {
309      ss.str("");
310      ss << "set key height " << 2*plotno;
311      gp->command(ss.str());
312      ss.str("");
313      ss << get_back(*stat, i->first) << " " << i->first;
314      gp->yrange(yrange_max);
315      gp->linetitle(ss.str());
316      ss.str("");
317      ss << "steps " << --plotno+2;
318      gp->linestyle(ss.str());
319      gp->plot(i->second);
320    }
321    ss.str("");
322    ss << get_back(*stat, "all") << " total";
323    gp->command("set key height 0");
324    gp->linetitle(ss.str());
325    gp->linestyle("steps 1");
326    gp->plot(total);
327
328    gp->command("unset multiplot");
329    gp->yrange();
330
331    return filename;
332  }
333
334
335  void Stats::plot_init(const std::string& filename) const
336  {
337    GnuplotFE* gp=GnuplotFE::instance();
338    gp->command("set term png");
339    gp->command("set output '"+filename+"'");
340    gp->command("set xtics nomirror");
341    gp->command("set ytics nomirror");
342    gp->command("set key default");
343    gp->command("set key left Left reverse");
344    gp->command("set multiplot");
345  }
346
347
348  void Stats::plot_summary(const std::string& filename) const
349  {
350    plot_init(filename);
351    GnuplotFE* gp=GnuplotFE::instance();
352    std::vector<u_int> total = get_vector(total_, "all");
353    double yrange_max=1.03*total.back()+1;
354    gp->yrange(yrange_max);
355    std::stringstream ss;
356   
357    ss.str("");
358    std::vector<u_int> x(get_vector(code_, "all"));
359    ss << x.back() << " code";
360    gp->command("set key height 2");
361    gp->linetitle(ss.str());
362    gp->linestyle("steps 2");
363    gp->plot(x);
364
365    ss.str("");
366    x = get_vector(comments_, "all");
367    ss << x.back() << " comment";
368    gp->command("set key height 4");
369    gp->linetitle(ss.str());
370    gp->linestyle("steps 3");
371    gp->plot(x);
372
373    ss.str("");
374    x = get_vector(other_, "all");
375    ss << x.back() << " other";
376    gp->command("set key height 6");
377    gp->linetitle(ss.str());
378    gp->linestyle("steps 4");
379    gp->plot(x);
380
381    ss.str("");
382    ss << total.back() << " total";
383    gp->command("set key height 0");
384    gp->linetitle(ss.str());
385    gp->linestyle("steps 1");
386    gp->plot(total);
387
388    gp->command("unset multiplot");
389    gp->yrange();
390  }
391
392
393  void Stats::print(std::ostream& os) const
394  {
395    os << last_changed_rev() << " ";
396    os << authors().size() << " ";
397
398    std::copy(authors().begin(), authors().end(), 
399              std::ostream_iterator<std::string>(os, "\n"));
400    os << code_cache() << "\n";
401    print(os, code_);
402    os << "\n" << comments_cache() << "\n";
403    print(os, comments_);
404    os << "\n" << other_cache() << "\n";
405    print(os, other_);
406    os << "\n" << total_cache() << "\n";
407    print(os, total_);
408    os << "\n" << end_of_cache() << "\n";
409  }
410
411
412  void Stats::print(std::ostream& os, const Author2Vector& m) const
413  {
414    for (A2VConstIter i(m.begin()); i!=m.end(); ++i){
415      os << i->first << "\n";
416      os << i->second.front() << " ";
417      for (size_t j=1; j<i->second.size(); ++j)
418        if (i->second[j] != i->second[j-1])
419          os << j << " " << i->second[j] - i->second[j-1] << " ";
420      os << last_changed_rev()+1 << " ";
421    }
422  }
423
424  void Stats::reset(void)
425  {
426    total_.clear();
427    code_.clear();
428    comments_.clear();
429    other_.clear();
430    authors_.clear();
431    std::vector<u_int> vec;
432    code_["all"] = vec;
433    comments_["all"] = vec;
434    other_["all"] = vec;
435    total_["all"] = vec;
436  }
437
438
439  Stats& Stats::operator+=(const Stats& rhs)
440  {
441    revision_ = std::max(revision_, rhs.revision_);
442    last_changed_rev_ = std::max(last_changed_rev_, rhs.last_changed_rev_);
443    add_authors(rhs.authors().begin(), rhs.authors().end());
444    map_add(rhs.code_.begin(), rhs.code_.end(), code_);
445    map_add(rhs.comments_.begin(), rhs.comments_.end(), comments_);
446    map_add(rhs.other_.begin(), rhs.other_.end(), other_);
447    map_add(rhs.total_.begin(), rhs.total_.end(), total_);
448    assert(rhs.other_.size());
449    assert(other_.size());
450   
451    return *this;
452  }
453
454
455}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.