source: branches/0.9-stable/lib/Stats.cc @ 1276

Last change on this file since 1276 was 1276, checked in by Peter Johansson, 12 years ago

Let Graph class hold information about last point (rev_max_) rather
than defining via the size of Vector. Not resizing the Vectors means
we can avoid some copying in Stats class. But it also means we need to
handle the case of empty Vector in Stats::max_element. Made the
function Node::svn_info(void) public. In svndigest.cc set rev_min also
when not using dates as well as setting the new static variable rev_max.
closes #485

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 16.2 KB
Line 
1// $Id: Stats.cc 1276 2010-11-05 12:24:26Z 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    const SumVector& total=get_vector(*stat, "all");   
443    double yrange_max=1.03 * max_element(total) +1.0;
444    gp.ymax(yrange_max);
445
446    typedef std::vector<std::pair<std::string, SumVector> > vec_type;
447    vec_type author_cont;
448    author_cont.reserve(stat->size());
449    for (std::set<std::string>::const_iterator i=authors_.begin(); 
450         i != authors_.end(); ++i) {
451      assert(stat->find(*i)!=stat->end());
452      const SumVector& vec = get_vector(*stat,*i);
453      if (max_element(vec)) {
454        author_cont.push_back(std::make_pair(*i,vec));
455      }
456    }
457
458    LessReversed<SumVector> lr;
459    PairSecondCompare<std::string, SumVector, LessReversed<SumVector> > 
460      compare(lr);
461    std::sort(author_cont.begin(), author_cont.end(), compare);
462
463    vec_type::iterator end(author_cont.end());
464    vec_type::iterator i(author_cont.begin());
465    const vec_type::size_type maxauthors=8;
466    int authskip=author_cont.size()-maxauthors;
467    if (authskip>1) {
468      // only use others if there is more than 1 author to be skipped,
469      // there is no reason to add only 1 author to others.
470      vec_type::iterator j(i);
471      i+=authskip;
472      SumVector others;
473      sum(j, i, others, PairValuePlusAssign<std::string, SumVector>());
474      unsigned char r, g, b;
475      std::string label("others");
476      Colors::instance().get_color(label, r,g,b);
477      gp.current_color(r,g,b);
478      gp.plot(others, label, others.back());
479    }
480    for ( ; i!=end; ++i) {
481      unsigned char r, g, b;
482      Colors::instance().get_color(i->first,r,g,b);
483      gp.current_color(r,g,b);
484      gp.plot(i->second, i->first, get_back(*stat, i->first));
485    }
486    gp.current_color(255,0,0);
487    gp.plot(total, "total", get_back(*stat, "all"));
488  }
489
490
491  void Stats::plot_summary(const std::string& filename) const
492  {
493    const std::string& format = Configuration::instance().image_format();
494    if (format=="none")
495      return;
496    plot_summary(filename, format);
497    const std::string& anchor_format = 
498      Configuration::instance().image_anchor_format();
499
500    if (format!=anchor_format)
501      plot_summary(filename, anchor_format);
502  }
503
504
505  void Stats::plot_summary(const std::string& filename, 
506                           const std::string& format) const
507  {
508    Graph gp(filename+"."+format, format);
509    const SumVector& total = get_vector(total_stats(), "all");
510    double yrange_max=1.03*max_element(total)+1;
511    gp.ymax(yrange_max);
512   
513    const SumVector& x(get_vector(code_stats(), "all"));
514    gp.current_color(255,255,0);
515    gp.plot(x, "code", x.back());
516
517    const SumVector& y = get_vector(comment_or_copy_stats(), "all");
518    gp.current_color(0,0,255);
519    gp.plot(y, "comment", y.back());
520
521    const SumVector& z = get_vector(other_stats(), "all");
522    gp.current_color(0,255,0);
523    gp.plot(z, "other", z.back());
524
525    gp.current_color(255,0,0);
526    gp.plot(total, "total", total.back());
527  }
528
529
530  void Stats::print(std::ostream& os) const
531  {
532    // indicate that cache file is version 8 here , but keep remaning
533    // 'CACHE FILE VERSION' as 'VERSION 7' to allow load_cache7() to
534    // be used from load_cache8().
535    os << "CACHE FILE VERSION 8\n";
536    os << config_code_ << "\n";
537    os << last_changed_rev() << " ";
538    os << authors().size() << "\n";
539
540    std::copy(authors().begin(), authors().end(), 
541              std::ostream_iterator<std::string>(os, "\n"));
542    os << cache_check_str() << "\n";
543    for (size_t i=0; i<stats_.size(); ++i){
544      print(os, stats_[i]);
545      os << cache_check_str() << "\n";
546    }
547  }
548
549
550  void Stats::print(std::ostream& os, const Author2Vector& m) const
551  {
552    for (A2VConstIter i(m.begin()); i!=m.end(); ++i){
553      os << i->first << "\n";
554      const SumVector& vec = i->second;
555      if (vec.size()) {
556        SumVector::const_iterator v = vec.begin();
557        if (v->second)
558          os << v->first << " " << v->second << " ";
559        ++v;
560        SumVector::const_iterator prev = vec.begin();
561        while (v != vec.end()) {
562          assert(vec[v->first - 1] == prev->second);
563          // FIXME: this if should not be needed if SumVector was
564          // truly sparse and collapsed when possible.
565          if (v->second != prev->second)
566            os << v->first << " " << v->second - prev->second << " ";
567          ++v;
568          ++prev;
569        }
570      }
571      os << "\n";
572    }
573  }
574
575  void Stats::reset(void)
576  {
577    for (size_t i=0; i<stats_.size(); ++i){
578      stats_[i].clear();
579      stats_[i]["all"].resize(revision()+1);
580    }
581    authors_.clear();
582  }
583
584
585  void Stats::resize(svn_revnum_t rev)
586  {
587    // set size on vectors
588    for (size_t i=0; i<stats_.size(); ++i) {
589      for (A2VIter iter=stats_[i].begin(); iter!=stats_[i].end(); ++iter) {
590        iter->second.resize(rev);
591      }
592    }
593  }
594
595
596  svn_revnum_t Stats::revision(void) const 
597  { 
598    return revision_; 
599  }
600
601
602  Stats& Stats::operator+=(const Stats& rhs)
603  {
604    revision_ = std::max(revision_, rhs.revision_);
605    last_changed_rev_ = std::max(last_changed_rev_, rhs.last_changed_rev_);
606    add_authors(rhs.authors().begin(), rhs.authors().end());
607    assert(stats_.size()==rhs.stats_.size());
608    for (size_t i=0; i<stats_.size(); ++i)
609      map_add(rhs.stats_[i].begin(), rhs.stats_[i].end(), stats_[i]);
610   
611    return *this;
612  }
613
614 
615  size_t Stats::operator()(int linetype, std::string author, 
616                           svn_revnum_t rev) const
617  {
618    assert(linetype<=LineTypeParser::total);
619    assert(static_cast<size_t>(linetype) < stats_.size());
620    assert(rev>=0);
621    A2VConstIter i = stats_[linetype].find(author);
622    if (i==stats_[linetype].end()){
623      std::stringstream msg;
624      msg << __FILE__ << ": author: " << author << " does not exist"; 
625      throw std::runtime_error(msg.str());
626    }
627    assert(rev <= revision());
628    //    assert(rev < static_cast<svn_revnum_t>(i->second.size()));
629    return i->second[rev];
630  }
631
632}} // end of namespace svndigest and namespace theplu
Note: See TracBrowser for help on using the repository browser.