示例#1
0
 def test_charts_read_yaml(self):
     values = [
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:24:57"),
             chart=V1EventChart(kind="plotly", figure={"foo": "bar"}),
             step=12,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:25:57"),
             chart=V1EventChart(kind="vega", figure={"foo2": "bar2"}),
             step=13,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:26:57"),
             chart=V1EventChart(kind="bokeh", figure={"foo3": "bar3"}),
             step=14,
         ),
     ]
     events = V1Events.read(
         name="foo",
         kind="chart",
         data=os.path.abspath("tests/fixtures/polyboard/chart/chart_events.plx"),
     )
     assert events.name == "foo"
     assert len(events.df.values) == 3
     for i in range(3):
         assert events.get_event_at(i).to_dict() == values[i].to_dict()
示例#2
0
 def test_artifacts_read_yaml(self):
     values = [
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:24:57"),
             artifact=V1EventArtifact(kind="dataframe", path="path1"),
             step=12,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:25:57"),
             artifact=V1EventArtifact(kind="tsv", path="path2"),
             step=13,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:26:57"),
             artifact=V1EventArtifact(kind="csv", path="path3"),
             step=14,
         ),
     ]
     events = V1Events.read(
         name="foo",
         kind="artifact",
         data=os.path.abspath(
             "tests/fixtures/polyboard/artifact/artifact_events.plx"
         ),
     )
     assert events.name == "foo"
     assert len(events.df.values) == 3
     for i in range(3):
         assert events.get_event_at(i).to_dict() == values[i].to_dict()
示例#3
0
 def test_htmls_read_yaml(self):
     values = [
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:24:57"),
             html="<div>1</div>",
             step=12,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:25:57"),
             html="<div>2</div>",
             step=13,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:26:57"),
             html="<div>3</div>",
             step=14,
         ),
     ]
     events = V1Events.read(
         name="foo",
         kind="html",
         data=os.path.abspath("tests/fixtures/polyboard/html/html_events.plx"),
     )
     assert events.name == "foo"
     assert len(events.df.values) == 3
     for i in range(3):
         assert events.get_event_at(i).to_dict() == values[i].to_dict()
示例#4
0
 def test_audios_read_yaml(self):
     values = [
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:24:57"),
             audio=V1EventAudio(
                 sample_rate=1.1, num_channels=2, length_frames=2, path="test"
             ),
             step=12,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:25:57"),
             audio=V1EventAudio(
                 sample_rate=1.11,
                 num_channels=22,
                 length_frames=22,
                 path="test",
                 content_type="wav",
             ),
             step=13,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:26:57"),
             audio=V1EventAudio(path="testwave", content_type="wav"),
             step=14,
         ),
     ]
     events = V1Events.read(
         name="foo",
         kind="audio",
         data=os.path.abspath("tests/fixtures/polyboard/audio/audio_events.plx"),
     )
     assert events.name == "foo"
     assert len(events.df.values) == 3
     for i in range(3):
         assert events.get_event_at(i).to_dict() == values[i].to_dict()
示例#5
0
 def test_dataframes_read_yaml(self):
     values = [
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:24:57"),
             dataframe=V1EventDataframe(path="path1", content_type="parquet"),
             step=12,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:25:57"),
             dataframe=V1EventDataframe(path="path2", content_type="pickle"),
             step=13,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:26:57"),
             dataframe=V1EventDataframe(path="path3"),
             step=14,
         ),
     ]
     events = V1Events.read(
         name="foo",
         kind="dataframe",
         data=os.path.abspath(
             "tests/fixtures/polyboard/dataframe/dataframe_events.plx"
         ),
     )
     assert events.name == "foo"
     assert len(events.df.values) == 3
     for i in range(3):
         assert events.get_event_at(i).to_dict() == values[i].to_dict()
示例#6
0
 def test_videos_read_yaml(self):
     values = [
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:24:57"),
             video=V1EventVideo(path="test", content_type="mp4"),
             step=12,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:25:57"),
             video=V1EventVideo(height=1, width=1),
             step=13,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:26:57"),
             video=V1EventVideo(height=10, width=10, colorspace=2),
             step=14,
         ),
     ]
     events = V1Events.read(
         name="foo",
         kind="video",
         data=os.path.abspath("tests/fixtures/polyboard/video/video_events.plx"),
     )
     assert events.name == "foo"
     assert len(events.df.values) == 3
     for i in range(3):
         assert events.get_event_at(i).to_dict() == values[i].to_dict()
