source: trunk/lib/Stats.cc @ 1203

Last change on this file since 1203 was 1203, checked in by Peter Johansson, 11 years ago

refs #475. prefer operator+= and remove operator+ for Vector class to ensure we use +=

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 16.1 KB
Line 
1// $Id: Stats.cc 1203 2010-10-05 12:33:57Z peter $
2
3/*
4  Copyright (C) 2005 Peter Johansson
5  Copyright (C) 2006, 2007, 2008, 2009 Jari Häkkinen, Peter Johansson
6  Copyright (C) 2010 Peter Johansson
7
8  This file is part of svndigest, http://dev.thep.lu.se/svndigest
9
10  svndigest is free software; you can redistribute it and/or modify it
11  under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 3 of the License, or
13  (at your option) any later version.
14
15  svndigest is distributed in the hope that it will be useful, but
16  WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  General Public License for more details.
19
20  You should have received a copy of the GNU General Public License
21  along with svndigest. If not, see <http://www.gnu.org/licenses/>.
22*/
23
24#include "Stats.h"
25
26#include "Colors.h"
27#include "Configuration.h"
28#include "Functor.h"
29#include "Graph.h"
30#include "SVNblame.h"
31#include "SVNinfo.h"
32#include "utility.h"
33
34#include <algorithm>
35#include <cassert>
36#include <cstdlib>
37#include <fstream>
38#include <iostream>
39#include <iterator>
40#include <map>
41#include <numeric>
42#include <string>
43#include <sstream>
44#include <unistd.h>
45#include <utility>
46#include <vector>
47
48namespace theplu{
49namespace svndigest{
50
51
52  Stats::Stats(const std::string& path)
53    : stats_(std::vector<Author2Vector>(LineTypeParser::total+1))
54  {
55    // Make sure latest revision is set properly
56    SVNinfo svn_info(path);
57    revision_=svn_info.rev();
58    last_changed_rev_=svn_info.last_changed_rev();
59    reset();
60
61    std::ostringstream ss;
62    if (const std::vector<std::pair<std::string, std::string> >* 
63        codons = Configuration::instance().codon(path)) {
64      using std::string;
65      typedef std::vector<std::pair<string, string> >::const_iterator citerator;
66      for ( citerator codon=codons->begin(); codon!=codons->end(); ++codon) 
67        ss << codon->first << codon->second;
68    }
69    config_code_ = ss.str();
70    // we don't allow newline in codon because we parse the string
71    // with newline and thus want the string to be a one-liner.
72    std::replace(config_code_.begin(), config_code_.end(), '\n', ' ');
73  }
74
75
76  Stats::~Stats(void)
77  {
78  }
79
80 
81  void Stats::add(const std::vector<std::map<std::string, SparseVector> >& data)
82  {
83    // loop over line types
84    for (size_t lt = 0; lt<data.size(); ++lt) {
85      std::map<std::string, SparseVector>::const_iterator iter=data[lt].begin();
86      std::map<std::string, SparseVector>::const_iterator last=data[lt].end();
87      // loop over users
88      for ( ; iter!=last; ++iter) {
89        add_author(iter->first);
90        SumVector tmpvec;
91        accumulate(iter->second, tmpvec);
92        stats_[lt][iter->first] += tmpvec;
93      }
94    }
95  }
96
97
98  /*
99  void Stats::accumulate(std::vector<unsigned int>& vec,
100                         svn_revnum_t rev) const
101  {
102    assert(rev>0);
103    if (vec.empty()){
104      // just to allow call to vec.back() below
105      vec.resize(1,0);
106    }
107    else if (vec.begin()+rev-1 < vec.end())
108      std::partial_sum(vec.begin()+rev-1,vec.end(),vec.begin()+rev-1);
109    // static_cast to remove annoying compiler warning
110    if (vec.size() < static_cast<size_t>(revision()+1))
111      vec.resize(revision()+1, vec.back());
112  }
113  */
114  /*
115  void Stats::accumulate_stats(svn_revnum_t rev)
116  {
117    if (!rev)
118      rev = 1;
119    for (std::set<std::string>::const_iterator iter(authors().begin());
120         iter!=authors().end(); ++iter) {
121      std::vector<unsigned int>& code = code_stats()[*iter];
122      accumulate(code, rev);
123      std::vector<unsigned int>& comments = comment_stats()[*iter];
124      accumulate(comments, rev);
125      std::vector<unsigned int>& other = other_stats()[*iter];
126      accumulate(other, rev);
127      std::vector<unsigned int>& copyright = copyright_stats()[*iter];
128      accumulate(copyright, rev);
129    }
130  }
131  */
132  /*
133  void Stats::add(const std::string& user, svn_revnum_t rev,
134                  LineTypeParser::line_type lt, unsigned int n)
135  {
136    assert(user.size());
137    add_author(user);
138
139    if (lt==LineTypeParser::code)
140      add(code_stats()[user], rev, n);
141    else if (lt==LineTypeParser::comment)
142      add(comment_stats()[user], rev, n);
143    else if (lt==LineTypeParser::other)
144      add(other_stats()[user], rev, n);
145    else if (lt==LineTypeParser::copyright)
146      add(copyright_stats()[user], rev, n);
147  }
148
149  void Stats::add(SumVector& vec, svn_revnum_t rev, unsigned int n)
150  {
151    vec.set(rev, vec[rev] + n);
152  }
153  */
154
155
156  void Stats::add_author(std::string name)
157  {
158    authors_.insert(name);
159  }
160
161
162  void Stats::add_authors(std::set<std::string>::const_iterator first, 
163                          std::set<std::string>::const_iterator last)
164  {
165    authors_.insert(first, last);
166  }
167
168
169  const std::set<std::string>& Stats::authors(void) const
170  {
171    return authors_;
172  }
173
174
175  void Stats::calc_all(void)
176  {
177    for (int lt=0; lt <= 4; ++lt) {
178      stats_[lt]["all"].clear();
179      for (std::map<std::string, SumVector>::iterator i = stats_[lt].begin();
180           i!=stats_[lt].end(); ++i) {
181        stats_[lt]["all"] += i->second;
182      }
183    }
184
185    comment_or_copy_stats()["all"] = comment_stats()["all"];
186    comment_or_copy_stats()["all"] += copyright_stats()["all"];
187
188    total_stats()["all"] = comment_or_copy_stats()["all"];
189    total_stats()["all"] += code_stats()["all"];
190    total_stats()["all"] += other_stats()["all"];
191  }
192
193
194  void Stats::calc_total(void)
195  {
196    for (std::set<std::string>::const_iterator iter(authors().begin());
197         iter!=authors().end(); ++iter) {
198      SumVector& total = total_stats()[*iter];
199      total = code_stats()[*iter];
200      total += comment_stats()[*iter];
201      total += other_stats()[*iter];
202      total += copyright_stats()[*iter];
203    }
204  }
205
206
207  void Stats::calc_comment_or_copy(void)
208  {
209    for (std::set<std::string>::const_iterator iter(authors().begin());
210         iter!=authors().end(); ++iter) {
211      const SumVector& comments = comment_stats()[*iter];
212      const SumVector& copy = copyright_stats()[*iter];
213
214      comment_or_copy_stats()[*iter] = comments;
215      comment_or_copy_stats()[*iter] += copy;
216    }
217
218  }
219
220
221  unsigned int Stats::code(const std::string& user) const
222  {
223    return get_back(code_stats(), user);
224  }
225
226
227  unsigned int Stats::comments(const std::string& user) const
228  {
229    return get_back(comment_or_copy_stats(), user);
230  }
231
232
233  unsigned int Stats::empty(const std::string& user) const
234  {
235    return get_back(other_stats(), user);
236  }
237
238
239  unsigned int Stats::get_back(const Author2Vector& m, std::string user) const
240  {
241    A2VConstIter iter(m.find(std::string(user)));
242    if (iter==m.end() || iter->second.empty()) 
243      return 0;
244    return iter->second.back();
245  }
246
247
248  const SumVector& Stats::get_vector(const Author2Vector& m, 
249                                     const std::string& user) const
250  {
251    A2VConstIter iter(m.find(std::string(user)));
252    if (iter==m.end()) 
253      throw std::runtime_error(user+std::string(" not found i Stats"));
254    return iter->second;
255  }
256
257
258  svn_revnum_t Stats::last_changed_rev(void) const
259  {
260    return last_changed_rev_;
261  }
262
263
264  unsigned int Stats::lines(const std::string& user) const
265  {
266    return get_back(total_stats(), user);
267  }
268
269
270  void Stats::load(std::istream& is, Author2Vector& m)
271  {
272    while (m.size() < authors().size()+1 && is.good()) {
273      std::string name;
274      std::getline(is, name);
275      SparseVector vec;
276      std::string line;
277      std::getline(is, line);
278      std::stringstream ss(line);
279      while (ss.good()) {
280        svn_revnum_t rev=0;
281        unsigned int count=0;
282        ss >> rev;
283        ss >> count;
284        assert(rev<=revision_);
285        if (!count)
286          break;
287        vec.set(rev, count);
288      }
289      SumVector& sumvec = m[name];
290      accumulate(vec, sumvec);
291    }
292  }
293
294
295  svn_revnum_t Stats::load_cache(std::istream& is, bool& latest_ver)
296  {
297    std::string str;
298    getline(is, str);
299    if (str == "CACHE FILE VERSION 8") {
300      latest_ver = true;
301      return load_cache8(is);
302    }
303    latest_ver = false;
304    if (str == "CACHE FILE VERSION 7")
305      return load_cache7(is);
306
307    if (str == "CACHE FILE VERSION 6")
308      std::cout << "cache file is obsolete; "
309                << "retrieving statistics from repository.\n";
310    return 0;
311  }
312
313
314  svn_revnum_t Stats::load_cache8(std::istream& is)
315  {
316    std::string line;
317    getline(is, line);
318    if (line!=config_code_) {
319      std::cout << "cache file is for different configuration.\n"
320                << "config code: '" << config_code_ << "'\n"
321                << "config code in cache file: '" << line << "'\n"
322                << "retrieving statistics from repository.\n";
323      return 0;
324    }
325    return load_cache7(is);
326  }
327
328
329  svn_revnum_t Stats::load_cache7(std::istream& is)
330  {
331    svn_revnum_t rev;
332    is >> rev;
333    reset();
334    size_t a_size=0;
335    is >> a_size;
336    std::string str;
337    while (authors().size()<a_size && is.good()){
338      getline(is, str);
339      if (str.size())
340        add_author(str);
341    }
342    getline(is, str);
343    if (str!=cache_check_str()) {
344      return 0;
345    }
346    for (size_t i=0; i<stats_.size(); ++i){
347      load(is, stats_[i]);
348      getline(is, str);
349      if (str!=cache_check_str()) {
350        return 0;
351      }
352    }
353    return rev;
354  }
355
356
357  void Stats::map_add(A2VConstIter first1, A2VConstIter last1, 
358                      Author2Vector& map)
359  {
360    A2VIter first2(map.begin());
361    Author2Vector::key_compare compare;
362    while ( first1 != last1) { 
363      // key of first1 less than key of first2
364      if (first2==map.end() || compare(first1->first,first2->first)) {
365        first2 = map.insert(first2, *first1);
366        ++first1;
367      }
368      // key of first2 less than key of first1
369      else if ( compare(first2->first, first1->first)) {
370        ++first2;
371      }
372      // keys are equivalent
373      else {
374        first2->second += first1->second;
375        ++first1;
376        ++first2;
377      }
378    }
379  }
380
381
382  unsigned int Stats::max_element(const SumVector& vec) const
383  {
384    return std::max_element(vec.begin(), vec.end(), 
385       pair_value_compare<const svn_revnum_t, unsigned int>())->second;
386  }
387
388
389  void Stats::parse(const std::string& path, svn_revnum_t rev)
390  {
391    // reset stats to zero for [rev, inf)
392    for (size_t i=0; i<stats_.size(); ++i)
393      for (A2VIter iter=stats_[i].begin(); iter!=stats_[i].end(); ++iter) {
394        //iter->second.resize(rev,0);
395        //iter->second.resize(revision(),0);
396      }
397    do_parse(path, rev);
398    calc_comment_or_copy();
399    calc_total();
400    calc_all();
401   
402    assert(total_stats().size());
403    assert(code_stats().size());
404    assert(comment_or_copy_stats().size());
405    assert(other_stats().size());
406  }
407
408
409  std::string Stats::plot(const std::string& filename,
410                          const std::string& linetype) const
411  {
412    const std::string& format = Configuration::instance().image_format();
413    if (format=="none")
414      return filename;
415    plot(filename, linetype, format);
416    const std::string& anchor_format = 
417      Configuration::instance().image_anchor_format();
418
419    if (format!=anchor_format)
420      plot(filename, linetype, anchor_format);
421    return filename;
422  }
423
424
425  void Stats::plot(const std::string& filename,
426                   const std::string& linetype,
427                   const std::string& format) const
428  {
429    Graph gp(filename+"."+format, format);
430    const Author2Vector* stat=NULL;
431    if (linetype=="total")
432      stat = &total_stats();
433    else if (linetype=="code")
434      stat = &code_stats();
435    else if (linetype=="comments")
436      stat = &comment_or_copy_stats();
437    else if (linetype=="empty")
438      stat = &other_stats();
439    assert(stat);
440    assert(stat->size());
441    assert(stat->find("all")!=stat->end());
442    // FIXME: try keep a const& to avoid copying
443    SumVector total=get_vector(*stat, "all");   
444    total.resize(revision());
445    double yrange_max=1.03 * max_element(total) +1.0;
446    gp.ymax(yrange_max);
447
448    typedef std::vector<std::pair<std::string, SumVector> > vec_type;
449    vec_type author_cont;
450    author_cont.reserve(stat->size());
451    for (std::set<std::string>::const_iterator i=authors_.begin(); 
452         i != authors_.end(); ++i) {
453      assert(stat->find(*i)!=stat->end());
454      // FIXME: avoid this copying?
455      SumVector vec = get_vector(*stat,*i);
456      vec.resize(revision());
457      if (max_element(vec)) {
458        author_cont.push_back(std::make_pair(*i,vec));
459      }
460    }
461
462    LessReversed<SumVector> lr;
463    PairSecondCompare<std::string, SumVector, LessReversed<SumVector> > 
464      compare(lr);
465    std::sort(author_cont.begin(), author_cont.end(), compare);
466
467    vec_type::iterator end(author_cont.end());
468    vec_type::iterator i(author_cont.begin());
469    const vec_type::size_type maxauthors=8;
470    int authskip=author_cont.size()-maxauthors;
471    if (authskip>1) {
472      // only use others if there is more than 1 author to be skipped,
473      // there is no reason to add only 1 author to others.
474      vec_type::iterator j(i);
475      i+=authskip;
476      SumVector others;
477      sum(j, i, others, PairValuePlusAssign<std::string, SumVector>());
478      unsigned char r, g, b;
479      std::string label("others");
480      Colors::instance().get_color(label, r,g,b);
481      gp.current_color(r,g,b);
482      gp.plot(others, label, others.back());
483    }
484    for ( ; i!=end; ++i) {
485      unsigned char r, g, b;
486      Colors::instance().get_color(i->first,r,g,b);
487      gp.current_color(r,g,b);
488      gp.plot(i->second, i->first, get_back(*stat, i->first));
489    }
490    gp.current_color(255,0,0);
491    gp.plot(total, "total", get_back(*stat, "all"));
492  }
493
494
495  void Stats::plot_summary(const std::string& filename) const
496  {
497    const std::string& format = Configuration::instance().image_format();
498    if (format=="none")
499      return;
500    plot_summary(filename, format);
501    const std::string& anchor_format = 
502      Configuration::instance().image_anchor_format();
503
504    if (format!=anchor_format)
505      plot_summary(filename, anchor_format);
506  }
507
508
509  void Stats::plot_summary(const std::string& filename, 
510                           const std::string& format) const
511  {
512    Graph gp(filename+"."+format, format);
513    // FIXME: why not const&
514    SumVector total = get_vector(total_stats(), "all");
515    double yrange_max=1.03*total.back()+1;
516    gp.ymax(yrange_max);
517   
518    // FIXME: why not const&
519    SumVector x(get_vector(code_stats(), "all"));
520    gp.current_color(255,255,0);
521    gp.plot(x, "code", x.back());
522
523    x = get_vector(comment_or_copy_stats(), "all");
524    gp.current_color(0,0,255);
525    gp.plot(x, "comment", x.back());
526
527    x = get_vector(other_stats(), "all");
528    gp.current_color(0,255,0);
529    gp.plot(x, "other", x.back());
530
531    gp.current_color(255,0,0);
532    gp.plot(total, "total", total.back());
533  }
534
535
536  void Stats::print(std::ostream& os) const
537  {
538    // indicate that cache file is version 8 here , but keep remaning
539    // 'CACHE FILE VERSION' as 'VERSION 7' to allow load_cache7() to
540    // be used from load_cache8().
541    os << "CACHE FILE VERSION 8\n";
542    os << config_code_ << "\n";
543    os << last_changed_rev() << " ";
544    os << authors().size() << "\n";
545
546    std::copy(authors().begin(), authors().end(), 
547              std::ostream_iterator<std::string>(os, "\n"));
548    os << cache_check_str() << "\n";
549    for (size_t i=0; i<stats_.size(); ++i){
550      print(os, stats_[i]);
551      os << cache_check_str() << "\n";
552    }
553  }
554
555
556  void Stats::print(std::ostream& os, const Author2Vector& m) const
557  {
558    for (A2VConstIter i(m.begin()); i!=m.end(); ++i){
559      os << i->first << "\n";
560      if (i->second[0])
561        os << 0 << " " << i->second[0] << " ";
562      // FIXME: ise Vector::iterator
563      for (size_t j=1; j<i->second.size(); ++j) {
564        // only print if stats changes in this rev
565        if (i->second[j] != i->second[j-1]) {
566          os << j << " " << i->second[j] - i->second[j-1] << " ";
567        }
568      }
569      os << "\n";
570    }
571  }
572
573  void Stats::reset(void)
574  {
575    for (size_t i=0; i<stats_.size(); ++i){
576      stats_[i].clear();
577      stats_[i]["all"].resize(revision()+1);
578    }
579    authors_.clear();
580  }
581
582
583  void Stats::resize(svn_revnum_t rev)
584  {
585    // set size on vectors
586    for (size_t i=0; i<stats_.size(); ++i) {
587      for (A2VIter iter=stats_[i].begin(); iter!=stats_[i].end(); ++iter) {
588        iter->second.resize(rev);
589      }
590    }
591  }
592
593
594  svn_revnum_t Stats::revision(void) const 
595  { 
596    return revision_; 
597  }
598
599
600  Stats& Stats::operator+=(const Stats& rhs)
601  {
602    revision_ = std::max(revision_, rhs.revision_);
603    last_changed_rev_ = std::max(last_changed_rev_, rhs.last_changed_rev_);
604    add_authors(rhs.authors().begin(), rhs.authors().end());
605    assert(stats_.size()==rhs.stats_.size());
606    for (size_t i=0; i<stats_.size(); ++i)
607      map_add(rhs.stats_[i].begin(), rhs.stats_[i].end(), stats_[i]);
608   
609    return *this;
610  }
611
612 
613  size_t Stats::operator()(int linetype, std::string author, 
614                           svn_revnum_t rev) const
615  {
616    assert(linetype<=LineTypeParser::total);
617    assert(static_cast<size_t>(linetype) < stats_.size());
618    assert(rev>=0);
619    A2VConstIter i = stats_[linetype].find(author);
620    if (i==stats_[linetype].end()){
621      std::stringstream msg;
622      msg << __FILE__ << ": author: " << author << " does not exist"; 
623      throw std::runtime_error(msg.str());
624    }
625    assert(rev <= revision());
626    //    assert(rev < static_cast<svn_revnum_t>(i->second.size()));
627    return i->second[rev];
628  }
629
630}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.