Commit 36477bb2 authored by Ell's avatar Ell

app, icons, menus: add performance-log recording to the dashboard

Add an option to record a performance log through the dashboard.
The log contains a series of samples of the dashboard variables, as
well as the full program backtrace, when available.  As such, it
essentially acts as a built-in profiler, which allows us to
correlate program execution with the information available through
the dashboard.  It is meant to be used for creating logs to
accompany perofrmance-related bug reports, as well as for profiling
GIMP during development.

The sample frequency defaults to 10 samples per second, but can be
overridden using the GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY
environment variable.  Backtraces are included by default when
available, but can be suppressed using the
GIMP_PERFORMANCE_LOG_NO_BACKTRACE environment variable.

Logs are created through the new "record" button at the bottom of
the dashboard dialog.  When pressed, a file dialog is opened to
select the log file, and, once confirmed, data is being recorded to
the selected file.  Recording is stopped by pressing the "record"
button again (we use a highlight to indicate that recording is
active.)

While recording, the "reset" button is replaced with an "add marker"
button, which can be used to add event markers to the log.  These
can be used to mark events of interest, such as "started painting"
and "stopped painting", which then appear in the log as part of the
sample stream.  Markers are numbered sequentually, and the number
of the next (to-be-added) marker appears on the button.  Shift-
clicking the button adds an empty (description-less) marker, which
is only identified by its number; this can be used when markers
need to be added quickly.

The log is an XML file, containing some extra information (such as
the output of "$ gimp -v", and symbol information) in addition to
the samples.  The data in the file is delta-encoded to reduce the
file size, meaning that samples (as well as some other elements)
only specify the changes since the previous sample.  This adds a
necessary decoding step before data can be processed; the next
commit adds a tool that does that.