示例#7
0
 def test_audio(self):
     events = LoggedEventListSpec(
         name="foo",
         kind="audio",
         events=[
             V1Event(
                 timestamp=parse_datetime("2018-12-11 10:24:57"),
                 audio=V1EventAudio(
                     sample_rate=1.1, num_channels=2, length_frames=2, path="test"
                 ),
                 step=12,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 11:24:57"),
                 audio=V1EventAudio(
                     sample_rate=1.1, num_channels=2, length_frames=2, path="test"
                 ),
                 step=13,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 12:24:57"),
                 audio=V1EventAudio(
                     sample_rate=1.12, num_channels=22, length_frames=22, path="test"
                 ),
                 step=14,
             ),
         ],
     )
     events_dict = events.to_dict()
     assert events_dict == events.from_dict(events_dict).to_dict()
示例#8
0
 def test_histograms_read_yaml(self):
     values = [
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:24:57"),
             histogram=V1EventHistogram(values=[10], counts=[1]),
             step=12,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:25:57"),
             histogram=V1EventHistogram(values=[10, 1, 1], counts=[1, 1, 1]),
             step=13,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:26:57"),
             histogram=V1EventHistogram(
                 values=[10, 112, 12, 1], counts=[12, 1, 1, 1]
             ),
             step=14,
         ),
     ]
     events = V1Events.read(
         name="foo",
         kind="histogram",
         data=os.path.abspath(
             "tests/fixtures/polyboard/histogram/histogram_events.plx"
         ),
     )
     assert events.name == "foo"
     assert len(events.df.values) == 3
     for i in range(3):
         assert events.get_event_at(i).to_dict() == values[i].to_dict()
示例#9
0
 def test_logs_with_files(self):
     logs = V1Logs(
         last_file=1000,
         last_time=now(),
         files=["file1", "file2"],
         logs=[
             V1Log(
                 value="foo",
                 timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
                 node="node1",
                 pod="pod1",
                 container="container1",
             ),
             V1Log(
                 value="foo",
                 timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
                 node="node1",
                 pod="pod1",
                 container="container1",
             ),
             V1Log(
                 value="foo",
                 timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
                 node="node1",
                 pod="pod1",
                 container="container1",
             ),
         ],
     )
     logs_dict = logs.to_light_dict()
     assert logs_dict == logs.from_dict(logs_dict).to_light_dict()
     assert logs_dict == logs.read(logs.to_dict(dump=True)).to_light_dict()
示例#10
0
    def test_chunk_logs(self):
        logs = [
            V1Log(
                value="foo1",
                timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
                node="node1",
                pod="pod1",
                container="container1",
            ),
            V1Log(
                value="foo2",
                timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
                node="node1",
                pod="pod1",
                container="container1",
            ),
            V1Log(
                value="foo3",
                timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
                node="node1",
                pod="pod1",
                container="container1",
            ),
        ]

        V1Logs.CHUNK_SIZE = 2
        chunks = [c for c in V1Logs.chunk_logs(logs)]
        # 1 chunk
        assert [i.value for i in chunks[0].logs] == ["foo1", "foo2"]

        # 2 chunk
        assert [i.value for i in chunks[1].logs] == ["foo3"]
示例#11
0
 def test_logs_to_csv(self):
     logs = V1Logs(
         last_file=1000,
         logs=[
             V1Log(
                 value="foo",
                 timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
                 node="node1",
                 pod="pod1",
                 container="container1",
             ),
             V1Log(
                 value="foo",
                 timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
                 node="node1",
                 pod="pod1",
                 container="container1",
             ),
             V1Log(
                 value="foo",
                 timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
                 node="node1",
                 pod="pod1",
                 container="container1",
             ),
         ],
     )
     assert logs.to_csv() == "".join([
         "\n{}".format(logs.logs[0].to_csv()),
         "\n{}".format(logs.logs[1].to_csv()),
         "\n{}".format(logs.logs[2].to_csv()),
     ])
