blob: bb8b6701bfde203e7f26f68e158eae8717af9f23 [file] [log] [blame]
package org.apache.helix.tools.commandtools;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.zip.GZIPInputStream;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* utility for grep zk transaction/snapshot logs
* - to grep a pattern by t1 use:
* zkgrep --zkCfg zkCfg --by t1 --pattern patterns...
* - to grep a pattern between t1 and t2 use:
* zkgrep --zkCfg zkCfg --between t1 t2 --pattern patterns...
* for example, to find fail-over latency between t1 and t2, use:
* 1) zkgrep --zkCfg zkCfg --by t1 --pattern "/{cluster}/LIVEINSTNCES/" | grep {fail-node}
* 2) zkgrep --zkCfg zkCfg --between t1 t2 --pattern "closeSession" | grep {fail-node session-id}
* 3) zkgrep --zkCfg zkCfg --between t1 t2 --pattern "/{cluster}" | grep "CURRENTSTATES" |
* grep "setData" | tail -1
* fail-over latency = timestamp difference between 2) and 3)
*/
public class ZkGrep {
private static Logger LOG = LoggerFactory.getLogger(ZkGrep.class);
private static final String zkCfg = "zkCfg";
private static final String pattern = "pattern";
private static final String by = "by";
private static final String between = "between";
public static final String log = "log";
public static final String snapshot = "snapshot";
private static final String gzSuffix = ".gz";
@SuppressWarnings("static-access")
private static Options constructCommandLineOptions() {
Option zkCfgOption =
OptionBuilder.hasArgs(1).isRequired(false).withLongOpt(zkCfg).withArgName("zoo.cfg")
.withDescription("provide zoo.cfg").create();
Option patternOption =
OptionBuilder.hasArgs().isRequired(true).withLongOpt(pattern)
.withArgName("grep-patterns...").withDescription("provide patterns (required)")
.create();
Option betweenOption =
OptionBuilder.hasArgs(2).isRequired(false).withLongOpt(between)
.withArgName("t1 t2 (timestamp in ms or yyMMdd_hhmmss_SSS)")
.withDescription("grep between t1 and t2").create();
Option byOption =
OptionBuilder.hasArgs(1).isRequired(false).withLongOpt(by)
.withArgName("t (timestamp in ms or yyMMdd_hhmmss_SSS)").withDescription("grep by t")
.create();
OptionGroup group = new OptionGroup();
group.setRequired(true);
group.addOption(betweenOption);
group.addOption(byOption);
Options options = new Options();
options.addOption(zkCfgOption);
options.addOption(patternOption);
options.addOptionGroup(group);
return options;
}
/**
* get zk transaction log dir and zk snapshot log dir
* @param zkCfgFile
* @return String[0]: zk-transaction-log-dir, String[1]: zk-snapshot-dir
*/
static String[] getZkDataDirs(String zkCfgFile) {
String[] zkDirs = new String[2];
FileInputStream fis = null;
BufferedReader br = null;
try {
fis = new FileInputStream(zkCfgFile);
br = new BufferedReader(new InputStreamReader(fis));
String line;
while ((line = br.readLine()) != null) {
String key = "dataDir=";
if (line.startsWith(key)) {
zkDirs[1] = zkDirs[0] = line.substring(key.length()) + "/version-2";
}
key = "dataLogDir=";
if (line.startsWith(key)) {
zkDirs[0] = line.substring(key.length()) + "/version-2";
}
}
} catch (Exception e) {
LOG.error("exception in read file: " + zkCfgFile, e);
} finally {
try {
if (br != null) {
br.close();
}
if (fis != null) {
fis.close();
}
} catch (Exception e) {
LOG.error("exception in closing file: " + zkCfgFile, e);
}
}
return zkDirs;
}
// debug
static void printFiles(File[] files) {
System.out.println("START print");
for (int i = 0; i < files.length; i++) {
File file = files[i];
System.out.println(file.getName() + ", " + file.lastModified());
}
System.out.println("END print");
}
/**
* get files under dir in order of last modified time
* @param dir
* @param pattern
* @return
*/
static File[] getSortedFiles(String dirPath, final String pattern) {
File dir = new File(dirPath);
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isFile() && (file.getName().indexOf(pattern) != -1);
}
});
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
int sign = (int) Math.signum(o1.lastModified() - o2.lastModified());
return sign;
}
});
return files;
}
/**
* get value for an attribute in a parsed zk log; e.g.
* "time:1384984016778 session:0x14257d1d17e0004 cxid:0x5 zxid:0x46899 type:error err:-101"
* given "time" return "1384984016778"
* @param line
* @param attribute
* @return value
*/
static String getAttributeValue(String line, String attribute) {
if (line == null) {
return null;
}
if (!attribute.endsWith(":")) {
attribute = attribute + ":";
}
String[] parts = line.split("\\s");
if (parts != null && parts.length > 0) {
for (int i = 0; i < parts.length; i++) {
if (parts[i].startsWith(attribute)) {
String val = parts[i].substring(attribute.length());
return val;
}
}
}
return null;
}
static long getTimestamp(String line) {
String timestamp = getAttributeValue(line, "time");
return Long.parseLong(timestamp);
}
/**
* parse a time string either in timestamp form or "yyMMdd_hhmmss_SSS" form
* @param time
* @return timestamp or -1 on error
*/
static long parseTimeString(String time) {
try {
return Long.parseLong(time);
} catch (NumberFormatException e) {
try {
SimpleDateFormat formatter = new SimpleDateFormat("yyMMdd_hhmmss_SSS");
Date date = formatter.parse(time);
return date.getTime();
} catch (java.text.ParseException ex) {
LOG.error("fail to parse time string: " + time, e);
}
}
return -1;
}
public static void grepZkLog(File zkLog, long start, long end, String... patterns) {
FileInputStream fis = null;
BufferedReader br = null;
try {
fis = new FileInputStream(zkLog);
br = new BufferedReader(new InputStreamReader(fis));
String line;
while ((line = br.readLine()) != null) {
try {
long timestamp = getTimestamp(line);
if (timestamp > end) {
break;
}
if (timestamp < start) {
continue;
}
boolean match = true;
for (String pattern : patterns) {
if (line.indexOf(pattern) == -1) {
match = false;
break;
}
}
if (match) {
System.out.println(line);
}
} catch (NumberFormatException e) {
// ignore
}
}
} catch (Exception e) {
LOG.error("exception in grep zk-log: " + zkLog, e);
} finally {
try {
if (br != null) {
br.close();
}
if (fis != null) {
fis.close();
}
} catch (Exception e) {
LOG.error("exception in closing zk-log: " + zkLog, e);
}
}
}
public static void grepZkLogDir(List<File> parsedZkLogs, long start, long end, String... patterns) {
for (File file : parsedZkLogs) {
grepZkLog(file, start, end, patterns);
}
}
public static void grepZkSnapshot(File zkSnapshot, String... patterns) {
FileInputStream fis = null;
BufferedReader br = null;
try {
fis = new FileInputStream(zkSnapshot);
br = new BufferedReader(new InputStreamReader(fis));
String line;
while ((line = br.readLine()) != null) {
try {
boolean match = true;
for (String pattern : patterns) {
if (line.indexOf(pattern) == -1) {
match = false;
break;
}
}
if (match) {
System.out.println(line);
}
} catch (NumberFormatException e) {
// ignore
}
}
} catch (Exception e) {
LOG.error("exception in grep zk-snapshot: " + zkSnapshot, e);
} finally {
try {
if (br != null) {
br.close();
}
if (fis != null) {
fis.close();
}
} catch (Exception e) {
LOG.error("exception in closing zk-snapshot: " + zkSnapshot, e);
}
}
}
/**
* guess zoo.cfg dir
* @return absolute path to zoo.cfg
*/
static String guessZkCfgDir() {
// TODO impl this
return null;
}
public static void printUsage(Options cliOptions) {
HelpFormatter helpFormatter = new HelpFormatter();
helpFormatter.setWidth(1000);
helpFormatter.printHelp("java " + ZkGrep.class.getName(), cliOptions);
}
/**
* parse zk-transaction-logs between start and end, if not already parsed
* @param zkLogDir
* @param start
* @param end
* @return list of parsed zklogs between start and end, in order of last modified timestamp
*/
static List<File> parseZkLogs(String zkLogDir, long start, long end) {
File zkParsedDir = new File(String.format("%s/zklog-parsed", System.getProperty("user.home")));
File[] zkLogs = getSortedFiles(zkLogDir, log);
// printFiles(zkDataFiles);
List<File> parsedZkLogs = new ArrayList<File>();
boolean stop = false;
for (File zkLog : zkLogs) {
if (stop) {
break;
}
if (zkLog.lastModified() < start) {
continue;
}
if (zkLog.lastModified() > end) {
stop = true;
}
try {
File parsedZkLog = new File(zkParsedDir, stripGzSuffix(zkLog.getName()) + ".parsed");
if (!parsedZkLog.exists() || parsedZkLog.lastModified() <= zkLog.lastModified()) {
if (zkLog.getName().endsWith(gzSuffix)) {
// copy and gunzip it
FileUtils.copyFileToDirectory(zkLog, zkParsedDir);
File zkLogGz = new File(zkParsedDir, zkLog.getName());
File tmpZkLog = gunzip(zkLogGz);
// parse gunzip file
ZKLogFormatter
.main(new String[] { log, tmpZkLog.getAbsolutePath(), parsedZkLog.getAbsolutePath()
});
// delete it
zkLogGz.delete();
tmpZkLog.delete();
} else {
// parse it directly
ZKLogFormatter.main(new String[] {
log, zkLog.getAbsolutePath(), parsedZkLog.getAbsolutePath()
});
}
}
parsedZkLogs.add(parsedZkLog);
} catch (Exception e) {
LOG.error("fail to parse zkLog: " + zkLog, e);
}
}
return parsedZkLogs;
}
/**
* Strip off a .gz suffix if any
* @param filename
* @return
*/
static String stripGzSuffix(String filename) {
if (filename.endsWith(gzSuffix)) {
return filename.substring(0, filename.length() - gzSuffix.length());
}
return filename;
}
/**
* Gunzip a file
* @param zipFile
* @return
*/
static File gunzip(File zipFile) {
File outputFile = new File(stripGzSuffix(zipFile.getAbsolutePath()));
byte[] buffer = new byte[1024];
try {
GZIPInputStream gzis = new GZIPInputStream(new FileInputStream(zipFile));
FileOutputStream out = new FileOutputStream(outputFile);
int len;
while ((len = gzis.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
gzis.close();
out.close();
return outputFile;
} catch (IOException e) {
LOG.error("fail to gunzip file: " + zipFile, e);
}
return null;
}
/**
* parse the last zk-snapshots by by-time, if not already parsed
* @param zkSnapshotDir
* @param byTime
* @return File array which the first element is the last zk-snapshot by by-time and the second
* element is its parsed file
*/
static File[] parseZkSnapshot(String zkSnapshotDir, long byTime) {
File[] retFiles = new File[2];
File zkParsedDir = new File(String.format("%s/zklog-parsed", System.getProperty("user.home")));
File[] zkSnapshots = getSortedFiles(zkSnapshotDir, snapshot);
// printFiles(zkDataFiles);
File lastZkSnapshot = null;
for (int i = 0; i < zkSnapshots.length; i++) {
File zkSnapshot = zkSnapshots[i];
if (zkSnapshot.lastModified() >= byTime) {
break;
}
lastZkSnapshot = zkSnapshot;
retFiles[0] = lastZkSnapshot;
}
try {
File parsedZkSnapshot =
new File(zkParsedDir, stripGzSuffix(lastZkSnapshot.getName()) + ".parsed");
if (!parsedZkSnapshot.exists()
|| parsedZkSnapshot.lastModified() <= lastZkSnapshot.lastModified()) {
if (lastZkSnapshot.getName().endsWith(gzSuffix)) {
// copy and gunzip it
FileUtils.copyFileToDirectory(lastZkSnapshot, zkParsedDir);
File lastZkSnapshotGz = new File(zkParsedDir, lastZkSnapshot.getName());
File tmpLastZkSnapshot = gunzip(lastZkSnapshotGz);
// parse gunzip file
ZKLogFormatter.main(new String[] {
snapshot, tmpLastZkSnapshot.getAbsolutePath(), parsedZkSnapshot.getAbsolutePath()
});
// delete it
lastZkSnapshotGz.delete();
tmpLastZkSnapshot.delete();
} else {
// parse it directly
ZKLogFormatter.main(new String[] {
snapshot, lastZkSnapshot.getAbsolutePath(), parsedZkSnapshot.getAbsolutePath()
});
}
}
retFiles[1] = parsedZkSnapshot;
return retFiles;
} catch (Exception e) {
LOG.error("fail to parse zkSnapshot: " + lastZkSnapshot, e);
}
return null;
}
public static void processCommandLineArgs(String[] cliArgs) {
CommandLineParser cliParser = new GnuParser();
Options cliOptions = constructCommandLineOptions();
CommandLine cmd = null;
try {
cmd = cliParser.parse(cliOptions, cliArgs);
} catch (ParseException pe) {
System.err.println("CommandLineClient: failed to parse command-line options: " + pe);
printUsage(cliOptions);
System.exit(1);
}
String zkCfgDirValue = null;
String zkCfgFile = null;
if (cmd.hasOption(zkCfg)) {
zkCfgDirValue = cmd.getOptionValue(zkCfg);
}
if (zkCfgDirValue == null) {
zkCfgDirValue = guessZkCfgDir();
}
if (zkCfgDirValue == null) {
LOG.error("couldn't figure out path to zkCfg file");
System.exit(1);
}
// get zoo.cfg path from cfg-dir
zkCfgFile = zkCfgDirValue;
if (!zkCfgFile.endsWith(".cfg")) {
// append with default zoo.cfg
zkCfgFile = zkCfgFile + "/zoo.cfg";
}
if (!new File(zkCfgFile).exists()) {
LOG.error("zoo.cfg file doen't exist: " + zkCfgFile);
System.exit(1);
}
String[] patterns = cmd.getOptionValues(pattern);
String[] zkDataDirs = getZkDataDirs(zkCfgFile);
// parse zk data files
if (zkDataDirs == null || zkDataDirs[0] == null || zkDataDirs[1] == null) {
LOG.error("invalid zkCfgDir: " + zkCfgDirValue);
System.exit(1);
}
File zkParsedDir = new File(String.format("%s/zklog-parsed", System.getProperty("user.home")));
if (!zkParsedDir.exists()) {
LOG.info("creating zklog-parsed dir: " + zkParsedDir.getAbsolutePath());
zkParsedDir.mkdirs();
}
if (cmd.hasOption(between)) {
String[] timeStrings = cmd.getOptionValues(between);
long startTime = parseTimeString(timeStrings[0]);
if (startTime == -1) {
LOG.error("invalid start time string: " + timeStrings[0]
+ ", should be either timestamp or yyMMdd_hhmmss_SSS");
System.exit(1);
}
long endTime = parseTimeString(timeStrings[1]);
if (endTime == -1) {
LOG.error("invalid end time string: " + timeStrings[1]
+ ", should be either timestamp or yyMMdd_hhmmss_SSS");
System.exit(1);
}
if (startTime > endTime) {
LOG.warn("empty window: " + startTime + " - " + endTime);
System.exit(1);
}
// zkDataDirs[0] is the transaction log dir
List<File> parsedZkLogs = parseZkLogs(zkDataDirs[0], startTime, endTime);
grepZkLogDir(parsedZkLogs, startTime, endTime, patterns);
} else if (cmd.hasOption(by)) {
String timeString = cmd.getOptionValue(by);
long byTime = parseTimeString(timeString);
if (byTime == -1) {
LOG.error("invalid by time string: " + timeString
+ ", should be either timestamp or yyMMdd_hhmmss_SSS");
System.exit(1);
}
// zkDataDirs[1] is the snapshot dir
File[] lastZkSnapshot = parseZkSnapshot(zkDataDirs[1], byTime);
// lastZkSnapshot[1] is the parsed last snapshot by byTime
grepZkSnapshot(lastZkSnapshot[1], patterns);
// need to grep transaction logs between last-modified-time of snapshot and byTime also
// lastZkSnapshot[0] is the last snapshot by byTime
long startTime = lastZkSnapshot[0].lastModified();
// zkDataDirs[0] is the transaction log dir
List<File> parsedZkLogs = parseZkLogs(zkDataDirs[0], startTime, byTime);
grepZkLogDir(parsedZkLogs, startTime, byTime, patterns);
}
}
public static void main(String[] args) {
processCommandLineArgs(args);
}
}