[cmd] add support for times in goto

pull/257/head
Timothy Stack 9 years ago
parent 7cd7bf4dc4
commit ec473edc85

@ -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 \

@ -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;
}
else{
lnav_data.ld_session_file_index -= 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);
}
}
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,40 +288,46 @@ 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;
int off_year = 0, off_month = 0, off_day = 0, off_hour = 0;
std::vector<logline>::iterator iter;
bool do_change = true;
if (diff > (5 * 60)) {
int off_year = 0, off_month = 0, off_day = 0, off_hour = 0;
std::vector<logline>::iterator iter;
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 {
off_hour = 1;
}
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 if (!(etm.et_flags & ETF_DAY_SET)) {
off_hour = 1;
} else {
do_change = false;
}
for (iter = dst.begin(); iter != dst.end(); iter++) {
time_t ot = iter->get_time();
struct tm otm;
if (!do_change) {
return;
}
for (iter = dst.begin(); iter != dst.end(); iter++) {
time_t ot = iter->get_time();
struct tm otm;
gmtime_r(&ot, &otm);
otm.tm_year -= off_year;
otm.tm_mon -= off_month;
otm.tm_yday -= off_day;
otm.tm_hour -= off_hour;
iter->set_time(tm2sec(&otm));
}
gmtime_r(&ot, &otm);
otm.tm_year -= off_year;
otm.tm_mon -= off_month;
otm.tm_yday -= off_day;
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);
}

@ -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;
}
}

@ -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;
paths.push_back(getenv("test_dir"));
if (getenv("test_dir") != NULL) {
paths.push_back(getenv("test_dir"));
}
load_formats(paths, errors);
}

@ -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

@ -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…
Cancel
Save