示例#12
0
    def process_log_line(
        cls,
        value: Text,
        node: Optional[str],
        pod: Optional[str],
        container: Optional[str],
        timestamp=None,
    ) -> "V1Log":
        if not value:
            return None

        if not isinstance(value, str):
            value = value.decode("utf-8")

        value = value.strip()

        if not timestamp:
            value, timestamp = timestamp_search_regex(ISO_DATETIME_REGEX, value)
            if not timestamp:
                value, timestamp = timestamp_search_regex(DATETIME_REGEX, value)
        if isinstance(timestamp, str):
            try:
                timestamp = parse_datetime(timestamp)
            except Exception as e:
                raise ValidationError("Received an invalid timestamp") from e

        return cls(
            timestamp=timestamp if timestamp else now(tzinfo=True),
            node=node,
            pod=pod,
            container=container,
            value=value,
        )
    def test_log_line_has_iso_datetime(self):
        log_line = "2018-12-11T08:49:07.163495183Z foo"

        log_value, ts = timestamp_search_regex(ISO_DATETIME_REGEX, log_line)

        assert ts == parse_datetime("2018-12-11T08:49:07.163495183Z")
        assert log_value == "foo"
示例#14
0
def timestamp_search_regex(regex, log_line):
    log_search = regex.search(log_line)
    if not log_search:
        return log_line, None

    ts = log_search.group()
    ts = parse_datetime(ts)

    return re.sub(regex, "", log_line), ts
示例#15
0
    def test_models_read_yaml(self):
        values = [
            V1Event(
                timestamp=parse_datetime("2018-12-11 10:24:57"),
                model=V1EventModel(framework="tensorflow", path="path1"),
                step=12,
            ),
            V1Event(
                timestamp=parse_datetime("2018-12-11 10:25:57"),
                model=V1EventModel(framework="pytorch", path="path2"),
                step=13,
            ),
            V1Event(
                timestamp=parse_datetime("2018-12-11 10:26:57"),
                model=V1EventModel(framework="onnx", path="path3"),
                step=14,
            ),
        ]
        events = V1Events.read(
            name="foo",
            kind="model",
            data=os.path.abspath("tests/fixtures/polyboard/model/model_events.plx"),
        )
        assert events.name == "foo"
        assert len(events.df.values) == 3
        for i in range(3):
            assert events.get_event_at(i).to_dict() == values[i].to_dict()

        # Test single event
        events = V1Events.read(
            name="foo",
            kind="model",
            data=os.path.abspath(
                "tests/fixtures/polyboard/model/model_events_without_step.plx"
            ),
        )
        assert events.name == "foo"
        assert len(events.df.values) == 1
        event = V1Event(
            timestamp=parse_datetime("2018-12-11 10:24:57"),
            model=V1EventModel(framework="tensorflow", path="path1"),
        )
        events.get_event_at(0).to_dict()
        assert events.get_event_at(0).to_dict() == event.to_dict()
示例#16
0
    def test_to_csv(self):
        log_line = V1Log(
            value="foo",
            timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
            node="node1",
            pod="pod1",
            container="container1",
        )

        assert log_line.to_csv(
        ) == '{}|node1|pod1|container1|{{"_":"foo"}}'.format(
            log_line.timestamp)
示例#17
0
 def test_metrics_read_yaml(self):
     values = [
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:24:57"), metric=0.1, step=12
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:25:57"), metric=0.2, step=13
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:26:57"), metric=0.3, step=14
         ),
     ]
     events = V1Events.read(
         name="metric_events",
         kind="metric",
         data=os.path.abspath("tests/fixtures/polyboard/metric/metric_events.plx"),
     )
     assert events.name == "metric_events"
     assert len(events.df.values) == 3
     for i in range(3):
         assert events.get_event_at(i).to_dict() == values[i].to_dict()
示例#18
0
 def test_curve(self):
     events = LoggedEventListSpec(
         name="foo",
         kind="curve",
         events=[
             V1Event(
                 timestamp=parse_datetime("2018-12-11 10:24:57"),
                 curve=V1EventCurve(
                     kind="roc",
                     x=[1.1, 3.1, 5.1],
                     y=[0.1, 0.3, 0.4],
                     annotation="0.1",
                 ),
                 step=12,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 11:24:57"),
                 curve=V1EventCurve(
                     kind="pr",
                     x=[1.1, 3.1, 5.1],
                     y=[0.1, 0.3, 0.4],
                     annotation="0.21",
                 ),
                 step=13,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 12:24:57"),
                 curve=V1EventCurve(
                     kind="custom",
                     x=[1.1, 3.1, 5.1],
                     y=[0.1, 0.3, 0.4],
                     annotation="0.1",
                 ),
                 step=14,
             ),
         ],
     )
     events_dict = events.to_dict()
     assert events_dict == events.from_dict(events_dict).to_dict()
