mirror of
https://github.com/tstack/lnav.git
synced 2024-08-17 00:40:30 +03:00
[cmd] add support for times in goto
This commit is contained in:
parent
7cd7bf4dc4
commit
ec473edc85
7
NEWS
7
NEWS
@ -29,6 +29,13 @@ lnav v0.8.0:
|
||||
* When typing in a command, the status bar will display a short
|
||||
summary of the currently entered command.
|
||||
* Added a "delete-filter" command.
|
||||
* The 'goto' command now supports relative time values like
|
||||
'a minute ago', 'an hour later', and many more.
|
||||
|
||||
Interface Changes:
|
||||
* The 'r/R' hotkeys have been reassigned to navigate through the log
|
||||
messages by the relative time value that was last used with the
|
||||
'goto' command.
|
||||
|
||||
Fixes:
|
||||
* The pretty-print view should now work for text files.
|
||||
|
@ -29,8 +29,9 @@ with the following commands:
|
||||
Navigation
|
||||
----------
|
||||
|
||||
* goto <line#|N%|time> - Go to the given line number, N percent into the
|
||||
file, or the given timestamp in the log view.
|
||||
* goto <line#|N%|abs-time|relative-time> - Go to the given line number, N
|
||||
percent into the file, the given timestamp in the log view, or by the
|
||||
relative time (e.g. 'a minute ago').
|
||||
* relative-goto <line#|N%> - Move the current view up or down by the given
|
||||
amount.
|
||||
* next-mark error|warning|search|user|file|partition - Move to the next
|
||||
|
@ -146,6 +146,9 @@ Chronological Navigation
|
||||
* - |ks| 0 |ke|
|
||||
- |ks| Shift |ke| + |ks| 0 |ke|
|
||||
- Next/previous day
|
||||
* - |ks| r |ke|
|
||||
- |ks| Shift |ke| + |ks| r |ke|
|
||||
- Forward/backward by the relative time that was last used with the goto command.
|
||||
|
||||
Bookmarks
|
||||
---------
|
||||
|
@ -38,6 +38,7 @@ set(diag_STAT_SRCS
|
||||
readline_curses.cc
|
||||
readline_highlighters.cc
|
||||
readline_possibilities.cc
|
||||
relative_time.cc
|
||||
session_data.cc
|
||||
sequence_matcher.cc
|
||||
shared_buffer.cc
|
||||
@ -108,6 +109,7 @@ set(diag_STAT_SRCS
|
||||
pthreadpp.hh
|
||||
readline_callbacks.hh
|
||||
readline_possibilities.hh
|
||||
relative_time.hh
|
||||
sequence_sink.hh
|
||||
status_controllers.hh
|
||||
strong_int.hh
|
||||
|
@ -151,6 +151,7 @@ noinst_HEADERS = \
|
||||
readline_curses.hh \
|
||||
readline_highlighters.hh \
|
||||
readline_possibilities.hh \
|
||||
relative_time.hh \
|
||||
sequence_matcher.hh \
|
||||
sequence_sink.hh \
|
||||
session_data.hh \
|
||||
@ -235,6 +236,7 @@ libdiag_a_SOURCES = \
|
||||
readline_curses.cc \
|
||||
readline_highlighters.cc \
|
||||
readline_possibilities.cc \
|
||||
relative_time.cc \
|
||||
session_data.cc \
|
||||
sequence_matcher.cc \
|
||||
shared_buffer.cc \
|
||||
|
18
src/help.txt
18
src/help.txt
@ -207,6 +207,13 @@ through the file.
|
||||
|
||||
0/Shift 0 Move to the next/previous day boundary.
|
||||
|
||||
r/R Move forward/backward based on the relative time that
|
||||
was last used with the 'goto' command. For example,
|
||||
executing ':goto a minute later' will move the log view
|
||||
forward a minute and then pressing 'r' will move it
|
||||
forward a minute again. Pressing 'R' will then move the
|
||||
view in the opposite direction, so backwards a minute.
|
||||
|
||||
m Mark/unmark the line at the top of the display.
|
||||
The line will be highlighted with reverse video to
|
||||
indicate that it is a user bookmark. You can use
|
||||
@ -345,9 +352,6 @@ through the file.
|
||||
|
||||
CTRL-W Toggle word-wrapping.
|
||||
|
||||
r/R Restore the next/previous session. The current session is
|
||||
saved and then the new state is restored.
|
||||
|
||||
F2 Toggle mouse support.
|
||||
|
||||
|
||||
@ -383,11 +387,15 @@ COMMANDS
|
||||
current-time Print the current time in human-readable form and
|
||||
as a unix-timestamp.
|
||||
|
||||
goto <line#|N%|time>
|
||||
goto <line#|N%|abs-time|relative-time>
|
||||
Go to the given line number, N percent into the
|
||||
file, or the given timestamp in the log view. If the
|
||||
line number is negative, it is considered an offset
|
||||
from the last line.
|
||||
from the last line. Relative time values, like
|
||||
'a minute ago', 'an hour later', and many other formats
|
||||
are supported. When using a relative time, the 'r/R'
|
||||
hotkeys can be used to move the same amount again or in
|
||||
the same amount in the opposite direction.
|
||||
|
||||
relative-goto <line#|N%>
|
||||
Move the current view up or down by the given amount.
|
||||
|
@ -1030,35 +1030,44 @@ void handle_paging_key(int ch)
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
if (!lnav_data.ld_session_file_names.empty()) {
|
||||
lnav_data.ld_session_file_index =
|
||||
(lnav_data.ld_session_file_index + 1) %
|
||||
lnav_data.ld_session_file_names.size();
|
||||
reset_session();
|
||||
load_session();
|
||||
rebuild_indexes(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
if (lnav_data.ld_session_file_index == 0) {
|
||||
lnav_data.ld_session_file_index =
|
||||
lnav_data.ld_session_file_names.size() - 1;
|
||||
if (lss) {
|
||||
if (lnav_data.ld_last_relative_time.empty()) {
|
||||
lnav_data.ld_rl_view->set_value(
|
||||
"Use the 'goto' command to set the relative time to move by");
|
||||
}
|
||||
else {
|
||||
vis_line_t vl = tc->get_top();
|
||||
relative_time rt = lnav_data.ld_last_relative_time;
|
||||
struct timeval tv;
|
||||
content_line_t cl;
|
||||
struct exttm tm;
|
||||
|
||||
if (ch == 'R') {
|
||||
rt.negate();
|
||||
}
|
||||
|
||||
cl = lnav_data.ld_log_source.at(vl);
|
||||
logline *ll = lnav_data.ld_log_source.find_line(cl);
|
||||
ll->to_exttm(tm);
|
||||
rt.add(tm);
|
||||
tv.tv_sec = timegm(&tm.et_tm);
|
||||
tv.tv_usec = tm.et_nsec / 1000;
|
||||
vl = lnav_data.ld_log_source.find_from_time(tv);
|
||||
if (rt.is_negative() && (vl > vis_line_t(0))) {
|
||||
--vl;
|
||||
if (vl == tc->get_top()) {
|
||||
vl = vis_line_t(0);
|
||||
}
|
||||
}
|
||||
tc->set_top(vl);
|
||||
}
|
||||
else{
|
||||
lnav_data.ld_session_file_index -= 1;
|
||||
}
|
||||
reset_session();
|
||||
load_session();
|
||||
rebuild_indexes(true);
|
||||
break;
|
||||
|
||||
case KEY_CTRL_R:
|
||||
reset_session();
|
||||
rebuild_indexes(true);
|
||||
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_2(
|
||||
r, R,
|
||||
"to restore the next/previous session"));
|
||||
break;
|
||||
|
||||
case KEY_CTRL_W:
|
||||
|
@ -61,6 +61,7 @@
|
||||
#include "ansi_scrubber.hh"
|
||||
#include "curl_looper.hh"
|
||||
#include "papertrail_proc.hh"
|
||||
#include "relative_time.hh"
|
||||
|
||||
/** The command modes that are available while viewing a file. */
|
||||
typedef enum {
|
||||
@ -248,6 +249,8 @@ struct _lnav_data {
|
||||
input_state_tracker ld_input_state;
|
||||
|
||||
curl_looper ld_curl_looper;
|
||||
|
||||
relative_time ld_last_relative_time;
|
||||
};
|
||||
|
||||
extern struct _lnav_data lnav_data;
|
||||
|
@ -49,6 +49,7 @@
|
||||
#include "command_executor.hh"
|
||||
#include "url_loader.hh"
|
||||
#include "readline_curses.hh"
|
||||
#include "relative_time.hh"
|
||||
#include "log_search_table.hh"
|
||||
|
||||
using namespace std;
|
||||
@ -249,20 +250,52 @@ static string com_current_time(string cmdline, vector<string> &args)
|
||||
|
||||
static string com_goto(string cmdline, vector<string> &args)
|
||||
{
|
||||
string retval = "error: expecting line number/percentage or timestamp";
|
||||
string retval = "error: expecting line number/percentage, timestamp, or relative time";
|
||||
|
||||
if (args.size() == 0) {
|
||||
args.push_back("line-time");
|
||||
}
|
||||
else if (args.size() > 1) {
|
||||
string all_args = cmdline.substr(cmdline.find(args[1], args[0].size()));
|
||||
textview_curses *tc = lnav_data.ld_view_stack.top();
|
||||
int line_number, consumed;
|
||||
date_time_scanner dts;
|
||||
struct relative_time::parse_error pe;
|
||||
relative_time rt;
|
||||
struct timeval tv;
|
||||
struct exttm tm;
|
||||
float value;
|
||||
|
||||
if (dts.scan(args[1].c_str(), args[1].size(), NULL, &tm, tv) != NULL) {
|
||||
if (rt.parse(all_args, pe)) {
|
||||
if (tc == &lnav_data.ld_views[LNV_LOG]) {
|
||||
content_line_t cl;
|
||||
vis_line_t vl;
|
||||
logline *ll;
|
||||
|
||||
if (!rt.is_absolute()) {
|
||||
lnav_data.ld_last_relative_time = rt;
|
||||
}
|
||||
|
||||
vl = tc->get_top();
|
||||
cl = lnav_data.ld_log_source.at(vl);
|
||||
ll = lnav_data.ld_log_source.find_line(cl);
|
||||
ll->to_exttm(tm);
|
||||
rt.add(tm);
|
||||
tv.tv_sec = timegm(&tm.et_tm);
|
||||
tv.tv_usec = tm.et_nsec / 1000;
|
||||
|
||||
vl = lnav_data.ld_log_source.find_from_time(tv);
|
||||
tc->set_top(vl);
|
||||
retval = "";
|
||||
if (!rt.is_absolute() && lnav_data.ld_rl_view != NULL) {
|
||||
lnav_data.ld_rl_view->set_alt_value(
|
||||
HELP_MSG_2(r, R, "to move forward/backward the same amount of time"));
|
||||
}
|
||||
} else {
|
||||
retval = "error: relative time values only work in the log view";
|
||||
}
|
||||
}
|
||||
else if (dts.scan(args[1].c_str(), args[1].size(), NULL, &tm, tv) != NULL) {
|
||||
if (tc == &lnav_data.ld_views[LNV_LOG]) {
|
||||
vis_line_t vl;
|
||||
|
||||
@ -751,6 +784,7 @@ static string com_highlight(string cmdline, vector<string> &args)
|
||||
}
|
||||
|
||||
retval = "info: highlight pattern now active";
|
||||
tc->reload_data();
|
||||
}
|
||||
}
|
||||
|
||||
@ -777,6 +811,7 @@ static string com_clear_highlight(string cmdline, vector<string> &args)
|
||||
else {
|
||||
hm.erase(hm_iter);
|
||||
retval = "info: highlight pattern cleared";
|
||||
tc->reload_data();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1945,10 +1980,6 @@ static string com_redraw(string cmdline, vector<string> &args)
|
||||
}
|
||||
else if (lnav_data.ld_window) {
|
||||
redrawwin(lnav_data.ld_window);
|
||||
if (lnav_data.ld_rl_view != NULL) {
|
||||
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(
|
||||
CTRL-L, "to redraw the window"));
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
|
@ -430,6 +430,7 @@ const char *date_time_scanner::scan(const char *time_dest,
|
||||
}
|
||||
tv_out.tv_sec = gmt;
|
||||
tv_out.tv_usec = 0;
|
||||
tm_out->et_flags = ETF_DAY_SET|ETF_MONTH_SET|ETF_YEAR_SET;
|
||||
|
||||
this->dts_fmt_lock = curr_time_fmt;
|
||||
this->dts_fmt_len = off;
|
||||
|
@ -288,29 +288,36 @@ const char *log_format::log_scanf(const char *line,
|
||||
return retval;
|
||||
}
|
||||
|
||||
void log_format::check_for_new_year(std::vector<logline> &dst,
|
||||
const struct timeval &log_tv)
|
||||
void log_format::check_for_new_year(std::vector<logline> &dst, exttm etm,
|
||||
struct timeval log_tv)
|
||||
{
|
||||
if (dst.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
time_t diff = dst.back().get_time() - log_tv.tv_sec;
|
||||
|
||||
if (diff > (5 * 60)) {
|
||||
int off_year = 0, off_month = 0, off_day = 0, off_hour = 0;
|
||||
std::vector<logline>::iterator iter;
|
||||
bool do_change = true;
|
||||
|
||||
if (diff <= 0) {
|
||||
return;
|
||||
}
|
||||
if (diff > (60 * 24 * 60 * 60)) {
|
||||
off_year = 1;
|
||||
} else if (diff > (15 * 24 * 60 * 60)) {
|
||||
off_month = 1;
|
||||
} else if (diff > (12 * 60 * 60)) {
|
||||
off_day = 1;
|
||||
} else {
|
||||
} else if (!(etm.et_flags & ETF_DAY_SET)) {
|
||||
off_hour = 1;
|
||||
} else {
|
||||
do_change = false;
|
||||
}
|
||||
|
||||
if (!do_change) {
|
||||
return;
|
||||
}
|
||||
for (iter = dst.begin(); iter != dst.end(); iter++) {
|
||||
time_t ot = iter->get_time();
|
||||
struct tm otm;
|
||||
@ -322,7 +329,6 @@ void log_format::check_for_new_year(std::vector<logline> &dst,
|
||||
otm.tm_hour -= off_hour;
|
||||
iter->set_time(tm2sec(&otm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -706,7 +712,7 @@ bool external_log_format::scan(std::vector<logline> &dst,
|
||||
if (!((log_time_tm.et_flags & ETF_DAY_SET) &&
|
||||
(log_time_tm.et_flags & ETF_MONTH_SET) &&
|
||||
(log_time_tm.et_flags & ETF_YEAR_SET))) {
|
||||
this->check_for_new_year(dst, log_tv);
|
||||
this->check_for_new_year(dst, log_time_tm, log_tv);
|
||||
}
|
||||
|
||||
if (mod_cap != NULL) {
|
||||
|
@ -150,6 +150,11 @@ public:
|
||||
/** @return The timestamp for the line. */
|
||||
time_t get_time() const { return this->ll_time; };
|
||||
|
||||
void to_exttm(struct exttm &tm_out) const {
|
||||
tm_out.et_tm = *gmtime(&this->ll_time);
|
||||
tm_out.et_nsec = this->ll_millis * 1000 * 1000;
|
||||
};
|
||||
|
||||
void set_time(time_t t) { this->ll_time = t; };
|
||||
|
||||
/** @return The millisecond timestamp for the line. */
|
||||
@ -255,7 +260,8 @@ public:
|
||||
|
||||
bool operator<(const struct timeval &rhs) const {
|
||||
return ((this->ll_time < rhs.tv_sec) ||
|
||||
(this->ll_millis < (rhs.tv_usec / 1000)));
|
||||
((this->ll_time == rhs.tv_sec) &&
|
||||
(this->ll_millis < (rhs.tv_usec / 1000))));
|
||||
};
|
||||
|
||||
private:
|
||||
@ -654,8 +660,8 @@ public:
|
||||
return &this->lf_timestamp_format[0];
|
||||
};
|
||||
|
||||
void check_for_new_year(std::vector<logline> &dst,
|
||||
const struct timeval &log_tv);
|
||||
void check_for_new_year(std::vector<logline> &dst, exttm log_tv,
|
||||
timeval timeval1);
|
||||
|
||||
virtual std::string get_pattern_name() const {
|
||||
char name[32];
|
||||
|
@ -154,7 +154,7 @@ class generic_log_format : public log_format {
|
||||
logline::level_t level_val = logline::string2level(
|
||||
level_str, level.length());
|
||||
|
||||
this->check_for_new_year(dst, log_tv);
|
||||
this->check_for_new_year(dst, log_time, log_tv);
|
||||
|
||||
dst.push_back(logline(offset, log_tv, level_val));
|
||||
retval = true;
|
||||
|
@ -175,6 +175,7 @@ inline bool ptime_s(struct exttm *dst, const char *str, off_t &off_inout, ssize_
|
||||
}
|
||||
|
||||
secs2tm(&epoch, &dst->et_tm);
|
||||
dst->et_flags = ETF_DAY_SET|ETF_MONTH_SET|ETF_YEAR_SET;
|
||||
|
||||
return (epoch > 0);
|
||||
}
|
||||
@ -241,6 +242,7 @@ inline bool ptime_i(struct exttm *dst, const char *str, off_t &off_inout, ssize_
|
||||
dst->et_nsec = (epoch_ms % 1000ULL) * 1000000;
|
||||
epoch = (epoch_ms / 1000ULL);
|
||||
secs2tm(&epoch, &dst->et_tm);
|
||||
dst->et_flags = ETF_DAY_SET|ETF_MONTH_SET|ETF_YEAR_SET;
|
||||
|
||||
return (epoch_ms > 0);
|
||||
}
|
||||
|
274
src/relative_time.cc
Normal file
274
src/relative_time.cc
Normal file
@ -0,0 +1,274 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "pcrepp.hh"
|
||||
#include "lnav_util.hh"
|
||||
#include "relative_time.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static struct {
|
||||
const char *name;
|
||||
pcrepp pcre;
|
||||
} MATCHERS[relative_time::RTT__MAX] = {
|
||||
{ "ws", pcrepp("\\A\\s+\\b") },
|
||||
{ "am", pcrepp("\\Aam|a\\.m\\.\\b") },
|
||||
{ "pm", pcrepp("\\Apm|p\\.m\\.\\b") },
|
||||
{ "a", pcrepp("\\Aa\\b") },
|
||||
{ "an", pcrepp("\\Aan\\b") },
|
||||
{ "time", pcrepp("\\A(\\d{1,2}):(\\d{2})(?::(\\d{2}))?") },
|
||||
{ "num", pcrepp("\\A((?:-|\\+)?\\d+)") },
|
||||
{ "us", pcrepp("\\Amicros(?:econds?)?|us(?![a-zA-Z])") },
|
||||
{ "ms", pcrepp("\\Amillis(?:econds?)?|ms(?![a-zA-Z])") },
|
||||
{ "sec", pcrepp("\\As(?:ec(?:onds?)?)?(?![a-zA-Z])") },
|
||||
{ "min", pcrepp("\\Am(?:in(?:utes?)?)?(?![a-zA-Z])") },
|
||||
{ "h", pcrepp("\\Ah(?:ours?)?(?![a-zA-Z])") },
|
||||
{ "day", pcrepp("\\Ad(?:ays?)?(?![a-zA-Z])") },
|
||||
{ "week", pcrepp("\\Aw(?:eeks?)?(?![a-zA-Z])") },
|
||||
{ "mon", pcrepp("\\Amon(?:ths?)?(?![a-zA-Z])") },
|
||||
{ "year", pcrepp("\\Ay(?:ears?)?(?![a-zA-Z])") },
|
||||
{ "today", pcrepp("\\Atoday\\b") },
|
||||
{ "yest", pcrepp("\\Ayesterday\\b") },
|
||||
{ "tomo", pcrepp("\\Atomorrow\\b") },
|
||||
{ "noon", pcrepp("\\Anoon\\b") },
|
||||
{ "and", pcrepp("\\Aand\\b") },
|
||||
{ "ago", pcrepp("\\Aago\\b") },
|
||||
{ "lter", pcrepp("\\Alater\\b") },
|
||||
{ "bfor", pcrepp("\\Abefore\\b") },
|
||||
};
|
||||
|
||||
static int64_t TIME_SCALES[] = {
|
||||
1000 * 1000,
|
||||
60,
|
||||
60,
|
||||
24,
|
||||
};
|
||||
|
||||
bool relative_time::parse(const char *str, size_t len, struct parse_error &pe_out)
|
||||
{
|
||||
pcre_input pi(str, 0, len);
|
||||
pcre_context_static<30> pc;
|
||||
int64_t number = 0;
|
||||
bool number_set = false;
|
||||
|
||||
pe_out.pe_column = -1;
|
||||
pe_out.pe_msg.clear();
|
||||
|
||||
while (true) {
|
||||
if (pi.pi_next_offset >= pi.pi_length) {
|
||||
if (number_set) {
|
||||
pe_out.pe_msg = "Number given without a time unit";
|
||||
return false;
|
||||
}
|
||||
|
||||
this->rollover();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (int lpc = 0; lpc < RTT__MAX && !found; lpc++) {
|
||||
token_t token = (token_t) lpc;
|
||||
if (!MATCHERS[lpc].pcre.match(pc, pi, PCRE_ANCHORED)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pe_out.pe_column = pc.all()->c_begin;
|
||||
found = true;
|
||||
if (RTT_MICROS <= token && token <= RTT_YEARS) {
|
||||
if (!number_set) {
|
||||
pe_out.pe_msg = "Expecting a number before time unit";
|
||||
return false;
|
||||
}
|
||||
number_set = false;
|
||||
}
|
||||
switch (token) {
|
||||
case RTT_INVALID:
|
||||
case RTT_WHITE:
|
||||
case RTT_AND:
|
||||
break;
|
||||
case RTT_AM:
|
||||
case RTT_PM:
|
||||
if (number_set) {
|
||||
this->rt_field[RTF_HOURS] = number;
|
||||
this->rt_is_absolute[RTF_HOURS] = true;
|
||||
this->rt_field[RTF_MINUTES] = 0;
|
||||
this->rt_is_absolute[RTF_MINUTES] = true;
|
||||
this->rt_field[RTF_SECONDS] = 0;
|
||||
this->rt_is_absolute[RTF_SECONDS] = true;
|
||||
this->rt_field[RTF_MICROSECONDS] = 0;
|
||||
this->rt_is_absolute[RTF_MICROSECONDS] = true;
|
||||
number_set = false;
|
||||
}
|
||||
if (!this->rt_is_absolute[RTF_HOURS]) {
|
||||
pe_out.pe_msg = "Expecting absolute time with A.M. or P.M.";
|
||||
return false;
|
||||
}
|
||||
if (token == RTT_AM) {
|
||||
if (this->rt_field[RTF_HOURS] == 12) {
|
||||
this->rt_field[RTF_HOURS] = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this->rt_field[RTF_HOURS] += 12;
|
||||
}
|
||||
break;
|
||||
case RTT_A:
|
||||
case RTT_AN:
|
||||
number = 1;
|
||||
number_set = true;
|
||||
break;
|
||||
case RTT_TIME: {
|
||||
string hstr = pi.get_substr(pc[0]);
|
||||
string mstr = pi.get_substr(pc[1]);
|
||||
this->rt_field[RTF_HOURS] = atoi(hstr.c_str());
|
||||
this->rt_is_absolute[RTF_HOURS] = true;
|
||||
this->rt_field[RTF_MINUTES] = atoi(mstr.c_str());
|
||||
this->rt_is_absolute[RTF_MINUTES] = true;
|
||||
if (pc[2]->is_valid()) {
|
||||
string sstr = pi.get_substr(pc[2]);
|
||||
this->rt_field[RTF_SECONDS] = atoi(sstr.c_str());
|
||||
}
|
||||
else {
|
||||
this->rt_field[RTF_SECONDS] = 0;
|
||||
}
|
||||
this->rt_is_absolute[RTF_SECONDS] = true;
|
||||
this->rt_field[RTF_MICROSECONDS] = 0;
|
||||
this->rt_is_absolute[RTF_MICROSECONDS] = true;
|
||||
break;
|
||||
}
|
||||
case RTT_NUMBER: {
|
||||
if (number_set) {
|
||||
pe_out.pe_msg = "No time unit given for the previous number";
|
||||
return false;
|
||||
}
|
||||
|
||||
string numstr = pi.get_substr(pc[0]);
|
||||
|
||||
if (sscanf(numstr.c_str(), "%qd", &number) != 1) {
|
||||
pe_out.pe_msg = "Invalid number: " + numstr;
|
||||
return false;
|
||||
}
|
||||
number_set = true;
|
||||
break;
|
||||
}
|
||||
case RTT_MICROS:
|
||||
this->rt_field[RTF_MICROSECONDS] = number;
|
||||
break;
|
||||
case RTT_MILLIS:
|
||||
this->rt_field[RTF_MICROSECONDS] = number * 1000;
|
||||
break;
|
||||
case RTT_SECONDS:
|
||||
this->rt_field[RTF_SECONDS] = number;
|
||||
break;
|
||||
case RTT_MINUTES:
|
||||
this->rt_field[RTF_MINUTES] = number;
|
||||
break;
|
||||
case RTT_HOURS:
|
||||
this->rt_field[RTF_HOURS] = number;
|
||||
break;
|
||||
case RTT_DAYS:
|
||||
this->rt_field[RTF_DAYS] = number;
|
||||
break;
|
||||
case RTT_WEEKS:
|
||||
this->rt_field[RTF_DAYS] = number * 7;
|
||||
break;
|
||||
case RTT_MONTHS:
|
||||
this->rt_field[RTF_MONTHS] = number;
|
||||
break;
|
||||
case RTT_YEARS:
|
||||
this->rt_field[RTF_YEARS] = number;
|
||||
break;
|
||||
case RTT_BEFORE:
|
||||
case RTT_AGO:
|
||||
if (this->empty()) {
|
||||
pe_out.pe_msg = "Expecting a time unit";
|
||||
return false;
|
||||
}
|
||||
for (int field = 0; field < RTF__MAX; field++) {
|
||||
if (this->rt_field[field] > 0) {
|
||||
this->rt_field[field] = -this->rt_field[field];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RTT_LATER:
|
||||
if (this->empty()) {
|
||||
pe_out.pe_msg = "Expecting a time unit before 'later'";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case RTT_TODAY:
|
||||
break;
|
||||
case RTT_YESTERDAY:
|
||||
this->rt_field[RTF_DAYS] = -1;
|
||||
break;
|
||||
case RTT_TOMORROW:
|
||||
this->rt_field[RTF_DAYS] = 1;
|
||||
break;
|
||||
case RTT_NOON:
|
||||
this->rt_field[RTF_HOURS] = 12;
|
||||
this->rt_is_absolute[RTF_HOURS] = true;
|
||||
break;
|
||||
|
||||
case RTT__MAX:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
pe_out.pe_msg = "Unrecognized input";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void relative_time::rollover()
|
||||
{
|
||||
for (int lpc = 0; lpc < RTF_DAYS; lpc++) {
|
||||
int64_t val = this->rt_field[lpc];
|
||||
this->rt_field[lpc] = val % TIME_SCALES[lpc];
|
||||
this->rt_field[lpc + 1] += val / TIME_SCALES[lpc];
|
||||
}
|
||||
if (std::abs(this->rt_field[RTF_DAYS]) > 31) {
|
||||
int64_t val = this->rt_field[RTF_DAYS];
|
||||
this->rt_field[RTF_DAYS] = val % 31;
|
||||
this->rt_field[RTF_MONTHS] += val / 31;
|
||||
}
|
||||
if (std::abs(this->rt_field[RTF_MONTHS]) > 12) {
|
||||
int64_t val = this->rt_field[RTF_MONTHS];
|
||||
this->rt_field[RTF_MONTHS] = val % 12;
|
||||
this->rt_field[RTF_YEARS] += val / 12;
|
||||
}
|
||||
}
|
214
src/relative_time.hh
Normal file
214
src/relative_time.hh
Normal file
@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef LNAV_RELATIVE_TIME_HH
|
||||
#define LNAV_RELATIVE_TIME_HH
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ptimec.hh"
|
||||
|
||||
class relative_time {
|
||||
public:
|
||||
enum token_t {
|
||||
RTT_INVALID = -1,
|
||||
|
||||
RTT_WHITE,
|
||||
RTT_AM,
|
||||
RTT_PM,
|
||||
RTT_A,
|
||||
RTT_AN,
|
||||
RTT_TIME,
|
||||
RTT_NUMBER,
|
||||
RTT_MICROS,
|
||||
RTT_MILLIS,
|
||||
RTT_SECONDS,
|
||||
RTT_MINUTES,
|
||||
RTT_HOURS,
|
||||
RTT_DAYS,
|
||||
RTT_WEEKS,
|
||||
RTT_MONTHS,
|
||||
RTT_YEARS,
|
||||
RTT_TODAY,
|
||||
RTT_YESTERDAY,
|
||||
RTT_TOMORROW,
|
||||
RTT_NOON,
|
||||
RTT_AND,
|
||||
RTT_AGO,
|
||||
RTT_LATER,
|
||||
RTT_BEFORE,
|
||||
|
||||
RTT__MAX
|
||||
};
|
||||
|
||||
relative_time() {
|
||||
this->clear();
|
||||
};
|
||||
|
||||
void clear() {
|
||||
memset(this->rt_field, 0, sizeof(this->rt_field));
|
||||
memset(this->rt_is_absolute, 0, sizeof(this->rt_is_absolute));
|
||||
};
|
||||
|
||||
void negate() {
|
||||
for (int lpc = 0; lpc < RTF__MAX; lpc++) {
|
||||
if (!this->rt_is_absolute[lpc] && this->rt_field[lpc] != 0) {
|
||||
this->rt_field[lpc] = -this->rt_field[lpc];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool is_negative() const {
|
||||
for (int lpc = 0; lpc < RTF__MAX; lpc++) {
|
||||
if (this->rt_field[lpc] < 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bool is_absolute() const {
|
||||
for (int lpc = 0; lpc < RTF__MAX; lpc++) {
|
||||
if (this->rt_is_absolute[lpc]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bool empty() const {
|
||||
for (int lpc = 0; lpc < RTF__MAX; lpc++) {
|
||||
if (this->rt_field[lpc]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
struct parse_error {
|
||||
int pe_column;
|
||||
std::string pe_msg;
|
||||
};
|
||||
|
||||
bool parse(const char *str, size_t len, struct parse_error &pe_out);
|
||||
|
||||
bool parse(const std::string &str, struct parse_error &pe_out) {
|
||||
return this->parse(str.c_str(), str.length(), pe_out);
|
||||
}
|
||||
|
||||
void add(struct exttm &tm) {
|
||||
if (this->rt_is_absolute[RTF_MICROSECONDS]) {
|
||||
tm.et_nsec = this->rt_field[RTF_MICROSECONDS] * 1000;
|
||||
}
|
||||
else {
|
||||
tm.et_nsec += this->rt_field[RTF_MICROSECONDS] * 1000;
|
||||
}
|
||||
if (this->rt_is_absolute[RTF_SECONDS]) {
|
||||
tm.et_tm.tm_sec = this->rt_field[RTF_SECONDS];
|
||||
}
|
||||
else {
|
||||
tm.et_tm.tm_sec += this->rt_field[RTF_SECONDS];
|
||||
}
|
||||
if (this->rt_is_absolute[RTF_MINUTES]) {
|
||||
tm.et_tm.tm_min = this->rt_field[RTF_MINUTES];
|
||||
}
|
||||
else {
|
||||
tm.et_tm.tm_min += this->rt_field[RTF_MINUTES];
|
||||
}
|
||||
if (this->rt_is_absolute[RTF_HOURS]) {
|
||||
tm.et_tm.tm_hour = this->rt_field[RTF_HOURS];
|
||||
}
|
||||
else {
|
||||
tm.et_tm.tm_hour += this->rt_field[RTF_HOURS];
|
||||
}
|
||||
if (this->rt_is_absolute[RTF_DAYS]) {
|
||||
tm.et_tm.tm_mday = this->rt_field[RTF_DAYS];
|
||||
}
|
||||
else {
|
||||
tm.et_tm.tm_mday += this->rt_field[RTF_DAYS];
|
||||
}
|
||||
if (this->rt_is_absolute[RTF_MONTHS]) {
|
||||
tm.et_tm.tm_mon = this->rt_field[RTF_MONTHS];
|
||||
}
|
||||
else {
|
||||
tm.et_tm.tm_mon += this->rt_field[RTF_MONTHS];
|
||||
}
|
||||
if (this->rt_is_absolute[RTF_YEARS]) {
|
||||
tm.et_tm.tm_year = this->rt_field[RTF_YEARS];
|
||||
}
|
||||
else {
|
||||
tm.et_tm.tm_year += this->rt_field[RTF_YEARS];
|
||||
}
|
||||
};
|
||||
|
||||
std::string to_string() {
|
||||
char dst[128];
|
||||
|
||||
snprintf(dst, sizeof(dst),
|
||||
"%qd%c%qd%c%qd%c%qd%c%qd%c%qd%c%qd%c",
|
||||
this->rt_field[RTF_YEARS],
|
||||
this->rt_is_absolute[RTF_YEARS] ? 'Y' : 'y',
|
||||
this->rt_field[RTF_MONTHS],
|
||||
this->rt_is_absolute[RTF_MONTHS] ? 'M' : 'm',
|
||||
this->rt_field[RTF_DAYS],
|
||||
this->rt_is_absolute[RTF_DAYS] ? 'D' : 'd',
|
||||
this->rt_field[RTF_HOURS],
|
||||
this->rt_is_absolute[RTF_HOURS] ? 'H' : 'h',
|
||||
this->rt_field[RTF_MINUTES],
|
||||
this->rt_is_absolute[RTF_MINUTES] ? 'M' : 'm',
|
||||
this->rt_field[RTF_SECONDS],
|
||||
this->rt_is_absolute[RTF_SECONDS] ? 'S' : 's',
|
||||
this->rt_field[RTF_MICROSECONDS],
|
||||
this->rt_is_absolute[RTF_MICROSECONDS] ? 'U' : 'u');
|
||||
return dst;
|
||||
};
|
||||
|
||||
void rollover();
|
||||
|
||||
enum {
|
||||
RTF_MICROSECONDS,
|
||||
RTF_SECONDS,
|
||||
RTF_MINUTES,
|
||||
RTF_HOURS,
|
||||
RTF_DAYS,
|
||||
RTF_MONTHS,
|
||||
RTF_YEARS,
|
||||
|
||||
RTF__MAX
|
||||
};
|
||||
|
||||
int64_t rt_field[RTF__MAX];
|
||||
bool rt_is_absolute[RTF__MAX];
|
||||
};
|
||||
|
||||
#endif //LNAV_RELATIVE_TIME_HH
|
@ -1300,7 +1300,6 @@ void reset_session(void)
|
||||
textview_curses::highlight_map_t &hmap =
|
||||
lnav_data.ld_views[LNV_LOG].get_highlights();
|
||||
textview_curses::highlight_map_t::iterator hl_iter = hmap.begin();
|
||||
logfile_sub_source &lss = lnav_data.ld_log_source;
|
||||
|
||||
log_info("reset session: time=%d", lnav_data.ld_session_time);
|
||||
|
||||
@ -1328,8 +1327,19 @@ void reset_session(void)
|
||||
lf->clear_time_offset();
|
||||
}
|
||||
|
||||
lnav_data.ld_log_source.get_filters().clear_filters();
|
||||
for (int lpc = 0; lpc < LNV__MAX; lpc++) {
|
||||
textview_curses &tc = lnav_data.ld_views[lpc];
|
||||
text_sub_source *tss = tc.get_sub_source();
|
||||
|
||||
lss.get_user_bookmarks()[&textview_curses::BM_USER].clear();
|
||||
lss.get_user_bookmarks()[&textview_curses::BM_PARTITION].clear();
|
||||
if (tss == NULL) {
|
||||
continue;
|
||||
}
|
||||
tss->get_filters().clear_filters();
|
||||
tss->text_filters_changed();
|
||||
tss->text_clear_marks(&textview_curses::BM_USER);
|
||||
tc.get_bookmarks()[&textview_curses::BM_USER].clear();
|
||||
tss->text_clear_marks(&textview_curses::BM_PARTITION);
|
||||
tc.get_bookmarks()[&textview_curses::BM_PARTITION].clear();
|
||||
tc.reload_data();
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,10 @@ include_directories(../../lbuild/src ../src/ /opt/local/include)
|
||||
|
||||
add_executable(test_chunky_index test_chunky_index.cc)
|
||||
add_executable(test_pcrepp test_pcrepp.cc ../src/lnav_log.cc ../src/pcrepp.cc)
|
||||
add_executable(test_reltime test_reltime.cc
|
||||
../src/relative_time.cc
|
||||
../src/pcrepp.cc
|
||||
../src/lnav_log.cc)
|
||||
link_directories(/opt/local/lib)
|
||||
target_link_libraries(test_pcrepp /opt/local/lib/libpcre.a)
|
||||
target_link_libraries(test_reltime /opt/local/lib/libpcre.a)
|
||||
|
@ -44,6 +44,7 @@ check_PROGRAMS = \
|
||||
test_line_buffer2 \
|
||||
test_log_accel \
|
||||
test_pcrepp \
|
||||
test_reltime \
|
||||
test_top_status \
|
||||
test_yajlpp
|
||||
|
||||
@ -99,6 +100,9 @@ test_concise_LDADD = ../src/libdiag.a
|
||||
test_json_ptr_SOURCES = test_json_ptr.cc
|
||||
test_json_ptr_LDADD = ../src/libdiag.a
|
||||
|
||||
test_reltime_SOURCES = test_reltime.cc
|
||||
test_reltime_LDADD = ../src/libdiag.a
|
||||
|
||||
drive_line_buffer_SOURCES = drive_line_buffer.cc
|
||||
drive_line_buffer_LDADD = ../src/libdiag.a $(CURSES_LIB) -lz
|
||||
|
||||
@ -244,6 +248,7 @@ dist_noinst_DATA = \
|
||||
logfile_syslog.1 \
|
||||
logfile_syslog.2 \
|
||||
logfile_syslog_with_access_log.0 \
|
||||
logfile_syslog_with_mixed_times.0 \
|
||||
logfile_tcf.0 \
|
||||
logfile_tcf.1 \
|
||||
logfile_tcsh_history.0 \
|
||||
@ -293,6 +298,7 @@ TESTS = \
|
||||
test_log_accel \
|
||||
test_logfile.sh \
|
||||
test_pcrepp \
|
||||
test_reltime \
|
||||
test_sessions.sh \
|
||||
test_sql.sh \
|
||||
test_sql_coll_func.sh \
|
||||
|
@ -67,7 +67,9 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
std::vector<std::string> paths, errors;
|
||||
|
||||
if (getenv("test_dir") != NULL) {
|
||||
paths.push_back(getenv("test_dir"));
|
||||
}
|
||||
load_formats(paths, errors);
|
||||
}
|
||||
|
||||
|
13
test/logfile_syslog_with_mixed_times.0
Normal file
13
test/logfile_syslog_with_mixed_times.0
Normal file
@ -0,0 +1,13 @@
|
||||
Sep 13 00:58:45 Tim-Stacks-iMac kernel[0]: AirParrot device perform power state change 0 -> 1
|
||||
Sep 13 00:59:30 Tim-Stacks-iMac.local airportd[59]: _configureScanOffloadParameters: Unable to configure scan offloading on en1 (Device power is off)
|
||||
Sep 13 01:23:54 Tim-Stacks-iMac kernel[0]: RTC: PowerByCalendarDate setting ignored
|
||||
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: vm_compressor_record_warmup (9478314 - 9492476)
|
||||
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: AppleBCM5701Ethernet [en0]: 0 0 memWrInd fBJP_Wakeup_Timer
|
||||
Sep 13 01:25:39 Tim-Stacks-iMac kernel[0]: AppleThunderboltNHIType2::waitForOk2Go2Sx - retries = 60000
|
||||
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: hibernate_page_list_setall(preflight 0) start 0xffffff8428276000, 0xffffff8428336000
|
||||
Sep 13 03:12:58 Tim-Stacks-iMac kernel[0]: *** kernel exceeded 500 log message per second limit - remaining messages this second discarded ***
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: IOThunderboltSwitch<0xffffff803f4b3000>(0x0)::listenerCallback - Thunderbolt HPD packet for route = 0x0 port = 11 unplug = 0
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: vm_compressor_flush - starting
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: AppleBCM5701Ethernet [en0]: 0 0 memWrInd fBJP_Wakeup_Timer
|
||||
Sep 13 03:13:16 Tim-Stacks-iMac kernel[0]: AppleThunderboltNHIType2::waitForOk2Go2Sx - retries = 60000
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: hibernate_page_list_setall(preflight 0) start 0xffffff838f1fc000, 0xffffff838f2bc000
|
@ -30,12 +30,45 @@ check_output "goto -1 is not working" <<EOF
|
||||
EOF
|
||||
|
||||
|
||||
run_test ${lnav_test} -n \
|
||||
-c ":goto 0" \
|
||||
-c ":goto 2 hours later" \
|
||||
${test_dir}/logfile_syslog_with_mixed_times.0
|
||||
|
||||
check_output "goto 3:45 is not working?" <<EOF
|
||||
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: vm_compressor_record_warmup (9478314 - 9492476)
|
||||
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: AppleBCM5701Ethernet [en0]: 0 0 memWrInd fBJP_Wakeup_Timer
|
||||
Sep 13 01:25:39 Tim-Stacks-iMac kernel[0]: AppleThunderboltNHIType2::waitForOk2Go2Sx - retries = 60000
|
||||
Sep 13 03:12:04 Tim-Stacks-iMac kernel[0]: hibernate_page_list_setall(preflight 0) start 0xffffff8428276000, 0xffffff8428336000
|
||||
Sep 13 03:12:58 Tim-Stacks-iMac kernel[0]: *** kernel exceeded 500 log message per second limit - remaining messages this second discarded ***
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: IOThunderboltSwitch<0xffffff803f4b3000>(0x0)::listenerCallback - Thunderbolt HPD packet for route = 0x0 port = 11 unplug = 0
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: vm_compressor_flush - starting
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: AppleBCM5701Ethernet [en0]: 0 0 memWrInd fBJP_Wakeup_Timer
|
||||
Sep 13 03:13:16 Tim-Stacks-iMac kernel[0]: AppleThunderboltNHIType2::waitForOk2Go2Sx - retries = 60000
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: hibernate_page_list_setall(preflight 0) start 0xffffff838f1fc000, 0xffffff838f2bc000
|
||||
EOF
|
||||
|
||||
|
||||
run_test ${lnav_test} -n \
|
||||
-c ":goto 0" \
|
||||
-c ":goto 3:45" \
|
||||
${test_dir}/logfile_syslog_with_mixed_times.0
|
||||
|
||||
check_output "goto 3:45 is not working?" <<EOF
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: IOThunderboltSwitch<0xffffff803f4b3000>(0x0)::listenerCallback - Thunderbolt HPD packet for route = 0x0 port = 11 unplug = 0
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: vm_compressor_flush - starting
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: AppleBCM5701Ethernet [en0]: 0 0 memWrInd fBJP_Wakeup_Timer
|
||||
Sep 13 03:13:16 Tim-Stacks-iMac kernel[0]: AppleThunderboltNHIType2::waitForOk2Go2Sx - retries = 60000
|
||||
Sep 13 03:46:03 Tim-Stacks-iMac kernel[0]: hibernate_page_list_setall(preflight 0) start 0xffffff838f1fc000, 0xffffff838f2bc000
|
||||
EOF
|
||||
|
||||
|
||||
run_test ${lnav_test} -n \
|
||||
-c ":goto invalid" \
|
||||
${test_dir}/logfile_access_log.0
|
||||
|
||||
check_error_output "goto invalid is working" <<EOF
|
||||
error: expecting line number/percentage or timestamp
|
||||
error: expecting line number/percentage, timestamp, or relative time
|
||||
EOF
|
||||
|
||||
check_output "goto invalid is not working" <<EOF
|
||||
|
@ -121,6 +121,27 @@ Apr 10 02:58:07 2015 -- 123
|
||||
Apr 10 02:58:07 2015 -- 456
|
||||
EOF
|
||||
|
||||
|
||||
touch -t 201509130923 ${srcdir}/logfile_syslog_with_mixed_times.0
|
||||
run_test ./drive_logfile -t -f syslog_log ${srcdir}/logfile_syslog_with_mixed_times.0
|
||||
|
||||
check_output "syslog_log with mixed times interpreted incorrectly?" <<EOF
|
||||
Sep 13 00:58:45 2015 -- 000
|
||||
Sep 13 00:59:30 2015 -- 000
|
||||
Sep 13 01:23:54 2015 -- 000
|
||||
Sep 13 03:12:04 2015 -- 000
|
||||
Sep 13 03:12:04 2015 -- 000
|
||||
Sep 13 03:12:04 2015 -- 000
|
||||
Sep 13 03:12:04 2015 -- 000
|
||||
Sep 13 03:12:58 2015 -- 000
|
||||
Sep 13 03:46:03 2015 -- 000
|
||||
Sep 13 03:46:03 2015 -- 000
|
||||
Sep 13 03:46:03 2015 -- 000
|
||||
Sep 13 03:46:03 2015 -- 000
|
||||
Sep 13 03:46:03 2015 -- 000
|
||||
EOF
|
||||
|
||||
|
||||
##
|
||||
|
||||
run_test ./drive_logfile -v -f syslog_log ${srcdir}/logfile_syslog.0
|
||||
|
151
test/test_reltime.cc
Normal file
151
test/test_reltime.cc
Normal file
@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2015, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "relative_time.hh"
|
||||
|
||||
struct {
|
||||
const char *reltime;
|
||||
const char *expected;
|
||||
} TEST_DATA[] = {
|
||||
{ "a minute ago", "0y0m0d0h-1m0s0u" },
|
||||
{ "1m ago", "0y0m0d0h-1m0s0u" },
|
||||
{ "a min ago", "0y0m0d0h-1m0s0u" },
|
||||
{ "a m ago", "0y0m0d0h-1m0s0u" },
|
||||
{ "+1 minute ago", "0y0m0d0h-1m0s0u" },
|
||||
{ "-1 minute ago", "0y0m0d0h-1m0s0u" },
|
||||
{ "-1 minute", "0y0m0d0h-1m0s0u" },
|
||||
{ "1:40", "0y0m0d1H40M0S0U" },
|
||||
{ "01:40", "0y0m0d1H40M0S0U" },
|
||||
{ "1h40m", "0y0m0d1h40m0s0u" },
|
||||
{ "1pm", "0y0m0d13H0M0S0U" },
|
||||
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
struct {
|
||||
const char *reltime;
|
||||
const char *expected_error;
|
||||
} BAD_TEST_DATA[] = {
|
||||
{ "ago", "" },
|
||||
{ "minute", "" },
|
||||
{ "1 2", "" },
|
||||
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
time_t base_time = 1317913200;
|
||||
struct exttm base_tm;
|
||||
base_tm.et_tm = *gmtime(&base_time);
|
||||
struct relative_time::parse_error pe;
|
||||
struct exttm tm;
|
||||
time_t new_time;
|
||||
|
||||
relative_time rt;
|
||||
|
||||
for (int lpc = 0; TEST_DATA[lpc].reltime; lpc++) {
|
||||
rt.clear();
|
||||
rt.parse(TEST_DATA[lpc].reltime, pe);
|
||||
printf("%s %s %s\n", TEST_DATA[lpc].reltime, TEST_DATA[lpc].expected, rt.to_string().c_str());
|
||||
assert(std::string(TEST_DATA[lpc].expected) == rt.to_string());
|
||||
}
|
||||
|
||||
for (int lpc = 0; BAD_TEST_DATA[lpc].reltime; lpc++) {
|
||||
bool rc;
|
||||
rt.clear();
|
||||
rc = rt.parse(BAD_TEST_DATA[lpc].reltime, pe);
|
||||
printf("%s -- %s\n", BAD_TEST_DATA[lpc].reltime, pe.pe_msg.c_str());
|
||||
assert(!rc);
|
||||
}
|
||||
|
||||
rt.parse("a minute ago", pe);
|
||||
assert(rt.rt_field[relative_time::RTF_MINUTES] == -1);
|
||||
|
||||
rt.parse("5 milliseconds", pe);
|
||||
|
||||
assert(rt.rt_field[relative_time::RTF_MICROSECONDS] == 5 * 1000);
|
||||
|
||||
rt.clear();
|
||||
rt.parse("5000 ms ago", pe);
|
||||
assert(rt.rt_field[relative_time::RTF_SECONDS] == -5);
|
||||
|
||||
rt.clear();
|
||||
rt.parse("5 hours 20 minutes ago", pe);
|
||||
|
||||
assert(rt.rt_field[relative_time::RTF_HOURS] == -5);
|
||||
assert(rt.rt_field[relative_time::RTF_MINUTES] == -20);
|
||||
|
||||
rt.clear();
|
||||
rt.parse("5 hours and 20 minutes ago", pe);
|
||||
|
||||
assert(rt.rt_field[relative_time::RTF_HOURS] == -5);
|
||||
assert(rt.rt_field[relative_time::RTF_MINUTES] == -20);
|
||||
|
||||
rt.clear();
|
||||
rt.parse("1:23", pe);
|
||||
|
||||
assert(rt.rt_field[relative_time::RTF_HOURS] == 1);
|
||||
assert(rt.rt_is_absolute[relative_time::RTF_HOURS]);
|
||||
assert(rt.rt_field[relative_time::RTF_MINUTES] == 23);
|
||||
assert(rt.rt_is_absolute[relative_time::RTF_MINUTES]);
|
||||
|
||||
rt.clear();
|
||||
rt.parse("1:23:45", pe);
|
||||
|
||||
assert(rt.rt_field[relative_time::RTF_HOURS] == 1);
|
||||
assert(rt.rt_is_absolute[relative_time::RTF_HOURS]);
|
||||
assert(rt.rt_field[relative_time::RTF_MINUTES] == 23);
|
||||
assert(rt.rt_is_absolute[relative_time::RTF_MINUTES]);
|
||||
assert(rt.rt_field[relative_time::RTF_SECONDS] == 45);
|
||||
assert(rt.rt_is_absolute[relative_time::RTF_SECONDS]);
|
||||
|
||||
tm = base_tm;
|
||||
rt.add(tm);
|
||||
|
||||
new_time = timegm(&tm.et_tm);
|
||||
tm.et_tm = *gmtime(&new_time);
|
||||
assert(tm.et_tm.tm_hour == 1);
|
||||
assert(tm.et_tm.tm_min == 23);
|
||||
|
||||
rt.clear();
|
||||
rt.parse("5 minutes ago", pe);
|
||||
|
||||
tm = base_tm;
|
||||
rt.add(tm);
|
||||
|
||||
new_time = timegm(&tm.et_tm);
|
||||
|
||||
assert(new_time == (base_time - (5 * 60)));
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user