source: trunk/lib/Stats.cc @ 531

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

moving more stuff to base class and add class for Blame Statistics

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 12.2 KB
Line 
1// $Id: Stats.cc 531 2007-12-25 18:51:07Z 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(const std::string& user, const u_int& rev, 
106                  const Parser::line_type& lt, u_int n)
107  {
108    assert(user.size());
109    add_author(user);
110
111    std::vector<u_int>& total = total_[user];
112    if (total.size() < rev+1){
113      total.reserve(revision() + 1);
114      total.insert(total.end(), rev - total.size(), 0);
115      total.push_back(n);
116    }
117    else
118      total[rev]+=n;
119
120    std::vector<u_int>& code = code_[user];
121    if (code.size() < rev+1){
122      code.reserve(revision() + 1);
123      code.insert(code.end(), rev - code.size(), 0);
124      if (lt == Parser::code)
125        code.push_back(n);
126      else 
127        code.push_back(0);
128    }
129    else if (lt == Parser::code)
130      code[rev]+=n;
131
132    std::vector<u_int>& comments = comments_[user];
133    if (comments.size() < rev+1){
134      comments.reserve(revision() + 1);
135      comments.insert(comments.end(), rev - comments.size(), 0);
136      if (lt == Parser::comment)
137        code.push_back(n);
138      else 
139        comments.push_back(0);
140    }
141    else if (lt == Parser::comment)
142      comments[rev]+=n;
143
144    std::vector<u_int>& other = other_[user];
145    if (other.size() < rev+1){
146      other.reserve(revision() + 1);
147      other.insert(other.end(), rev - other.size(), 0);
148      if (lt == Parser::other)
149        other.push_back(n);
150      else 
151        other.push_back(0);
152    }
153    else if (lt == Parser::other)
154      other[rev]+=n;
155  }
156
157
158  void Stats::add_author(std::string name)
159  {
160    authors_.insert(name);
161  }
162
163
164  void Stats::add_authors(std::set<std::string>::const_iterator first, 
165                          std::set<std::string>::const_iterator last)
166  {
167    authors_.insert(first, last);
168  }
169
170
171  const std::set<std::string>& Stats::authors(void) const
172  {
173    return authors_;
174  }
175
176
177  u_int Stats::code(const std::string& user) const
178  {
179    return get_back(code_, user);
180  }
181
182
183  u_int Stats::comments(const std::string& user) const
184  {
185    return get_back(comments_, user);
186  }
187
188
189  u_int Stats::empty(const std::string& user) const
190  {
191    return get_back(other_, user);
192  }
193
194
195  u_int Stats::get_back(const Author2Vector& m, std::string user) const
196  {
197    A2VConstIter iter(m.find(std::string(user)));
198    if (iter==m.end() || iter->second.empty()) 
199      return 0;
200    return iter->second.back();
201  }
202
203
204  const std::vector<u_int>& Stats::get_vector(const Author2Vector& m, 
205                                              std::string user) const
206  {
207    A2VConstIter iter(m.find(std::string(user)));
208    if (iter==m.end()) 
209      throw std::runtime_error(user+std::string(" not found i Stats"));
210    return iter->second;
211  }
212
213
214  svn_revnum_t Stats::last_changed_rev(void) const
215  {
216    return last_changed_rev_;
217  }
218
219
220  u_int Stats::lines(const std::string& user) const
221  {
222    return get_back(total_, user);
223  }
224
225
226  void Stats::load(std::istream& is, Author2Vector& m)
227  {
228    while (m.size() < authors().size()+1 && is.good()) {
229      std::string name;
230      std::getline(is, name);
231      if (name.empty())
232        continue;
233      std::vector<u_int>& vec=m[name];
234      svn_revnum_t rev=0;
235      while (rev < last_changed_rev() && is.good()) {
236        u_int count;
237        is >> count;
238        if ( rev+1 > static_cast<svn_revnum_t>(vec.size()) )
239        vec.resize(rev+1);
240        vec[rev] = count;
241        is >> rev;
242      }
243    }
244  }
245
246
247  bool Stats::load_cache(std::istream& is)
248  {
249    svn_revnum_t rev;
250    is >> rev;
251    if (rev<last_changed_rev()){
252      return false; // cache is not up to date
253    }
254    reset();
255    size_t a_size=0;
256    is >> a_size;
257    std::string str;
258    while (authors().size()<a_size && is.good()){
259      getline(is, str);
260      if (str.size())
261        add_author(str);
262    }
263    getline(is, str);
264    if (str!=code_cache()){
265      return false;
266    }
267    load(is, code_);
268    getline(is, str);
269    getline(is, str);
270    if (str!=comments_cache()){
271      return false;
272    }
273    load(is, comments_);
274    getline(is, str);
275    getline(is, str);
276    if (str!=other_cache()){
277      return false;
278    }
279    load(is, other_);
280    getline(is, str);
281    getline(is, str);
282    if (str!=total_cache()){
283      return false;
284    }
285    load(is, total_);
286    getline(is,str);
287    getline(is,str);
288    return str==end_of_cache();
289  }
290
291
292  void Stats::map_add(A2VConstIter first1, A2VConstIter last1, 
293                      Author2Vector& map)
294  {
295    A2VIter first2(map.begin());
296    Author2Vector::key_compare compare;
297    while ( first1 != last1) { 
298      // key of first1 less than key of first2
299      if (first2==map.end() || compare(first1->first,first2->first)) {
300        first2 = map.insert(first2, *first1);
301        ++first1;
302      }
303      // key of first2 less than key of first1
304      else if ( compare(first2->first, first1->first)) {
305        ++first2;
306      }
307      // keys are equivalent
308      else {
309        VectorPlus<Author2Vector::mapped_type::value_type> vp;
310        first2->second = vp(first1->second, first2->second);
311        ++first1;
312        ++first2;
313      }
314    }
315  }
316
317
318  void Stats::parse(const std::string& path)
319  {
320    do_parse(path);
321    accumulate_stats();
322  }
323
324  std::string Stats::plot(const std::string& filename,
325                          const std::string& linetype) const
326  {
327    plot_init(filename);
328    GnuplotFE* gp=GnuplotFE::instance();
329    const Author2Vector* stat=NULL;
330    if (linetype=="total")
331      stat = &total_;
332    else if (linetype=="code")
333      stat = &code_;
334    else if (linetype=="comments")
335      stat = &comments_;
336    else if (linetype=="empty")
337      stat = &other_;
338    assert(stat);
339    assert(stat->size());
340    std::vector<u_int> total=get_vector(*stat, "all");   
341    double yrange_max=1.03*total.back()+1;
342    gp->yrange(yrange_max);
343
344    typedef std::vector<std::pair<std::string, std::vector<u_int> > > vec_type;
345    vec_type author_cont;
346    author_cont.reserve(stat->size());
347    for (std::set<std::string>::const_iterator i=authors_.begin(); 
348         i != authors_.end(); ++i) {
349      if (lines(*i))
350        author_cont.push_back(std::make_pair(*i,get_vector(*stat,*i)));
351    }
352
353    LessReversed<std::vector<u_int> > lr;
354    PairSecondCompare<std::string, std::vector<u_int>, 
355      LessReversed<std::vector<u_int> > > compare(lr);
356    std::sort(author_cont.begin(), author_cont.end(), compare);
357
358    size_t plotno=author_cont.size();
359    std::stringstream ss;
360    vec_type::iterator end(author_cont.end());
361    for (vec_type::iterator i(author_cont.begin()); i!=end; ++i) {
362      ss.str("");
363      ss << "set key height " << 2*plotno;
364      gp->command(ss.str());
365      ss.str("");
366      ss << get_back(*stat, i->first) << " " << i->first;
367      gp->yrange(yrange_max);
368      gp->linetitle(ss.str());
369      ss.str("");
370      ss << "steps " << --plotno+2;
371      gp->linestyle(ss.str());
372      gp->plot(i->second);
373    }
374    ss.str("");
375    ss << get_back(*stat, "all") << " total";
376    gp->command("set key height 0");
377    gp->linetitle(ss.str());
378    gp->linestyle("steps 1");
379    gp->plot(total);
380
381    gp->command("unset multiplot");
382    gp->yrange();
383
384    return filename;
385  }
386
387
388  void Stats::plot_init(const std::string& filename) const
389  {
390    GnuplotFE* gp=GnuplotFE::instance();
391    gp->command("set term png");
392    gp->command("set output '"+filename+"'");
393    gp->command("set xtics nomirror");
394    gp->command("set ytics nomirror");
395    gp->command("set key default");
396    gp->command("set key left Left reverse");
397    gp->command("set multiplot");
398  }
399
400
401  void Stats::plot_summary(const std::string& filename) const
402  {
403    plot_init(filename);
404    GnuplotFE* gp=GnuplotFE::instance();
405    std::vector<u_int> total = get_vector(total_, "all");
406    double yrange_max=1.03*total.back()+1;
407    gp->yrange(yrange_max);
408    std::stringstream ss;
409   
410    ss.str("");
411    std::vector<u_int> x(get_vector(code_, "all"));
412    ss << x.back() << " code";
413    gp->command("set key height 2");
414    gp->linetitle(ss.str());
415    gp->linestyle("steps 2");
416    gp->plot(x);
417
418    ss.str("");
419    x = get_vector(comments_, "all");
420    ss << x.back() << " comment";
421    gp->command("set key height 4");
422    gp->linetitle(ss.str());
423    gp->linestyle("steps 3");
424    gp->plot(x);
425
426    ss.str("");
427    x = get_vector(other_, "all");
428    ss << x.back() << " other";
429    gp->command("set key height 6");
430    gp->linetitle(ss.str());
431    gp->linestyle("steps 4");
432    gp->plot(x);
433
434    ss.str("");
435    ss << total.back() << " total";
436    gp->command("set key height 0");
437    gp->linetitle(ss.str());
438    gp->linestyle("steps 1");
439    gp->plot(total);
440
441    gp->command("unset multiplot");
442    gp->yrange();
443  }
444
445
446  void Stats::print(std::ostream& os) const
447  {
448    os << last_changed_rev() << " ";
449    os << authors().size() << " ";
450
451    std::copy(authors().begin(), authors().end(), 
452              std::ostream_iterator<std::string>(os, "\n"));
453    os << code_cache() << "\n";
454    print(os, code_);
455    os << "\n" << comments_cache() << "\n";
456    print(os, comments_);
457    os << "\n" << other_cache() << "\n";
458    print(os, other_);
459    os << "\n" << total_cache() << "\n";
460    print(os, total_);
461    os << "\n" << end_of_cache() << "\n";
462  }
463
464
465  void Stats::print(std::ostream& os, const Author2Vector& m) const
466  {
467    for (A2VConstIter i(m.begin()); i!=m.end(); ++i){
468      os << i->first << "\n";
469      os << i->second.front() << " ";
470      for (size_t j=1; j<i->second.size(); ++j)
471        if (i->second[j] != i->second[j-1])
472          os << j << " " << i->second[j] - i->second[j-1] << " ";
473      os << last_changed_rev()+1 << " ";
474    }
475  }
476
477  void Stats::reset(void)
478  {
479    total_.clear();
480    code_.clear();
481    comments_.clear();
482    other_.clear();
483    authors_.clear();
484    std::vector<u_int> vec;
485    code_["all"] = vec;
486    comments_["all"] = vec;
487    other_["all"] = vec;
488    total_["all"] = vec;
489  }
490
491
492  Stats& Stats::operator+=(const Stats& rhs)
493  {
494    revision_ = std::max(revision_, rhs.revision_);
495    last_changed_rev_ = std::max(last_changed_rev_, rhs.last_changed_rev_);
496    add_authors(rhs.authors().begin(), rhs.authors().end());
497    map_add(rhs.code_.begin(), rhs.code_.end(), code_);
498    map_add(rhs.comments_.begin(), rhs.comments_.end(), comments_);
499    map_add(rhs.other_.begin(), rhs.other_.end(), other_);
500    map_add(rhs.total_.begin(), rhs.total_.end(), total_);
501    assert(rhs.other_.size());
502    assert(other_.size());
503   
504    return *this;
505  }
506
507
508}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.