示例#19
0
 def test_curves_read_yaml(self):
     values = [
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:24:57"),
             curve=V1EventCurve(
                 kind="roc", x=[1.1, 3.1, 5.1], y=[0.1, 0.3, 0.4], annotation="0.1"
             ),
             step=12,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:25:57"),
             curve=V1EventCurve(
                 kind="pr", x=[1.1, 3.1, 5.1], y=[0.1, 0.3, 0.4], annotation="0.21"
             ),
             step=13,
         ),
         V1Event(
             timestamp=parse_datetime("2018-12-11 10:26:57"),
             curve=V1EventCurve(
                 kind="custom",
                 x=[1.1, 3.1, 5.1],
                 y=[0.1, 0.3, 0.4],
                 annotation="0.1",
             ),
             step=14,
         ),
     ]
     events = V1Events.read(
         name="foo",
         kind="curve",
         data=os.path.abspath("tests/fixtures/polyboard/curve/curve_events.plx"),
     )
     assert events.name == "foo"
     assert len(events.df.values) == 3
     for i in range(3):
         assert events.get_event_at(i).to_dict() == values[i].to_dict()
示例#20
0
 def test_log_line_has_iso_datetime(self):
     parsed = V1Log.process_log_line(
         value="2018-12-11T08:49:07.163495183Z foo",
         node="node1",
         pod="pod1",
         container="container1",
     )
     expected = V1Log(
         value="foo",
         timestamp=parse_datetime("2018-12-11T08:49:07.163495+00:00"),
         node="node1",
         pod="pod1",
         container="container1",
     )
     assert parsed == expected
示例#21
0
 def test_log_line_has_datetime(self):
     parsed = V1Log.process_log_line(
         value="2018-12-11 10:24:57 UTC foo",
         node="node1",
         pod="pod1",
         container="container1",
     )
     expected = V1Log(
         value="foo",
         timestamp=parse_datetime("2018-12-11 10:24:57 UTC"),
         node="node1",
         pod="pod1",
         container="container1",
     )
     assert parsed == expected
示例#22
0
 def test_histogram(self):
     events = LoggedEventListSpec(
         name="foo",
         kind="histogram",
         events=[
             V1Event(
                 timestamp=parse_datetime("2018-12-11 10:24:57"),
                 histogram=V1EventHistogram(values=[10], counts=[1]),
                 step=12,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 11:24:57"),
                 histogram=V1EventHistogram(values=[10], counts=[1]),
                 step=13,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 12:24:57"),
                 histogram=V1EventHistogram(values=[10], counts=[1]),
                 step=14,
             ),
         ],
     )
     events_dict = events.to_dict()
     assert events_dict == events.from_dict(events_dict).to_dict()
示例#23
0
 def test_video(self):
     events = LoggedEventListSpec(
         name="foo",
         kind="video",
         events=[
             V1Event(
                 timestamp=parse_datetime("2018-12-11 10:24:57"),
                 video=V1EventVideo(height=1, width=1, colorspace=1, path="path"),
                 step=12,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 11:24:57"),
                 video=V1EventVideo(height=10, width=1, colorspace=0, path="path"),
                 step=13,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 12:24:57"),
                 video=V1EventVideo(height=1, width=10, colorspace=2, path="path"),
                 step=14,
             ),
         ],
     )
     events_dict = events.to_dict()
     assert events_dict == events.from_dict(events_dict).to_dict()
示例#24
0
 def test_metrics(self):
     events = LoggedEventListSpec(
         name="foo",
         kind="metric",
         events=[
             V1Event(
                 timestamp=parse_datetime("2018-12-11 10:24:57"),
                 metric=0.1,
                 step=12,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 12:24:57"),
                 metric=0.112,
                 step=13,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 11:24:57"),
                 metric=0.1,
                 step=14,
             ),
         ],
     )
     events_dict = events.to_dict()
     assert events_dict == events.from_dict(events_dict).to_dict()
示例#25
0
 def test_html(self):
     events = LoggedEventListSpec(
         name="foo",
         kind="html",
         events=[
             V1Event(
                 timestamp=parse_datetime("2018-12-11 10:24:57"),
                 html="<div>1</div>",
                 step=12,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 11:24:57"),
                 html="<div>2</div>",
                 step=13,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 12:24:57"),
                 html="<div>3</div>",
                 step=14,
             ),
         ],
     )
     events_dict = events.to_dict()
     assert events_dict == events.from_dict(events_dict).to_dict()