There are currently no tools to actually analyze the data -- that's
still TBD -- but at least we can start gathering it.
parent 80bf686c
...@@ -47,6 +47,24 @@ static const GimpActionEntry dashboard_actions[] = ...@@ -47,6 +47,24 @@ static const GimpActionEntry dashboard_actions[] =
{ "dashboard-history-duration", NULL, { "dashboard-history-duration", NULL,
NC_("dashboard-action", "_History Duration") }, NC_("dashboard-action", "_History Duration") },
{ "dashboard-log-record", GIMP_ICON_RECORD,
NC_("dashboard-action", "_Start/Stop Recording..."), NULL,
NC_("dashboard-action", "Start/stop recording performance log"),
G_CALLBACK (dashboard_log_record_cmd_callback),
GIMP_HELP_DASHBOARD_LOG_RECORD },
{ "dashboard-log-add-marker", GIMP_ICON_MARKER,
NC_("dashboard-action", "_Add Marker..."), NULL,
NC_("dashboard-action", "Add an event marker "
"to the performance log"),
G_CALLBACK (dashboard_log_add_marker_cmd_callback),
GIMP_HELP_DASHBOARD_LOG_ADD_MARKER },
{ "dashboard-log-add-empty-marker", GIMP_ICON_MARKER,
NC_("dashboard-action", "Add _Empty Marker"), NULL,
NC_("dashboard-action", "Add an empty event marker "
"to the performance log"),
G_CALLBACK (dashboard_log_add_empty_marker_cmd_callback),
GIMP_HELP_DASHBOARD_LOG_ADD_EMPTY_MARKER },
{ "dashboard-reset", GIMP_ICON_RESET, { "dashboard-reset", GIMP_ICON_RESET,
NC_("dashboard-action", "_Reset"), NULL, NC_("dashboard-action", "_Reset"), NULL,
NC_("dashboard-action", "Reset cumulative data"), NC_("dashboard-action", "Reset cumulative data"),
...@@ -153,7 +171,12 @@ dashboard_actions_update (GimpActionGroup *group, ...@@ -153,7 +171,12 @@ dashboard_actions_update (GimpActionGroup *group,
gpointer data) gpointer data)
{ {
GimpDashboard *dashboard = GIMP_DASHBOARD (data); GimpDashboard *dashboard = GIMP_DASHBOARD (data);
gboolean recording;
recording = gimp_dashboard_log_is_recording (dashboard);
#define SET_SENSITIVE(action,condition) \
gimp_action_group_set_action_sensitive (group, action, (condition) != 0)
#define SET_ACTIVE(action,condition) \ #define SET_ACTIVE(action,condition) \
gimp_action_group_set_action_active (group, action, (condition) != 0) gimp_action_group_set_action_active (group, action, (condition) != 0)
...@@ -195,8 +218,13 @@ dashboard_actions_update (GimpActionGroup *group, ...@@ -195,8 +218,13 @@ dashboard_actions_update (GimpActionGroup *group,
break; break;
} }
SET_SENSITIVE ("dashboard-log-add-marker", recording);
SET_SENSITIVE ("dashboard-log-add-empty-marker", recording);
SET_SENSITIVE ("dashboard-reset", !recording);
SET_ACTIVE ("dashboard-low-swap-space-warning", SET_ACTIVE ("dashboard-low-swap-space-warning",
gimp_dashboard_get_low_swap_space_warning (dashboard)); gimp_dashboard_get_low_swap_space_warning (dashboard));
#undef SET_SENSITIVE
#undef SET_ACTIVE #undef SET_ACTIVE
} }
...@@ -24,14 +24,30 @@ ...@@ -24,14 +24,30 @@
#include "actions-types.h" #include "actions-types.h"
#include "core/gimp.h"
#include "widgets/gimpdashboard.h" #include "widgets/gimpdashboard.h"
#include "widgets/gimphelp-ids.h" #include "widgets/gimphelp-ids.h"
#include "widgets/gimpuimanager.h"
#include "dialogs/dialogs.h"
#include "dashboard-commands.h" #include "dashboard-commands.h"
#include "gimp-intl.h" #include "gimp-intl.h"
/* local function prototypes */
static void dashboard_log_record_response (GtkWidget *dialog,
int response_id,
GimpDashboard *dashboard);
static void dashboard_log_add_marker_response (GtkWidget *dialog,
const gchar *description,
GimpDashboard *dashboard);
/* public functions */ /* public functions */
...@@ -61,6 +77,148 @@ dashboard_history_duration_cmd_callback (GtkAction *action, ...@@ -61,6 +77,148 @@ dashboard_history_duration_cmd_callback (GtkAction *action,
gimp_dashboard_set_history_duration (dashboard, history_duration); gimp_dashboard_set_history_duration (dashboard, history_duration);
} }
void
dashboard_log_record_cmd_callback (GtkAction *action,
gpointer data)
{
GimpDashboard *dashboard = GIMP_DASHBOARD (data);
if (! gimp_dashboard_log_is_recording (dashboard))
{
GtkWidget *dialog;
#define LOG_RECORD_KEY "gimp-dashboard-log-record-dialog"
dialog = dialogs_get_dialog (G_OBJECT (dashboard), LOG_RECORD_KEY);
if (! dialog)
{
GtkFileFilter *filter;
GFile *folder;
dialog = gtk_file_chooser_dialog_new (
"Record Performance Log", NULL, GTK_FILE_CHOOSER_ACTION_SAVE,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Record"), GTK_RESPONSE_OK,
NULL);
gtk_dialog_set_default_response (GTK_DIALOG (dialog),
GTK_RESPONSE_OK);
gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
gtk_window_set_screen (
GTK_WINDOW (dialog),
gtk_widget_get_screen (GTK_WIDGET (dashboard)));
gtk_window_set_role (GTK_WINDOW (dialog),
"gimp-dashboard-log-record");
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
gtk_file_chooser_set_do_overwrite_confirmation (
GTK_FILE_CHOOSER (dialog), TRUE);
filter = gtk_file_filter_new ();
gtk_file_filter_set_name (filter, _("All Files"));
gtk_file_filter_add_pattern (filter, "*");
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
filter = gtk_file_filter_new ();
gtk_file_filter_set_name (filter, _("Log Files (*.log)"));
gtk_file_filter_add_pattern (filter, "*.log");
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
folder = g_object_get_data (G_OBJECT (dashboard),
"gimp-dashboard-log-record-folder");
if (folder)
{
gtk_file_chooser_set_current_folder_file (
GTK_FILE_CHOOSER (dialog), folder, NULL);
}
gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog),
"gimp-performance.log");
g_signal_connect (dialog, "response",
G_CALLBACK (dashboard_log_record_response),
dashboard);
g_signal_connect (dialog, "delete-event",
G_CALLBACK (gtk_true),
NULL);
gimp_help_connect (dialog, gimp_standard_help_func,
GIMP_HELP_DASHBOARD_LOG_RECORD, NULL);
dialogs_attach_dialog (G_OBJECT (dashboard), LOG_RECORD_KEY, dialog);
g_signal_connect_object (dashboard, "destroy",
G_CALLBACK (gtk_widget_destroy),
dialog,
G_CONNECT_SWAPPED);
#undef LOG_RECORD_KEY
}
gtk_window_present (GTK_WINDOW (dialog));
}
else
{
GError *error = NULL;
if (! gimp_dashboard_log_stop_recording (dashboard, &error))
{
gimp_message_literal (
gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard))->gimp,
NULL, GIMP_MESSAGE_ERROR, error->message);
}
}
}
void
dashboard_log_add_marker_cmd_callback (GtkAction *action,
gpointer data)
{
GimpDashboard *dashboard = GIMP_DASHBOARD (data);
GtkWidget *dialog;
#define LOG_ADD_MARKER_KEY "gimp-dashboard-log-add-marker-dialog"
dialog = dialogs_get_dialog (G_OBJECT (dashboard), LOG_ADD_MARKER_KEY);
if (! dialog)
{
dialog = gimp_query_string_box (
_("Add Marker"), GTK_WIDGET (dashboard),
gimp_standard_help_func, GIMP_HELP_DASHBOARD_LOG_ADD_MARKER,
_("Enter a description for the marker"),
NULL,
G_OBJECT (dashboard), "destroy",
(GimpQueryStringCallback) dashboard_log_add_marker_response,
dashboard);
dialogs_attach_dialog (G_OBJECT (dashboard), LOG_ADD_MARKER_KEY, dialog);
#undef LOG_ADD_MARKER_KEY
}
gtk_window_present (GTK_WINDOW (dialog));
}
void
dashboard_log_add_empty_marker_cmd_callback (GtkAction *action,
gpointer data)
{
GimpDashboard *dashboard = GIMP_DASHBOARD (data);
gimp_dashboard_log_add_marker (dashboard, NULL);
}
void void
dashboard_reset_cmd_callback (GtkAction *action, dashboard_reset_cmd_callback (GtkAction *action,
gpointer data) gpointer data)
...@@ -81,3 +239,45 @@ dashboard_low_swap_space_warning_cmd_callback (GtkAction *action, ...@@ -81,3 +239,45 @@ dashboard_low_swap_space_warning_cmd_callback (GtkAction *action,
gimp_dashboard_set_low_swap_space_warning (dashboard, low_swap_space_warning); gimp_dashboard_set_low_swap_space_warning (dashboard, low_swap_space_warning);
} }
/* private functions */
static void
dashboard_log_record_response (GtkWidget *dialog,
int response_id,
GimpDashboard *dashboard)
{
if (response_id == GTK_RESPONSE_OK)
{
GFile *file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
GError *error = NULL;
g_object_set_data_full (G_OBJECT (dashboard),
"gimp-dashboard-log-record-folder",
g_file_get_parent (file),
g_object_unref);
if (! gimp_dashboard_log_start_recording (dashboard, file, &error))
{
gimp_message_literal (
gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard))->gimp,
NULL, GIMP_MESSAGE_ERROR, error->message);
g_clear_error (&error);
}
g_object_unref (file);
}
gtk_widget_destroy (dialog);
}
static void
dashboard_log_add_marker_response (GtkWidget *dialog,
const gchar *description,
GimpDashboard *dashboard)
{
gimp_dashboard_log_add_marker (dashboard, description);
}
...@@ -26,6 +26,13 @@ void dashboard_history_duration_cmd_callback (GtkAction *action, ...@@ -26,6 +26,13 @@ void dashboard_history_duration_cmd_callback (GtkAction *action,
GtkAction *current, GtkAction *current,
gpointer data); gpointer data);
void dashboard_log_record_cmd_callback (GtkAction *action,
gpointer data);
void dashboard_log_add_marker_cmd_callback (GtkAction *action,
gpointer data);
void dashboard_log_add_empty_marker_cmd_callback (GtkAction *action,
gpointer data);
void dashboard_reset_cmd_callback (GtkAction *action, void dashboard_reset_cmd_callback (GtkAction *action,
gpointer data); gpointer data);
......
This diff is collapsed.
...@@ -51,25 +51,34 @@ struct _GimpDashboardClass ...@@ -51,25 +51,34 @@ struct _GimpDashboardClass
GType gimp_dashboard_get_type (void) G_GNUC_CONST; GType gimp_dashboard_get_type (void) G_GNUC_CONST;
GtkWidget * gimp_dashboard_new (Gimp *gimp, GtkWidget * gimp_dashboard_new (Gimp *gimp,
GimpMenuFactory *menu_factory); GimpMenuFactory *menu_factory);
void gimp_dashboard_reset (GimpDashboard *dashboard); gboolean gimp_dashboard_log_start_recording (GimpDashboard *dashboard,
GFile *file,
void gimp_dashboard_set_update_interval (GimpDashboard *dashboard, GError **error);
GimpDashboardUpdateInteval update_interval); gboolean gimp_dashboard_log_stop_recording (GimpDashboard *dashboard,
GimpDashboardUpdateInteval gimp_dashboard_get_update_interval (GimpDashboard *dashboard); GError **error);
gboolean gimp_dashboard_log_is_recording (GimpDashboard *dashboard);
void gimp_dashboard_set_history_duration (GimpDashboard *dashboard, void gimp_dashboard_log_add_marker (GimpDashboard *dashboard,
GimpDashboardHistoryDuration history_duration); const gchar *description);
GimpDashboardHistoryDuration gimp_dashboard_get_history_duration (GimpDashboard *dashboard);
void gimp_dashboard_reset (GimpDashboard *dashboard);
void gimp_dashboard_set_low_swap_space_warning (GimpDashboard *dashboard,
gboolean low_swap_space_warning); void gimp_dashboard_set_update_interval (GimpDashboard *dashboard,
gboolean gimp_dashboard_get_low_swap_space_warning (GimpDashboard *dashboard); GimpDashboardUpdateInteval update_interval);
GimpDashboardUpdateInteval gimp_dashboard_get_update_interval (GimpDashboard *dashboard);
void gimp_dashboard_menu_setup (GimpUIManager *manager,
const gchar *ui_path); void gimp_dashboard_set_history_duration (GimpDashboard *dashboard,
GimpDashboardHistoryDuration history_duration);
GimpDashboardHistoryDuration gimp_dashboard_get_history_duration (GimpDashboard *dashboard);
void gimp_dashboard_set_low_swap_space_warning (GimpDashboard *dashboard,
gboolean low_swap_space_warning);
gboolean gimp_dashboard_get_low_swap_space_warning (GimpDashboard *dashboard);
void gimp_dashboard_menu_setup (GimpUIManager *manager,
const gchar *ui_path);
#endif /* __GIMP_DASHBOARD_H__ */ #endif /* __GIMP_DASHBOARD_H__ */
...@@ -674,6 +674,9 @@ ...@@ -674,6 +674,9 @@
#define GIMP_HELP_DASHBOARD_GROUPS "gimp-dashboard-groups" #define GIMP_HELP_DASHBOARD_GROUPS "gimp-dashboard-groups"
#define GIMP_HELP_DASHBOARD_UPDATE_INTERVAL "gimp-dashboard-update-interval" #define GIMP_HELP_DASHBOARD_UPDATE_INTERVAL "gimp-dashboard-update-interval"
#define GIMP_HELP_DASHBOARD_HISTORY_DURATION "gimp-dashboard-history-duration" #define GIMP_HELP_DASHBOARD_HISTORY_DURATION "gimp-dashboard-history-duration"
#define GIMP_HELP_DASHBOARD_LOG_RECORD "gimp-dashboard-log-record"
#define GIMP_HELP_DASHBOARD_LOG_ADD_MARKER "gimp-dashboard-log-add-marker"
#define GIMP_HELP_DASHBOARD_LOG_ADD_EMPTY_MARKER "gimp-dashboard-log-add-empty-marker"
#define GIMP_HELP_DASHBOARD_RESET "gimp-dashboard-reset" #define GIMP_HELP_DASHBOARD_RESET "gimp-dashboard-reset"
#define GIMP_HELP_DASHBOARD_LOW_SWAP_SPACE_WARNING "gimp-dashboard-low-swap-space-warning" #define GIMP_HELP_DASHBOARD_LOW_SWAP_SPACE_WARNING "gimp-dashboard-low-swap-space-warning"
......
This diff is collapsed.
...@@ -42,6 +42,7 @@ scalable_images = \ ...@@ -42,6 +42,7 @@ scalable_images = \
scalable/media-optical.svg \ scalable/media-optical.svg \
scalable/media-playback-pause.svg \ scalable/media-playback-pause.svg \
scalable/media-playback-start.svg \ scalable/media-playback-start.svg \
scalable/media-record.svg \
scalable/media-seek-backward.svg \ scalable/media-seek-backward.svg \
scalable/media-skip-backward.svg \ scalable/media-skip-backward.svg \
scalable/media-skip-forward.svg \ scalable/media-skip-forward.svg \
...@@ -173,6 +174,7 @@ scalable_images = \ ...@@ -173,6 +174,7 @@ scalable_images = \
scalable/gimp-line-spacing.svg \ scalable/gimp-line-spacing.svg \
scalable/gimp-linked.svg \ scalable/gimp-linked.svg \
scalable/gimp-list.svg \ scalable/gimp-list.svg \
scalable/gimp-marker.svg \
scalable/gimp-menu-left.svg \ scalable/gimp-menu-left.svg \
scalable/gimp-menu-right.svg \ scalable/gimp-menu-right.svg \
scalable/gimp-merge-down.svg \ scalable/gimp-merge-down.svg \
...@@ -549,6 +551,7 @@ icons16_images = \ ...@@ -549,6 +551,7 @@ icons16_images = \
16/media-optical.png \ 16/media-optical.png \
16/media-playback-pause.png \ 16/media-playback-pause.png \
16/media-playback-start.png \ 16/media-playback-start.png \
16/media-record.png \
16/media-seek-backward.png \ 16/media-seek-backward.png \
16/media-skip-backward.png \ 16/media-skip-backward.png \
16/media-skip-forward.png \ 16/media-skip-forward.png \
...@@ -659,6 +662,7 @@ icons16_images = \ ...@@ -659,6 +662,7 @@ icons16_images = \
16/gimp-layer.png \ 16/gimp-layer.png \
16/gimp-layers.png \ 16/gimp-layers.png \
16/gimp-list.png \ 16/gimp-list.png \
16/gimp-marker.png \
16/gimp-merge-down.png \ 16/gimp-merge-down.png \
16/gimp-move-to-screen.png \ 16/gimp-move-to-screen.png \
16/gimp-navigation.png \ 16/gimp-navigation.png \
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 4.2333332 4.2333335"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="gimp-marker.svg">
<title
id="title8619">GIMP Marker</title>
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="44.25"
inkscape:cx="8"
inkscape:cy="8"
inkscape:document-units="pc"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1535"
inkscape:window-height="876"
inkscape:window-x="65"
inkscape:window-y="24"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid815"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>GIMP Marker</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Ell</dc:title>
</cc:Agent>
</dc:creator>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-292.76665)"
style="display:inline">
<g
id="gimp-marker">
<path
inkscape:connector-curvature="0"
id="path1551"
d="m 0.26458333,296.7354 0.79374997,-0.79375 v -2.91042 h 2.6458332 v 1.5875 H 1.5875 v 1.32292 l 0.79375,0.79375 z"
style="display:inline;fill:none;fill-rule:evenodd;stroke:#c1c1ff;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g8601">
<g
id="g8587">
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path1431"
d="M 0.26454594,296.7354 H 2.3812127 l -1.0583334,-1.05833 z"
style="fill:#202020;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
y="293.03122"
x="1.058296"
height="3.175"
width="0.5291667"
id="rect1433"
style="fill:#202020;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
id="g8583">
<rect
y="293.03122"
x="1.5874625"
height="1.5875138"
width="2.1166668"
id="rect1437"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<g
transform="translate(-0.26462073)"
style="fill:#2020ff;fill-opacity:1"
id="g1511">
<rect
style="fill:#2020ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect1439"
width="0.52916664"
height="0.52916664"
x="1.8520833"
y="293.03122" />
<rect
style="fill:#2020ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect1439-3"
width="0.52916664"
height="0.52916664"
x="2.9104166"
y="293.03122" />
<rect
style="fill:#2020ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect1439-6"
width="0.52916664"
height="0.52916664"
x="2.3812499"
y="293.56039" />
<rect
style="fill:#2020ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect1439-3-7"
width="0.52916664"
height="0.52916664"
x="3.4395833"
y="293.56039" />
<rect
style="fill:#2020ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect1439-5"
width="0.52916664"
height="0.52916664"
x="1.8520833"
y="294.08957" />
<rect
style="fill:#2020ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect1439-3-3"
width="0.52916664"
height="0.52916664"
x="2.9104166"
y="294.08957" />
</g>
</g>
</g>
</g>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 4.2333332 4.2333335"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="media-record.svg">
<title
id="title7906">Media Record</title>
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient846">
<stop
style="stop-color:#f95757;stop-opacity:1"
offset="0"
id="stop842" />
<stop
style="stop-color:#d91919;stop-opacity:1"
offset="1"
id="stop844" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient846"
id="linearGradient848"
x1="2.1166668"
y1="293.29581"
x2="2.1166668"
y2="296.47083"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="44.25"
inkscape:cx="1.2768362"
inkscape:cy="8"
inkscape:document-units="pc"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1535"
inkscape:window-height="876"
inkscape:window-x="65"
inkscape:window-y="24"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid815"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Media Record</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Ell</dc:title>
</cc:Agent>
</dc:creator>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-292.76665)">
<g
id="media-record">
<circle
r="1.8520833"
cy="294.88333"
cx="2.1166666"
id="path836"