source: trunk/lib/Stats.cc @ 537

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

Blame stats is implemented - fixes #24

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