示例#26
0
 def test_dataframe(self):
     events = LoggedEventListSpec(
         name="foo",
         kind="dataframe",
         events=[
             V1Event(
                 timestamp=parse_datetime("2018-12-11 10:24:57"),
                 dataframe=V1EventDataframe(path="path", content_type="parquet"),
                 step=12,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 11:24:57"),
                 dataframe=V1EventDataframe(path="path", content_type="pickle"),
                 step=13,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 12:24:57"),
                 dataframe=V1EventDataframe(path="path"),
                 step=14,
             ),
         ],
     )
     events_dict = events.to_dict()
     assert events_dict == events.from_dict(events_dict).to_dict()
示例#27
0
 def test_chart(self):
     events = LoggedEventListSpec(
         name="foo",
         kind="chart",
         events=[
             V1Event(
                 timestamp=parse_datetime("2018-12-11 10:24:57"),
                 chart=V1EventChart(kind="plotly", figure={"foo": "bar"}),
                 step=12,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 11:24:57"),
                 chart=V1EventChart(kind="vega", figure={"foo": "bar"}),
                 step=13,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 12:24:57"),
                 chart=V1EventChart(kind="bokeh", figure={"foo": "bar"}),
                 step=14,
             ),
         ],
     )
     events_dict = events.to_dict()
     assert events_dict == events.from_dict(events_dict).to_dict()
示例#28
0
 def test_model(self):
     events = LoggedEventListSpec(
         name="foo",
         kind="model",
         events=[
             V1Event(
                 timestamp=parse_datetime("2018-12-11 10:24:57"),
                 model=V1EventModel(framework="tensorflow", path="path"),
                 step=12,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 11:24:57"),
                 model=V1EventModel(framework="pytorch", path="path"),
                 step=13,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 12:24:57"),
                 model=V1EventModel(framework="onnx", path="path"),
                 step=14,
             ),
         ],
     )
     events_dict = events.to_dict()
     assert events_dict == events.from_dict(events_dict).to_dict()
示例#29
0
 def test_artifact(self):
     events = LoggedEventListSpec(
         name="foo",
         kind="artifact",
         events=[
             V1Event(
                 timestamp=parse_datetime("2018-12-11 10:24:57"),
                 artifact=V1EventArtifact(kind="dataframe", path="path"),
                 step=12,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 11:24:57"),
                 artifact=V1EventArtifact(kind="tsv", path="path"),
                 step=13,
             ),
             V1Event(
                 timestamp=parse_datetime("2018-12-11 12:24:57"),
                 artifact=V1EventArtifact(kind="csv", path="path"),
                 step=14,
             ),
         ],
     )
     events_dict = events.to_dict()
     assert events_dict == events.from_dict(events_dict).to_dict()
示例#30
0
def humanize_timesince(start_time):  # pylint:disable=too-many-return-statements
    """Creates a string representation of time since the given `start_time`."""
    if not start_time:
        return start_time

    start_time = parse_datetime(start_time)

    delta = now() - start_time

    # assumption: negative delta values originate from clock
    #             differences on different app server machines
    if delta.total_seconds() < 0:
        return "a few seconds ago"

    num_years = delta.days // 365
    if num_years > 0:
        return "{} year{} ago".format(*((num_years,
                                         "s") if num_years > 1 else (num_years,
                                                                     "")))

    num_weeks = delta.days // 7
    if num_weeks > 0:
        return "{} week{} ago".format(*((num_weeks,
                                         "s") if num_weeks > 1 else (num_weeks,
                                                                     "")))

    num_days = delta.days
    if num_days > 0:
        return "{} day{} ago".format(*((num_days,
                                        "s") if num_days > 1 else (num_days,
                                                                   "")))

    num_hours = delta.seconds // 3600
    if num_hours > 0:
        return "{} hour{} ago".format(*((num_hours,
                                         "s") if num_hours > 1 else (num_hours,
                                                                     "")))

    num_minutes = delta.seconds // 60
    if num_minutes > 0:
        return "{} minute{} ago".format(
            *((num_minutes, "s") if num_minutes > 1 else (num_minutes, "")))

    return "a few seconds ago"