手里有把锤子,看什么都像钉子。
都想去敲。
最近遇到的问题是,要统计各个组件编程过程中有多少警告。这些警告统计出来便于安排资源去清理,目标是一个干净的编译环境。问题是编译Log比较大,有好几M,肯定不能靠数。怎么办?
首先分析GCC的编译警告是有规律的,它给出了警告所在文件,所在的行数,又哪个警告选项产生的。例如下面的警告就是代码违背了-Wunused-but-set-variable规则,变量未使用。还有一个总要的规律,是同一个文件的警告肯定是连续的。
<Line No.>: <File Path>:4023:9: warning: variable 'isError' set but not used [-Wunused-but-set-variable]
另外,我们增加一个关闭所有警告(-w)的编译Log,结合原有警告是连续的这个规律,这两个Log文件的Delta就是相邻位置对应文件的警告数目。很容易通过一个Perl脚本来实现。
#!/user/bin/perl use strict; use warnings; # file 1 should contine more lines my $file1 = 'baseline.txt'; my $file2 = 'fix.txt'; open(FILE1, "<$file1") || die "Could not read file, program halting."; my @lines1 = <FILE1>; close(FILE1); open(FILE2, "<$file2") || die "Could not read file, program halting."; my @lines2 = <FILE2>; close(FILE2); my $len_lines1 = $#lines1; my $len_lines2 = $#lines2; print "len1 = $len_lines1, len2 = $len_lines2\r\n"; if($len_lines1 < $len_lines1) { die "file order is incorrect."; } my $i = 0; my $j = 0; my $plusi_count = 0; my $warn_file = ""; while($j < $len_lines2) { #print "" . $lines1[$i] . " VS\r\n" . $lines2[$j]; if($lines1[$i] eq $lines2[$j]) { if($plusi_count > 0) { print $plusi_count . "\t" . $warn_file; } $plusi_count = 0; $warn_file = ""; ++$i; ++$j; } else { #print $lines2[$j]; $warn_file = $lines2[$j - 1]; ++$plusi_count; ++$i; } }
细节未完善。
这个办法有个不足就是我们需要一个对比的无警告的文件做参考。从实现上讲,我们可以改进:筛选出有警告的行,然后把文件路径提取出来,然后相同文件的统计一个数目,这个数目就是每个文件的警告总数。
如果我们增加需求,需要按组件统计数目又该怎么办?我们继续改进,根据前面统计得出的文件结果,根据不同组件使用不同的路径,再把相同路径的统计一个数目,这个数目就是每个组件的警告总数。
随着需求的增加,Perl越来越显得无力。因为Perl的实现,需要增加很多复杂的逻辑。
换个角度考虑,把整个统计的过程看成是一个工作链,一个节点处理完了交给下一个节点处理。其实这就是Streaming或者Chain的思想。Spark对这样的处理要求支持的非常好。我们用Spark实现它:
import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.function.Function; import org.apache.spark.api.java.function.Function2; import org.apache.spark.api.java.function.PairFunction; import scala.Tuple2; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class HLWarningCount { private static final Pattern FILE_PATTERN = Pattern.compile("(.*?[c|cpp|h]):(\\d+):(\\d+): warning:"); public static void main(String[] args) throws Exception { SparkConf sparkConf = new SparkConf().setAppName("WarnCount"); JavaSparkContext ctx = new JavaSparkContext(sparkConf); JavaRDD<String> lines = ctx.textFile("log.txt", 1); JavaRDD<String> warningLines = lines.filter(new Function<String, Boolean>() { @Override public Boolean call(String s) throws Exception { if(s.contains("warning:")){ return true; }else{ return false; } } }).filter(new Function<String, Boolean>() { @Override public Boolean call(String s) throws Exception { /* TODO: update to filter real code warning */ if(s.contains(".mk")){ return false; }else{ return true; } } }); JavaPairRDD<String, Integer> mappedLines = warningLines.mapToPair(new PairFunction<String, String, Integer>() { @Override public Tuple2<String, Integer> call(String s) { Matcher m = FILE_PATTERN.matcher(s); if(m.find()){ return new Tuple2<String, Integer>(m.group(1), 1); }else{ System.out.println("ERROR - " + s); return null; } } }); JavaPairRDD<String, Integer> countedLines = mappedLines.reduceByKey(new Function2<Integer, Integer, Integer>() { @Override public Integer call(Integer i1, Integer i2) { return i1 + i2; } }); List<Tuple2<String, Integer>> output = countedLines.toArray(); for (Tuple2<?,?> tuple : output) { if(tuple != null){ System.out.println(tuple._1() + ": " + tuple._2()); } } ctx.stop(); } }
Spark的链式处理办法,很容易扩展,我们很容易做些过滤(filter)合并(reduce)操作。
当然我们只是使用Spark提供的编程范式和易用的API,并不是它的大数据能力。使用Scala一样可以做到,因为Spark很多变换操作其实是Scala 的Iterator接口。